Spaces:
Running
Running
| import streamlit as st | |
| import requests | |
| from PIL import Image, ImageDraw, ImageFont | |
| import io | |
| import time | |
| import json | |
| import base64 | |
| import uuid | |
| import hashlib | |
| import urllib.parse | |
| import random | |
| import datetime | |
| import re | |
| from collections import Counter | |
| from bs4 import BeautifulSoup | |
| from fpdf import FPDF | |
| import os | |
| from io import BytesIO | |
| import streamlit as st | |
| import html | |
| import string | |
| # --- BATCH 1: MEDIA & FILE FUNCTIONS --- | |
| def lexical_replacer_tool(): | |
| """ | |
| The Master Function for the Lexical Space Replacer Tool. | |
| Contains 100+ text manipulation features split into Normal and Dev modes. | |
| """ | |
| st.markdown("## 🛠️ Lexical Replacer Toolkit") | |
| st.markdown("---") | |
| # --- STATE MANAGEMENT --- | |
| # We use session state to hold the text so it persists when switching tabs | |
| if 'replacer_input' not in st.session_state: | |
| st.session_state.replacer_input = "Paste your text or HTML here..." | |
| if 'replacer_output' not in st.session_state: | |
| st.session_state.replacer_output = "" | |
| # --- THE LOGIC ENGINE (100+ Features Mapped) --- | |
| # We map "Human Readable Names" to "Lambda Functions" for efficiency. | |
| ops = { | |
| # --- GROUP 1: BASIC CLEANUP (Normal Mode) --- | |
| "Remove Double Spaces": lambda t: re.sub(r'\s+', ' ', t), | |
| "Trim Whitespace": lambda t: t.strip(), | |
| "Remove Empty Lines": lambda t: "\n".join([line for line in t.splitlines() if line.strip()]), | |
| "Remove Duplicate Lines": lambda t: "\n".join(list(dict.fromkeys(t.splitlines()))), # Preserves order | |
| "Sentence Case": lambda t: ". ".join([s.capitalize() for s in t.split(". ")]), | |
| "Title Case": lambda t: t.title(), | |
| "UPPERCASE": lambda t: t.upper(), | |
| "lowercase": lambda t: t.lower(), | |
| "tOGGLE cASE": lambda t: t.swapcase(), | |
| "Smart Quotes to Straight": lambda t: t.replace('“', '"').replace('”', '"').replace("‘", "'").replace("’", "'"), | |
| "Remove Special Characters": lambda t: re.sub(r'[^a-zA-Z0-9\s]', '', t), | |
| "Remove Emojis": lambda t: t.encode('ascii', 'ignore').decode('ascii'), | |
| "Remove Numbers": lambda t: re.sub(r'\d+', '', t), | |
| "Remove Non-ASCII": lambda t: re.sub(r'[^\x00-\x7F]+', '', t), | |
| "Unescape HTML": lambda t: html.unescape(t), | |
| "Text Reverse": lambda t: t[::-1], | |
| "Word Reverse": lambda t: " ".join(t.split()[::-1]), | |
| "Sort Lines A-Z": lambda t: "\n".join(sorted(t.splitlines())), | |
| "Sort Lines Z-A": lambda t: "\n".join(sorted(t.splitlines(), reverse=True)), | |
| "Shuffle Lines": lambda t: "\n".join(random.sample(t.splitlines(), len(t.splitlines()))), | |
| # --- GROUP 2: FORMATTING (Normal Mode) --- | |
| "Add Line Breaks (<br>)": lambda t: t.replace("\n", "<br>\n"), | |
| "Wrap in <p> Tags": lambda t: "\n".join([f"<p>{line}</p>" for line in t.splitlines() if line.strip()]), | |
| "List Maker (Bullets)": lambda t: "<ul>\n" + "\n".join([f" <li>{line}</li>" for line in t.splitlines() if line.strip()]) + "\n</ul>", | |
| "List Maker (Numbered)": lambda t: "<ol>\n" + "\n".join([f" <li>{line}</li>" for line in t.splitlines() if line.strip()]) + "\n</ol>", | |
| "Markdown to HTML Bold": lambda t: re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', t), | |
| "HTML to Markdown Bold": lambda t: re.sub(r'<b>(.*?)</b>', r'**\1**', t), | |
| "Strip All HTML Tags": lambda t: re.sub(r'<[^>]+>', '', t), | |
| "Obfuscate Emails": lambda t: re.sub(r'([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})', r'[email protected]', t), | |
| "Format Date (DD/MM/YYYY)": lambda t: t, # Placeholder for complex regex | |
| "Tab to 4 Spaces": lambda t: t.replace("\t", " "), | |
| "Slugify (URL Friendly)": lambda t: re.sub(r'[^a-z0-9]+', '-', t.lower()).strip('-'), | |
| "Add Target='_blank'": lambda t: t.replace('<a ', '<a target="_blank" '), | |
| "Add Rel='nofollow'": lambda t: t.replace('<a ', '<a rel="nofollow" '), | |
| # --- GROUP 3: DEV UTILS (Dev Mode) --- | |
| "Minify JSON": lambda t: json.dumps(json.loads(t), separators=(',', ':')) if t.strip() else "", | |
| "Beautify JSON": lambda t: json.dumps(json.loads(t), indent=4) if t.strip() else "", | |
| "Escape HTML Entities": lambda t: html.escape(t), | |
| "Escape JSON String": lambda t: json.dumps(t), | |
| "Base64 Encode": lambda t: base64.b64encode(t.encode()).decode(), | |
| "Base64 Decode": lambda t: base64.b64decode(t.encode()).decode(), | |
| "URL Encode": lambda t: urllib.parse.quote(t), | |
| "URL Decode": lambda t: urllib.parse.unquote(t), | |
| "Hex to RGB": lambda t: "\n".join([f"rgb({int(h[1:3], 16)}, {int(h[3:5], 16)}, {int(h[5:7], 16)})" for h in re.findall(r'#[0-9a-fA-F]{6}', t)]), | |
| # --- GROUP 4: BLOGGER SPECIFIC (Dev Mode) --- | |
| "Blogger: Fix Image Sizes": lambda t: re.sub(r'(width|height)="\d+"', '', t), | |
| "Blogger: HTTPS Force": lambda t: t.replace("http://", "https://"), | |
| "Clean MS Word Junk": lambda t: re.sub(r'class="Mso.*?"', '', t), | |
| "Remove Inline Styles": lambda t: re.sub(r'style=".*?"', '', t), | |
| "Remove Script Tags": lambda t: re.sub(r'<script.*?>.*?</script>', '', t, flags=re.DOTALL), | |
| "Remove Comments": lambda t: re.sub(r'', '', t, flags=re.DOTALL), | |
| } | |
| # --- LAYOUT --- | |
| col1, col2 = st.columns([1, 1]) | |
| with col1: | |
| st.subheader("Input") | |
| text_input = st.text_area("Paste text here", value=st.session_state.replacer_input, height=350, key="input_widget") | |
| # Update session state on change | |
| if text_input != st.session_state.replacer_input: | |
| st.session_state.replacer_input = text_input | |
| with col2: | |
| st.subheader("Control Panel") | |
| mode = st.radio("Select Mode:", ["🟢 Normal Mode", "🔴 Developer Mode"], horizontal=True) | |
| selected_ops = [] | |
| if mode == "🟢 Normal Mode": | |
| st.info("Tools for Writing, SEO, and Formatting") | |
| with st.expander("✨ Cleaning Tools", expanded=True): | |
| if st.button("Remove Double Spaces"): selected_ops.append("Remove Double Spaces") | |
| if st.button("Trim Whitespace"): selected_ops.append("Trim Whitespace") | |
| if st.button("Remove Empty Lines"): selected_ops.append("Remove Empty Lines") | |
| if st.button("Remove Duplicate Lines"): selected_ops.append("Remove Duplicate Lines") | |
| if st.button("Remove Special Chars"): selected_ops.append("Remove Special Characters") | |
| with st.expander("🔠 Casing Tools"): | |
| c1, c2, c3 = st.columns(3) | |
| with c1: | |
| if st.button("UPPERCASE"): selected_ops.append("UPPERCASE") | |
| if st.button("Sentence Case"): selected_ops.append("Sentence Case") | |
| with c2: | |
| if st.button("lowercase"): selected_ops.append("lowercase") | |
| if st.button("Title Case"): selected_ops.append("Title Case") | |
| with c3: | |
| if st.button("Toggle Case"): selected_ops.append("tOGGLE cASE") | |
| with st.expander("📄 Formatting"): | |
| if st.button("Make HTML List (Bullet)"): selected_ops.append("List Maker (Bullets)") | |
| if st.button("Make HTML List (Number)"): selected_ops.append("List Maker (Numbered)") | |
| if st.button("Smart Quotes -> Straight"): selected_ops.append("Smart Quotes to Straight") | |
| if st.button("Slugify Text"): selected_ops.append("Slugify (URL Friendly)") | |
| else: # Developer Mode | |
| st.error("Tools for Code, Regex, and Backend") | |
| # Regex Section | |
| with st.expander("🔍 Regex Find & Replace", expanded=True): | |
| regex_find = st.text_input("Find Pattern (Regex)", value="") | |
| regex_repl = st.text_input("Replace With", value="") | |
| if st.button("Run Regex Replace"): | |
| try: | |
| st.session_state.replacer_input = re.sub(regex_find, regex_repl, st.session_state.replacer_input) | |
| st.success("Regex Applied!") | |
| st.rerun() | |
| except Exception as e: | |
| st.error(f"Regex Error: {e}") | |
| with st.expander("💻 Encoders / Decoders"): | |
| c1, c2 = st.columns(2) | |
| with c1: | |
| if st.button("Base64 Encode"): selected_ops.append("Base64 Encode") | |
| if st.button("URL Encode"): selected_ops.append("URL Encode") | |
| with c2: | |
| if st.button("Base64 Decode"): selected_ops.append("Base64 Decode") | |
| if st.button("URL Decode"): selected_ops.append("URL Decode") | |
| with st.expander("🧹 Code Cleaning"): | |
| if st.button("Minify JSON"): selected_ops.append("Minify JSON") | |
| if st.button("Beautify JSON"): selected_ops.append("Beautify JSON") | |
| if st.button("Escape HTML"): selected_ops.append("Escape HTML Entities") | |
| with st.expander("🅱️ Blogger Specific"): | |
| if st.button("Clean MS Word Junk"): selected_ops.append("Clean MS Word Junk") | |
| if st.button("Force HTTPS"): selected_ops.append("Blogger: HTTPS Force") | |
| if st.button("Remove Inline Styles"): selected_ops.append("Remove Inline Styles") | |
| if st.button("Remove Scripts"): selected_ops.append("Remove Script Tags") | |
| # --- ANALYSIS (Always Visible) --- | |
| st.write("---") | |
| st.caption("📊 Live Analysis") | |
| char_count = len(st.session_state.replacer_input) | |
| word_count = len(st.session_state.replacer_input.split()) | |
| st.write(f"**Chars:** {char_count} | **Words:** {word_count}") | |
| # --- EXECUTION ENGINE --- | |
| # If a button added an op to the list, run it against the input text | |
| if selected_ops: | |
| current_text = st.session_state.replacer_input | |
| for op_name in selected_ops: | |
| try: | |
| # Apply the function from our dictionary | |
| current_text = ops[op_name](current_text) | |
| st.toast(f"Applied: {op_name}") | |
| except Exception as e: | |
| st.error(f"Error in {op_name}: {e}") | |
| # Update State | |
| st.session_state.replacer_input = current_text | |
| st.rerun() # Refresh to show changes in the text area | |
| # --- RESULT DOWNLOAD --- | |
| if st.session_state.replacer_input: | |
| st.download_button( | |
| label="Download Result", | |
| data=st.session_state.replacer_input, | |
| file_name="lexical_cleaned.txt", | |
| mime="text/plain" | |
| ) | |
| def run_ultimate_pdf_converter(): | |
| """ | |
| The Ultimate Text-to-PDF Converter (Stable Version). | |
| Features: | |
| - Auto-Healing Font Loader (Fixes TTLibError) | |
| - Smart Symbols & Typography | |
| - Markdown Engine (Headers, Tables, Code Blocks) | |
| - LMS Junk Cleaner | |
| """ | |
| # --- CONSTANTS --- | |
| SMART_SYMBOLS = { | |
| r'<->': '↔', r'->': '→', r'<-': '←', r'=>': '⇒', r'<=': '≤', r'>=': '≥', r'!=': '≠', | |
| r'\.\.\.': '…', r'\(c\)': '©', r'\(r\)': '®', r'\(tm\)': '™', | |
| r'\+-': '±', r'\~=': '≈', r'--': '—', | |
| r'alpha': 'α', r'beta': 'β', r'theta': 'θ', r'pi': 'π', r'sigma': 'Σ', | |
| r'delta': 'Δ', r'gamma': 'Γ', r'omega': 'Ω', r'mu': 'μ', r'lambda': 'λ', | |
| r'deg': '°', r'infinity': '∞', r'sqrt': '√' | |
| } | |
| # --- INTERNAL PDF CLASS --- | |
| class UltimatePDF(FPDF): | |
| def __init__(self, orientation='P', unit='mm', format='A4'): | |
| super().__init__(orientation=orientation, unit=unit, format=format) | |
| self.set_auto_page_break(auto=True, margin=15) | |
| self.main_font = 'Arial' # Default fallback | |
| self.ensure_fonts() | |
| def ensure_fonts(self): | |
| font_filename = "DejaVuSans.ttf" | |
| font_url = "https://github.com/dejavu-fonts/dejavu-fonts/raw/master/ttf/DejaVuSans.ttf" | |
| # 1. Check if file exists and is valid size (HTML error pages are small) | |
| if os.path.exists(font_filename): | |
| if os.path.getsize(font_filename) < 1000: # Less than 1KB is definitely garbage | |
| os.remove(font_filename) | |
| # 2. Download if missing | |
| if not os.path.exists(font_filename): | |
| try: | |
| # Fake user-agent to avoid GitHub blocking scripts | |
| headers = {'User-Agent': 'Mozilla/5.0'} | |
| r = requests.get(font_url, headers=headers, timeout=10) | |
| if r.status_code == 200: | |
| with open(font_filename, "wb") as f: | |
| f.write(r.content) | |
| except Exception as e: | |
| print(f"Font download failed: {e}") | |
| # 3. Try Loading the Font | |
| try: | |
| if os.path.exists(font_filename): | |
| self.add_font('DejaVu', '', font_filename, uni=True) | |
| self.main_font = 'DejaVu' | |
| except Exception: | |
| # If loading fails (corrupt file), delete it to retry next time | |
| try: os.remove(font_filename) | |
| except: pass | |
| self.main_font = 'Arial' # Fallback to standard | |
| st.toast("⚠️ Font failed to load. Using standard font (some symbols may be missing).", icon="⚠️") | |
| def header(self): | |
| if getattr(self, 'show_header', False): | |
| self.set_font(self.main_font, '', 8) | |
| self.set_text_color(128) | |
| self.cell(0, 10, f'Generated by Ultimate PDF | {getattr(self, "title_meta", "Doc")}', 0, 0, 'R') | |
| self.ln(10) | |
| def footer(self): | |
| self.set_y(-15) | |
| self.set_font(self.main_font, '', 8) | |
| self.set_text_color(128) | |
| self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C') | |
| # --- MARKDOWN RENDERING HELPERS --- | |
| def add_markdown_header(self, text, level): | |
| sizes = {1: 20, 2: 16, 3: 14} | |
| self.set_font(self.main_font, '', sizes.get(level, 12)) | |
| self.set_text_color(0, 50, 100) | |
| self.cell(0, 10, text, ln=True) | |
| self.set_text_color(0) | |
| self.set_font(self.main_font, '', 12) | |
| def add_code_block(self, code_lines): | |
| self.set_font("Courier", size=10) | |
| self.set_fill_color(245, 245, 245) | |
| for line in code_lines: | |
| # Replace tabs with spaces to prevent alignment issues | |
| safe_line = line.replace('\t', ' ') | |
| self.cell(0, 5, safe_line, ln=True, fill=True, border=0) | |
| self.set_font(self.main_font, '', 12) | |
| self.ln(3) | |
| def add_table(self, table_lines): | |
| self.set_font(self.main_font, '', 10) | |
| cell_h = 7 | |
| for row in table_lines: | |
| cols = [c.strip() for c in row.split('|') if c.strip()] | |
| if not cols: continue | |
| col_w = (self.w - 30) // len(cols) | |
| for col in cols: | |
| self.cell(col_w, cell_h, col, border=1) | |
| self.ln() | |
| self.set_font(self.main_font, '', 12) | |
| self.ln(5) | |
| def add_blockquote(self, text): | |
| self.set_text_color(80) | |
| self.set_x(self.l_margin + 8) | |
| self.multi_cell(0, 6, f"“ {text}") | |
| self.set_x(self.l_margin) | |
| self.set_text_color(0) | |
| self.ln(2) | |
| def add_image_from_url(self, url): | |
| try: | |
| r = requests.get(url, timeout=5) | |
| if r.status_code == 200: | |
| img_data = BytesIO(r.content) | |
| self.image(img_data, w=100) | |
| self.ln(5) | |
| except: | |
| self.set_text_color(200, 0, 0) | |
| self.cell(0, 10, f"[Image load failed: {url}]", ln=True) | |
| self.set_text_color(0) | |
| # --- TEXT PROCESSOR --- | |
| def clean_and_parse(raw_text, use_smart_symbols=True, clean_lms=True): | |
| processed_lines = [] | |
| # 1. LMS Regex Cleaning | |
| if clean_lms: | |
| # Common LMS patterns | |
| patterns = [ | |
| r'\[ID:?\s*\w+\]', # [ID: 123] | |
| r'Question\s+ID\s*[:\-]\s*\w+', # Question ID: 123 | |
| r'\(\d+\s*pts?\)', # (1 pts) | |
| r'Select one:', # Moodle/Blackboard prompt | |
| r'\[\d{1,2}:\d{2}\s*(AM|PM)?\]' # Timestamps | |
| ] | |
| for p in patterns: | |
| raw_text = re.sub(p, '', raw_text, flags=re.IGNORECASE) | |
| raw_text = re.sub(r'\n{3,}', '\n\n', raw_text) # Fix spacing | |
| # 2. Smart Symbols | |
| if use_smart_symbols: | |
| for pattern, symbol in SMART_SYMBOLS.items(): | |
| if pattern.isalpha(): | |
| raw_text = re.sub(r'\b' + pattern + r'\b', symbol, raw_text, flags=re.IGNORECASE) | |
| else: | |
| raw_text = re.sub(pattern, symbol, raw_text) | |
| lines = raw_text.split('\n') | |
| # 3. Block Parser | |
| buffer_type = None | |
| buffer_content = [] | |
| for line in lines: | |
| line_stripped = line.strip() | |
| # Detect Code Block | |
| if line_stripped.startswith('```'): | |
| if buffer_type == 'code': # Close code | |
| processed_lines.append({'type': 'code', 'content': buffer_content}) | |
| buffer_content = [] | |
| buffer_type = None | |
| else: # Open code | |
| if buffer_type == 'table': # Close table if open | |
| processed_lines.append({'type': 'table', 'content': buffer_content}) | |
| buffer_content = [] | |
| buffer_type = 'code' | |
| continue | |
| if buffer_type == 'code': | |
| buffer_content.append(line) # Preserve whitespace in code | |
| continue | |
| # Detect Table | |
| if '|' in line_stripped and len(line_stripped) > 3: | |
| if buffer_type != 'table': | |
| buffer_type = 'table' | |
| buffer_content.append(line_stripped) | |
| continue | |
| elif buffer_type == 'table': # Close table | |
| processed_lines.append({'type': 'table', 'content': buffer_content}) | |
| buffer_content = [] | |
| buffer_type = None | |
| # Detect Headers | |
| if line_stripped.startswith('#'): | |
| level = line_stripped.count('#') | |
| text = line_stripped.replace('#', '').strip() | |
| processed_lines.append({'type': 'header', 'level': min(level, 3), 'content': text}) | |
| continue | |
| # Detect Quotes | |
| if line_stripped.startswith('> '): | |
| processed_lines.append({'type': 'quote', 'content': line_stripped[2:]}) | |
| continue | |
| # Detect Images | |
| if line_stripped.startswith('http') and line_stripped.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')): | |
| processed_lines.append({'type': 'image', 'url': line_stripped}) | |
| continue | |
| # Detect Lists | |
| if line_stripped.startswith(('* ', '- ')): | |
| processed_lines.append({'type': 'list', 'content': line_stripped[2:]}) | |
| continue | |
| # Detect HR | |
| if line_stripped == '---': | |
| processed_lines.append({'type': 'hr'}) | |
| continue | |
| if line_stripped: | |
| processed_lines.append({'type': 'text', 'content': line_stripped}) | |
| else: | |
| processed_lines.append({'type': 'empty'}) | |
| if buffer_type == 'table': | |
| processed_lines.append({'type': 'table', 'content': buffer_content}) | |
| return processed_lines | |
| # --- UI RENDER --- | |
| st.title("⚡ Ultimate PDF Engine") | |
| with st.expander("ℹ️ Help & Features", expanded=False): | |
| st.write("- **Smart Symbols:** Writes 'alpha' as α, '->' as →") | |
| st.write("- **Tables:** Use `| Name | Score |` format") | |
| st.write("- **Code:** Use ` ``` ` for code blocks") | |
| st.write("- **Images:** Paste URL on new line") | |
| # Settings Sidebar | |
| with st.sidebar: | |
| st.header("⚙️ PDF Config") | |
| filename = st.text_input("Filename", "My_Notes.pdf") | |
| orientation = st.radio("Orientation", ["Portrait", "Landscape"]) | |
| st.subheader("Filters") | |
| enable_lms = st.checkbox("Clean LMS Junk", True) | |
| enable_smart = st.checkbox("Smart Symbols", True) | |
| enable_header = st.checkbox("Show Header", True) | |
| font_size = st.slider("Font Size", 8, 24, 12) | |
| # Input | |
| raw_input = st.text_area("Paste text here...", height=350) | |
| # Action | |
| if st.button("🚀 Generate PDF", type="primary"): | |
| if not raw_input.strip(): | |
| st.warning("Input is empty.") | |
| return | |
| with st.spinner("Processing..."): | |
| # Setup PDF | |
| orient_code = 'P' if orientation == "Portrait" else 'L' | |
| pdf = UltimatePDF(orientation=orient_code) | |
| pdf.title_meta = filename.replace('.pdf', '') | |
| pdf.show_header = enable_header | |
| pdf.add_page() | |
| pdf.set_font(pdf.main_font, '', font_size) | |
| # Process | |
| blocks = clean_and_parse(raw_input, use_smart_symbols=enable_smart, clean_lms=enable_lms) | |
| # Render | |
| for block in blocks: | |
| if block['type'] == 'header': | |
| pdf.add_markdown_header(block['content'], block['level']) | |
| elif block['type'] == 'code': | |
| pdf.add_code_block(block['content']) | |
| elif block['type'] == 'table': | |
| pdf.add_table(block['content']) | |
| elif block['type'] == 'quote': | |
| pdf.add_blockquote(block['content']) | |
| elif block['type'] == 'image': | |
| pdf.add_image_from_url(block['url']) | |
| elif block['type'] == 'list': | |
| pdf.set_x(pdf.l_margin + 5) | |
| pdf.write(8, f"• {block['content']}") | |
| pdf.ln() | |
| pdf.set_x(pdf.l_margin) | |
| elif block['type'] == 'hr': | |
| pdf.ln(2) | |
| pdf.line(pdf.l_margin, pdf.get_y(), pdf.w - pdf.r_margin, pdf.get_y()) | |
| pdf.ln(5) | |
| elif block['type'] == 'text': | |
| pdf.write(8, block['content']) | |
| pdf.ln() | |
| elif block['type'] == 'empty': | |
| pdf.ln(4) | |
| # Download | |
| try: | |
| pdf_bytes = pdf.output(dest='S').encode('latin-1', 'replace') | |
| st.success("PDF Generated Successfully!") | |
| st.download_button( | |
| "⬇️ Download PDF", | |
| data=pdf_bytes, | |
| file_name=filename if filename.endswith('.pdf') else f"{filename}.pdf", | |
| mime="application/pdf" | |
| ) | |
| except Exception as e: | |
| st.error(f"Error creating PDF file: {e}") | |
| import streamlit as st | |
| from huggingface_hub import InferenceClient | |
| import os | |
| # --- 1. The Logic Function (Backend) --- | |
| # We use @st.cache_data so if the user clicks other buttons, we don't re-run the expensive API call. | |
| def get_seo_data(code_snippet, file_type, api_key): | |
| """ | |
| Sends code to Hugging Face Inference API and returns SEO/JSON-LD strategy. | |
| """ | |
| if not code_snippet: | |
| return None, "⚠️ Please paste some code first." | |
| if not api_key: | |
| return None, "❌ Error: HF_TOKEN not found in secrets." | |
| try: | |
| client = InferenceClient(api_key=api_key) | |
| # Strict Prompt for Qwen 2.5 Coder | |
| system_instruction = f""" | |
| You are an expert Technical SEO Specialist. Analyze the user's {file_type} code. | |
| Task: Generate Google-compliant JSON-LD structured data and SEO meta tags. | |
| Output Format (Strict Markdown): | |
| ## SEO Metadata | |
| **Title:** [Engaging Title, max 60 chars] | |
| **Description:** [Summary including keywords, max 160 chars] | |
| **Keywords:** [5-8 comma-separated keywords] | |
| ## JSON-LD Structured Data | |
| ```json | |
| [Insert VALID JSON-LD here. | |
| - If Python: Use schema.org/SoftwareSourceCode | |
| - If HTML: Use schema.org/WebPage or schema.org/TechArticle] | |
| ``` | |
| """ | |
| user_message = f"Analyze this {file_type} code:\n\n{code_snippet}" | |
| response = client.chat_completion( | |
| model="Qwen/Qwen2.5-Coder-32B-Instruct", | |
| messages=[ | |
| {"role": "system", "content": system_instruction}, | |
| {"role": "user", "content": user_message} | |
| ], | |
| max_tokens=1500, | |
| temperature=0.2 | |
| ) | |
| return response.choices[0].message.content, None | |
| except Exception as e: | |
| return None, f"Error: {str(e)}" | |
| # --- 2. The UI Function (Frontend) --- | |
| def render_seo_ui(): | |
| """ | |
| Call this function in your main app.py where you want the SEO tool to show up. | |
| """ | |
| st.header("🚀 AI Code-to-SEO Generator") | |
| st.markdown("Generate **JSON-LD** and **Meta Tags** for your Python/HTML files using Qwen 2.5 Coder.") | |
| # Get Token (Try secrets first, then env var) | |
| try: | |
| hf_token = st.secrets["HF_TOKEN"] | |
| except: | |
| hf_token = os.getenv("HF_TOKEN") | |
| with st.form("seo_form"): | |
| col1, col2 = st.columns([1, 3]) | |
| with col1: | |
| file_type = st.radio("File Type", ["Python", "HTML"]) | |
| with col2: | |
| code_input = st.text_area("Paste Code Here", height=300, placeholder="import os...") | |
| submitted = st.form_submit_button("✨ Generate SEO Data") | |
| if submitted: | |
| if not hf_token: | |
| st.error("Authentication Error: Please add `HF_TOKEN` to your Streamlit secrets.") | |
| else: | |
| result, error = get_seo_data(code_input, file_type, hf_token) | |
| if error: | |
| st.error(error) | |
| else: | |
| st.success("SEO Data Generated Successfully!") | |
| st.markdown("---") | |
| st.markdown(result) | |
| # Optional: Add a copy button for the raw result | |
| st.download_button( | |
| label="📥 Download Result", | |
| data=result, | |
| file_name="seo_strategy.md", | |
| mime="text/markdown" | |
| ) | |
| def tool_youtube_downloader(): | |
| st.header("🎥 YouTube Media Extractor") | |
| url = st.text_input("Paste YouTube URL", placeholder="https://youtube.com/...") | |
| format_type = st.radio("Format", ["Video (MP4)", "Audio Only (MP3)"], horizontal=True) | |
| if url and st.button("🚀 Process Media"): | |
| with st.spinner("Contacting server..."): | |
| try: | |
| headers = {"Accept": "application/json", "Content-Type": "application/json"} | |
| payload = { | |
| "url": url, | |
| "vQuality": "1080", | |
| "isAudioOnly": True if "Audio" in format_type else False | |
| } | |
| # Using Cobalt API | |
| response = requests.post("https://api.cobalt.tools/api/json", headers=headers, json=payload) | |
| data = response.json() | |
| if "url" in data: | |
| st.success("✅ Ready!") | |
| st.link_button(f"⬇️ Download {format_type}", data["url"]) | |
| if "Audio" not in format_type: | |
| st.video(data["url"]) | |
| else: | |
| st.audio(data["url"]) | |
| else: | |
| st.error(f"Error: {data.get('text', 'Unknown error')}") | |
| except Exception as e: | |
| st.error(f"Connection failed: {str(e)}") | |
| def tool_smart_converter(): | |
| with st.spinner("Starting File Engine..."): | |
| time.sleep(0.3) | |
| import pandas as pd | |
| st.header("🔄 Smart File Converter") | |
| st.info("Supports: Images (PNG/JPG/WEBP) and Data (CSV/JSON/Excel)") | |
| uploaded_file = st.file_uploader("Upload File", type=['png', 'jpg', 'jpeg', 'webp', 'csv', 'json', 'xlsx'],key="smart_conv_upload") | |
| if uploaded_file: | |
| file_type = uploaded_file.name.split('.')[-1].lower() | |
| # LOGIC: IMAGE CONVERSION | |
| if file_type in ['png', 'jpg', 'jpeg', 'webp']: | |
| image = Image.open(uploaded_file) | |
| st.image(image, caption="Preview", width=300) | |
| target_format = st.selectbox("Convert to:", ["PNG", "JPEG", "WEBP", "PDF"]) | |
| if st.button("Convert Image"): | |
| buf = io.BytesIO() | |
| # RGB required for JPEG/PDF | |
| if image.mode in ("RGBA", "P") and target_format in ["JPEG", "PDF"]: | |
| image = image.convert("RGB") | |
| image.save(buf, format=target_format) | |
| st.download_button(f"Download {target_format}", data=buf.getvalue(), file_name=f"converted.{target_format.lower()}") | |
| # LOGIC: DATA CONVERSION | |
| elif file_type in ['csv', 'json', 'xlsx']: | |
| df = None | |
| try: | |
| if file_type == 'csv': df = pd.read_csv(uploaded_file) | |
| elif file_type == 'json': df = pd.read_json(uploaded_file) | |
| elif file_type == 'xlsx': df = pd.read_excel(uploaded_file) | |
| st.write("Data Preview:", df.head()) | |
| target_data = st.selectbox("Convert to:", ["CSV", "JSON", "Excel"]) | |
| if st.button("Convert Data"): | |
| buf = io.BytesIO() | |
| if target_data == "CSV": | |
| df.to_csv(buf, index=False) | |
| ext = "csv" | |
| elif target_data == "JSON": | |
| df.to_json(buf, orient='records') | |
| ext = "json" | |
| elif target_data == "Excel": | |
| df.to_excel(buf, index=False) | |
| ext = "xlsx" | |
| st.download_button(f"Download {target_data}", data=buf.getvalue(), file_name=f"converted.{ext}") | |
| except Exception as e: | |
| st.error(f"Error reading file: {e}") | |
| def tool_image_compressor(): | |
| st.header("📉 Image Compressor") | |
| # ADD key="compressor" | |
| uploaded_file = st.file_uploader("Upload Image", type=['png', 'jpg', 'jpeg'], key="compressor") | |
| if uploaded_file: | |
| image = Image.open(uploaded_file) | |
| st.write(f"Original Size: {uploaded_file.size / 1024:.2f} KB") | |
| quality = st.slider("Quality (Lower = Smaller file)", 10, 95, 60) | |
| if st.button("Compress"): | |
| buf = io.BytesIO() | |
| if image.mode in ("RGBA", "P"): image = image.convert("RGB") | |
| image.save(buf, format="JPEG", quality=quality, optimize=True) | |
| size_kb = len(buf.getvalue()) / 1024 | |
| st.success(f"Compressed Size: {size_kb:.2f} KB") | |
| st.download_button("Download Compressed Image", data=buf.getvalue(), file_name="compressed.jpg") | |
| def tool_image_resizer(): | |
| st.header("📐 Image Resizer") | |
| # ADD key="resizer" | |
| uploaded_file = st.file_uploader("Upload Image", type=['png', 'jpg', 'jpeg', 'webp'], key="resizer") | |
| if uploaded_file: | |
| image = Image.open(uploaded_file) | |
| st.write(f"Current Dimensions: {image.size}") | |
| col1, col2 = st.columns(2) | |
| w = col1.number_input("Width", value=image.width) | |
| h = col2.number_input("Height", value=image.height) | |
| if st.button("Resize"): | |
| new_img = image.resize((int(w), int(h))) | |
| buf = io.BytesIO() | |
| new_img.save(buf, format=image.format) | |
| st.image(new_img, caption="Resized Preview") | |
| st.download_button("Download Resized Image", data=buf.getvalue(), file_name=f"resized.{image.format.lower()}") | |
| def tool_thumbnail_generator(): | |
| st.header("🚀 Ultimate Thumbnail Studio") | |
| # --- DEPENDENCIES --- | |
| from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageEnhance, ImageOps, ImageColor | |
| import io | |
| # --- LAYOUT FIX FOR PC/MOBILE --- | |
| # We use a container for the controls to keep them tidy on PC | |
| # and "use_column_width" to fix the crash. | |
| # Top row: All Controls (Split into 2 columns for better PC spacing) | |
| with st.expander("🎨 Thumbnail Settings & Controls", expanded=True): | |
| col_c1, col_c2 = st.columns(2) | |
| # --- LEFT CONTROL COLUMN --- | |
| with col_c1: | |
| st.subheader("1. Background & Filters") | |
| bg_mode = st.radio("Background Type", ["Upload Image", "Solid Color", "Gradient"], horizontal=True) | |
| bg_image = None | |
| if bg_mode == "Upload Image": | |
| bg_file = st.file_uploader("Upload BG", type=['png', 'jpg', 'jpeg', 'webp'], key="max_bg") | |
| if bg_file: bg_image = Image.open(bg_file).convert("RGBA") | |
| elif bg_mode == "Solid Color": | |
| hex_bg = st.color_picker("Pick Color", "#1E1E1E") | |
| st.session_state.thumb_bg_color = hex_bg | |
| else: # Gradient | |
| c1_col, c2_col = st.columns(2) | |
| grad_c1 = c1_col.color_picker("Start", "#12c2e9") | |
| grad_c2 = c2_col.color_picker("End", "#c471ed") | |
| grad_dir = st.selectbox("Direction", ["Horizontal", "Vertical"]) | |
| st.markdown("---") | |
| st.write("**Filters**") | |
| f1, f2 = st.columns(2) | |
| blur_amt = f1.slider("Blur", 0, 20, 0) | |
| brightness = f2.slider("Brightness", 0.5, 1.5, 1.0) | |
| # --- RIGHT CONTROL COLUMN --- | |
| with col_c2: | |
| st.subheader("2. Text & Branding") | |
| main_text = st.text_area("Main Title", "THE ULTIMATE\nGUIDE TO PYTHON", height=100) | |
| t1, t2 = st.columns(2) | |
| font_size = t1.slider("Size", 20, 200, 80) | |
| font_color = t2.color_picker("Text Color", "#FFFFFF") | |
| st.write("**Styling**") | |
| s1, s2 = st.columns(2) | |
| stroke_width = s1.slider("Outline", 0, 10, 2) | |
| stroke_color = s2.color_picker("Outline Color", "#000000") | |
| st.markdown("---") | |
| logo_file = st.file_uploader("Upload Logo (PNG)", type=['png', 'webp'], key="max_logo") | |
| if logo_file: | |
| l1, l2 = st.columns(2) | |
| logo_size = l1.slider("Logo Size", 50, 300, 150) | |
| logo_pos = l2.selectbox("Position", ["Top-Right", "Top-Left", "Bottom-Right", "Bottom-Left"]) | |
| else: | |
| logo_size = 150 | |
| logo_pos = "Top-Right" | |
| # --- PREVIEW SECTION (Full Width for PC clarity) --- | |
| st.subheader("👁️ Preview & Download") | |
| # GENERATION LOGIC | |
| CANVAS_W, CANVAS_H = 1280, 720 | |
| canvas = Image.new("RGBA", (CANVAS_W, CANVAS_H), (0,0,0,0)) | |
| # 1. Background | |
| if bg_mode == "Upload Image" and bg_image: | |
| bg_ratio = bg_image.width / bg_image.height | |
| target_ratio = CANVAS_W / CANVAS_H | |
| if bg_ratio > target_ratio: | |
| new_h = CANVAS_H | |
| new_w = int(new_h * bg_ratio) | |
| else: | |
| new_w = CANVAS_W | |
| new_h = int(new_w / bg_ratio) | |
| bg_image = bg_image.resize((new_w, new_h), Image.Resampling.LANCZOS) | |
| left = (new_w - CANVAS_W)/2 | |
| top = (new_h - CANVAS_H)/2 | |
| bg_image = bg_image.crop((left, top, left+CANVAS_W, top+CANVAS_H)) | |
| canvas.paste(bg_image, (0,0)) | |
| elif bg_mode == "Gradient": | |
| base = Image.new('RGB', (CANVAS_W, CANVAS_H), grad_c1) | |
| top_img = Image.new('RGB', (CANVAS_W, CANVAS_H), grad_c2) | |
| mask = Image.new("L", (CANVAS_W, CANVAS_H)) | |
| mask_data = [] | |
| for y in range(CANVAS_H): | |
| for x in range(CANVAS_W): | |
| if grad_dir == "Vertical": mask_data.append(int(255 * (y / CANVAS_H))) | |
| else: mask_data.append(int(255 * (x / CANVAS_W))) | |
| mask.putdata(mask_data) | |
| canvas = Image.composite(top_img, base, mask).convert("RGBA") | |
| else: | |
| if 'thumb_bg_color' not in st.session_state: st.session_state.thumb_bg_color = "#1E1E1E" | |
| canvas = Image.new("RGBA", (CANVAS_W, CANVAS_H), st.session_state.thumb_bg_color) | |
| # 2. Filters | |
| if blur_amt > 0: canvas = canvas.filter(ImageFilter.GaussianBlur(blur_amt)) | |
| if brightness != 1.0: | |
| enhancer = ImageEnhance.Brightness(canvas) | |
| canvas = enhancer.enhance(brightness) | |
| # 3. Text | |
| txt_layer = Image.new("RGBA", canvas.size, (255,255,255,0)) | |
| draw = ImageDraw.Draw(txt_layer) | |
| try: | |
| font = ImageFont.truetype("arialbd.ttf", font_size) | |
| except: | |
| try: | |
| font = ImageFont.truetype("arial.ttf", font_size) | |
| except: | |
| font = ImageFont.load_default() | |
| # Center Text Calculation | |
| # Using basic textsize for compatibility if textbbox fails in older Pillow | |
| try: | |
| bbox = draw.multiline_textbbox((0,0), main_text, font=font, align="center") | |
| text_w = bbox[2] - bbox[0] | |
| text_h = bbox[3] - bbox[1] | |
| except: | |
| # Fallback for very old Pillow versions | |
| text_w, text_h = draw.textsize(main_text, font=font) | |
| cx, cy = CANVAS_W // 2, CANVAS_H // 2 | |
| text_x = cx - (text_w // 2) | |
| text_y = cy - (text_h // 2) | |
| # Shadow | |
| draw.multiline_text((text_x + 8, text_y + 8), main_text, font=font, align="center", fill="black") | |
| # Main Text | |
| draw.multiline_text((text_x, text_y), main_text, font=font, align="center", fill=font_color, stroke_width=stroke_width, stroke_fill=stroke_color) | |
| # 4. Logo | |
| if logo_file: | |
| logo = Image.open(logo_file).convert("RGBA") | |
| logo.thumbnail((logo_size, logo_size), Image.Resampling.LANCZOS) | |
| pad = 30 | |
| if "Top" in logo_pos: ly = pad | |
| elif "Bottom" in logo_pos: ly = CANVAS_H - logo.height - pad | |
| if "Left" in logo_pos: lx = pad | |
| elif "Right" in logo_pos: lx = CANVAS_W - logo.width - pad | |
| txt_layer.paste(logo, (lx, ly), logo) | |
| # Final Composite | |
| final_comp = Image.alpha_composite(canvas, txt_layer) | |
| # --- DISPLAY & DOWNLOAD --- | |
| # Centered layout for PC look | |
| col_show, col_down = st.columns([3, 1]) | |
| with col_show: | |
| # FIX: Changed use_container_width to use_column_width | |
| st.image(final_comp, caption="Result", use_column_width=True) | |
| with col_down: | |
| st.write("### Ready?") | |
| buf = io.BytesIO() | |
| final_comp.convert("RGB").save(buf, format="PNG") | |
| st.download_button("💾 Download PNG", data=buf.getvalue(), file_name="thumbnail.png", mime="image/png") | |
| # --- MAIN ROUTER (Paste this at the VERY END of app.py) --- | |
| # This checks the URL params and decides which function to run | |
| if __name__ == "__main__": | |
| st.set_page_config(page_title="Lexical Space Tools", layout="centered") | |
| # Get the 'mode' from the URL (e.g. ?mode=youtube) | |
| params = st.query_params | |
| mode = params.get("mode", "home") | |
| # --- BATCH 2: WEBMASTER & SEO FUNCTIONS --- | |
| def tool_meta_tag_generator(): | |
| st.header("🏷️ Meta Tag Generator") | |
| title = st.text_input("Site Title", "My Awesome Blog") | |
| desc = st.text_area("Description", "A blog about technology and coding.") | |
| keywords = st.text_input("Keywords (comma separated)", "tech, coding, python") | |
| author = st.text_input("Author", "Lexical Space") | |
| if st.button("Generate Tags"): | |
| code = f""" | |
| <title>{title}</title> | |
| <meta name="description" content="{desc}"> | |
| <meta name="keywords" content="{keywords}"> | |
| <meta name="author" content="{author}"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <meta property="og:type" content="website"> | |
| <meta property="og:title" content="{title}"> | |
| <meta property="og:description" content="{desc}"> | |
| """ | |
| st.code(code, language="html") | |
| def tool_slug_generator(): | |
| st.header("🐌 URL Slug Generator") | |
| text = st.text_input("Enter Post Title", "How to Install Python on Windows 10!") | |
| if text: | |
| # Lowercase, strip whitespace, replace spaces with dashes, remove non-alphanumeric | |
| slug = text.lower().strip() | |
| slug = re.sub(r'[^a-z0-9\s-]', '', slug) | |
| slug = re.sub(r'[\s-]+', '-', slug) | |
| st.success(f"Slug: {slug}") | |
| st.code(slug, language="text") | |
| def tool_robots_generator(): | |
| st.header("🤖 Robots.txt Generator") | |
| st.write("Control which crawlers can access your site.") | |
| all_agents = st.checkbox("Apply to all robots (*)", value=True) | |
| disallow_admin = st.checkbox("Disallow /admin", value=True) | |
| disallow_private = st.checkbox("Disallow /private", value=False) | |
| sitemap_url = st.text_input("Sitemap URL (Optional)", "https://yoursite.com/sitemap.xml") | |
| if st.button("Generate Robots.txt"): | |
| agent = "*" if all_agents else "Googlebot" | |
| txt = f"User-agent: {agent}\n" | |
| if disallow_admin: txt += "Disallow: /admin/\n" | |
| if disallow_private: txt += "Disallow: /private/\n" | |
| if sitemap_url: txt += f"\nSitemap: {sitemap_url}" | |
| st.text_area("Result", txt, height=150) | |
| def tool_sitemap_builder(): | |
| st.header("🗺️ XML Sitemap Builder") | |
| urls = st.text_area("Paste URLs (one per line)", "https://site.com\nhttps://site.com/about") | |
| if st.button("Build XML"): | |
| xml = '<?xml version="1.0" encoding="UTF-8"?>\n' | |
| xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' | |
| for url in urls.split('\n'): | |
| if url.strip(): | |
| xml += f' <url>\n <loc>{url.strip()}</loc>\n <changefreq>monthly</changefreq>\n </url>\n' | |
| xml += '</urlset>' | |
| st.text_area("Sitemap.xml", xml, height=200) | |
| def tool_keyword_density(): | |
| with st.spinner("Loading Analytics..."): | |
| import pandas as pd | |
| from collections import Counter | |
| st.header("📊 Keyword Density Checker") | |
| text = st.text_area("Paste Article Text", height=200,key="density_text") | |
| if st.button("Analyze"): | |
| # Simple stopword list to ignore | |
| stopwords = set(['the', 'and', 'is', 'in', 'it', 'of', 'to', 'a', 'for', 'on', 'that', 'with', 'as']) | |
| words = re.findall(r'\w+', text.lower()) | |
| filtered = [w for w in words if w not in stopwords and len(w) > 2] | |
| counts = Counter(filtered).most_common(10) | |
| df = pd.DataFrame(counts, columns=["Keyword", "Count"]) | |
| df['Density %'] = (df['Count'] / len(words) * 100).round(2) | |
| st.table(df) | |
| def tool_plagiarism_check(): | |
| st.header("🕵️ Plagiarism Scanner (Google Check)") | |
| st.info("Splits text into sentences and searches Google for exact matches.") | |
| text = st.text_area("Paste Text to Check", height=150,key="plag_text") | |
| if st.button("Check Text"): | |
| sentences = re.split(r'[.!?]', text) | |
| clean_sentences = [s.strip() for s in sentences if len(s.strip()) > 20] | |
| for i, s in enumerate(clean_sentences[:5]): # Limit to first 5 for demo | |
| query = f'"{s}"' | |
| url = f"https://www.google.com/search?q={query}" | |
| st.markdown(f"**Sentence {i+1}:** {s[:50]}...") | |
| st.link_button(f"🔍 Check Google for Match", url) | |
| def tool_code_minifier(): | |
| st.header("🧹 Code Minifier") | |
| mode = st.radio("Type", ["HTML", "CSS"]) | |
| raw_code = st.text_area("Input Code", height=200) | |
| if st.button("Minify"): | |
| minified = "" | |
| if mode == "HTML": | |
| # Basic whitespace removal between tags | |
| lines = raw_code.split('\n') | |
| minified = "".join([line.strip() for line in lines]) | |
| elif mode == "CSS": | |
| # Remove comments and whitespace | |
| # 1. Remove comments | |
| minified = re.sub(r'/\*[\s\S]*?\*/', '', raw_code) | |
| # 2. Remove whitespace around braces/colons | |
| minified = re.sub(r'\s*([{:;,])\s*', r'\1', minified) | |
| # 3. Remove newlines | |
| minified = minified.replace('\n', '').replace('\r', '') | |
| st.text_area("Minified Output", minified, height=200) | |
| # --- BATCH 3: DEVELOPER TOOLS --- | |
| def tool_qr_generator(): | |
| with st.spinner("Initializing QR Tool..."): | |
| import qrcode | |
| import io | |
| st.header("🏁 QR Code Generator") | |
| data = st.text_input("Enter Link or Text", "https://lexicalspace.blogspot.com") | |
| if data: | |
| qr = qrcode.QRCode(version=1, box_size=10, border=5) | |
| qr.add_data(data) | |
| qr.make(fit=True) | |
| img = qr.make_image(fill='black', back_color='white') | |
| buf = io.BytesIO() | |
| img.save(buf) | |
| st.image(img.get_image(), width=300) | |
| st.download_button("Download QR", data=buf.getvalue(), file_name="qrcode.png") | |
| def tool_json_formatter(): | |
| st.header("✨ JSON Prettifier") | |
| raw = st.text_area("Paste Messy JSON", '{"id":1,"name":"Lexical","roles":["admin","dev"]}', height=150) | |
| col1, col2 = st.columns(2) | |
| if col1.button("Format (Pretty)"): | |
| try: | |
| parsed = json.loads(raw) | |
| st.code(json.dumps(parsed, indent=4), language="json") | |
| except Exception as e: | |
| st.error(f"Invalid JSON: {e}") | |
| if col2.button("Minify (Compact)"): | |
| try: | |
| parsed = json.loads(raw) | |
| st.code(json.dumps(parsed, separators=(',', ':')), language="json") | |
| except Exception as e: | |
| st.error(f"Invalid JSON: {e}") | |
| def tool_base64(): | |
| st.header("🔐 Base64 Converter") | |
| mode = st.radio("Action", ["Encode", "Decode"], horizontal=True) | |
| text = st.text_area("Input Text") | |
| if st.button("Process"): | |
| try: | |
| if mode == "Encode": | |
| res = base64.b64encode(text.encode()).decode() | |
| else: | |
| res = base64.b64decode(text).decode() | |
| st.code(res) | |
| except Exception as e: | |
| st.error(f"Error: {e}") | |
| def tool_url_encoder(): | |
| st.header("🔗 URL Encoder/Decoder") | |
| text = st.text_input("Input URL", "https://example.com/search?q=hello world") | |
| c1, c2 = st.columns(2) | |
| with c1: | |
| if st.button("Encode"): | |
| st.code(urllib.parse.quote(text)) | |
| with c2: | |
| if st.button("Decode"): | |
| st.code(urllib.parse.unquote(text)) | |
| def tool_markdown_editor(): | |
| st.header("📝 Markdown Editor") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| md_text = st.text_area("Write Markdown", "# Hello\n* Item 1\n* Item 2", height=400) | |
| with col2: | |
| st.markdown("### Preview") | |
| st.markdown(md_text) | |
| def tool_regex_tester(): | |
| st.header("🧪 Regex Tester") | |
| pattern = st.text_input("Regex Pattern", r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b") | |
| text = st.text_area("Test String", "Contact us at support@lexical.com or admin@site.org") | |
| if pattern and text: | |
| try: | |
| matches = re.findall(pattern, text) | |
| st.write(f"Found {len(matches)} matches:") | |
| st.json(matches) | |
| except Exception as e: | |
| st.error(f"Regex Error: {e}") | |
| def tool_uuid_gen(): | |
| st.header("🆔 UUID/GUID Generator") | |
| count = st.number_input("How many?", 1, 100, 5) | |
| if st.button("Generate"): | |
| uuids = [str(uuid.uuid4()) for _ in range(count)] | |
| st.code("\n".join(uuids), language="text") | |
| def tool_hash_gen(): | |
| st.header("🔑 Hash Generator") | |
| text = st.text_input("Input String", "mypassword") | |
| if text: | |
| st.write("**MD5:**") | |
| st.code(hashlib.md5(text.encode()).hexdigest()) | |
| st.write("**SHA256:**") | |
| st.code(hashlib.sha256(text.encode()).hexdigest()) | |
| # ... (Batch 1 & 2 Routing above) ... | |
| # BATCH 3 ROUTING | |
| elif mode == "qrcode": tool_qr_generator() | |
| elif mode == "json": tool_json_formatter() | |
| elif mode == "base64": tool_base64() | |
| elif mode == "url": tool_url_encoder() | |
| elif mode == "markdown": tool_markdown_editor() | |
| elif mode == "regex": tool_regex_tester() | |
| elif mode == "uuid": tool_uuid_gen() | |
| elif mode == "hash": tool_hash_gen() | |
| # ... (Home Dashboard below) ... | |
| st.write("### 🛠️ Developer Tools") | |
| st.markdown(""" | |
| * [🏁 QR Code Gen](?mode=qrcode) | |
| * [✨ JSON Prettifier](?mode=json) | |
| * [🔐 Base64 Converter](?mode=base64) | |
| * [🔗 URL Encode/Decode](?mode=url) | |
| * [📝 Markdown Editor](?mode=markdown) | |
| * [🧪 Regex Tester](?mode=regex) | |
| * [🆔 UUID Generator](?mode=uuid) | |
| * [🔑 Hash Generator](?mode=hash) | |
| """) | |
| # --- BATCH 4: TEXT, UTILITIES & EXTRAS --- | |
| def tool_case_converter(): | |
| st.header("🔠 Case Converter") | |
| text = st.text_area("Input Text", "hello world") | |
| c1, c2, c3, c4 = st.columns(4) | |
| if c1.button("UPPERCASE"): st.code(text.upper(), language="text") | |
| if c2.button("lowercase"): st.code(text.lower(), language="text") | |
| if c3.button("Title Case"): st.code(text.title(), language="text") | |
| if c4.button("aLtErNaTiNg"): | |
| res = "".join([c.upper() if i%2==0 else c.lower() for i, c in enumerate(text)]) | |
| st.code(res, language="text") | |
| def tool_lorem_ipsum(): | |
| st.header("📜 Lorem Ipsum Generator") | |
| paras = st.slider("Paragraphs", 1, 10, 3) | |
| dummy_text = [ | |
| "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", | |
| "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", | |
| "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.", | |
| "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum.", | |
| "Excepteur sint occaecat cupidatat non proident, sunt in culpa." | |
| ] | |
| if st.button("Generate"): | |
| result = "\n\n".join([random.choice(dummy_text) * 3 for _ in range(paras)]) | |
| st.text_area("Result", result, height=200) | |
| def tool_word_counter(): | |
| st.header("🧮 Word & Character Counter") | |
| text = st.text_area("Paste Text Here", height=200,key="counter_text") | |
| if text: | |
| words = len(text.split()) | |
| chars = len(text) | |
| no_space = len(text.replace(" ", "")) | |
| read_time = round(words / 200, 2) | |
| c1, c2, c3, c4 = st.columns(4) | |
| c1.metric("Words", words) | |
| c2.metric("Chars", chars) | |
| c3.metric("No Spaces", no_space) | |
| c4.metric("Read Time", f"{read_time} min") | |
| def tool_remove_duplicates(): | |
| st.header("🗑️ Remove Duplicate Lines") | |
| text = st.text_area("Paste List (One per line)", "Apple\nBanana\nApple\nOrange") | |
| if st.button("Clean"): | |
| lines = text.split('\n') | |
| seen = set() | |
| clean = [] | |
| for line in lines: | |
| if line not in seen and line.strip(): | |
| clean.append(line) | |
| seen.add(line) | |
| st.text_area("Cleaned List", "\n".join(clean), height=200) | |
| def tool_text_to_speech(): | |
| with st.spinner("Loading Audio Engine..."): | |
| from gtts import gTTS | |
| import io | |
| st.header("🗣️ Text to Speech") | |
| text = st.text_area("Enter Text", "Hello, welcome to Lexical Space.") | |
| lang = st.selectbox("Language", ["en", "es", "fr", "de", "hi"],key="tts_text") | |
| if st.button("Speak"): | |
| try: | |
| tts = gTTS(text=text, lang=lang, slow=False) | |
| buf = io.BytesIO() | |
| tts.write_to_fp(buf) | |
| st.audio(buf, format='audio/mp3') | |
| except Exception as e: | |
| st.error(f"Error: {e}") | |
| def tool_timestamp(): | |
| st.header("⏰ Unix Timestamp Converter") | |
| now = int(time.time()) | |
| st.write(f"Current Timestamp: `{now}`") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| ts_input = st.number_input("Timestamp to Date", value=now) | |
| if st.button("Convert to Date"): | |
| st.success(datetime.datetime.fromtimestamp(ts_input)) | |
| with col2: | |
| d_input = st.date_input("Date to Timestamp") | |
| if st.button("Convert to Timestamp"): | |
| ts = int(time.mktime(d_input.timetuple())) | |
| st.success(ts) | |
| def tool_color_palette(): | |
| st.header("🎨 Image Color Palette") | |
| # ADD key="palette" | |
| uploaded_file = st.file_uploader("Upload Image", type=['jpg', 'png'], key="palette") | |
| if uploaded_file: | |
| img = Image.open(uploaded_file).convert("RGB") | |
| st.image(img, width=200) | |
| # Simple extraction by resizing to 5 pixels | |
| small = img.resize((5, 1)) | |
| colors = small.getdata() | |
| st.write("Dominant Colors:") | |
| cols = st.columns(5) | |
| for i, color in enumerate(colors): | |
| hex_code = '#{:02x}{:02x}{:02x}'.format(*color) | |
| cols[i].color_picker(f"Color {i+1}", hex_code, disabled=True) | |
| cols[i].code(hex_code) | |
| def tool_password_strength(): | |
| st.header("💪 Password Strength") | |
| pwd = st.text_input("Test Password", type="password") | |
| if pwd: | |
| score = 0 | |
| if len(pwd) >= 8: score += 1 | |
| if re.search(r"[A-Z]", pwd): score += 1 | |
| if re.search(r"[a-z]", pwd): score += 1 | |
| if re.search(r"\d", pwd): score += 1 | |
| if re.search(r"[!@#$%^&*]", pwd): score += 1 | |
| st.progress(score / 5) | |
| if score < 3: st.warning("Weak") | |
| elif score < 5: st.info("Moderate") | |
| else: st.success("Strong!") | |
| def tool_aspect_ratio(): | |
| st.header("🖥️ Aspect Ratio Calculator") | |
| w = st.number_input("Width", 1920) | |
| h = st.number_input("Height", 1080) | |
| if w and h: | |
| def gcd(a, b): | |
| while b: a, b = b, a % b | |
| return a | |
| divisor = gcd(int(w), int(h)) | |
| st.metric("Aspect Ratio", f"{int(w/divisor)}:{int(h/divisor)}") | |
| def tool_stopwatch(): | |
| st.header("⏱️ Stopwatch") | |
| if 'start_time' not in st.session_state: st.session_state.start_time = None | |
| if st.button("Start/Reset"): | |
| st.session_state.start_time = time.time() | |
| if st.session_state.start_time: | |
| elapsed = time.time() - st.session_state.start_time | |
| st.metric("Time Elapsed", f"{elapsed:.2f}s") | |
| if st.button("Stop"): | |
| st.session_state.start_time = None | |
| def tool_python_checker(): | |
| st.header("🐍 Python Syntax & Error Checker") | |
| st.markdown("Paste your Python code or upload a `.py` file to check for syntax errors.") | |
| # Import dependencies inside the function to avoid global scope clutter | |
| import tempfile | |
| import os | |
| import io | |
| try: | |
| from pylint.lint import Run | |
| from pylint.reporters.text import TextReporter | |
| except ImportError: | |
| st.error("⚠️ Pylint is not installed. Please add `pylint` to your requirements.txt") | |
| return | |
| # --- INPUTS --- | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| paste_code = st.text_area("Paste Code Here", height=300) | |
| with col2: | |
| file_obj = st.file_uploader("Or Upload .py File", type=[".py"]) | |
| # --- PROCESS BUTTON --- | |
| if st.button("Check Syntax 🚀", type="primary"): | |
| code_to_check = "" | |
| # Logic: Prefer File > Paste | |
| if file_obj is not None: | |
| try: | |
| code_to_check = file_obj.getvalue().decode("utf-8") | |
| except Exception as e: | |
| st.error(f"❌ Error reading file: {str(e)}") | |
| return | |
| elif paste_code.strip() != "": | |
| code_to_check = paste_code | |
| else: | |
| st.warning("⚠️ Please either paste code or upload a file.") | |
| return | |
| # --- LINTING LOGIC --- | |
| # 1. Create Temp File (Pylint needs a file on disk) | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".py", mode='w', encoding='utf-8') as temp: | |
| temp.write(code_to_check) | |
| temp_path = temp.name | |
| # 2. Run Pylint | |
| pylint_output = io.StringIO() | |
| reporter = TextReporter(pylint_output) | |
| with st.spinner("Analyzing syntax..."): | |
| try: | |
| # --errors-only hides warnings, showing only code-breaking errors | |
| Run([temp_path, "--errors-only"], reporter=reporter, exit=False) | |
| except Exception as e: | |
| st.error(f"System Error: {e}") | |
| # 3. Cleanup & Display | |
| os.unlink(temp_path) # Delete temp file | |
| result = pylint_output.getvalue() | |
| st.markdown("---") | |
| if not result: | |
| st.success("✅ No Syntax Errors Found! (Code looks valid)") | |
| st.balloons() | |
| else: | |
| st.error("❌ Errors Found:") | |
| # Hide the messy temp file path from the user | |
| clean_report = result.replace(temp_path, "Your_Script.py") | |
| st.code(clean_report, language="text") | |
| # --- FINAL MAIN ROUTER (SPA VERSION) --- | |
| if __name__ == "__main__": | |
| # 1. INITIALIZE SESSION STATE (This replaces the URL logic) | |
| if 'mode' not in st.session_state: | |
| st.session_state.mode = 'home' | |
| # 3. NAVIGATION HANDLING | |
| # Function to change mode without URL reload | |
| def set_mode(new_mode): | |
| st.session_state.mode = new_mode | |
| # Show "Back to Dashboard" button if not home | |
| if st.session_state.mode != 'home': | |
| if st.button("⬅️ Back to Grid"): | |
| set_mode('home') | |
| st.rerun() | |
| # 4. TOOL ROUTING (Checks session_state instead of URL) | |
| mode = st.session_state.mode | |
| if mode == "youtube": tool_youtube_downloader() | |
| elif mode == "smart_converter": tool_smart_converter() | |
| elif mode == "compressor": tool_image_compressor() | |
| elif mode == "resizer": tool_image_resizer() | |
| elif mode == "thumbnail": tool_thumbnail_generator() | |
| elif mode == "metatags": tool_meta_tag_generator() | |
| elif mode == "slug": tool_slug_generator() | |
| elif mode == "robots": tool_robots_generator() | |
| elif mode == "sitemap": tool_sitemap_builder() | |
| elif mode == "density": tool_keyword_density() | |
| elif mode == "plagiarism": tool_plagiarism_check() | |
| elif mode == "minify": tool_code_minifier() | |
| elif mode == "qrcode": tool_qr_generator() | |
| elif mode == "json": tool_json_formatter() | |
| elif mode == "base64": tool_base64() | |
| elif mode == "url": tool_url_encoder() | |
| elif mode == "markdown": tool_markdown_editor() | |
| elif mode == "regex": tool_regex_tester() | |
| elif mode == "uuid": tool_uuid_gen() | |
| elif mode == "hash": tool_hash_gen() | |
| elif mode == "case": tool_case_converter() | |
| elif mode == "lorem": tool_lorem_ipsum() | |
| elif mode == "counter": tool_word_counter() | |
| elif mode == "dedupe": tool_remove_duplicates() | |
| elif mode == "tts": tool_text_to_speech() | |
| elif mode == "timestamp": tool_timestamp() | |
| elif mode == "palette": tool_color_palette() | |
| elif mode == "password": tool_password_strength() | |
| elif mode == "ratio": tool_aspect_ratio() | |
| elif mode == "stopwatch": tool_stopwatch() | |
| elif mode == "python": tool_python_checker() | |
| elif mode == "seo": render_seo_ui() | |
| elif mode == "Pdf Converter": run_ultimate_pdf_converter() | |
| elif mode == "Replacer Tool": lexical_replacer_tool() | |
| # 5. HOME DASHBOARD (Button Grid) | |
| else: | |
| st.write("### ⚡ Select a tool to get started:") | |
| # We use standard Streamlit columns to create a grid layout | |
| # This replaces the Markdown links with actual Buttons | |
| c1, c2 = st.columns(2) | |
| with c1: | |
| st.info("**📂 Media & Files**") | |
| if st.button("Converter"): set_mode("Replacer Tool"); st.rerun() | |
| if st.button("🎥 Pdf"): set_mode("Pdf Converter"); st.rerun() | |
| if st.button("🎥 YouTube Downloader"): set_mode("youtube"); st.rerun() | |
| if st.button("🎥 Seo Generator"): set_mode("seo"); st.rerun() | |
| if st.button("🔄 Smart File Converter"): set_mode("smart_converter"); st.rerun() | |
| if st.button("📉 Image Compressor"): set_mode("compressor"); st.rerun() | |
| if st.button("📐 Image Resizer"): set_mode("resizer"); st.rerun() | |
| if st.button("🖼️ Thumbnail Gen"): set_mode("thumbnail"); st.rerun() | |
| st.info("**🛠️ Developer Tools**") | |
| if st.button("🏁 QR Code Gen"): set_mode("qrcode"); st.rerun() | |
| if st.button("✨ JSON Prettifier"): set_mode("json"); st.rerun() | |
| if st.button("🔐 Base64 Converter"): set_mode("base64"); st.rerun() | |
| if st.button("🔗 URL Encoder"): set_mode("url"); st.rerun() | |
| if st.button("📝 Markdown Editor"): set_mode("markdown"); st.rerun() | |
| if st.button("🧪 Regex Tester"): set_mode("regex"); st.rerun() | |
| if st.button("🆔 UUID Gen"): set_mode("uuid"); st.rerun() | |
| if st.button("🔑 Hash Gen"): set_mode("hash"); st.rerun() | |
| with c2: | |
| st.info("**🕸️ SEO & Webmaster**") | |
| if st.button("🏷️ Meta Tag Gen"): set_mode("metatags"); st.rerun() | |
| if st.button("🐌 Slug Generator"): set_mode("slug"); st.rerun() | |
| if st.button("🤖 Robots.txt Gen"): set_mode("robots"); st.rerun() | |
| if st.button("🗺️ Sitemap Builder"): set_mode("sitemap"); st.rerun() | |
| if st.button("📊 Density Checker"): set_mode("density"); st.rerun() | |
| if st.button("🕵️ Plagiarism Check"): set_mode("plagiarism"); st.rerun() | |
| if st.button("🧹 Code Minifier"): set_mode("minify"); st.rerun() | |
| st.info("**📝 Text & Utilities**") | |
| if st.button("🔠 Case Converter"): set_mode("case"); st.rerun() | |
| if st.button("📜 Lorem Ipsum"): set_mode("lorem"); st.rerun() | |
| if st.button("🧮 Word Counter"): set_mode("counter"); st.rerun() | |
| if st.button("🗑️ Dedupe Lines"): set_mode("dedupe"); st.rerun() | |
| if st.button("🗣️ Text to Speech"): set_mode("tts"); st.rerun() | |
| if st.button("⏰ Unix Timestamp"): set_mode("timestamp"); st.rerun() | |
| if st.button("🎨 Color Palette"): set_mode("palette"); st.rerun() | |
| if st.button("💪 Password Strength"): set_mode("password"); st.rerun() | |
| if st.button("🖥️ Aspect Ratio"): set_mode("ratio"); st.rerun() | |
| if st.button("⏱️ Stopwatch"): set_mode("stopwatch"); st.rerun() | |
| if st.button("Python Checker"): set_mode("python"); st.rerun() |