CoolFace
Apppublic

RosettaDecoder/Rosetta-Decoder

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
app.py519 linesDownload Raw Back to root
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", "."])