"""AI Model X-Ray - Structural Health Scanner. Single-file Gradio app. Upload or select a transformer; get a per-layer structural-health diagnostic: which layers are compressible (immune), which are fragile (critical), and how the model compares to reference architectures. Dark mission-control theme (Octopus palette). All panels are gr.HTML blocks re-rendered from a scan result produced by analysis.py. Pre-loaded models read from a precomputed cache (instant, no GPU); custom models trigger a live scan. Run: python app.py -> http://localhost:7860 """ from __future__ import annotations import html import math import traceback import gradio as gr import analysis as ax # Optional ZeroGPU decorator (no-op when `spaces` is unavailable, e.g. locally). try: import spaces # type: ignore GPU = spaces.GPU(duration=300) except Exception: # pragma: no cover - local / non-Spaces environment def GPU(fn): return fn # --------------------------------------------------------------------------- # # Palette # --------------------------------------------------------------------------- # BG = "#0f0f14" CARD = "#1a1b26" BORDER = "#2a2b36" FG = "#f8f8fb" FG2 = "#8888a0" EMERALD = "#10B981" AMBER = "#EF9F27" RED = "#E24B4A" REGIME_COLOR = {"immune": EMERALD, "buffer": AMBER, "critical": RED} DROPDOWN_CHOICES = [ ("bert-base-uncased (Encoder, 12L x 12H, 110M)", "bert-base-uncased"), ("gpt2 (Decoder, 12L x 12H, 124M)", "gpt2"), ("google/vit-base-patch16-224 (Vision, 12L x 12H, 86M)", "google/vit-base-patch16-224"), ("distilbert-base-uncased (Encoder, 6L x 12H, 66M)", "distilbert-base-uncased"), ] # =========================================================================== # CSS # =========================================================================== CSS = """ .gradio-container { background:#0f0f14 !important; color:#f8f8fb !important; max-width:100% !important; font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif !important; } .gradio-container .block, .gradio-container .form, .gradio-container .panel, .gradio-container .gap { background:transparent !important; border:none !important; box-shadow:none !important; } footer { display:none !important; } .gradio-container button { background:#191a24 !important; color:#cdd2e6 !important; border:1px solid #2a2b36 !important; box-shadow:none !important; font-weight:600 !important; transition:border-color .15s ease,color .15s ease; } .gradio-container button:hover { border-color:#3a3d50 !important; color:#f3f4fa !important; } /* ---- top bar ---- */ #xr-topbar { display:flex !important; flex-direction:row !important; align-items:center; gap:10px; padding:12px 18px; width:100%; box-sizing:border-box; background:linear-gradient(90deg,#15161f,#1a1b26); border:1px solid #2a2b36; border-radius:14px; margin-bottom:8px; } #xr-topbar > * { flex-shrink:0; } #tb-logo-col { flex:1 1 auto; min-width:0; } .tb-logo { font-size:21px; font-weight:800; color:#f8f8fb; letter-spacing:.02em; } .tb-logo .xr { color:#10B981; } .tb-sub { font-size:11.5px; color:#8888a0; margin-top:2px; letter-spacing:.06em; text-transform:uppercase; } .tb-badge { display:inline-flex; align-items:center; gap:8px; padding:7px 14px; border-radius:999px; font-size:12px; font-weight:700; letter-spacing:.04em; white-space:nowrap; background:rgba(16,185,129,.12); color:#10B981; border:1px solid #1c5641; } /* ---- section titles + cards ---- */ .sec-title { font-size:11px; letter-spacing:.13em; text-transform:uppercase; color:#8888a0; font-weight:800; margin:16px 2px 8px; } .sec-title .n { color:#10B981; } .card { background:#1a1b26; border:1px solid #2a2b36; border-radius:14px; padding:14px 16px; } /* ---- model info rows ---- */ .kv { display:flex; justify-content:space-between; align-items:center; padding:7px 2px; border-bottom:1px solid #20212b; font-size:13px; } .kv:last-child { border-bottom:none; } .kv .k { color:#8888a0; } .kv .v { font-weight:800; color:#f8f8fb; font-family:ui-monospace,Consolas,monospace; } .pill-arch { padding:2px 10px; border-radius:999px; font-size:11px; font-weight:800; background:rgba(16,185,129,.14); color:#10B981; } /* ---- arc gauge ---- */ .gauge-wrap { position:relative; width:200px; height:150px; margin:6px auto 0; animation:pop .5s ease; } .gauge-svg { width:200px; height:200px; position:absolute; top:0; left:0; } .g-track { fill:none; stroke:#23242f; stroke-width:13; stroke-linecap:round; } .g-val { fill:none; stroke-width:13; stroke-linecap:round; transition:stroke-dashoffset .8s cubic-bezier(.3,1,.4,1),stroke .3s; } .gauge-center { position:absolute; top:48px; left:0; width:200px; text-align:center; } .gauge-num { font-size:44px; font-weight:800; line-height:1; } .gauge-lbl { font-size:12px; letter-spacing:.12em; font-weight:800; margin-top:6px; text-transform:uppercase; } .gauge-cap { text-align:center; color:#8888a0; font-size:11px; margin-top:30px; letter-spacing:.04em; } @keyframes pop { from{opacity:0;transform:scale(.94)} to{opacity:1;transform:scale(1)} } /* ---- key metrics ---- */ .metrics { display:grid; grid-template-columns:1fr 1fr; gap:10px; } .metric { background:#14151d; border:1px solid #2a2b36; border-radius:11px; padding:11px 12px; } .metric .mv { font-size:23px; font-weight:800; font-family:ui-monospace,Consolas,monospace; line-height:1; } .metric .ml { font-size:10.5px; color:#8888a0; margin-top:6px; letter-spacing:.04em; text-transform:uppercase; } /* ---- risk map ---- */ .pills { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:12px; } .pill { display:inline-flex; align-items:center; gap:7px; padding:6px 13px; border-radius:999px; font-size:12px; font-weight:700; border:1px solid #2a2b36; background:#14151d; } .pill .dot { width:9px; height:9px; border-radius:50%; } .riskgrid { display:grid; grid-template-columns:repeat(auto-fill,minmax(112px,1fr)); gap:9px; } .rcell { background:#14151d; border:1px solid #2a2b36; border-radius:11px; padding:11px 11px 10px; cursor:pointer; position:relative; overflow:hidden; transition:transform .12s ease,border-color .15s ease; animation:pop .35s ease; } .rcell:hover { transform:translateY(-2px); border-color:#3a3d50; } .rcell.sel { border-color:#f8f8fb; box-shadow:0 0 0 2px #f8f8fb inset, 0 6px 18px rgba(0,0,0,.45); transform:translateY(-2px); } .rcell.flash { animation:cellflash .45s ease; } @keyframes cellflash { 0% { box-shadow:0 0 0 2px #f8f8fb inset, 0 0 0 0 rgba(248,248,251,.55); } 100% { box-shadow:0 0 0 2px #f8f8fb inset, 0 0 0 16px rgba(248,248,251,0); } } .rcell .bar-top { position:absolute; top:0; left:0; right:0; height:3px; } .rcell .rc-l { font-size:11px; color:#8888a0; font-weight:700; letter-spacing:.04em; } .rcell .rc-rho { font-size:21px; font-weight:800; margin:5px 0 2px; font-family:ui-monospace,Consolas,monospace; } .rcell .rc-badge { font-size:9.5px; font-weight:800; letter-spacing:.05em; text-transform:uppercase; } /* ---- spectral profile ---- */ .spec-row { display:flex; align-items:center; gap:9px; margin:6px 0; font-size:12px; } .spec-lbl { width:34px; color:#8888a0; font-family:ui-monospace,Consolas,monospace; font-size:11px; flex:0 0 auto; } .spec-track { flex:1; height:22px; background:#14151d; border-radius:6px; position:relative; overflow:hidden; } .spec-fill { height:100%; border-radius:6px; animation:grow .6s ease; transform-origin:left; } .spec-inval { position:absolute; left:9px; top:0; height:22px; line-height:22px; font-family:ui-monospace,Consolas,monospace; font-size:11.5px; font-weight:800; color:#ffffff; text-shadow:0 1px 2px rgba(0,0,0,.6); pointer-events:none; } @keyframes grow { from{transform:scaleX(.02)} to{transform:scaleX(1)} } /* color key shown beside the section title */ .spec-key { float:right; font-size:10.5px; font-weight:700; letter-spacing:0; text-transform:none; color:#cdd2e6; } .spec-key b { font-weight:700; color:#cdd2e6; } /* ---- layer detail ---- */ .detail .kv .v.big { font-size:15px; } .regime-banner { padding:10px 12px; border-radius:11px; font-weight:800; font-size:13px; margin-bottom:10px; display:flex; align-items:center; justify-content:space-between; letter-spacing:.03em; } /* ---- comparison table ---- */ .cmp { width:100%; border-collapse:collapse; font-size:12px; } .cmp th { text-align:left; color:#8888a0; font-weight:700; padding:6px 7px; border-bottom:1px solid #2a2b36; font-size:10.5px; letter-spacing:.04em; text-transform:uppercase; } .cmp td { padding:7px 7px; border-bottom:1px solid #20212b; color:#dfe2ee; font-family:ui-monospace,Consolas,monospace; } .cmp tr.me td { background:rgba(16,185,129,.1); color:#f8f8fb; font-weight:800; } .cmp tr.me td:first-child { color:#10B981; } /* ---- pruning rec ---- */ .rec-big { font-size:30px; font-weight:800; color:#10B981; line-height:1; font-family:ui-monospace,Consolas,monospace; } .rec-sub { color:#8888a0; font-size:12px; margin:5px 0 12px; } .rec-list { list-style:none; padding:0; margin:8px 0 0; } .rec-list li { display:flex; justify-content:space-between; padding:5px 2px; font-size:12.5px; border-bottom:1px solid #20212b; } .rec-list li .ln { color:#dfe2ee; } .rec-list li .hd { color:#8888a0; font-family:ui-monospace,Consolas,monospace; } .rec-warn { margin-top:12px; padding:10px 12px; border-radius:10px; background:rgba(226,75,74,.1); border:1px solid rgba(226,75,74,.4); color:#ff8f8e; font-size:12.5px; font-weight:600; } .rec-ok { margin-top:12px; padding:10px 12px; border-radius:10px; background:rgba(16,185,129,.08); border:1px solid rgba(16,185,129,.35); color:#7fe8c4; font-size:12.5px; font-weight:600; } /* ---- scan button ---- */ #scan-btn, #scan-btn button { background:linear-gradient(90deg,#0c7a57,#10B981) !important; color:#fff !important; font-weight:800 !important; letter-spacing:.05em !important; border:none !important; font-size:14px !important; padding:11px !important; } #scan-btn:hover, #scan-btn button:hover { filter:brightness(1.09); } /* dark inputs */ #xr-controls { --input-background-fill:#101019; --block-background-fill:#1a1b26; --border-color-primary:#2a2b36; --body-text-color:#e8eaf2; --body-text-color-subdued:#9aa0b5; } #xr-controls input, #xr-controls textarea, #xr-controls .wrap, #xr-controls .container { background:#101019 !important; color:#e8eaf2 !important; border-color:#2a2b36 !important; } #xr-controls ul.options, #xr-controls .options { background:#101019 !important; border:1px solid #2a2b36 !important; color:#e8eaf2 !important; } #xr-controls .options .item:hover, #xr-controls li.item:hover { background:#20212e !important; } #xr-controls span, #xr-controls label { color:#c8cce0 !important; } .muted { color:#8888a0; font-size:13px; padding:14px 4px; text-align:center; } /* hidden click relay — kept mounted in the DOM (display:none, not unmounted) */ #layer-relay { display:none !important; } /* ---- footer ---- */ #xr-footer { text-align:center; color:#6f7590; font-size:12px; padding:14px 0 6px; margin-top:10px; border-top:1px solid #20212b; line-height:1.7; } #xr-footer b { color:#10B981; } #xr-footer .law { color:#cdd2e6; font-family:ui-monospace,Consolas,monospace; } /* ---- top-bar "?" guide button ---- */ #guide-btn, #guide-btn button { width:40px !important; min-width:40px !important; height:40px !important; border-radius:50% !important; padding:0 !important; font-size:18px !important; font-weight:800 !important; color:#10B981 !important; } /* ---- guide modal ---- */ /* No `display` here: Gradio's visible=False sets display:none; when visible the column is display:flex, so align/justify center the card. (Octopus pattern.) */ #guide-modal { position:fixed; inset:0; z-index:1000; background:rgba(8,8,12,.84); align-items:center; justify-content:center; padding:30px; } .modal-card { max-width:700px; max-height:86vh; overflow-y:auto; margin:0 auto; background:#1a1b26; border:1px solid #2a2b36; border-radius:16px; padding:26px 30px; box-shadow:0 24px 70px rgba(0,0,0,.6); } .modal-h1 { font-size:23px; font-weight:800; color:#f8f8fb; margin-bottom:4px; } .modal-sec { margin-top:16px; } .modal-h2 { display:inline-block; font-size:12px; font-weight:800; color:#10B981; letter-spacing:.08em; text-transform:uppercase; margin-bottom:4px; } .modal-sec p { color:#f8f8fb; font-size:13px; margin:4px 0 0; line-height:1.6; } .modal-sec ul { margin:7px 0 0; padding-left:18px; color:#f8f8fb; font-size:13px; line-height:1.75; } .modal-term { font-weight:800; color:#f8f8fb; font-size:13.5px; margin-top:12px; } .modal-law { font-family:ui-monospace,Consolas,monospace; color:#cdd2e6; } .modal-foot { margin-top:20px; padding-top:14px; border-top:1px solid #20212b; text-align:center; color:#8888a0; font-size:12.5px; line-height:1.7; } .modal-foot b { color:#10B981; } .modal-x, .modal-x button { position:fixed !important; top:24px; right:28px; z-index:1001; width:40px !important; min-width:40px !important; height:40px !important; flex:none !important; border-radius:10px !important; font-size:16px !important; background:#1a1b26 !important; border:1px solid #2a2b36 !important; color:#c8cce0 !important; padding:0 !important; } .modal-x:hover, .modal-x button:hover { border-color:#E24B4A !important; color:#ff8f8e !important; } """ # JS relay: clicking a risk-map cell writes the layer index into a hidden # textbox and fires its input event, which drives the Python detail handler. # Visual feedback (select highlight + flash) is applied immediately on click, # before the server round-trip, so the response always feels instant. HEAD_JS = """ """ # =========================================================================== # Guide modal content # =========================================================================== GUIDE_HTML = """ """ # =========================================================================== # Small formatting helpers # =========================================================================== def _fnum(x, d=3): return "—" if x is None else f"{x:.{d}f}" def _params_to_millions(params): """'110M' -> 110.0, '1.1B' -> 1100.0, None -> None.""" if not params: return None s = params.strip().upper() try: if s.endswith("B"): return float(s[:-1]) * 1000.0 if s.endswith("M"): return float(s[:-1]) return float(s) / 1e6 except ValueError: return None # =========================================================================== # Renderers (pure: result/summary -> HTML) # =========================================================================== def render_gauge(score, label, color): """270-degree arc gauge for the structural-integrity score.""" r = 80 cx = cy = 100 arc = 0.75 # fraction of the full circle drawn (270 deg) c = 2 * math.pi * r track_dash = f"{arc * c:.1f} {c:.1f}" val_len = arc * c * max(0.0, min(100.0, score)) / 100.0 val_dash = f"{val_len:.1f} {c:.1f}" # rotate so the 90-degree gap sits centred at the bottom rot = "transform:rotate(135deg);transform-origin:100px 100px;" return ( "
" "" f"" f"" "" "
" f"
{score:.0f}
" f"
{label}
" "
" "
Structural integrity · 100 = fully redundant
" ) def render_model_info(result): arch = html.escape(result["arch"]) params = result.get("params") or "—" src = "cached" if result.get("source") == "cached" else "live scan" return ( "
" f"
Model" f"{html.escape(result['model_id'])}
" f"
Parameters{params}
" f"
Layers × Heads" f"{result['n_layers']} × {result['n_heads']}
" f"
Hidden size{result.get('hidden','—')}
" f"
Architecture" f"{arch}
" f"
Source{src}
" "
" ) def render_integrity(summary): label, color = ax.score_band(summary["score"]) return render_gauge(summary["score"], label, color) def render_metrics(summary): rho = summary["rho_mean"] rho_s = "—" if rho is None else f"{rho:.2f}" fi_pct = summary["fi_head"] * 100.0 return ( "
" f"
{fi_pct:.2f}%
" "
Fragility index
" f"
{rho_s}
" "
Coherence ratio ρ
" f"
{summary['n_eligible']}/{summary['n_layers']}
" "
Eligible layers
" f"
{summary['violations']}
" "
Hierarchy violations
" "
" ) def render_riskmap(result, summary, selected=None): pills = ( "
" f"" f"{summary['n_immune']} immune" f"" f"{summary['n_buffer']} buffer" f"" f"{summary['n_critical']} critical" "
" ) cells = [] for rec in result["per_layer"]: L = rec["layer"] regime = ax.classify_regime(rec) color = REGIME_COLOR[regime] rho = rec.get("rho") rho_s = "n/a" if rho is None else f"{rho:.2f}" sel = " sel" if selected == L else "" cells.append( f"
" f"
" f"
LAYER {L}
" f"
{rho_s}
" f"
{regime}
" "
" ) return pills + "
" + "".join(cells) + "
" def render_spectral(result): """Horizontal ρ-per-layer bars. Each bar is colored by regime and scaled 0..1 (so ρ=0.56 is ~56% width); layers where ρ is undefined show a thin gray 'n/a' bar. The ρ value is printed inside the bar, left-aligned.""" rows = [] for rec in result["per_layer"]: L = rec["layer"] rho = rec.get("rho") if rho is None: color = "#3a3d50" # gray — ρ undefined (T(G) disconnected) width = 14.0 # thin stub so n/a never reads as a full bar label = "n/a" else: color = REGIME_COLOR[ax.classify_regime(rec)] width = max(4.0, min(1.0, rho) * 100.0) # 0..1 scale, tiny floor label = f"{rho:.2f}" rows.append( "
" f"L{L}" "
" f"
" f"{label}" "
" ) return "
" + "".join(rows) + "
" def render_layer_detail(result, selected): if selected is None or selected >= len(result["per_layer"]): return ("
Click a layer card in the " "risk map (or use the selector above) to inspect it.
") rec = result["per_layer"][selected] regime = ax.classify_regime(rec) meta = ax.REGIME_META[regime] color = meta["color"] rho = rec.get("rho") l2tg = "—" if not rec["eligible"] else _fnum(rec["l2TG"]) elig = "yes" if rec["eligible"] else "no" fi_pct = (rec.get("fi") or 0.0) * 100.0 return ( "
" f"
" f"Layer {rec['layer']} · {meta['label']}" f"{meta['advice']}
" f"
ρ = λ₂(T)/λ₂(G)" f"{'n/a' if rho is None else f'{rho:.3f}'}
" f"
λ₂(G){_fnum(rec['l2G'])}
" f"
λ₂(T(G)){l2tg}
" f"
Fragility (FI){fi_pct:.2f}%
" f"
Edges in graph{rec['edges']}
" f"
Heads{result['n_heads']}
" f"
Eligible{elig}
" "
" ) def render_comparison(result, summary): rows = ax.reference_rows() body = [] for r in rows: body.append( "" + html.escape(r["model"]) + "" f"{_fnum(r['rho_mean'],2)}" f"{_fnum(r['fi_base'],3)}" f"{html.escape(r['modality'].title())}" ) rho = summary["rho_mean"] me = ( "→ " + html.escape(result["label"]) + "" f"{'n/a' if rho is None else f'{rho:.2f}'}" f"{summary['fi_head']:.3f}" f"{html.escape(result['modality'].title())}" ) return ( "
" "" + "".join(body) + me + "
Modelρ meanFIType
" ) def render_pruning(result, summary): immune = [r for r in result["per_layer"] if ax.classify_regime(r) == "immune"] critical = [r for r in result["per_layer"] if ax.classify_regime(r) == "critical"] n_heads = result["n_heads"] total_heads = result["n_layers"] * n_heads immune_heads = len(immune) * n_heads pct = (immune_heads / total_heads * 100.0) if total_heads else 0.0 # Rough fp32 memory estimate: immune fraction of total parameters. pm = _params_to_millions(result.get("params")) mem = "" if pm is not None and result["n_layers"]: frac = len(immune) / result["n_layers"] saved_mb = pm * 1e6 * frac * 4 / 1e6 # fp32 bytes -> MB mem = (f"
Estimated savings: " f"~{saved_mb:.0f} MB (fp32, " f"{frac*100:.0f}% of weights)
") items = "".join( f"
  • Layer {r['layer']}" f"{n_heads} heads
  • " for r in immune ) or "
  • No fully-immune layers
  • " if critical: crit_labels = ", ".join(f"L{r['layer']}" for r in critical) warn = (f"
    ⚠ Do NOT prune layers: {crit_labels} " "(low redundancy — structurally load-bearing).
    ") else: warn = ("
    ✓ No critical layers — this model has " "no structurally load-bearing bottlenecks at ρ < 0.5.
    ") return ( "
    " f"
    {pct:.0f}%
    " f"
    of heads ({immune_heads}/{total_heads}) sit in immune " "layers — safe to prune.
    " + mem + "" + warn + "
    " ) # Empty-state placeholders EMPTY = "
    Run a scan to populate this panel.
    " def empty_bundle(): return (EMPTY, render_gauge(0, "—", FG2), EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY) # =========================================================================== # Event handlers # =========================================================================== # Only the genuine live scan (forward passes) goes through the GPU decorator. # The cached path — including the BERT auto-load — never touches it, so the app # works on any hardware (cpu-basic, ZeroGPU) for the pre-loaded models. @GPU def _live_scan(model_id): return ax.scan_model(model_id) def on_scan(dropdown_id, custom_id, force_rescan): """Resolve the model id, scan (cached or live), render every panel.""" model_id = (custom_id or "").strip() or dropdown_id if not model_id: gr.Warning("Select a model or enter a HuggingFace model ID.") return (None, *empty_bundle(), gr.update()) custom = bool((custom_id or "").strip()) want_live = force_rescan or custom try: result = None if want_live else ax.cached_result(model_id) if result is None: result = _live_scan(model_id) except Exception as e: # surface a friendly error, keep the app alive traceback.print_exc() gr.Warning(f"Scan failed for '{model_id}': {e}") return (None, *empty_bundle(), gr.update()) summary = ax.summarize(result) n = result["n_layers"] layer_choices = [(f"Layer {i}", i) for i in range(n)] return ( result, render_model_info(result), render_integrity(summary), render_metrics(summary), render_riskmap(result, summary, None), render_spectral(result), render_layer_detail(result, None), render_comparison(result, summary), render_pruning(result, summary), gr.update(choices=layer_choices, value=None), ) def on_select_dropdown(result, layer_idx): if result is None or layer_idx is None: return gr.update(), gr.update() summary = ax.summarize(result) return (render_layer_detail(result, int(layer_idx)), render_riskmap(result, summary, int(layer_idx))) def on_relay(result, relay_value): """Hidden-textbox relay from a risk-map cell click (':').""" if result is None or not relay_value: return gr.update(), gr.update(), gr.update() try: idx = int(str(relay_value).split(":")[0]) except (ValueError, IndexError): return gr.update(), gr.update(), gr.update() summary = ax.summarize(result) return (render_layer_detail(result, idx), render_riskmap(result, summary, idx), gr.update(value=idx)) # =========================================================================== # App # =========================================================================== def build_app(): with gr.Blocks(css=CSS, head=HEAD_JS, title="AI Model X-Ray", theme=gr.themes.Base()) as demo: state = gr.State(None) # ----- top bar ----- # with gr.Row(elem_id="xr-topbar"): with gr.Column(elem_id="tb-logo-col"): gr.HTML("" "
    Structural Health Scanner
    ") gr.HTML("Built by Cognitive Engineering " "🇨🇭") guide_btn = gr.Button("?", elem_id="guide-btn", scale=0, min_width=44) with gr.Row(): # ===================== LEFT (25%) ===================== # with gr.Column(scale=25, elem_id="xr-controls"): gr.HTML("
    1. Select model
    ") dropdown = gr.Dropdown( choices=DROPDOWN_CHOICES, value="bert-base-uncased", label="Pre-loaded models", interactive=True) custom = gr.Textbox( label="...or a custom HuggingFace model ID", lines=1, placeholder="e.g. roberta-base, microsoft/deberta-v3-small") force = gr.Checkbox(label="Force live re-scan (ignore cache)", value=False) scan_btn = gr.Button("\U0001F52C SCAN MODEL", elem_id="scan-btn") gr.HTML("
    Model info
    ") info_html = gr.HTML(EMPTY) gr.HTML("
    Structural integrity
    ") gauge_html = gr.HTML(render_gauge(0, "—", FG2)) gr.HTML("
    Key metrics
    ") metrics_html = gr.HTML(EMPTY) # ===================== CENTER (50%) ===================== # with gr.Column(scale=50): gr.HTML("
    2. Risk map " "— per-layer prunability
    ") riskmap_html = gr.HTML(EMPTY) gr.HTML("
    Spectral profile — ρ per layer" "🟢 safe to prune   " "🟡 caution   🔴 fragile
    ") spectral_html = gr.HTML(EMPTY) # ===================== RIGHT (25%) ===================== # with gr.Column(scale=25, elem_id="xr-controls"): gr.HTML("
    3. Layer detail
    ") layer_sel = gr.Dropdown(choices=[], label="Inspect layer", value=None, interactive=True) detail_html = gr.HTML(render_layer_detail({"per_layer": []}, None)) gr.HTML("
    Architecture comparison
    ") comparison_html = gr.HTML(EMPTY) gr.HTML("
    Pruning recommendation
    ") pruning_html = gr.HTML(EMPTY) # click-to-select relay for risk-map cells. Kept mounted (CSS display:none # via #layer-relay) rather than visible=False, so the input element always # exists in the DOM for the JS to write into. relay = gr.Textbox(value="", label="", elem_id="layer-relay") # ----- guide modal (hidden until the "?" button is clicked) ----- # with gr.Column(visible=False, elem_id="guide-modal") as guide_modal: guide_close = gr.Button("✕", elem_classes=["modal-x"]) gr.HTML(GUIDE_HTML) gr.HTML( "") # ----- wiring ----- # OUTPUTS = [state, info_html, gauge_html, metrics_html, riskmap_html, spectral_html, detail_html, comparison_html, pruning_html, layer_sel] scan_btn.click(on_scan, inputs=[dropdown, custom, force], outputs=OUTPUTS) layer_sel.change(on_select_dropdown, inputs=[state, layer_sel], outputs=[detail_html, riskmap_html]) relay.input(on_relay, inputs=[state, relay], outputs=[detail_html, riskmap_html, layer_sel]) # guide modal open/close guide_btn.click(lambda: gr.update(visible=True), outputs=guide_modal) guide_close.click(lambda: gr.update(visible=False), outputs=guide_modal) # Auto-scan the default model (BERT) on page load so the app is never empty. demo.load(lambda: on_scan("bert-base-uncased", "", False), outputs=OUTPUTS) return demo if __name__ == "__main__": build_app().launch()