RosettaDecoder/Rosetta-Decoder
0
1import gradio as gr2from ultralytics import YOLO3from PIL import Image, ImageDraw, ImageFont4from google import genai5import os6import json7import matplotlib.pyplot as plt8import re9from huggingface_hub import hf_hub_download10import tempfile11import numpy as np12import shutil13import zipfile14from typing import List, Tuple, Dict, Any15 16# --- 1. CONFIGURATION & SECRETS ---17GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")18HF_TOKEN = os.environ.get("HF_TOKEN")19 20MODEL_REPO = "youkii-xr/hieroglyphic-detection"21MODEL_FILENAME = "best.pt"22JSON_DB_PATH = "gardiner_codes.json"23 24os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"25os.makedirs("/tmp/gradio_results", exist_ok=True)26 27# --- 2. DATA LOADING ---28 29def load_gardiner_database():30 if os.path.exists(JSON_DB_PATH):31 try:32 print(f"System: Loading Gardiner codes from {JSON_DB_PATH}...")33 with open(JSON_DB_PATH, "r", encoding='utf-8') as f:34 return json.load(f)35 except Exception as e:36 print(f"⚠️ Error reading JSON: {e}")37 return {}38 else:39 print(f"⚠️ Warning: {JSON_DB_PATH} not found.")40 return {}41 42gardiner_data = load_gardiner_database()43gardiner_map = {k: v.get("Description", k) for k, v in gardiner_data.items()}44 45# --- 3. CORE LOGIC FUNCTIONS ---46 47def create_labeled_zip(image, detections):48 """49 Crops glyphs, draws label (Code + Conf) on bottom right, and zips them.50 """51 if not detections: return None52 53 zip_dir = tempfile.mkdtemp()54 zip_path = os.path.join(tempfile.gettempdir(), "rosetta_glyphs.zip")55 56 try:57 # Load a font (try-except block for system compatibility)58 try:59 # Try loading a standard font, fallback to default if fails60 font = ImageFont.truetype("arial.ttf", 16)61 except IOError:62 font = ImageFont.load_default()63 64 for i, d in enumerate(detections):65 # Crop66 box = d['box']67 crop = image.crop((box[0], box[1], box[2], box[3]))68 69 # Prepare Label70 label_text = f"{d['code']} {int(d['confidence']*100)}%"71 draw = ImageDraw.Draw(crop)72 73 # Calculate text size using textbbox (newer PIL versions)74 left, top, right, bottom = draw.textbbox((0, 0), label_text, font=font)75 text_w = right - left76 text_h = bottom - top77 78 img_w, img_h = crop.size79 80 # Draw background rectangle (bottom right)81 # Check if image is too small for label, if so, skip drawing to avoid crash82 if img_w > text_w and img_h > text_h:83 rect_x0 = img_w - text_w - 484 rect_y0 = img_h - text_h - 485 rect_x1 = img_w86 rect_y1 = img_h87 88 draw.rectangle([rect_x0, rect_y0, rect_x1, rect_y1], fill="black")89 draw.text((rect_x0 + 2, rect_y0), label_text, fill="white", font=font)90 91 # Save crop92 filename = f"{d['code']}_{i}.png"93 crop.save(os.path.join(zip_dir, filename))94 95 # Create Zip96 shutil.make_archive(zip_path.replace('.zip', ''), 'zip', zip_dir)97 return zip_path98 99 except Exception as e:100 print(f"Zip creation error: {e}")101 return None102 finally:103 shutil.rmtree(zip_dir, ignore_errors=True)104 105def core_detect(image, conf_threshold):106 """Core YOLO detection logic."""107 if image is None or model is None:108 return None, [], []109 110 results = model.predict(source=image, conf=conf_threshold, iou=0.45, imgsz=640, verbose=False, device='cpu', max_det=300)111 annotated_array = results[0].plot() 112 annotated_image = Image.fromarray(annotated_array[..., ::-1])113 114 detections = []115 crops = []116 117 for box in results[0].boxes:118 if box.cls.numel() > 0:119 cls_id = int(box.cls[0])120 if 0 <= cls_id < len(model.names):121 code = model.names[cls_id]122 conf = float(box.conf[0])123 xyxy = box.xyxy[0].tolist()124 125 # Create detection object126 detection = {127 "code": code,128 "description": gardiner_map.get(code, "Unknown"),129 "confidence": round(conf, 2),130 "box": xyxy131 }132 detections.append(detection)133 134 # Create crop (Clean crop for Gallery display)135 crop_img = image.crop((xyxy[0], xyxy[1], xyxy[2], xyxy[3]))136 crops.append((crop_img, f"{code}\n({int(conf*100)}%)"))137 138 return annotated_image, detections, crops139 140def core_translate(keywords_list):141 """Core Gemini translation logic."""142 if not GOOGLE_API_KEY:143 return "Error: API Key Missing", "Error: API Key Missing"144 if not keywords_list:145 return "No symbols detected", "No symbols detected"146 147 keywords_str = ", ".join(keywords_list)148 prompt = f"""149 You are an expert Egyptologist AI. I have detected these symbols: [{keywords_str}].150 Please provide 2 distinct outputs separated by "|||SEPARATOR|||".151 1. A Mystical Story: Highly atmospheric, sounding like an ancient prophecy. 152 Do NOT number this section. Use HTML tags <b> for bolding keywords and <br> for new lines.153 2. An Academic Translation: Direct, linguistic, focusing on grammar. Use standard text.154 """155 try:156 client = genai.Client(api_key=GOOGLE_API_KEY)157 response = client.models.generate_content(model="gemini-2.5-flash", contents=prompt)158 parts = response.text.split("|||SEPARATOR|||")159 160 def clean(t): return t.replace("**", "").strip()161 162 if len(parts) < 2: return clean(response.text), "Could not parse academic style."163 return clean(parts[0]), clean(parts[1])164 except Exception as e:165 return f"Error: {str(e)}", f"Error: {str(e)}"166 167def core_analytics(detections, img_w, img_h):168 """Core Matplotlib logic."""169 if not detections: return None170 171 codes = [d['code'] for d in detections]172 confs = [d['confidence'] for d in detections]173 x_centers = [d['box'][0] + (d['box'][2] - d['box'][0])/2 for d in detections]174 y_centers = [d['box'][1] + (d['box'][3] - d['box'][1])/2 for d in detections]175 176 fig = plt.figure(figsize=(10, 15))177 fig.patch.set_facecolor('#0f0f23') 178 179 # 1. Frequency180 ax1 = plt.subplot(3, 1, 1)181 unique_codes = list(set(codes))182 counts = [codes.count(c) for c in unique_codes]183 ax1.bar(unique_codes, counts, color='#d4af37')184 ax1.set_title('Symbol Frequency', color='white', fontsize=12, pad=10)185 ax1.tick_params(colors='white')186 ax1.set_facecolor('none')187 for spine in ax1.spines.values(): spine.set_color('#d4af37')188 189 # 2. Confidence190 ax2 = plt.subplot(3, 1, 2)191 ax2.scatter(range(len(confs)), confs, color='#d4af37', alpha=0.7, s=50)192 ax2.set_title('AI Confidence Levels', color='white', fontsize=12, pad=10)193 ax2.set_ylim(0, 1.1)194 ax2.tick_params(colors='white')195 ax2.set_facecolor('none')196 for spine in ax2.spines.values(): spine.set_color('#d4af37')197 198 # 3. Heatmap199 ax3 = plt.subplot(3, 1, 3)200 h = ax3.hist2d(x_centers, y_centers, bins=[20, 20], range=[[0, img_w], [0, img_h]], cmap='inferno')201 ax3.set_title('Glyph Spatial Heatmap', color='white', fontsize=12, pad=10)202 ax3.set_xlim(0, img_w)203 ax3.set_ylim(img_h, 0)204 ax3.tick_params(colors='white')205 cbar = plt.colorbar(h[3], ax=ax3)206 cbar.ax.yaxis.set_tick_params(color='white')207 plt.setp(plt.getp(cbar.ax.axes, 'yticklabels'), color='white')208 209 plt.tight_layout(pad=4.0)210 return fig211 212# --- 4. LOAD MODEL ---213 214print("System: Initializing Rosetta Decoder Core...")215try:216 model_path = hf_hub_download(217 repo_id=MODEL_REPO,218 filename=MODEL_FILENAME,219 token=HF_TOKEN220 )221 model = YOLO(model_path)222 print("System: Model loaded successfully.")223except Exception as e:224 print(f"Error loading model: {e}")225 model = None226 227# --- 5. MAIN UI PIPELINE (Orchestrator) ---228 229def process_pipeline(image, conf_threshold):230 """231 Main function used by the Web UI. 232 """233 if image is None: return None, None, None, "", "", None, "", "", "", []234 if model is None: return None, None, None, "Error: Model not loaded.", "", None, "", "", "", []235 236 try:237 img_w, img_h = image.size238 # 1. Detect239 annotated_img, detections, crops = core_detect(image, conf_threshold)240 241 # 2. Prepare Downloads242 # A. Annotated Image243 ann_path = os.path.join(tempfile.gettempdir(), "annotated_hieroglyphs.jpg")244 annotated_img.save(ann_path)245 246 # B. Zip File with Labels247 zip_path = create_labeled_zip(image, detections)248 249 # 3. Extract Keywords & Translate250 unique_codes = list(set([d['code'] for d in detections]))251 mapped_words = [gardiner_map.get(code, f"[{code}]") for code in unique_codes]252 mystical, academic = core_translate(mapped_words)253 254 # 4. Analytics255 analytics_plot = core_analytics(detections, img_w, img_h)256 257 # 5. Reports258 text_report = f"Total Symbols: {len(detections)}\nUnique Codes: {', '.join(unique_codes)}"259 json_output = {"count": len(detections), "detections": detections}260 formatted_mystical = f"""<div class="mystical-container"><h3>✨ THE ANCIENT WHISPER</h3><p>{mystical}</p></div>"""261 262 return annotated_img, ann_path, zip_path, formatted_mystical, academic, analytics_plot, text_report, json_output, crops263 264 except Exception as e:265 print(f"Pipeline Error: {e}")266 return None, None, None, f"System Failure: {str(e)}", "", None, str(e), None, []267 268# --- 6. MCP API FUNCTIONS ---269 270def detect_hieroglyphs_api(image: Image.Image, conf: float = 0.25) -> Tuple[str, Dict[str, Any]]:271 ann_img, dets, _ = core_detect(image, conf)272 with tempfile.NamedTemporaryFile(dir="/tmp/gradio_results", suffix=".jpg", delete=False) as t:273 ann_img.save(t.name)274 path = t.name275 return path, {"count": len(dets), "detections": dets}276 277def translate_codes_api(keywords_text: str) -> Tuple[str, str]:278 if isinstance(keywords_text, str):279 keywords = [k.strip() for k in keywords_text.split(',')]280 else:281 keywords = ["Unknown"]282 mystical, academic = core_translate(keywords)283 clean_mystical = re.sub('<[^<]+?>', '', mystical) 284 return clean_mystical, academic285 286def get_analytics_chart_api(json_data: Dict[str, Any]) -> str:287 dets = json_data.get("detections", [])288 fig = core_analytics(dets, 640, 640) 289 if fig is None: return "No data."290 with tempfile.NamedTemporaryFile(dir="/tmp/gradio_results", suffix=".png", delete=False) as t:291 fig.savefig(t.name, format='png', facecolor='#0f0f23')292 path = t.name293 plt.close(fig)294 return path295 296def list_all_codes_api() -> Dict[str, Any]:297 return gardiner_data298 299# --- 7. HTML GENERATORS ---300 301CATEGORIES = {302 'A': "Men & Monarchs", 'B': "Women & Human Activities", 'C': "Deities",303 'D': "Parts of Human Body", 'E': "Mammals", 'F': "Parts of Mammals",304 'G': "Birds", 'H': "Parts of Birds", 'I': "Reptiles & Amphibians",305 'K': "Fishes", 'L': "Invertebrates", 'M': "Trees & Plants",306 'N': "Sky, Earth, Water", 'O': "Buildings", 'P': "Ships",307 'Q': "Furniture", 'R': "Temple Furniture", 'S': "Crowns & Dress",308 'T': "Warfare & Hunting", 'U': "Agriculture & Crafts", 'V': "Rope & Baskets",309 'W': "Vessels", 'X': "Loaves & Cakes", 'Y': "Writings & Games",310 'Z': "Strokes & Figures", 'Aa': "Unclassified"311}312 313def generate_gardiner_html():314 if not gardiner_data:315 return "<tr><td colspan='4'>No data loaded. Please upload gardiner_codes.json.</td></tr>"316 html_rows = ""317 grouped = {}318 for key, data in gardiner_data.items():319 match = re.match(r"([A-Za-z]+)", data.get("Code", key))320 prefix = match.group(1) if match else "Unk"321 if prefix not in grouped: grouped[prefix] = []322 grouped[prefix].append(data)323 sorted_prefixes = sorted(grouped.keys(), key=lambda x: (len(x), x))324 for prefix in sorted_prefixes:325 cat_name = CATEGORIES.get(prefix, f"Category {prefix}")326 html_rows += f"<tr><td colspan='4' class='category-header'>{cat_name}</td></tr>"327 items = sorted(grouped[prefix], key=lambda x: int(re.search(r'\d+', x.get("Code", "0")).group()) if re.search(r'\d+', x.get("Code", "0")) else 0)328 for item in items:329 html_rows += f"""<tr><td style="font-weight:bold; color: #fff;">{item.get("Code", "?")}</td><td>{item.get("Description", "-")}</td><td style="font-family:serif; font-size:1.1em;">{item.get("Transliteration", "-")}</td><td><span class="type-badge">{item.get("Type", "-")}</span></td></tr>"""330 return html_rows331 332GARDINER_TABLE_CONTENT = generate_gardiner_html()333 334# --- 8. UI STYLING & ASSETS ---335 336cursor_url = "url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj4KICA8ZyBmaWxsPSJub25lIiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41Ij4KICAgIDxwYXRoIGQ9Ik0xNiw4IEM2LDIwIDI2LDIwIDE2LDggWiIgZmlsbD0icmdiYSgyMTIsIDE3NSwgNTUsIDAuMSkiLz4KICAgIDxjaXJjbGUgY3g9IjE2IiBjeT0iMTUiIHI9IjMiIGZpbGw9IiNkNGFmMzciLz4KICAgIDxwYXRoIGQ9Ik0xNiwyMiBMMTYsMjggTDEwLDI4Ii8+CiAgPC9nPgo8L3N2Zz4=')"337 338custom_css = f"""339@import url('https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;600;700&display=swap');340@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');341:root, .dark, body {{ --bg-gradient: radial-gradient(circle at 50% 0%, #0a0a2e 0%, #000000 100%); --card-bg: rgba(15, 15, 35, 0.7); --text-primary: #e0e7ff; --text-accent: #d4af37; --border-color: #d4af37; --btn-grad: linear-gradient(135deg, #b8860b 0%, #d4af37 100%); --info-bg: rgba(212, 175, 55, 0.08); --info-border: #d4af37; --glow-color: rgba(212, 175, 55, 0.4); }}342body.light-mode, .gradio-container.light-mode {{ --bg-gradient: linear-gradient(135deg, #f0e6d2 0%, #e6dcc3 100%) !important; --card-bg: rgba(255, 255, 255, 0.6) !important; --text-primary: #3d342b !important; --text-accent: #8b4513 !important; --border-color: #8b4513 !important; --btn-grad: linear-gradient(135deg, #cd853f 0%, #8b4513 100%) !important; --info-bg: rgba(139, 69, 19, 0.05) !important; --info-border: #8b4513 !important; --glow-color: rgba(139, 69, 19, 0.3) !important; color: var(--text-primary) !important; }}343body, .gradio-container {{ background: var(--bg-gradient) !important; font-family: 'Cairo', sans-serif !important; color: var(--text-primary) !important; cursor: {cursor_url} 16 16, auto !important; transition: background 0.5s ease; }}344.gold-dust {{ position: fixed; width: 6px; height: 6px; background: var(--text-accent); border-radius: 50%; pointer-events: none; z-index: 9999; animation: fadeDust 0.6s linear forwards; box-shadow: 0 0 5px var(--text-accent); }}345@keyframes fadeDust {{ 0% {{ opacity: 1; transform: scale(1); }} 100% {{ opacity: 0; transform: scale(0); }} }}346button, a, .cursor-pointer {{ cursor: {cursor_url} 16 16, pointer !important; }}347.tabs button {{ padding: 5px 10px !important; font-size: 14px !important; min-width: auto !important; }}348.card {{ background: var(--card-bg) !important; border: 1px solid rgba(128, 128, 128, 0.2) !important; border-radius: 12px; padding: 24px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); backdrop-filter: blur(12px); margin-bottom: 24px; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); }}349.card:hover {{ transform: translateY(-4px); border-color: var(--border-color) !important; box-shadow: 0 10px 40px rgba(0,0,0,0.2), 0 0 20px var(--glow-color); }}350.card-title {{ font-family: 'Cairo', sans-serif; font-size: 20px; font-weight: 700; color: var(--text-accent) !important; text-transform: uppercase; letter-spacing: 2px; border-bottom: 1px solid rgba(128,128,128, 0.2); padding-bottom: 15px; margin-bottom: 20px; display: flex; align-items: center; justify-content: center; gap: 8px; }}351.guide-step {{ background: rgba(255, 255, 255, 0.03); border-left: 4px solid #d4af37; padding: 16px; margin-bottom: 16px; border-radius: 0 6px 6px 0; }}352.step-title {{ color: #d4af37; font-family: 'Space Mono', monospace; font-weight: bold; display: block; margin-bottom: 8px; font-size: 14px; }}353.path-highlight {{ background: rgba(212, 175, 55, 0.15); border: 1px solid #d4af37; padding: 2px 6px; border-radius: 4px; color: #fff; font-family: 'Space Mono', monospace; }}354code {{ font-family: 'Space Mono', monospace; background: rgba(0,0,0,0.3); padding: 2px 5px; border-radius: 4px; color: #e0e7ff; }}355.gardiner-table {{ width: 100%; border-collapse: collapse; font-family: 'Space Mono', monospace; font-size: 13px; margin-top: 10px; border: 1px solid #d4af37; }}356.gardiner-table th {{ color: #ffffff; text-align: left; padding: 12px; border-bottom: 2px solid #d4af37; text-transform: uppercase; letter-spacing: 1px; background: rgba(212, 175, 55, 0.1); }}357.gardiner-table td {{ padding: 10px; border-bottom: 1px solid rgba(212, 175, 55, 0.2); color: #e0e0e0; }}358.gardiner-table tr:hover {{ background: rgba(212, 175, 55, 0.1); }}359.category-header {{ background: rgba(212, 175, 55, 0.2); color: #d4af37; font-weight: bold; text-align: center; padding: 8px; text-transform: uppercase; letter-spacing: 2px; }}360.type-badge {{ border: 1px solid #d4af37; color: #d4af37; padding: 2px 6px; border-radius: 4px; font-size: 10px; text-transform: uppercase; letter-spacing: 1px; }}361.mystical-container {{ font-family: 'Cairo', serif; font-size: 18px; line-height: 1.8; color: #fff8e1; padding: 20px; border: 1px solid var(--border-color); background: rgba(212, 175, 55, 0.05); border-radius: 8px; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); }}362.mystical-container:hover {{ transform: translateY(-4px); box-shadow: 0 10px 40px rgba(0,0,0,0.2), 0 0 20px var(--glow-color); }}363.mystical-container h3 {{ color: var(--text-accent); text-align: center; border-bottom: 1px dashed var(--border-color); padding-bottom: 10px; }}364.mystical-container b {{ color: #d4af37; text-shadow: 0 0 5px rgba(212, 175, 55, 0.5); }}365.scrollable-box textarea {{ overflow-y: auto !important; max-height: 400px !important; background-color: rgba(0,0,0,0.3) !important; }}366button.primary-btn {{ background: var(--btn-grad) !important; border: 1px solid var(--border-color) !important; color: #000 !important; font-weight: 700 !important; font-size: 16px !important; }}367.gradio-image, .gradio-json {{ background: transparent !important; border: none !important; }}368button.toggle-btn {{ background: #0a0a2e !important; border: 1px solid var(--border-color) !important; color: var(--text-accent) !important; padding: 5px 15px !important; font-family: 'Space Mono', monospace; box-shadow: none !important; }}369button.toggle-btn:hover {{ background: var(--info-bg) !important; }}370"""371 372header_html = """373<div style="padding: 20px 0; border-bottom: 1px solid rgba(128,128,128,0.2); margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">374 <div style="display: flex; align-items: center; gap: 20px;">375 <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#d4af37" stroke-width="2">376 <rect x="3" y="3" width="18" height="18" rx="2" />377 <path d="M7 7h10" />378 <path d="M7 12h10" />379 <path d="M7 17h10" />380 <circle cx="12" cy="12" r="3" stroke="#d4af37" fill="none"/>381 </svg>382 <div>383 <h1 style="margin: 0; font-size: 36px; font-weight: 700; color: var(--text-primary); text-shadow: 0 0 10px rgba(212, 175, 55, 0.3);">ROSETTA DECODER</h1>384 <p style="margin: 0; font-size: 14px; color: var(--text-accent); letter-spacing: 3px; font-weight: 600;">HIEROGLYPHIC DETECTOR AND TRANSLATOR</p>385 </div>386 </div>387</div>388"""389 390# UPDATED VISION STATEMENT391mission_html = """392<div class="card"><div class="card-title">📡 VISION STATEMENT</div><p style="opacity: 0.9; font-size: 16px; line-height: 1.8; color: var(--text-primary);">393<b>Echoes of Humanity, Decoded.</b><br>394It's not just about code; it's about connection. For millennia, the voices of ancient Egypt have been locked in stone, waiting to be heard. 395Rosetta Decoder isn't just a tool—it's a bridge across time. We are using modern AI to re-awaken these silent stories, allowing us to listen 396to the hopes, prayers, and daily lives of those who walked before us. We are decoding history to understand our shared humanity.397</p></div>398"""399 400guide_html = """401<div class="card" style="border-color: #d4af37;">402 <div class="card-title" style="color: #d4af37;">🤖 CLAUDE DESKTOP SETUP GUIDE</div>403 <div class="guide-step"><span class="step-title">STEP 0: PREREQUISITE</span><p>Ensure you have <b>Node.js</b> installed.</p></div>404 <div class="guide-step"><span class="step-title">STEP 1: PREPARE WORKSPACE</span>1. Create: <span class="path-highlight">C:\\Claude_Work</span><br>2. Move images inside.</div>405 <div class="guide-step"><span class="step-title">STEP 2: CONFIGURE CLAUDE</span>1. Edit: <code>%APPDATA%\\Claude\\claude_desktop_config.json</code><br>2. Paste the JSON below.<br>3. Restart Claude.</div>406</div>407"""408 409# URL UPDATED TO: youkii-xr/hieroglyph-mcp-server410claude_json_content = """{411 "mcpServers": {412 "gradio": {413 "command": "npx",414 "args": [415 "mcp-remote",416 "https://youkii-xr-hieroglyph-mcp-server.hf.space/gradio_api/mcp/",417 "--transport",418 "streamable-http"419 ]420 },421 "upload_helper": {422 "command": "C:\\\\Python313\\\\python.exe",423 "args": [424 "-m",425 "gradio",426 "upload-mcp",427 "https://youkii-xr-hieroglyph-mcp-server.hf.space/",428 "C:\\\\Claude_Work"429 ]430 }431 }432}"""433 434trail_script = """<script>document.addEventListener('DOMContentLoaded', () => { document.addEventListener('mousemove', (e) => { if (Math.random() > 0.7) return; const dust = document.createElement('div'); dust.classList.add('gold-dust'); dust.style.left = e.clientX + 'px'; dust.style.top = e.clientY + 'px'; document.body.appendChild(dust); setTimeout(() => dust.remove(), 600); }); });</script>"""435 436# --- 9. MAIN APP ASSEMBLY ---437 438with gr.Blocks(title="Rosetta Decoder Ultimate") as demo:439 gr.HTML(f"<style>{custom_css}</style>")440 gr.HTML(trail_script)441 442 # --- MCP TOOL REGISTRATION LAYER (Hidden) ---443 with gr.Row(visible=False):444 btn_detect = gr.Button("Detect")445 btn_detect.click(fn=detect_hieroglyphs_api, inputs=[gr.Image(label="img"), gr.Number(label="conf")], outputs=[gr.Textbox(label="path"), gr.JSON(label="json")], api_name="detect")446 447 btn_trans = gr.Button("Translate")448 btn_trans.click(fn=translate_codes_api, inputs=[gr.Textbox(label="text")], outputs=[gr.Textbox(label="mystic"), gr.Textbox(label="academic")], api_name="translate")449 450 btn_anal = gr.Button("Analytics")451 btn_anal.click(fn=get_analytics_chart_api, inputs=[gr.JSON(label="data")], outputs=[gr.Textbox(label="chart_path")], api_name="analytics")452 453 btn_list = gr.Button("List")454 btn_list.click(fn=list_all_codes_api, inputs=[], outputs=[gr.JSON(label="data")], api_name="list_codes")455 456 # --- Visible UI ---457 with gr.Row(elem_classes="header-row"):458 with gr.Column(scale=4): gr.HTML(header_html)459 with gr.Column(scale=1): btn_toggle = gr.Button("🌗 Day / Night", elem_classes="toggle-btn")460 461 with gr.Tabs():462 # TAB 1: DECODER463 with gr.TabItem("🔮 DECODER WORKSTATION"):464 with gr.Row():465 with gr.Column(scale=1):466 gr.HTML('<div class="card"><div class="card-title">Input Source</div>')467 with gr.Tabs():468 with gr.TabItem("📜 Upload File"):469 img_upload = gr.Image(type="pil", sources=["upload", "clipboard"], label="Upload", height=280)470 slider_conf = gr.Slider(0.1, 1.0, 0.25, label="Scan Sensitivity")471 btn_upload = gr.Button("✨ START DECODING", elem_classes="primary-btn")472 with gr.TabItem("🎥 Live Camera"):473 img_cam = gr.Image(type="pil", sources=["webcam"], label="Camera", height=280)474 slider_conf_cam = gr.Slider(0.1, 1.0, 0.25, label="Scan Sensitivity")475 btn_cam = gr.Button("✨ START DECODING", elem_classes="primary-btn")476 gr.HTML('</div>')477 478 with gr.Column(scale=1):479 gr.HTML('<div class="card"><div class="card-title">Result</div>')480 with gr.Tabs():481 with gr.TabItem("🔮 Mystical"): out_mystical = gr.HTML(label="Prophecy")482 with gr.TabItem("🏛️ Academic"): out_academic = gr.Textbox(label="Scientific Translation", lines=15, show_label=False, elem_classes="scrollable-box")483 with gr.TabItem("🖼️ Visuals"):484 out_image = gr.Image(label="Annotated Result", interactive=False)485 with gr.Row():486 btn_download_img = gr.DownloadButton("💾 Download Annotated Image")487 btn_download_zip = gr.DownloadButton("📦 Download Glyphs (ZIP)")488 out_gallery = gr.Gallery(label="Extracted Glyphs", columns=4, height="auto")489 with gr.TabItem("📊 Analytics"): out_plot = gr.Plot(label="Analysis Charts")490 with gr.TabItem("🛠️ Logs"):491 out_report = gr.Textbox(label="Detection Log", lines=5)492 out_json = gr.JSON(label="JSON Data")493 gr.HTML('</div>')494 495 # TAB 2: ABOUT496 with gr.TabItem("📜 VISION & ABOUT"): gr.HTML(mission_html)497 498 # TAB 3: SETUP499 with gr.TabItem("🤖 SYSTEM SETUP"):500 gr.HTML(guide_html)501 gr.Code(value=claude_json_content, language="json", label="claude_desktop_config.json", interactive=False, lines=30)502 503 # TAB 4: GARDINER CODES504 with gr.TabItem("𓀀 GARDINER CODES"):505 gr.HTML(f"""<div class="card"><div class="card-title">📜 SUPPORTED GARDINER CODES</div><div style="overflow-x: auto; max-height: 600px; overflow-y: auto;"><table class="gardiner-table"><thead><tr><th>Code</th><th>Description</th><th>Transliteration</th><th>Type</th></tr></thead><tbody>{GARDINER_TABLE_CONTENT}</tbody></table></div></div>""")506 507 gr.HTML('<div style="text-align: center; color: var(--text-accent); opacity: 0.5; padding: 20px;"></div>')508 509 # Events510 btn_toggle.click(None, None, None, js="() => { document.body.classList.toggle('light-mode'); const container = document.querySelector('.gradio-container'); if(container) container.classList.toggle('light-mode'); }")511 512 # OUTPUTS: Image, ImgPath, ZipPath, Mystical, Academic, Plot, TextReport, JSON, Crops513 outputs = [out_image, btn_download_img, btn_download_zip, out_mystical, out_academic, out_plot, out_report, out_json, out_gallery]514 515 btn_upload.click(fn=process_pipeline, inputs=[img_upload, slider_conf], outputs=outputs)516 btn_cam.click(fn=process_pipeline, inputs=[img_cam, slider_conf_cam], outputs=outputs)517 518if __name__ == "__main__":519 demo.launch(mcp_server=True, ssr_mode=False, allowed_paths=["/tmp", "/tmp/gradio_results", "."])