Spaces:
Running on Zero
Running on Zero
Download app.py from benjaminogbonna/council-ai-theorem-explorer: direct link, hf CLI and curl.
- Browser
- Download file 26.1 kB
-
https://huggingface.co/spaces/benjaminogbonna/council-ai-theorem-explorer/resolve/main/app.py
- Command line
-
hf download hf://spaces/benjaminogbonna/council-ai-theorem-explorer/app.py
-
curl -L -o app.py https://huggingface.co/spaces/benjaminogbonna/council-ai-theorem-explorer/resolve/main/app.py
26.1 kB
| """ | |
| Council AI — Theorem Explorer | |
| ------------------------------ | |
| Interactive companion to "Council AI: Toward Deliberative Artificial Intelligence" | |
| (Michael Jidael, Udochukwu Nwokoma, Benjamin Ogbonna, et al., Nexenda, 2026). | |
| Implements exactly two things, both taken directly from the paper's proofs: | |
| Theorem 1 (Deliberation Convergence): | |
| ||H(t) - H*||_F <= gamma^t * ||H(0) - H*||_F | |
| Theorem 2 (Multi-Expert Safety Bound): | |
| P(catastrophic error) <= p * rho^(k-1) if p <= rho | |
| P(catastrophic error) <= p^k if p > rho | |
| No proprietary architecture names, expert-cluster names, or product names | |
| appear anywhere in this app or its outputs. Consistent with the naming | |
| constraints on Paper 1. | |
| """ | |
| import spaces # must come first, before numpy/matplotlib/gradio | |
| def _zerogpu_keepalive(): | |
| """No-op. ZeroGPU refuses to start a Space with no @spaces.GPU function.""" | |
| return None | |
| import numpy as np | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| from matplotlib.ticker import MaxNLocator | |
| import gradio as gr | |
| # Links | |
| PAPER_URL = "#" | |
| CODE_URL = "#" | |
| WEBSITE_URL = "https://nexendahq.com/" | |
| # Plot palette. Theme-neutral on purpose: transparent background, mid-grey | |
| # text/grid that reads on both the light and dark card, and the same | |
| # accent colours in both themes. The light/dark switch is therefore pure | |
| # client-side CSS (no server round trip, no theme state). | |
| ORANGE, GREEN, PINK = "#E8651A", "#5E9E4E", "#D6456B" | |
| INK = "#8F8D85" | |
| def style_axes(fig, ax): | |
| fig.patch.set_facecolor("none") | |
| ax.set_facecolor("none") | |
| for spine in ax.spines.values(): | |
| spine.set_color(INK) | |
| spine.set_alpha(0.35) | |
| spine.set_linewidth(0.8) | |
| ax.tick_params(colors=INK, labelsize=8.5, length=0, pad=8) | |
| ax.xaxis.label.set_color(INK) | |
| ax.yaxis.label.set_color(INK) | |
| ax.xaxis.label.set_size(8.5) | |
| ax.yaxis.label.set_size(8.5) | |
| ax.grid(color=INK, alpha=0.3, linewidth=0.7, linestyle=(0, (2, 3))) | |
| def legend_html(items): | |
| """items: (label, colour, dashed?) -> plot header with title + legend.""" | |
| keys = "".join( | |
| f'<span><i class="{"dash" if dashed else ""}" style="border-color:{c}"></i>{label}</span>' | |
| for label, c, dashed in items | |
| ) | |
| return f'<div class="plot-head"><div class="plot-title">{ICON_CHART}Plot</div><div class="legend">{keys}</div></div>' | |
| def stats_html(pairs): | |
| return '<div class="stats">' + "".join(f"<p>{k}: <b>{v}</b></p>" for k, v in pairs) + "</div>" | |
| # Theorem 1 — Deliberation Convergence | |
| def theorem1(gamma: float, rounds: int, d0: float, tau: float): | |
| t = np.arange(0, rounds + 1) | |
| dist = d0 * (gamma ** t) | |
| agreement = np.clip(1 - dist / d0, 0, 1) | |
| reached = np.argmax(agreement >= tau) if np.any(agreement >= tau) else None | |
| consensus_round = int(reached) if reached is not None and agreement[reached] >= tau else None | |
| fig, ax = plt.subplots(figsize=(8.6, 4.6), dpi=160) | |
| style_axes(fig, ax) | |
| ax.axhline(tau, color=PINK, linestyle=(0, (2, 2)), linewidth=1.4) | |
| ax.plot(t, dist / d0, color=ORANGE, linewidth=1.8) | |
| ax.plot(t, agreement, color=GREEN, linewidth=1.8) | |
| ax.set_xlabel("Deliberation round t", labelpad=12) | |
| ax.set_ylabel("Normalized value", labelpad=12) | |
| ax.set_ylim(-0.02, 1.05) | |
| fig.tight_layout() | |
| plt.close(fig) | |
| legend = legend_html([("Threshold τ", PINK, True), | |
| ("Disagreement Bound", ORANGE, False), | |
| ("Agreement", GREEN, False)]) | |
| stats = stats_html([ | |
| ("Round consensus reached", | |
| f"{consensus_round}/{rounds}" if consensus_round is not None else "not reached"), | |
| ("Residual disagreement at round T", f"{dist[-1]:.3g}"), | |
| ("Convergence rate", f"γ = {gamma:.2f} · lower γ = faster"), | |
| ]) | |
| return fig, legend, stats | |
| DEFAULTS_T1 = (0.55, 15, 1.2, 0.75) | |
| def reset_theorem1(): | |
| return (*DEFAULTS_T1, *theorem1(*DEFAULTS_T1)) | |
| # Theorem 2 — Multi-Expert Safety Bound | |
| def safety_bound(p: float, rho: float, k: int) -> float: | |
| if p <= rho: | |
| return p * (rho ** (k - 1)) | |
| return p ** k | |
| def theorem2(p: float, rho: float, n: int, k: int): | |
| k = min(k, n) | |
| regime = "p ≤ ρ · bound = p·ρ^(k−1)" if p <= rho else "p > ρ · tighter bound = p^k" | |
| ks = np.arange(1, n + 1) | |
| bounds = np.array([safety_bound(p, rho, kk) for kk in ks]) | |
| current_bound = safety_bound(p, rho, k) | |
| fig, ax = plt.subplots(figsize=(8.6, 4.6), dpi=160) | |
| style_axes(fig, ax) | |
| ax.axhline(p, color=PINK, linestyle=(0, (2, 2)), linewidth=1.4) | |
| ax.plot(ks, bounds, color=ORANGE, linewidth=1.6, marker="o", markersize=3) | |
| ax.scatter([k], [current_bound], color=ORANGE, s=90, zorder=5, edgecolor="white", linewidth=1.5) | |
| ax.set_yscale("log") | |
| ax.xaxis.set_major_locator(MaxNLocator(integer=True, min_n_ticks=1)) | |
| ax.set_xlabel("Experts required for high-confidence agreement (k)", labelpad=12) | |
| ax.set_ylabel("P(catastrophic error)", labelpad=12) | |
| fig.tight_layout() | |
| plt.close(fig) | |
| reduction = p / current_bound if current_bound > 0 else float("inf") | |
| verdict = "below baseline" if current_bound < p else "no improvement" | |
| legend = legend_html([(f"Single-model rate p = {p:.4f}", PINK, True), | |
| ("Council bound as k increases", ORANGE, False)]) | |
| stats = stats_html([ | |
| ("Regime", regime), | |
| ("Error rate (P)", f"{p:.4g} <em>baseline</em>"), | |
| (f"Council bound at k={k}", f"{current_bound:.4g} <em>{verdict}</em>"), | |
| ("Reduction factor", f"{reduction:,.1f}× <em>vs. model</em>"), | |
| ]) | |
| return fig, legend, stats | |
| DEFAULTS_T2 = (0.01, 0.30, 10, 5) | |
| def reset_theorem2(): | |
| return (*DEFAULTS_T2, *theorem2(*DEFAULTS_T2)) | |
| # Static HTML | |
| ICON_CHART = ('<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" ' | |
| 'stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">' | |
| '<path d="M2 2v11.5h12"/><path d="M4.5 10l3-3.5 2.5 2L13.5 4"/></svg>') | |
| ICON_MOON = ('<svg class="i-moon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" ' | |
| 'stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' | |
| '<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>') | |
| ICON_SUN = ('<svg class="i-sun" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" ' | |
| 'stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/>' | |
| '<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>') | |
| ICON_ARROW = ('<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" ' | |
| 'stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 9.5l7-7M4 2.5h5.5V8"/></svg>') | |
| ICON_CHEV = ('<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.6" ' | |
| 'stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 2.5L8 6l-3.5 3.5"/></svg>') | |
| ICON_INFO = ('<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" ' | |
| 'stroke-linecap="round"><circle cx="8" cy="8" r="6.3"/><path d="M8 7.2V11M8 5v.1"/></svg>') | |
| ICON_CHEVL = ('<svg width="15" height="15" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.4" ' | |
| 'stroke-linecap="round" stroke-linejoin="round"><path d="M7.5 2.5L4 6l3.5 3.5"/></svg>') | |
| ICON_CHEVR = ('<svg width="15" height="15" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.4" ' | |
| 'stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 2.5L8 6l-3.5 3.5"/></svg>') | |
| MOBILE_NAV_HTML = f""" | |
| <div class="mobile-tab-nav"> | |
| <button type="button" class="mobile-nav-btn" id="mobile-tab-prev" aria-label="Previous panel">{ICON_CHEVL}</button> | |
| <button type="button" class="mobile-nav-btn" id="mobile-tab-next" aria-label="Next panel">{ICON_CHEVR}</button> | |
| </div> | |
| """ | |
| TOPBAR_HTML = f""" | |
| <div class="topbar"> | |
| <div class="wordmark">Nexenda Alignment Research</div> | |
| <div class="top-actions"> | |
| <button id="theme-toggle" class="icon-btn" type="button" aria-label="Toggle light/dark theme">{ICON_MOON}{ICON_SUN}</button> | |
| <a class="pill" href="{WEBSITE_URL}" target="_blank" rel="noopener noreferrer">Website {ICON_ARROW}</a> | |
| </div> | |
| </div> | |
| """ | |
| HERO_HTML = f""" | |
| <div class="hero"> | |
| <div class="hero-title">Council AI - Theorem Explorer</div> | |
| <p class="hero-sub">Interactive companion to Council AI: Toward Deliberative Artificial Intelligence.<br> | |
| Both panels compute the paper's theorems live.</p> | |
| <div class="hero-actions"> | |
| <a class="pill" href="{PAPER_URL}" target="_blank" rel="noopener noreferrer">Read the paper</a> | |
| <a class="ghost" href="{CODE_URL}" target="_blank" rel="noopener noreferrer">View code {ICON_CHEV}</a> | |
| </div> | |
| </div> | |
| """ | |
| T1_STATEMENT = ('<p class="statement">If the deliberation operator <b>Φ</b> is contractive with constant <b>γ < 1</b>, ' | |
| 'the council state sequence converges exponentially to a unique fixed point: ' | |
| '<b>‖H(t) − H*‖ ≤ γᵗ ‖H(0) − H*‖</b>.</p>') | |
| T2_STATEMENT = ('<p class="statement">With <b>n experts</b>, individual error rate p, pairwise error correlation ρ, ' | |
| 'and <b>k experts</b> required for a high-confidence output: ' | |
| '<b>P(catastrophic error) ≤ p·ρ^(k−1) when p ≤ ρ</b>; ' | |
| 'the tighter bound pᵏ applies when p > ρ.</p>') | |
| NOTICE_HTML = f""" | |
| <div class="notice">{ICON_INFO}<span>This demo illustrates the mathematics proven in Theorems 1 and 2 of the | |
| Council AI paper. It does not run any trained neural model. Parameters are user-selected inputs to formal | |
| bounds, not active system telemetry.</span></div> | |
| """ | |
| # Client-side JS (runs once on load): theme init + toggle, and −/+ steppers | |
| # on every slider's number box. | |
| INIT_JS = """ | |
| () => { | |
| if (window.__nxInit) return; | |
| window.__nxInit = true; | |
| const body = document.body; | |
| const apply = (m) => { | |
| document.documentElement.dataset.theme = m; | |
| body.dataset.theme = m; | |
| body.classList.toggle('dark', m === 'dark'); | |
| // inline styles so the icon swap never depends on CSS specificity or load order | |
| document.querySelectorAll('#theme-toggle .i-sun').forEach((el) => { el.style.display = m === 'dark' ? 'block' : 'none'; }); | |
| document.querySelectorAll('#theme-toggle .i-moon').forEach((el) => { el.style.display = m === 'dark' ? 'none' : 'block'; }); | |
| }; | |
| let saved = null; | |
| try { saved = localStorage.getItem('nx-theme'); } catch (e) {} | |
| apply(saved || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')); | |
| document.addEventListener('click', (e) => { | |
| if (!e.target.closest('#theme-toggle')) return; | |
| const next = body.dataset.theme === 'dark' ? 'light' : 'dark'; | |
| apply(next); | |
| try { localStorage.setItem('nx-theme', next); } catch (e) {} | |
| }); | |
| const switchTab = (dir) => { | |
| const tabs = Array.from(document.querySelectorAll('[role="tablist"] [role="tab"]')); | |
| if (!tabs.length) return; | |
| const cur = tabs.findIndex((t) => t.getAttribute('aria-selected') === 'true'); | |
| const next = tabs[(cur + dir + tabs.length) % tabs.length]; | |
| if (next) next.click(); | |
| }; | |
| document.addEventListener('click', (e) => { | |
| if (e.target.closest('#mobile-tab-prev')) switchTab(-1); | |
| if (e.target.closest('#mobile-tab-next')) switchTab(1); | |
| }); | |
| const decorate = () => { | |
| apply(body.dataset.theme); | |
| document.querySelectorAll('.dslider').forEach((el) => { | |
| const num = el.querySelector('input[type=number]'); | |
| if (!num || num.dataset.stepper) return; | |
| num.dataset.stepper = '1'; | |
| const mk = (sym, dir) => { | |
| const b = document.createElement('button'); | |
| b.type = 'button'; | |
| b.className = 'step-btn'; | |
| b.textContent = sym; | |
| b.setAttribute('aria-label', dir > 0 ? 'Increase' : 'Decrease'); | |
| b.addEventListener('click', (ev) => { | |
| ev.preventDefault(); | |
| dir > 0 ? num.stepUp() : num.stepDown(); | |
| num.dispatchEvent(new Event('input', { bubbles: true })); | |
| num.dispatchEvent(new Event('change', { bubbles: true })); | |
| }); | |
| return b; | |
| }; | |
| num.parentNode.insertBefore(mk('−', -1), num); | |
| num.parentNode.insertBefore(mk('+', 1), num.nextSibling); | |
| }); | |
| }; | |
| new MutationObserver(decorate).observe(document.body, { childList: true, subtree: true }); | |
| decorate(); | |
| } | |
| """ | |
| # CSS | |
| CUSTOM_CSS = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Noto+Serif:ital,wght@0,400;0,500;1,400&display=swap'); | |
| :root { | |
| --bg:#FEFDF9; --card:#FFFFFF; --border:#E9E7DF; --chip:#F5F3EC; --track:#F0EEE6; | |
| --text:#151513; --muted:#5F5E58; --faint:#9A988F; --accent:#E8651A; | |
| --btn-bg:#111110; --btn-fg:#FEFDF9; --tab-active:#FFFFFF; | |
| } | |
| body[data-theme="dark"] { | |
| --bg:#121211; --card:#1B1B1A; --border:#2B2B29; --chip:#272725; --track:#262624; | |
| --text:#F3F1EA; --muted:#A3A199; --faint:#6D6C66; | |
| --btn-bg:#FEFDF9; --btn-fg:#111110; --tab-active:#2B2B29; | |
| } | |
| body, gradio-app, .gradio-container, .main, .app { background:var(--bg) !important; } | |
| .gradio-container { | |
| max-width:1440px !important; margin:0 auto !important; padding:0 48px 48px !important; | |
| font-family:'Inter',sans-serif !important; color:var(--text); | |
| } | |
| .gradio-container footer { display:none !important; } | |
| .gradio-container .block, .gradio-container .form { | |
| background:transparent !important; border:none !important; box-shadow:none !important; padding:0 !important; | |
| } | |
| /* ---- top bar ---- */ | |
| .topbar { display:flex; align-items:center; justify-content:space-between; padding-top:40px; } | |
| .wordmark { font-size:15px; font-weight:500; color:var(--text); letter-spacing:-0.005em; } | |
| .top-actions { display:flex; align-items:center; gap:10px; } | |
| .icon-btn { | |
| width:44px; height:44px; border-radius:50%; border:none; background:var(--chip); color:var(--text); | |
| display:grid; place-items:center; cursor:pointer; padding:0; flex:none; | |
| } | |
| .icon-btn .i-sun { display:none; } | |
| body[data-theme="dark"] .icon-btn .i-sun { display:block; } | |
| body[data-theme="dark"] .icon-btn .i-moon { display:none; } | |
| .gradio-container a.pill { | |
| display:inline-flex; align-items:center; gap:8px; height:44px; padding:0 28px; border-radius:999px; | |
| background:var(--btn-bg); color:var(--btn-fg) !important; text-decoration:none !important; | |
| font-size:14.5px; font-weight:500; letter-spacing:0.005em; white-space:nowrap; | |
| } | |
| .gradio-container a.ghost { | |
| display:inline-flex; align-items:center; gap:6px; height:44px; padding:0 16px; | |
| color:var(--text) !important; text-decoration:none !important; font-size:14.5px; font-weight:500; | |
| } | |
| .gradio-container a.pill:hover, .gradio-container a.ghost:hover, .icon-btn:hover { opacity:0.85; } | |
| .gradio-container a:focus-visible, .icon-btn:focus-visible { outline:2px solid var(--accent); outline-offset:2px; } | |
| /* ---- hero ---- */ | |
| .hero { text-align:center; padding:60px 0 52px; } | |
| .hero-title { | |
| font-family:'Noto Serif',serif !important; font-weight:500; font-size:clamp(30px,4.6vw,46px); | |
| letter-spacing:-0.015em; line-height:1.15; color:var(--text); | |
| } | |
| .hero-sub { | |
| font-family:'Noto Serif',serif !important; font-size:16px; line-height:1.6; color:var(--muted) !important; | |
| margin:20px auto 28px; max-width:600px; letter-spacing:0.001em; | |
| } | |
| .hero-actions { display:flex; justify-content:center; align-items:center; gap:24px; flex-wrap:wrap; row-gap:14px; } | |
| /* ---- mobile carousel arrows (tab nav on narrow screens) ---- */ | |
| .mobile-tab-nav { display:none; justify-content:flex-end; gap:10px; padding:8px 0 28px; } | |
| .mobile-nav-btn { | |
| width:36px; height:36px; border-radius:50%; border:1px solid var(--border); background:transparent; | |
| color:var(--faint); display:grid; place-items:center; cursor:pointer; padding:0; | |
| } | |
| .mobile-nav-btn:hover { color:var(--text); border-color:var(--faint); } | |
| /* ---- tabs (pill switcher) ---- */ | |
| .gradio-container [role="tablist"] { | |
| display:inline-flex !important; width:auto !important; gap:4px; padding:4px !important; | |
| border:1px solid var(--border) !important; border-radius:999px !important; background:var(--chip) !important; | |
| margin-bottom:36px !important; | |
| } | |
| .gradio-container [role="tablist"]::after, .gradio-container [role="tab"]::after { display:none !important; } | |
| .gradio-container [role="tab"] { | |
| display:flex !important; align-items:center !important; height:36px !important; box-sizing:border-box !important; | |
| border:1px solid transparent !important; border-radius:999px !important; background:transparent !important; | |
| color:var(--faint) !important; padding:0 18px !important; font-size:14.5px !important; font-weight:500 !important; | |
| letter-spacing:0.001em !important; | |
| } | |
| .gradio-container [role="tab"][aria-selected="true"] { | |
| background:var(--tab-active) !important; color:var(--text) !important; border-color:var(--border) !important; | |
| } | |
| .gradio-container .tabitem { background:transparent !important; border:none !important; padding:0 !important; } | |
| .statement { font-size:15px; line-height:1.65; color:var(--text) !important; margin:0 0 32px; letter-spacing:0.001em; } | |
| .statement b { font-weight:600; } | |
| /* ---- cards ---- */ | |
| .theorem-row { align-items:stretch !important; margin-bottom:32px; } | |
| .card { | |
| background:var(--card) !important; border:1px solid var(--border) !important; border-radius:16px !important; | |
| padding:22px 24px !important; gap:22px !important; box-sizing:border-box !important; | |
| } | |
| .card:not(.plotcard) { height:100% !important; } | |
| .plotcard { display:flex !important; flex-direction:column !important; gap:16px !important; } | |
| .plotcard > div:nth-child(2) img, .plotcard > div:nth-child(2) svg { | |
| width:100% !important; height:auto !important; display:block !important; | |
| } | |
| /* ---- sliders ---- */ | |
| .dslider { --slider-color:var(--accent); --neutral-200:var(--track); } | |
| .dslider .head { display:flex; align-items:center; justify-content:space-between; margin-bottom:14px; } | |
| .dslider label span, .dslider [data-testid="block-info"] { | |
| font-size:14px !important; font-weight:400 !important; color:var(--muted) !important; background:transparent !important; | |
| } | |
| .dslider .tab-like-container { display:flex; align-items:center; background:var(--chip) !important; border:none !important; border-radius:8px; padding:4px 6px; } | |
| .dslider input[type=number] { | |
| width:52px !important; text-align:center; background:transparent !important; border:none !important; box-shadow:none !important; | |
| color:var(--text) !important; font-weight:600; font-size:14px; padding:4px !important; -moz-appearance:textfield; | |
| } | |
| .dslider input[type=number]::-webkit-inner-spin-button, .dslider input[type=number]::-webkit-outer-spin-button { -webkit-appearance:none; margin:0; } | |
| .dslider .reset-button { display:none !important; } | |
| .step-btn { | |
| width:22px; height:22px; border:none; background:transparent; color:var(--faint); cursor:pointer; | |
| font-size:15px; line-height:1; border-radius:6px; padding:0; | |
| } | |
| .step-btn:hover { color:var(--text); background:var(--track); } | |
| .dslider .slider_input_container { display:flex; flex-wrap:wrap; align-items:center; } | |
| .dslider input[type=range] { | |
| order:-1; width:100% !important; height:6px !important; border-radius:999px !important; | |
| -webkit-appearance:none; appearance:none; background-color:var(--track) !important; | |
| } | |
| .dslider input[type=range]::-webkit-slider-thumb { | |
| -webkit-appearance:none; width:16px; height:16px; margin-top:-5px; border-radius:50%; background:var(--accent); | |
| border:2px solid #fff; box-shadow:0 0 0 1px rgba(0,0,0,0.08); cursor:pointer; | |
| } | |
| .dslider input[type=range]::-moz-range-thumb { | |
| width:12px; height:12px; border-radius:50%; background:var(--accent); border:2px solid #fff; cursor:pointer; | |
| } | |
| .dslider .min_value, .dslider .max_value { font-size:11px !important; color:var(--faint) !important; margin-top:8px; } | |
| .dslider .min_value { margin-right:auto; } | |
| .gradio-container button.reset-btn { | |
| width:100%; height:48px; border:none !important; border-radius:999px !important; background:var(--chip) !important; | |
| color:var(--text) !important; font-size:15px !important; font-weight:400 !important; box-shadow:none !important; | |
| } | |
| .gradio-container button.reset-btn:hover { background:var(--track) !important; } | |
| /* ---- plot card ---- */ | |
| .plot-head { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px 20px; } | |
| .plot-title { display:flex; align-items:center; gap:8px; font-size:15px; font-weight:500; color:var(--text); } | |
| .legend { display:flex; flex-wrap:wrap; gap:6px 18px; font-size:12px; color:var(--muted); } | |
| .legend i { display:inline-block; width:14px; height:0; border-top:2px solid; margin-right:6px; vertical-align:middle; } | |
| .legend i.dash { border-top-style:dashed; } | |
| /* ---- stats (now inside the plot card, under the chart) ---- */ | |
| .stats { | |
| display:flex; flex-direction:row; flex-wrap:wrap; align-items:baseline; gap:6px 26px; flex:none; | |
| padding-top:16px; border-top:1px solid var(--border); font-size:12px; color:var(--muted); | |
| } | |
| .stats p { margin:0; white-space:nowrap; } | |
| .stats b { color:var(--text); font-weight:600; } | |
| .stats em { color:var(--text); font-style:italic; font-weight:400; } | |
| .notice { | |
| display:flex; align-items:flex-start; gap:14px; padding:20px 18px; border:1px solid var(--border); | |
| border-radius:14px; background:var(--card); font-size:13px; line-height:1.6; color:var(--muted); | |
| margin-top:8px; | |
| } | |
| .notice svg { flex:none; margin-top:2px; } | |
| @media (max-width:768px) { | |
| .gradio-container { padding:0 16px 32px !important; } | |
| .topbar { padding-top:20px; } | |
| .wordmark { font-size:14px; } | |
| .icon-btn { width:38px; height:38px; } | |
| .gradio-container a.pill { padding:0 20px; height:40px; font-size:14px; } | |
| .hero { padding:36px 0 8px; } | |
| .hero-actions { flex-direction:column; gap:14px; } | |
| .gradio-container [role="tablist"] { display:none !important; } | |
| .mobile-tab-nav { display:flex !important; } | |
| .theorem-row { flex-direction:column !important; } | |
| .theorem-row .card { height:auto !important; } | |
| .stats { flex-direction:column; gap:8px; } | |
| .stats p { white-space:normal; } | |
| } | |
| """ | |
| HEAD_HTML = """ | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <script> | |
| (function () { | |
| const init = (%s); | |
| if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); | |
| else init(); | |
| })(); | |
| </script> | |
| """ % INIT_JS | |
| NEXENDA_THEME = gr.themes.Base( | |
| primary_hue=gr.themes.colors.orange, | |
| neutral_hue=gr.themes.colors.stone, | |
| font=gr.themes.GoogleFont("Inter"), | |
| ).set(slider_color="#E8651A", color_accent="#E8651A") | |
| # UI | |
| with gr.Blocks(title="Council AI — Theorem Explorer") as demo: | |
| gr.HTML(TOPBAR_HTML) | |
| gr.HTML(HERO_HTML) | |
| gr.HTML(MOBILE_NAV_HTML) | |
| with gr.Tab("Deliberation Convergence"): | |
| gr.HTML(T1_STATEMENT) | |
| with gr.Row(equal_height=True, elem_classes="theorem-row"): | |
| with gr.Column(scale=1, min_width=320, elem_classes="card"): | |
| gamma_s = gr.Slider(0.1, 0.9, value=DEFAULTS_T1[0], step=0.01, label="Contraction rate γ", elem_classes="dslider") | |
| rounds_s = gr.Slider(4, 30, value=DEFAULTS_T1[1], step=1, label="Deliberation rounds T", elem_classes="dslider") | |
| d0_s = gr.Slider(0.2, 2.5, value=DEFAULTS_T1[2], step=0.05, label="Initial disagreement ‖H(0)−H*‖", elem_classes="dslider") | |
| tau_s = gr.Slider(0.70, 0.99, value=DEFAULTS_T1[3], step=0.01, label="Consensus threshold τ", elem_classes="dslider") | |
| reset1_btn = gr.Button("Reset to defaults", elem_classes="reset-btn") | |
| with gr.Column(scale=3, min_width=420, elem_classes="card plotcard"): | |
| legend1 = gr.HTML() | |
| plot1 = gr.Plot(show_label=False, container=False) | |
| stats1 = gr.HTML() | |
| t1_inputs = [gamma_s, rounds_s, d0_s, tau_s] | |
| t1_outputs = [plot1, legend1, stats1] | |
| for ctrl in t1_inputs: | |
| ctrl.change(theorem1, inputs=t1_inputs, outputs=t1_outputs) | |
| reset1_btn.click(reset_theorem1, inputs=None, outputs=t1_inputs + t1_outputs) | |
| demo.load(theorem1, inputs=t1_inputs, outputs=t1_outputs) | |
| with gr.Tab("Multi-Expert Safety Bound"): | |
| gr.HTML(T2_STATEMENT) | |
| with gr.Row(equal_height=True, elem_classes="theorem-row"): | |
| with gr.Column(scale=1, min_width=320, elem_classes="card"): | |
| p_s = gr.Slider(0.001, 0.15, value=DEFAULTS_T2[0], step=0.001, label="Individual error rate p", elem_classes="dslider") | |
| rho_s = gr.Slider(0.0, 1.0, value=DEFAULTS_T2[1], step=0.01, label="Error correlation ρ", elem_classes="dslider") | |
| n_s = gr.Slider(2, 15, value=DEFAULTS_T2[2], step=1, label="Total experts n", elem_classes="dslider") | |
| k_s = gr.Slider(1, 15, value=DEFAULTS_T2[3], step=1, label="Agreement required k", elem_classes="dslider") | |
| reset2_btn = gr.Button("Reset to defaults", elem_classes="reset-btn") | |
| with gr.Column(scale=3, min_width=420, elem_classes="card plotcard"): | |
| legend2 = gr.HTML() | |
| plot2 = gr.Plot(show_label=False, container=False) | |
| stats2 = gr.HTML() | |
| t2_inputs = [p_s, rho_s, n_s, k_s] | |
| t2_outputs = [plot2, legend2, stats2] | |
| for ctrl in t2_inputs: | |
| ctrl.change(theorem2, inputs=t2_inputs, outputs=t2_outputs) | |
| reset2_btn.click(reset_theorem2, inputs=None, outputs=t2_inputs + t2_outputs) | |
| demo.load(theorem2, inputs=t2_inputs, outputs=t2_outputs) | |
| gr.HTML(NOTICE_HTML) | |
| # theme init/toggle + slider steppers (client-side only) | |
| demo.load(fn=None, inputs=None, outputs=None, js=INIT_JS) | |
| if __name__ == "__main__": | |
| demo.launch(css=CUSTOM_CSS, theme=NEXENDA_THEME, head=HEAD_HTML, ssr_mode=False) |