Delete app.py
Browse files
app.py
DELETED
|
@@ -1,1027 +0,0 @@
|
|
| 1 |
-
import streamlit as st
|
| 2 |
-
import requests
|
| 3 |
-
from PIL import Image, ImageDraw, ImageFont
|
| 4 |
-
import io
|
| 5 |
-
import time
|
| 6 |
-
import json
|
| 7 |
-
import base64
|
| 8 |
-
import uuid
|
| 9 |
-
import hashlib
|
| 10 |
-
import urllib.parse
|
| 11 |
-
import random
|
| 12 |
-
import datetime
|
| 13 |
-
import re
|
| 14 |
-
from collections import Counter
|
| 15 |
-
from bs4 import BeautifulSoup
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
# --- BATCH 1: MEDIA & FILE FUNCTIONS ---
|
| 20 |
-
import gradio as gr
|
| 21 |
-
import os
|
| 22 |
-
from huggingface_hub import InferenceClient
|
| 23 |
-
|
| 24 |
-
def run_seo_app():
|
| 25 |
-
"""
|
| 26 |
-
Encapsulates the entire SEO generation system, authentication, and UI
|
| 27 |
-
into a single executable function.
|
| 28 |
-
"""
|
| 29 |
-
|
| 30 |
-
# --- 1. SETUP & AUTHENTICATION ---
|
| 31 |
-
# Get the token from the Space secrets to fix "Auth" errors
|
| 32 |
-
hf_token = os.getenv("HF_TOKEN")
|
| 33 |
-
|
| 34 |
-
# Initialize the client with Qwen 2.5 Coder 32B
|
| 35 |
-
model_id = "Qwen/Qwen2.5-Coder-32B-Instruct"
|
| 36 |
-
client = InferenceClient(token=hf_token)
|
| 37 |
-
|
| 38 |
-
# --- 2. LOGIC FUNCTION ---
|
| 39 |
-
def generate_seo(code_snippet, file_type):
|
| 40 |
-
if not code_snippet.strip():
|
| 41 |
-
return "⚠️ Error: Please paste some code first."
|
| 42 |
-
|
| 43 |
-
# Define the strict SEO prompt
|
| 44 |
-
system_instruction = f"""
|
| 45 |
-
You are an expert Technical SEO Specialist. Analyze the user's {file_type} code.
|
| 46 |
-
|
| 47 |
-
Your Goal: Generate Google-compliant JSON-LD structured data and SEO meta tags.
|
| 48 |
-
|
| 49 |
-
Output Format (Strict Markdown):
|
| 50 |
-
## SEO Metadata
|
| 51 |
-
**Title:** [Engaging Title, max 60 chars]
|
| 52 |
-
**Description:** [Summary including keywords, max 160 chars]
|
| 53 |
-
**Keywords:** [5-8 comma-separated keywords]
|
| 54 |
-
|
| 55 |
-
## JSON-LD Structured Data
|
| 56 |
-
```json
|
| 57 |
-
[Insert VALID JSON-LD here.
|
| 58 |
-
- If Python: Use schema.org/SoftwareSourceCode
|
| 59 |
-
- If HTML: Use schema.org/WebPage or schema.org/TechArticle]
|
| 60 |
-
```
|
| 61 |
-
"""
|
| 62 |
-
|
| 63 |
-
user_message = f"Analyze this {file_type} code:\n\n{code_snippet}"
|
| 64 |
-
|
| 65 |
-
try:
|
| 66 |
-
# Call the Chat Completion API
|
| 67 |
-
response = client.chat_completion(
|
| 68 |
-
model=model_id,
|
| 69 |
-
messages=[
|
| 70 |
-
{"role": "system", "content": system_instruction},
|
| 71 |
-
{"role": "user", "content": user_message}
|
| 72 |
-
],
|
| 73 |
-
max_tokens=1500,
|
| 74 |
-
temperature=0.2
|
| 75 |
-
)
|
| 76 |
-
return response.choices[0].message.content
|
| 77 |
-
|
| 78 |
-
except Exception as e:
|
| 79 |
-
# Error handling logic
|
| 80 |
-
error_msg = str(e)
|
| 81 |
-
if "401" in error_msg:
|
| 82 |
-
return "🔒 Authentication Error: Please check that you added 'HF_TOKEN' to your Space Secrets."
|
| 83 |
-
elif "429" in error_msg:
|
| 84 |
-
return "⏳ Rate Limit: The free model is busy. Please wait 1 minute and try again."
|
| 85 |
-
elif "504" in error_msg:
|
| 86 |
-
return "⏱️ Timeout: The code snippet might be too long. Try a shorter piece of code."
|
| 87 |
-
else:
|
| 88 |
-
return f"❌ System Error: {error_msg}"
|
| 89 |
-
|
| 90 |
-
# --- 3. UI CONSTRUCTION ---
|
| 91 |
-
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 92 |
-
gr.Markdown(
|
| 93 |
-
"""
|
| 94 |
-
# ⚡ SEO & JSON-LD Generator (Authenticated)
|
| 95 |
-
**Status:** ✅ Connected to Qwen 2.5 Coder
|
| 96 |
-
"""
|
| 97 |
-
)
|
| 98 |
-
|
| 99 |
-
with gr.Row():
|
| 100 |
-
with gr.Column(scale=1):
|
| 101 |
-
input_type = gr.Radio(["python", "html"], label="Select File Type", value="python")
|
| 102 |
-
code_input = gr.Code(language="python", label="Paste Code Here", lines=15)
|
| 103 |
-
submit_btn = gr.Button("✨ Generate SEO Data", variant="primary", size="lg")
|
| 104 |
-
|
| 105 |
-
with gr.Column(scale=1):
|
| 106 |
-
output_markdown = gr.Markdown(label="Results will appear here...")
|
| 107 |
-
|
| 108 |
-
# Dynamic syntax highlighting
|
| 109 |
-
input_type.change(lambda x: gr.Code(language=x), inputs=input_type, outputs=code_input)
|
| 110 |
-
|
| 111 |
-
# Button Click Action
|
| 112 |
-
submit_btn.click(
|
| 113 |
-
fn=generate_seo,
|
| 114 |
-
inputs=[code_input, input_type],
|
| 115 |
-
outputs=output_markdown
|
| 116 |
-
)
|
| 117 |
-
|
| 118 |
-
# --- 4. LAUNCH ---
|
| 119 |
-
demo.launch()
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
def tool_youtube_downloader():
|
| 126 |
-
st.header("🎥 YouTube Media Extractor")
|
| 127 |
-
url = st.text_input("Paste YouTube URL", placeholder="https://youtube.com/...")
|
| 128 |
-
format_type = st.radio("Format", ["Video (MP4)", "Audio Only (MP3)"], horizontal=True)
|
| 129 |
-
|
| 130 |
-
if url and st.button("🚀 Process Media"):
|
| 131 |
-
with st.spinner("Contacting server..."):
|
| 132 |
-
try:
|
| 133 |
-
headers = {"Accept": "application/json", "Content-Type": "application/json"}
|
| 134 |
-
payload = {
|
| 135 |
-
"url": url,
|
| 136 |
-
"vQuality": "1080",
|
| 137 |
-
"isAudioOnly": True if "Audio" in format_type else False
|
| 138 |
-
}
|
| 139 |
-
# Using Cobalt API
|
| 140 |
-
response = requests.post("https://api.cobalt.tools/api/json", headers=headers, json=payload)
|
| 141 |
-
data = response.json()
|
| 142 |
-
|
| 143 |
-
if "url" in data:
|
| 144 |
-
st.success("�� Ready!")
|
| 145 |
-
st.link_button(f"⬇️ Download {format_type}", data["url"])
|
| 146 |
-
if "Audio" not in format_type:
|
| 147 |
-
st.video(data["url"])
|
| 148 |
-
else:
|
| 149 |
-
st.audio(data["url"])
|
| 150 |
-
else:
|
| 151 |
-
st.error(f"Error: {data.get('text', 'Unknown error')}")
|
| 152 |
-
except Exception as e:
|
| 153 |
-
st.error(f"Connection failed: {str(e)}")
|
| 154 |
-
|
| 155 |
-
def tool_smart_converter():
|
| 156 |
-
with st.spinner("Starting File Engine..."):
|
| 157 |
-
|
| 158 |
-
time.sleep(0.3)
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
import pandas as pd
|
| 162 |
-
st.header("🔄 Smart File Converter")
|
| 163 |
-
st.info("Supports: Images (PNG/JPG/WEBP) and Data (CSV/JSON/Excel)")
|
| 164 |
-
|
| 165 |
-
uploaded_file = st.file_uploader("Upload File", type=['png', 'jpg', 'jpeg', 'webp', 'csv', 'json', 'xlsx'],key="smart_conv_upload")
|
| 166 |
-
|
| 167 |
-
if uploaded_file:
|
| 168 |
-
file_type = uploaded_file.name.split('.')[-1].lower()
|
| 169 |
-
|
| 170 |
-
# LOGIC: IMAGE CONVERSION
|
| 171 |
-
if file_type in ['png', 'jpg', 'jpeg', 'webp']:
|
| 172 |
-
image = Image.open(uploaded_file)
|
| 173 |
-
st.image(image, caption="Preview", width=300)
|
| 174 |
-
target_format = st.selectbox("Convert to:", ["PNG", "JPEG", "WEBP", "PDF"])
|
| 175 |
-
|
| 176 |
-
if st.button("Convert Image"):
|
| 177 |
-
buf = io.BytesIO()
|
| 178 |
-
# RGB required for JPEG/PDF
|
| 179 |
-
if image.mode in ("RGBA", "P") and target_format in ["JPEG", "PDF"]:
|
| 180 |
-
image = image.convert("RGB")
|
| 181 |
-
|
| 182 |
-
image.save(buf, format=target_format)
|
| 183 |
-
st.download_button(f"Download {target_format}", data=buf.getvalue(), file_name=f"converted.{target_format.lower()}")
|
| 184 |
-
|
| 185 |
-
# LOGIC: DATA CONVERSION
|
| 186 |
-
elif file_type in ['csv', 'json', 'xlsx']:
|
| 187 |
-
df = None
|
| 188 |
-
try:
|
| 189 |
-
if file_type == 'csv': df = pd.read_csv(uploaded_file)
|
| 190 |
-
elif file_type == 'json': df = pd.read_json(uploaded_file)
|
| 191 |
-
elif file_type == 'xlsx': df = pd.read_excel(uploaded_file)
|
| 192 |
-
|
| 193 |
-
st.write("Data Preview:", df.head())
|
| 194 |
-
target_data = st.selectbox("Convert to:", ["CSV", "JSON", "Excel"])
|
| 195 |
-
|
| 196 |
-
if st.button("Convert Data"):
|
| 197 |
-
buf = io.BytesIO()
|
| 198 |
-
if target_data == "CSV":
|
| 199 |
-
df.to_csv(buf, index=False)
|
| 200 |
-
ext = "csv"
|
| 201 |
-
elif target_data == "JSON":
|
| 202 |
-
df.to_json(buf, orient='records')
|
| 203 |
-
ext = "json"
|
| 204 |
-
elif target_data == "Excel":
|
| 205 |
-
df.to_excel(buf, index=False)
|
| 206 |
-
ext = "xlsx"
|
| 207 |
-
|
| 208 |
-
st.download_button(f"Download {target_data}", data=buf.getvalue(), file_name=f"converted.{ext}")
|
| 209 |
-
except Exception as e:
|
| 210 |
-
st.error(f"Error reading file: {e}")
|
| 211 |
-
|
| 212 |
-
def tool_image_compressor():
|
| 213 |
-
st.header("📉 Image Compressor")
|
| 214 |
-
# ADD key="compressor"
|
| 215 |
-
uploaded_file = st.file_uploader("Upload Image", type=['png', 'jpg', 'jpeg'], key="compressor")
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
if uploaded_file:
|
| 220 |
-
image = Image.open(uploaded_file)
|
| 221 |
-
st.write(f"Original Size: {uploaded_file.size / 1024:.2f} KB")
|
| 222 |
-
quality = st.slider("Quality (Lower = Smaller file)", 10, 95, 60)
|
| 223 |
-
|
| 224 |
-
if st.button("Compress"):
|
| 225 |
-
buf = io.BytesIO()
|
| 226 |
-
if image.mode in ("RGBA", "P"): image = image.convert("RGB")
|
| 227 |
-
image.save(buf, format="JPEG", quality=quality, optimize=True)
|
| 228 |
-
|
| 229 |
-
size_kb = len(buf.getvalue()) / 1024
|
| 230 |
-
st.success(f"Compressed Size: {size_kb:.2f} KB")
|
| 231 |
-
st.download_button("Download Compressed Image", data=buf.getvalue(), file_name="compressed.jpg")
|
| 232 |
-
|
| 233 |
-
def tool_image_resizer():
|
| 234 |
-
st.header("📐 Image Resizer")
|
| 235 |
-
# ADD key="resizer"
|
| 236 |
-
uploaded_file = st.file_uploader("Upload Image", type=['png', 'jpg', 'jpeg', 'webp'], key="resizer")
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
if uploaded_file:
|
| 241 |
-
image = Image.open(uploaded_file)
|
| 242 |
-
st.write(f"Current Dimensions: {image.size}")
|
| 243 |
-
|
| 244 |
-
col1, col2 = st.columns(2)
|
| 245 |
-
w = col1.number_input("Width", value=image.width)
|
| 246 |
-
h = col2.number_input("Height", value=image.height)
|
| 247 |
-
|
| 248 |
-
if st.button("Resize"):
|
| 249 |
-
new_img = image.resize((int(w), int(h)))
|
| 250 |
-
buf = io.BytesIO()
|
| 251 |
-
new_img.save(buf, format=image.format)
|
| 252 |
-
st.image(new_img, caption="Resized Preview")
|
| 253 |
-
st.download_button("Download Resized Image", data=buf.getvalue(), file_name=f"resized.{image.format.lower()}")
|
| 254 |
-
|
| 255 |
-
def tool_thumbnail_generator():
|
| 256 |
-
st.header("🚀 Ultimate Thumbnail Studio")
|
| 257 |
-
|
| 258 |
-
# --- DEPENDENCIES ---
|
| 259 |
-
from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageEnhance, ImageOps, ImageColor
|
| 260 |
-
import io
|
| 261 |
-
|
| 262 |
-
# --- LAYOUT FIX FOR PC/MOBILE ---
|
| 263 |
-
# We use a container for the controls to keep them tidy on PC
|
| 264 |
-
# and "use_column_width" to fix the crash.
|
| 265 |
-
|
| 266 |
-
# Top row: All Controls (Split into 2 columns for better PC spacing)
|
| 267 |
-
with st.expander("🎨 Thumbnail Settings & Controls", expanded=True):
|
| 268 |
-
col_c1, col_c2 = st.columns(2)
|
| 269 |
-
|
| 270 |
-
# --- LEFT CONTROL COLUMN ---
|
| 271 |
-
with col_c1:
|
| 272 |
-
st.subheader("1. Background & Filters")
|
| 273 |
-
bg_mode = st.radio("Background Type", ["Upload Image", "Solid Color", "Gradient"], horizontal=True)
|
| 274 |
-
|
| 275 |
-
bg_image = None
|
| 276 |
-
if bg_mode == "Upload Image":
|
| 277 |
-
bg_file = st.file_uploader("Upload BG", type=['png', 'jpg', 'jpeg', 'webp'], key="max_bg")
|
| 278 |
-
if bg_file: bg_image = Image.open(bg_file).convert("RGBA")
|
| 279 |
-
elif bg_mode == "Solid Color":
|
| 280 |
-
hex_bg = st.color_picker("Pick Color", "#1E1E1E")
|
| 281 |
-
st.session_state.thumb_bg_color = hex_bg
|
| 282 |
-
else: # Gradient
|
| 283 |
-
c1_col, c2_col = st.columns(2)
|
| 284 |
-
grad_c1 = c1_col.color_picker("Start", "#12c2e9")
|
| 285 |
-
grad_c2 = c2_col.color_picker("End", "#c471ed")
|
| 286 |
-
grad_dir = st.selectbox("Direction", ["Horizontal", "Vertical"])
|
| 287 |
-
|
| 288 |
-
st.markdown("---")
|
| 289 |
-
st.write("**Filters**")
|
| 290 |
-
f1, f2 = st.columns(2)
|
| 291 |
-
blur_amt = f1.slider("Blur", 0, 20, 0)
|
| 292 |
-
brightness = f2.slider("Brightness", 0.5, 1.5, 1.0)
|
| 293 |
-
|
| 294 |
-
# --- RIGHT CONTROL COLUMN ---
|
| 295 |
-
with col_c2:
|
| 296 |
-
st.subheader("2. Text & Branding")
|
| 297 |
-
main_text = st.text_area("Main Title", "THE ULTIMATE\nGUIDE TO PYTHON", height=100)
|
| 298 |
-
|
| 299 |
-
t1, t2 = st.columns(2)
|
| 300 |
-
font_size = t1.slider("Size", 20, 200, 80)
|
| 301 |
-
font_color = t2.color_picker("Text Color", "#FFFFFF")
|
| 302 |
-
|
| 303 |
-
st.write("**Styling**")
|
| 304 |
-
s1, s2 = st.columns(2)
|
| 305 |
-
stroke_width = s1.slider("Outline", 0, 10, 2)
|
| 306 |
-
stroke_color = s2.color_picker("Outline Color", "#000000")
|
| 307 |
-
|
| 308 |
-
st.markdown("---")
|
| 309 |
-
logo_file = st.file_uploader("Upload Logo (PNG)", type=['png', 'webp'], key="max_logo")
|
| 310 |
-
if logo_file:
|
| 311 |
-
l1, l2 = st.columns(2)
|
| 312 |
-
logo_size = l1.slider("Logo Size", 50, 300, 150)
|
| 313 |
-
logo_pos = l2.selectbox("Position", ["Top-Right", "Top-Left", "Bottom-Right", "Bottom-Left"])
|
| 314 |
-
else:
|
| 315 |
-
logo_size = 150
|
| 316 |
-
logo_pos = "Top-Right"
|
| 317 |
-
|
| 318 |
-
# --- PREVIEW SECTION (Full Width for PC clarity) ---
|
| 319 |
-
st.subheader("👁️ Preview & Download")
|
| 320 |
-
|
| 321 |
-
# GENERATION LOGIC
|
| 322 |
-
CANVAS_W, CANVAS_H = 1280, 720
|
| 323 |
-
canvas = Image.new("RGBA", (CANVAS_W, CANVAS_H), (0,0,0,0))
|
| 324 |
-
|
| 325 |
-
# 1. Background
|
| 326 |
-
if bg_mode == "Upload Image" and bg_image:
|
| 327 |
-
bg_ratio = bg_image.width / bg_image.height
|
| 328 |
-
target_ratio = CANVAS_W / CANVAS_H
|
| 329 |
-
if bg_ratio > target_ratio:
|
| 330 |
-
new_h = CANVAS_H
|
| 331 |
-
new_w = int(new_h * bg_ratio)
|
| 332 |
-
else:
|
| 333 |
-
new_w = CANVAS_W
|
| 334 |
-
new_h = int(new_w / bg_ratio)
|
| 335 |
-
bg_image = bg_image.resize((new_w, new_h), Image.Resampling.LANCZOS)
|
| 336 |
-
left = (new_w - CANVAS_W)/2
|
| 337 |
-
top = (new_h - CANVAS_H)/2
|
| 338 |
-
bg_image = bg_image.crop((left, top, left+CANVAS_W, top+CANVAS_H))
|
| 339 |
-
canvas.paste(bg_image, (0,0))
|
| 340 |
-
elif bg_mode == "Gradient":
|
| 341 |
-
base = Image.new('RGB', (CANVAS_W, CANVAS_H), grad_c1)
|
| 342 |
-
top_img = Image.new('RGB', (CANVAS_W, CANVAS_H), grad_c2)
|
| 343 |
-
mask = Image.new("L", (CANVAS_W, CANVAS_H))
|
| 344 |
-
mask_data = []
|
| 345 |
-
for y in range(CANVAS_H):
|
| 346 |
-
for x in range(CANVAS_W):
|
| 347 |
-
if grad_dir == "Vertical": mask_data.append(int(255 * (y / CANVAS_H)))
|
| 348 |
-
else: mask_data.append(int(255 * (x / CANVAS_W)))
|
| 349 |
-
mask.putdata(mask_data)
|
| 350 |
-
canvas = Image.composite(top_img, base, mask).convert("RGBA")
|
| 351 |
-
else:
|
| 352 |
-
if 'thumb_bg_color' not in st.session_state: st.session_state.thumb_bg_color = "#1E1E1E"
|
| 353 |
-
canvas = Image.new("RGBA", (CANVAS_W, CANVAS_H), st.session_state.thumb_bg_color)
|
| 354 |
-
|
| 355 |
-
# 2. Filters
|
| 356 |
-
if blur_amt > 0: canvas = canvas.filter(ImageFilter.GaussianBlur(blur_amt))
|
| 357 |
-
if brightness != 1.0:
|
| 358 |
-
enhancer = ImageEnhance.Brightness(canvas)
|
| 359 |
-
canvas = enhancer.enhance(brightness)
|
| 360 |
-
|
| 361 |
-
# 3. Text
|
| 362 |
-
txt_layer = Image.new("RGBA", canvas.size, (255,255,255,0))
|
| 363 |
-
draw = ImageDraw.Draw(txt_layer)
|
| 364 |
-
|
| 365 |
-
try:
|
| 366 |
-
font = ImageFont.truetype("arialbd.ttf", font_size)
|
| 367 |
-
except:
|
| 368 |
-
try:
|
| 369 |
-
font = ImageFont.truetype("arial.ttf", font_size)
|
| 370 |
-
except:
|
| 371 |
-
font = ImageFont.load_default()
|
| 372 |
-
|
| 373 |
-
# Center Text Calculation
|
| 374 |
-
# Using basic textsize for compatibility if textbbox fails in older Pillow
|
| 375 |
-
try:
|
| 376 |
-
bbox = draw.multiline_textbbox((0,0), main_text, font=font, align="center")
|
| 377 |
-
text_w = bbox[2] - bbox[0]
|
| 378 |
-
text_h = bbox[3] - bbox[1]
|
| 379 |
-
except:
|
| 380 |
-
# Fallback for very old Pillow versions
|
| 381 |
-
text_w, text_h = draw.textsize(main_text, font=font)
|
| 382 |
-
|
| 383 |
-
cx, cy = CANVAS_W // 2, CANVAS_H // 2
|
| 384 |
-
text_x = cx - (text_w // 2)
|
| 385 |
-
text_y = cy - (text_h // 2)
|
| 386 |
-
|
| 387 |
-
# Shadow
|
| 388 |
-
draw.multiline_text((text_x + 8, text_y + 8), main_text, font=font, align="center", fill="black")
|
| 389 |
-
# Main Text
|
| 390 |
-
draw.multiline_text((text_x, text_y), main_text, font=font, align="center", fill=font_color, stroke_width=stroke_width, stroke_fill=stroke_color)
|
| 391 |
-
|
| 392 |
-
# 4. Logo
|
| 393 |
-
if logo_file:
|
| 394 |
-
logo = Image.open(logo_file).convert("RGBA")
|
| 395 |
-
logo.thumbnail((logo_size, logo_size), Image.Resampling.LANCZOS)
|
| 396 |
-
pad = 30
|
| 397 |
-
if "Top" in logo_pos: ly = pad
|
| 398 |
-
elif "Bottom" in logo_pos: ly = CANVAS_H - logo.height - pad
|
| 399 |
-
if "Left" in logo_pos: lx = pad
|
| 400 |
-
elif "Right" in logo_pos: lx = CANVAS_W - logo.width - pad
|
| 401 |
-
txt_layer.paste(logo, (lx, ly), logo)
|
| 402 |
-
|
| 403 |
-
# Final Composite
|
| 404 |
-
final_comp = Image.alpha_composite(canvas, txt_layer)
|
| 405 |
-
|
| 406 |
-
# --- DISPLAY & DOWNLOAD ---
|
| 407 |
-
# Centered layout for PC look
|
| 408 |
-
col_show, col_down = st.columns([3, 1])
|
| 409 |
-
|
| 410 |
-
with col_show:
|
| 411 |
-
# FIX: Changed use_container_width to use_column_width
|
| 412 |
-
st.image(final_comp, caption="Result", use_column_width=True)
|
| 413 |
-
|
| 414 |
-
with col_down:
|
| 415 |
-
st.write("### Ready?")
|
| 416 |
-
buf = io.BytesIO()
|
| 417 |
-
final_comp.convert("RGB").save(buf, format="PNG")
|
| 418 |
-
st.download_button("💾 Download PNG", data=buf.getvalue(), file_name="thumbnail.png", mime="image/png")
|
| 419 |
-
# --- MAIN ROUTER (Paste this at the VERY END of app.py) ---
|
| 420 |
-
# This checks the URL params and decides which function to run
|
| 421 |
-
if __name__ == "__main__":
|
| 422 |
-
st.set_page_config(page_title="Lexical Space Tools", layout="centered")
|
| 423 |
-
|
| 424 |
-
# Get the 'mode' from the URL (e.g. ?mode=youtube)
|
| 425 |
-
params = st.query_params
|
| 426 |
-
mode = params.get("mode", "home")
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
# --- BATCH 2: WEBMASTER & SEO FUNCTIONS ---
|
| 433 |
-
|
| 434 |
-
def tool_meta_tag_generator():
|
| 435 |
-
st.header("🏷️ Meta Tag Generator")
|
| 436 |
-
title = st.text_input("Site Title", "My Awesome Blog")
|
| 437 |
-
desc = st.text_area("Description", "A blog about technology and coding.")
|
| 438 |
-
keywords = st.text_input("Keywords (comma separated)", "tech, coding, python")
|
| 439 |
-
author = st.text_input("Author", "Lexical Space")
|
| 440 |
-
|
| 441 |
-
if st.button("Generate Tags"):
|
| 442 |
-
code = f"""
|
| 443 |
-
<title>{title}</title>
|
| 444 |
-
<meta name="description" content="{desc}">
|
| 445 |
-
<meta name="keywords" content="{keywords}">
|
| 446 |
-
<meta name="author" content="{author}">
|
| 447 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 448 |
-
<meta property="og:type" content="website">
|
| 449 |
-
<meta property="og:title" content="{title}">
|
| 450 |
-
<meta property="og:description" content="{desc}">
|
| 451 |
-
"""
|
| 452 |
-
st.code(code, language="html")
|
| 453 |
-
|
| 454 |
-
def tool_slug_generator():
|
| 455 |
-
st.header("🐌 URL Slug Generator")
|
| 456 |
-
text = st.text_input("Enter Post Title", "How to Install Python on Windows 10!")
|
| 457 |
-
|
| 458 |
-
if text:
|
| 459 |
-
# Lowercase, strip whitespace, replace spaces with dashes, remove non-alphanumeric
|
| 460 |
-
slug = text.lower().strip()
|
| 461 |
-
slug = re.sub(r'[^a-z0-9\s-]', '', slug)
|
| 462 |
-
slug = re.sub(r'[\s-]+', '-', slug)
|
| 463 |
-
st.success(f"Slug: {slug}")
|
| 464 |
-
st.code(slug, language="text")
|
| 465 |
-
|
| 466 |
-
def tool_robots_generator():
|
| 467 |
-
st.header("🤖 Robots.txt Generator")
|
| 468 |
-
st.write("Control which crawlers can access your site.")
|
| 469 |
-
|
| 470 |
-
all_agents = st.checkbox("Apply to all robots (*)", value=True)
|
| 471 |
-
disallow_admin = st.checkbox("Disallow /admin", value=True)
|
| 472 |
-
disallow_private = st.checkbox("Disallow /private", value=False)
|
| 473 |
-
sitemap_url = st.text_input("Sitemap URL (Optional)", "https://yoursite.com/sitemap.xml")
|
| 474 |
-
|
| 475 |
-
if st.button("Generate Robots.txt"):
|
| 476 |
-
agent = "*" if all_agents else "Googlebot"
|
| 477 |
-
txt = f"User-agent: {agent}\n"
|
| 478 |
-
if disallow_admin: txt += "Disallow: /admin/\n"
|
| 479 |
-
if disallow_private: txt += "Disallow: /private/\n"
|
| 480 |
-
if sitemap_url: txt += f"\nSitemap: {sitemap_url}"
|
| 481 |
-
|
| 482 |
-
st.text_area("Result", txt, height=150)
|
| 483 |
-
|
| 484 |
-
def tool_sitemap_builder():
|
| 485 |
-
st.header("🗺️ XML Sitemap Builder")
|
| 486 |
-
urls = st.text_area("Paste URLs (one per line)", "https://site.com\nhttps://site.com/about")
|
| 487 |
-
|
| 488 |
-
if st.button("Build XML"):
|
| 489 |
-
xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
| 490 |
-
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
| 491 |
-
|
| 492 |
-
for url in urls.split('\n'):
|
| 493 |
-
if url.strip():
|
| 494 |
-
xml += f' <url>\n <loc>{url.strip()}</loc>\n <changefreq>monthly</changefreq>\n </url>\n'
|
| 495 |
-
|
| 496 |
-
xml += '</urlset>'
|
| 497 |
-
st.text_area("Sitemap.xml", xml, height=200)
|
| 498 |
-
|
| 499 |
-
def tool_keyword_density():
|
| 500 |
-
with st.spinner("Loading Analytics..."):
|
| 501 |
-
import pandas as pd
|
| 502 |
-
from collections import Counter
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
st.header("📊 Keyword Density Checker")
|
| 506 |
-
text = st.text_area("Paste Article Text", height=200,key="density_text")
|
| 507 |
-
|
| 508 |
-
if st.button("Analyze"):
|
| 509 |
-
# Simple stopword list to ignore
|
| 510 |
-
stopwords = set(['the', 'and', 'is', 'in', 'it', 'of', 'to', 'a', 'for', 'on', 'that', 'with', 'as'])
|
| 511 |
-
|
| 512 |
-
words = re.findall(r'\w+', text.lower())
|
| 513 |
-
filtered = [w for w in words if w not in stopwords and len(w) > 2]
|
| 514 |
-
|
| 515 |
-
counts = Counter(filtered).most_common(10)
|
| 516 |
-
|
| 517 |
-
df = pd.DataFrame(counts, columns=["Keyword", "Count"])
|
| 518 |
-
df['Density %'] = (df['Count'] / len(words) * 100).round(2)
|
| 519 |
-
st.table(df)
|
| 520 |
-
|
| 521 |
-
def tool_plagiarism_check():
|
| 522 |
-
st.header("🕵️ Plagiarism Scanner (Google Check)")
|
| 523 |
-
st.info("Splits text into sentences and searches Google for exact matches.")
|
| 524 |
-
text = st.text_area("Paste Text to Check", height=150,key="plag_text")
|
| 525 |
-
|
| 526 |
-
if st.button("Check Text"):
|
| 527 |
-
sentences = re.split(r'[.!?]', text)
|
| 528 |
-
clean_sentences = [s.strip() for s in sentences if len(s.strip()) > 20]
|
| 529 |
-
|
| 530 |
-
for i, s in enumerate(clean_sentences[:5]): # Limit to first 5 for demo
|
| 531 |
-
query = f'"{s}"'
|
| 532 |
-
url = f"https://www.google.com/search?q={query}"
|
| 533 |
-
st.markdown(f"**Sentence {i+1}:** {s[:50]}...")
|
| 534 |
-
st.link_button(f"🔍 Check Google for Match", url)
|
| 535 |
-
|
| 536 |
-
def tool_code_minifier():
|
| 537 |
-
st.header("🧹 Code Minifier")
|
| 538 |
-
mode = st.radio("Type", ["HTML", "CSS"])
|
| 539 |
-
raw_code = st.text_area("Input Code", height=200)
|
| 540 |
-
|
| 541 |
-
if st.button("Minify"):
|
| 542 |
-
minified = ""
|
| 543 |
-
if mode == "HTML":
|
| 544 |
-
# Basic whitespace removal between tags
|
| 545 |
-
lines = raw_code.split('\n')
|
| 546 |
-
minified = "".join([line.strip() for line in lines])
|
| 547 |
-
elif mode == "CSS":
|
| 548 |
-
# Remove comments and whitespace
|
| 549 |
-
# 1. Remove comments
|
| 550 |
-
minified = re.sub(r'/\*[\s\S]*?\*/', '', raw_code)
|
| 551 |
-
# 2. Remove whitespace around braces/colons
|
| 552 |
-
minified = re.sub(r'\s*([{:;,])\s*', r'\1', minified)
|
| 553 |
-
# 3. Remove newlines
|
| 554 |
-
minified = minified.replace('\n', '').replace('\r', '')
|
| 555 |
-
|
| 556 |
-
st.text_area("Minified Output", minified, height=200)
|
| 557 |
-
# --- BATCH 3: DEVELOPER TOOLS ---
|
| 558 |
-
|
| 559 |
-
def tool_qr_generator():
|
| 560 |
-
with st.spinner("Initializing QR Tool..."):
|
| 561 |
-
import qrcode
|
| 562 |
-
import io
|
| 563 |
-
|
| 564 |
-
st.header("🏁 QR Code Generator")
|
| 565 |
-
data = st.text_input("Enter Link or Text", "https://lexicalspace.blogspot.com")
|
| 566 |
-
|
| 567 |
-
if data:
|
| 568 |
-
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
| 569 |
-
qr.add_data(data)
|
| 570 |
-
qr.make(fit=True)
|
| 571 |
-
img = qr.make_image(fill='black', back_color='white')
|
| 572 |
-
|
| 573 |
-
buf = io.BytesIO()
|
| 574 |
-
img.save(buf)
|
| 575 |
-
st.image(img.get_image(), width=300)
|
| 576 |
-
st.download_button("Download QR", data=buf.getvalue(), file_name="qrcode.png")
|
| 577 |
-
|
| 578 |
-
def tool_json_formatter():
|
| 579 |
-
st.header("✨ JSON Prettifier")
|
| 580 |
-
raw = st.text_area("Paste Messy JSON", '{"id":1,"name":"Lexical","roles":["admin","dev"]}', height=150)
|
| 581 |
-
|
| 582 |
-
col1, col2 = st.columns(2)
|
| 583 |
-
if col1.button("Format (Pretty)"):
|
| 584 |
-
try:
|
| 585 |
-
parsed = json.loads(raw)
|
| 586 |
-
st.code(json.dumps(parsed, indent=4), language="json")
|
| 587 |
-
except Exception as e:
|
| 588 |
-
st.error(f"Invalid JSON: {e}")
|
| 589 |
-
|
| 590 |
-
if col2.button("Minify (Compact)"):
|
| 591 |
-
try:
|
| 592 |
-
parsed = json.loads(raw)
|
| 593 |
-
st.code(json.dumps(parsed, separators=(',', ':')), language="json")
|
| 594 |
-
except Exception as e:
|
| 595 |
-
st.error(f"Invalid JSON: {e}")
|
| 596 |
-
|
| 597 |
-
def tool_base64():
|
| 598 |
-
st.header("🔐 Base64 Converter")
|
| 599 |
-
mode = st.radio("Action", ["Encode", "Decode"], horizontal=True)
|
| 600 |
-
text = st.text_area("Input Text")
|
| 601 |
-
|
| 602 |
-
if st.button("Process"):
|
| 603 |
-
try:
|
| 604 |
-
if mode == "Encode":
|
| 605 |
-
res = base64.b64encode(text.encode()).decode()
|
| 606 |
-
else:
|
| 607 |
-
res = base64.b64decode(text).decode()
|
| 608 |
-
st.code(res)
|
| 609 |
-
except Exception as e:
|
| 610 |
-
st.error(f"Error: {e}")
|
| 611 |
-
|
| 612 |
-
def tool_url_encoder():
|
| 613 |
-
st.header("🔗 URL Encoder/Decoder")
|
| 614 |
-
text = st.text_input("Input URL", "https://example.com/search?q=hello world")
|
| 615 |
-
|
| 616 |
-
c1, c2 = st.columns(2)
|
| 617 |
-
with c1:
|
| 618 |
-
if st.button("Encode"):
|
| 619 |
-
st.code(urllib.parse.quote(text))
|
| 620 |
-
with c2:
|
| 621 |
-
if st.button("Decode"):
|
| 622 |
-
st.code(urllib.parse.unquote(text))
|
| 623 |
-
|
| 624 |
-
def tool_markdown_editor():
|
| 625 |
-
st.header("📝 Markdown Editor")
|
| 626 |
-
|
| 627 |
-
col1, col2 = st.columns(2)
|
| 628 |
-
with col1:
|
| 629 |
-
md_text = st.text_area("Write Markdown", "# Hello\n* Item 1\n* Item 2", height=400)
|
| 630 |
-
|
| 631 |
-
with col2:
|
| 632 |
-
st.markdown("### Preview")
|
| 633 |
-
st.markdown(md_text)
|
| 634 |
-
|
| 635 |
-
def tool_regex_tester():
|
| 636 |
-
st.header("🧪 Regex Tester")
|
| 637 |
-
pattern = st.text_input("Regex Pattern", r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b")
|
| 638 |
-
text = st.text_area("Test String", "Contact us at support@lexical.com or admin@site.org")
|
| 639 |
-
|
| 640 |
-
if pattern and text:
|
| 641 |
-
try:
|
| 642 |
-
matches = re.findall(pattern, text)
|
| 643 |
-
st.write(f"Found {len(matches)} matches:")
|
| 644 |
-
st.json(matches)
|
| 645 |
-
except Exception as e:
|
| 646 |
-
st.error(f"Regex Error: {e}")
|
| 647 |
-
|
| 648 |
-
def tool_uuid_gen():
|
| 649 |
-
st.header("🆔 UUID/GUID Generator")
|
| 650 |
-
count = st.number_input("How many?", 1, 100, 5)
|
| 651 |
-
|
| 652 |
-
if st.button("Generate"):
|
| 653 |
-
uuids = [str(uuid.uuid4()) for _ in range(count)]
|
| 654 |
-
st.code("\n".join(uuids), language="text")
|
| 655 |
-
|
| 656 |
-
def tool_hash_gen():
|
| 657 |
-
st.header("🔑 Hash Generator")
|
| 658 |
-
text = st.text_input("Input String", "mypassword")
|
| 659 |
-
|
| 660 |
-
if text:
|
| 661 |
-
st.write("**MD5:**")
|
| 662 |
-
st.code(hashlib.md5(text.encode()).hexdigest())
|
| 663 |
-
st.write("**SHA256:**")
|
| 664 |
-
st.code(hashlib.sha256(text.encode()).hexdigest())
|
| 665 |
-
# ... (Batch 1 & 2 Routing above) ...
|
| 666 |
-
|
| 667 |
-
# BATCH 3 ROUTING
|
| 668 |
-
elif mode == "qrcode": tool_qr_generator()
|
| 669 |
-
elif mode == "json": tool_json_formatter()
|
| 670 |
-
elif mode == "base64": tool_base64()
|
| 671 |
-
elif mode == "url": tool_url_encoder()
|
| 672 |
-
elif mode == "markdown": tool_markdown_editor()
|
| 673 |
-
elif mode == "regex": tool_regex_tester()
|
| 674 |
-
elif mode == "uuid": tool_uuid_gen()
|
| 675 |
-
elif mode == "hash": tool_hash_gen()
|
| 676 |
-
|
| 677 |
-
# ... (Home Dashboard below) ...
|
| 678 |
-
|
| 679 |
-
st.write("### 🛠️ Developer Tools")
|
| 680 |
-
st.markdown("""
|
| 681 |
-
* [🏁 QR Code Gen](?mode=qrcode)
|
| 682 |
-
* [✨ JSON Prettifier](?mode=json)
|
| 683 |
-
* [🔐 Base64 Converter](?mode=base64)
|
| 684 |
-
* [🔗 URL Encode/Decode](?mode=url)
|
| 685 |
-
* [📝 Markdown Editor](?mode=markdown)
|
| 686 |
-
* [🧪 Regex Tester](?mode=regex)
|
| 687 |
-
* [🆔 UUID Generator](?mode=uuid)
|
| 688 |
-
* [🔑 Hash Generator](?mode=hash)
|
| 689 |
-
""")
|
| 690 |
-
# --- BATCH 4: TEXT, UTILITIES & EXTRAS ---
|
| 691 |
-
|
| 692 |
-
def tool_case_converter():
|
| 693 |
-
st.header("🔠 Case Converter")
|
| 694 |
-
text = st.text_area("Input Text", "hello world")
|
| 695 |
-
|
| 696 |
-
c1, c2, c3, c4 = st.columns(4)
|
| 697 |
-
if c1.button("UPPERCASE"): st.code(text.upper(), language="text")
|
| 698 |
-
if c2.button("lowercase"): st.code(text.lower(), language="text")
|
| 699 |
-
if c3.button("Title Case"): st.code(text.title(), language="text")
|
| 700 |
-
if c4.button("aLtErNaTiNg"):
|
| 701 |
-
res = "".join([c.upper() if i%2==0 else c.lower() for i, c in enumerate(text)])
|
| 702 |
-
st.code(res, language="text")
|
| 703 |
-
|
| 704 |
-
def tool_lorem_ipsum():
|
| 705 |
-
st.header("📜 Lorem Ipsum Generator")
|
| 706 |
-
paras = st.slider("Paragraphs", 1, 10, 3)
|
| 707 |
-
|
| 708 |
-
dummy_text = [
|
| 709 |
-
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
| 710 |
-
"Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
| 711 |
-
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
|
| 712 |
-
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum.",
|
| 713 |
-
"Excepteur sint occaecat cupidatat non proident, sunt in culpa."
|
| 714 |
-
]
|
| 715 |
-
|
| 716 |
-
if st.button("Generate"):
|
| 717 |
-
result = "\n\n".join([random.choice(dummy_text) * 3 for _ in range(paras)])
|
| 718 |
-
st.text_area("Result", result, height=200)
|
| 719 |
-
|
| 720 |
-
def tool_word_counter():
|
| 721 |
-
st.header("🧮 Word & Character Counter")
|
| 722 |
-
text = st.text_area("Paste Text Here", height=200,key="counter_text")
|
| 723 |
-
|
| 724 |
-
if text:
|
| 725 |
-
words = len(text.split())
|
| 726 |
-
chars = len(text)
|
| 727 |
-
no_space = len(text.replace(" ", ""))
|
| 728 |
-
read_time = round(words / 200, 2)
|
| 729 |
-
|
| 730 |
-
c1, c2, c3, c4 = st.columns(4)
|
| 731 |
-
c1.metric("Words", words)
|
| 732 |
-
c2.metric("Chars", chars)
|
| 733 |
-
c3.metric("No Spaces", no_space)
|
| 734 |
-
c4.metric("Read Time", f"{read_time} min")
|
| 735 |
-
|
| 736 |
-
def tool_remove_duplicates():
|
| 737 |
-
st.header("🗑️ Remove Duplicate Lines")
|
| 738 |
-
text = st.text_area("Paste List (One per line)", "Apple\nBanana\nApple\nOrange")
|
| 739 |
-
|
| 740 |
-
if st.button("Clean"):
|
| 741 |
-
lines = text.split('\n')
|
| 742 |
-
seen = set()
|
| 743 |
-
clean = []
|
| 744 |
-
for line in lines:
|
| 745 |
-
if line not in seen and line.strip():
|
| 746 |
-
clean.append(line)
|
| 747 |
-
seen.add(line)
|
| 748 |
-
st.text_area("Cleaned List", "\n".join(clean), height=200)
|
| 749 |
-
|
| 750 |
-
def tool_text_to_speech():
|
| 751 |
-
with st.spinner("Loading Audio Engine..."):
|
| 752 |
-
from gtts import gTTS
|
| 753 |
-
import io
|
| 754 |
-
|
| 755 |
-
st.header("🗣️ Text to Speech")
|
| 756 |
-
text = st.text_area("Enter Text", "Hello, welcome to Lexical Space.")
|
| 757 |
-
lang = st.selectbox("Language", ["en", "es", "fr", "de", "hi"],key="tts_text")
|
| 758 |
-
|
| 759 |
-
if st.button("Speak"):
|
| 760 |
-
try:
|
| 761 |
-
tts = gTTS(text=text, lang=lang, slow=False)
|
| 762 |
-
buf = io.BytesIO()
|
| 763 |
-
tts.write_to_fp(buf)
|
| 764 |
-
st.audio(buf, format='audio/mp3')
|
| 765 |
-
except Exception as e:
|
| 766 |
-
st.error(f"Error: {e}")
|
| 767 |
-
|
| 768 |
-
def tool_timestamp():
|
| 769 |
-
st.header("⏰ Unix Timestamp Converter")
|
| 770 |
-
now = int(time.time())
|
| 771 |
-
st.write(f"Current Timestamp: `{now}`")
|
| 772 |
-
|
| 773 |
-
col1, col2 = st.columns(2)
|
| 774 |
-
with col1:
|
| 775 |
-
ts_input = st.number_input("Timestamp to Date", value=now)
|
| 776 |
-
if st.button("Convert to Date"):
|
| 777 |
-
st.success(datetime.datetime.fromtimestamp(ts_input))
|
| 778 |
-
|
| 779 |
-
with col2:
|
| 780 |
-
d_input = st.date_input("Date to Timestamp")
|
| 781 |
-
if st.button("Convert to Timestamp"):
|
| 782 |
-
ts = int(time.mktime(d_input.timetuple()))
|
| 783 |
-
st.success(ts)
|
| 784 |
-
|
| 785 |
-
def tool_color_palette():
|
| 786 |
-
st.header("🎨 Image Color Palette")
|
| 787 |
-
# ADD key="palette"
|
| 788 |
-
uploaded_file = st.file_uploader("Upload Image", type=['jpg', 'png'], key="palette")
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
if uploaded_file:
|
| 793 |
-
img = Image.open(uploaded_file).convert("RGB")
|
| 794 |
-
st.image(img, width=200)
|
| 795 |
-
|
| 796 |
-
# Simple extraction by resizing to 5 pixels
|
| 797 |
-
small = img.resize((5, 1))
|
| 798 |
-
colors = small.getdata()
|
| 799 |
-
|
| 800 |
-
st.write("Dominant Colors:")
|
| 801 |
-
cols = st.columns(5)
|
| 802 |
-
for i, color in enumerate(colors):
|
| 803 |
-
hex_code = '#{:02x}{:02x}{:02x}'.format(*color)
|
| 804 |
-
cols[i].color_picker(f"Color {i+1}", hex_code, disabled=True)
|
| 805 |
-
cols[i].code(hex_code)
|
| 806 |
-
|
| 807 |
-
def tool_password_strength():
|
| 808 |
-
st.header("💪 Password Strength")
|
| 809 |
-
pwd = st.text_input("Test Password", type="password")
|
| 810 |
-
|
| 811 |
-
if pwd:
|
| 812 |
-
score = 0
|
| 813 |
-
if len(pwd) >= 8: score += 1
|
| 814 |
-
if re.search(r"[A-Z]", pwd): score += 1
|
| 815 |
-
if re.search(r"[a-z]", pwd): score += 1
|
| 816 |
-
if re.search(r"\d", pwd): score += 1
|
| 817 |
-
if re.search(r"[!@#$%^&*]", pwd): score += 1
|
| 818 |
-
|
| 819 |
-
st.progress(score / 5)
|
| 820 |
-
if score < 3: st.warning("Weak")
|
| 821 |
-
elif score < 5: st.info("Moderate")
|
| 822 |
-
else: st.success("Strong!")
|
| 823 |
-
|
| 824 |
-
def tool_aspect_ratio():
|
| 825 |
-
st.header("🖥️ Aspect Ratio Calculator")
|
| 826 |
-
w = st.number_input("Width", 1920)
|
| 827 |
-
h = st.number_input("Height", 1080)
|
| 828 |
-
|
| 829 |
-
if w and h:
|
| 830 |
-
def gcd(a, b):
|
| 831 |
-
while b: a, b = b, a % b
|
| 832 |
-
return a
|
| 833 |
-
divisor = gcd(int(w), int(h))
|
| 834 |
-
st.metric("Aspect Ratio", f"{int(w/divisor)}:{int(h/divisor)}")
|
| 835 |
-
|
| 836 |
-
def tool_stopwatch():
|
| 837 |
-
st.header("⏱️ Stopwatch")
|
| 838 |
-
if 'start_time' not in st.session_state: st.session_state.start_time = None
|
| 839 |
-
|
| 840 |
-
if st.button("Start/Reset"):
|
| 841 |
-
st.session_state.start_time = time.time()
|
| 842 |
-
|
| 843 |
-
if st.session_state.start_time:
|
| 844 |
-
elapsed = time.time() - st.session_state.start_time
|
| 845 |
-
st.metric("Time Elapsed", f"{elapsed:.2f}s")
|
| 846 |
-
if st.button("Stop"):
|
| 847 |
-
st.session_state.start_time = None
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
def tool_python_checker():
|
| 851 |
-
st.header("🐍 Python Syntax & Error Checker")
|
| 852 |
-
st.markdown("Paste your Python code or upload a `.py` file to check for syntax errors.")
|
| 853 |
-
|
| 854 |
-
# Import dependencies inside the function to avoid global scope clutter
|
| 855 |
-
import tempfile
|
| 856 |
-
import os
|
| 857 |
-
import io
|
| 858 |
-
try:
|
| 859 |
-
from pylint.lint import Run
|
| 860 |
-
from pylint.reporters.text import TextReporter
|
| 861 |
-
except ImportError:
|
| 862 |
-
st.error("⚠️ Pylint is not installed. Please add `pylint` to your requirements.txt")
|
| 863 |
-
return
|
| 864 |
-
|
| 865 |
-
# --- INPUTS ---
|
| 866 |
-
col1, col2 = st.columns(2)
|
| 867 |
-
with col1:
|
| 868 |
-
paste_code = st.text_area("Paste Code Here", height=300)
|
| 869 |
-
with col2:
|
| 870 |
-
file_obj = st.file_uploader("Or Upload .py File", type=[".py"])
|
| 871 |
-
|
| 872 |
-
# --- PROCESS BUTTON ---
|
| 873 |
-
if st.button("Check Syntax 🚀", type="primary"):
|
| 874 |
-
code_to_check = ""
|
| 875 |
-
|
| 876 |
-
# Logic: Prefer File > Paste
|
| 877 |
-
if file_obj is not None:
|
| 878 |
-
try:
|
| 879 |
-
code_to_check = file_obj.getvalue().decode("utf-8")
|
| 880 |
-
except Exception as e:
|
| 881 |
-
st.error(f"❌ Error reading file: {str(e)}")
|
| 882 |
-
return
|
| 883 |
-
elif paste_code.strip() != "":
|
| 884 |
-
code_to_check = paste_code
|
| 885 |
-
else:
|
| 886 |
-
st.warning("⚠️ Please either paste code or upload a file.")
|
| 887 |
-
return
|
| 888 |
-
|
| 889 |
-
# --- LINTING LOGIC ---
|
| 890 |
-
# 1. Create Temp File (Pylint needs a file on disk)
|
| 891 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".py", mode='w', encoding='utf-8') as temp:
|
| 892 |
-
temp.write(code_to_check)
|
| 893 |
-
temp_path = temp.name
|
| 894 |
-
|
| 895 |
-
# 2. Run Pylint
|
| 896 |
-
pylint_output = io.StringIO()
|
| 897 |
-
reporter = TextReporter(pylint_output)
|
| 898 |
-
|
| 899 |
-
with st.spinner("Analyzing syntax..."):
|
| 900 |
-
try:
|
| 901 |
-
# --errors-only hides warnings, showing only code-breaking errors
|
| 902 |
-
Run([temp_path, "--errors-only"], reporter=reporter, exit=False)
|
| 903 |
-
except Exception as e:
|
| 904 |
-
st.error(f"System Error: {e}")
|
| 905 |
-
|
| 906 |
-
# 3. Cleanup & Display
|
| 907 |
-
os.unlink(temp_path) # Delete temp file
|
| 908 |
-
result = pylint_output.getvalue()
|
| 909 |
-
|
| 910 |
-
st.markdown("---")
|
| 911 |
-
if not result:
|
| 912 |
-
st.success("✅ No Syntax Errors Found! (Code looks valid)")
|
| 913 |
-
st.balloons()
|
| 914 |
-
else:
|
| 915 |
-
st.error("❌ Errors Found:")
|
| 916 |
-
# Hide the messy temp file path from the user
|
| 917 |
-
clean_report = result.replace(temp_path, "Your_Script.py")
|
| 918 |
-
st.code(clean_report, language="text")
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
# --- FINAL MAIN ROUTER (SPA VERSION) ---
|
| 922 |
-
if __name__ == "__main__":
|
| 923 |
-
# 1. INITIALIZE SESSION STATE (This replaces the URL logic)
|
| 924 |
-
if 'mode' not in st.session_state:
|
| 925 |
-
st.session_state.mode = 'home'
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
# 3. NAVIGATION HANDLING
|
| 929 |
-
# Function to change mode without URL reload
|
| 930 |
-
def set_mode(new_mode):
|
| 931 |
-
st.session_state.mode = new_mode
|
| 932 |
-
|
| 933 |
-
# Show "Back to Dashboard" button if not home
|
| 934 |
-
if st.session_state.mode != 'home':
|
| 935 |
-
if st.button("⬅️ Back to Grid"):
|
| 936 |
-
set_mode('home')
|
| 937 |
-
st.rerun()
|
| 938 |
-
|
| 939 |
-
# 4. TOOL ROUTING (Checks session_state instead of URL)
|
| 940 |
-
mode = st.session_state.mode
|
| 941 |
-
|
| 942 |
-
if mode == "youtube": tool_youtube_downloader()
|
| 943 |
-
elif mode == "smart_converter": tool_smart_converter()
|
| 944 |
-
elif mode == "compressor": tool_image_compressor()
|
| 945 |
-
elif mode == "resizer": tool_image_resizer()
|
| 946 |
-
elif mode == "thumbnail": tool_thumbnail_generator()
|
| 947 |
-
elif mode == "metatags": tool_meta_tag_generator()
|
| 948 |
-
elif mode == "slug": tool_slug_generator()
|
| 949 |
-
elif mode == "robots": tool_robots_generator()
|
| 950 |
-
elif mode == "sitemap": tool_sitemap_builder()
|
| 951 |
-
elif mode == "density": tool_keyword_density()
|
| 952 |
-
elif mode == "plagiarism": tool_plagiarism_check()
|
| 953 |
-
elif mode == "minify": tool_code_minifier()
|
| 954 |
-
elif mode == "qrcode": tool_qr_generator()
|
| 955 |
-
elif mode == "json": tool_json_formatter()
|
| 956 |
-
elif mode == "base64": tool_base64()
|
| 957 |
-
elif mode == "url": tool_url_encoder()
|
| 958 |
-
elif mode == "markdown": tool_markdown_editor()
|
| 959 |
-
elif mode == "regex": tool_regex_tester()
|
| 960 |
-
elif mode == "uuid": tool_uuid_gen()
|
| 961 |
-
elif mode == "hash": tool_hash_gen()
|
| 962 |
-
elif mode == "case": tool_case_converter()
|
| 963 |
-
elif mode == "lorem": tool_lorem_ipsum()
|
| 964 |
-
elif mode == "counter": tool_word_counter()
|
| 965 |
-
elif mode == "dedupe": tool_remove_duplicates()
|
| 966 |
-
elif mode == "tts": tool_text_to_speech()
|
| 967 |
-
elif mode == "timestamp": tool_timestamp()
|
| 968 |
-
elif mode == "palette": tool_color_palette()
|
| 969 |
-
elif mode == "password": tool_password_strength()
|
| 970 |
-
elif mode == "ratio": tool_aspect_ratio()
|
| 971 |
-
elif mode == "stopwatch": tool_stopwatch()
|
| 972 |
-
elif mode == "python": tool_python_checker()
|
| 973 |
-
elif mode == "seo": run_seo_app()
|
| 974 |
-
|
| 975 |
-
|
| 976 |
-
# 5. HOME DASHBOARD (Button Grid)
|
| 977 |
-
else:
|
| 978 |
-
st.write("### ⚡ Select a tool to get started:")
|
| 979 |
-
|
| 980 |
-
# We use standard Streamlit columns to create a grid layout
|
| 981 |
-
# This replaces the Markdown links with actual Buttons
|
| 982 |
-
|
| 983 |
-
c1, c2 = st.columns(2)
|
| 984 |
-
|
| 985 |
-
with c1:
|
| 986 |
-
st.info("**📂 Media & Files**")
|
| 987 |
-
if st.button("Seo Writer"): set_mode("seo"); st.rerun()
|
| 988 |
-
if st.button("🎥 YouTube Downloader"): set_mode("youtube"); st.rerun()
|
| 989 |
-
if st.button("🔄 Smart File Converter"): set_mode("smart_converter"); st.rerun()
|
| 990 |
-
if st.button("📉 Image Compressor"): set_mode("compressor"); st.rerun()
|
| 991 |
-
if st.button("📐 Image Resizer"): set_mode("resizer"); st.rerun()
|
| 992 |
-
if st.button("🖼️ Thumbnail Gen"): set_mode("thumbnail"); st.rerun()
|
| 993 |
-
|
| 994 |
-
st.info("**🛠️ Developer Tools**")
|
| 995 |
-
if st.button("🏁 QR Code Gen"): set_mode("qrcode"); st.rerun()
|
| 996 |
-
if st.button("✨ JSON Prettifier"): set_mode("json"); st.rerun()
|
| 997 |
-
if st.button("🔐 Base64 Converter"): set_mode("base64"); st.rerun()
|
| 998 |
-
if st.button("🔗 URL Encoder"): set_mode("url"); st.rerun()
|
| 999 |
-
if st.button("📝 Markdown Editor"): set_mode("markdown"); st.rerun()
|
| 1000 |
-
if st.button("🧪 Regex Tester"): set_mode("regex"); st.rerun()
|
| 1001 |
-
if st.button("🆔 UUID Gen"): set_mode("uuid"); st.rerun()
|
| 1002 |
-
if st.button("🔑 Hash Gen"): set_mode("hash"); st.rerun()
|
| 1003 |
-
|
| 1004 |
-
with c2:
|
| 1005 |
-
st.info("**🕸️ SEO & Webmaster**")
|
| 1006 |
-
if st.button("🏷️ Meta Tag Gen"): set_mode("metatags"); st.rerun()
|
| 1007 |
-
if st.button("🐌 Slug Generator"): set_mode("slug"); st.rerun()
|
| 1008 |
-
if st.button("🤖 Robots.txt Gen"): set_mode("robots"); st.rerun()
|
| 1009 |
-
if st.button("🗺️ Sitemap Builder"): set_mode("sitemap"); st.rerun()
|
| 1010 |
-
if st.button("📊 Density Checker"): set_mode("density"); st.rerun()
|
| 1011 |
-
if st.button("🕵️ Plagiarism Check"): set_mode("plagiarism"); st.rerun()
|
| 1012 |
-
if st.button("🧹 Code Minifier"): set_mode("minify"); st.rerun()
|
| 1013 |
-
|
| 1014 |
-
st.info("**📝 Text & Utilities**")
|
| 1015 |
-
if st.button("🔠 Case Converter"): set_mode("case"); st.rerun()
|
| 1016 |
-
if st.button("📜 Lorem Ipsum"): set_mode("lorem"); st.rerun()
|
| 1017 |
-
if st.button("🧮 Word Counter"): set_mode("counter"); st.rerun()
|
| 1018 |
-
if st.button("🗑️ Dedupe Lines"): set_mode("dedupe"); st.rerun()
|
| 1019 |
-
if st.button("🗣️ Text to Speech"): set_mode("tts"); st.rerun()
|
| 1020 |
-
if st.button("⏰ Unix Timestamp"): set_mode("timestamp"); st.rerun()
|
| 1021 |
-
if st.button("🎨 Color Palette"): set_mode("palette"); st.rerun()
|
| 1022 |
-
if st.button("💪 Password Strength"): set_mode("password"); st.rerun()
|
| 1023 |
-
if st.button("🖥️ Aspect Ratio"): set_mode("ratio"); st.rerun()
|
| 1024 |
-
if st.button("⏱️ Stopwatch"): set_mode("stopwatch"); st.rerun()
|
| 1025 |
-
if st.button("Python Checker"): set_mode("python"); st.rerun()
|
| 1026 |
-
|
| 1027 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|