"""F4 - Daimon's entire UI as ONE gr.Blocks app (the Off-Brand custom UI, mounted at "/" via gr.mount_gradio_app - no separate static frontend, no second isolated Gradio app at "/gradio"). Same living loop (engine/loop.py) as before; this module is just its window. Layout (one gr.Row, three gr.Column): - Chat - gr.Chatbot + textbox, streamed turn-by-turn (F2) - State vector - the 10-layer spec (engine/recompile.layer_summaries), rendered as custom HTML: bars for L3/L5, GitHub-style diffs for their most recent mutations, qualitative summaries + edit-policy badges for the other 8 layers. - PERSONA.md + audit log + governance demo (F3) The CSS below is Daimon's own dark theme (ported from the previous app/frontend/styles.css), passed to gr.mount_gradio_app(..., css=...) so the whole page - chrome included - looks like Daimon, not default Gradio. """ from __future__ import annotations import html import sys from pathlib import Path from typing import Any REPO_ROOT = Path(__file__).resolve().parent.parent if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) import gradio as gr # noqa: E402 from engine import governance_demo, recompile # noqa: E402 from engine.loop import SLUG, finish_turn, step_stream # noqa: E402 from engine.spec_bridge import get_state # noqa: E402 # ── Daimon's dark theme, ported from app/frontend/styles.css ─────────────── CUSTOM_CSS = """ :root { --bg: #0a0a10; --panel: #14141f; --panel-2: #191926; --panel-border: rgba(255, 255, 255, 0.08); --ink: #f1f0f7; --ink-dim: #908dab; --accent: #8c7bf6; --accent-dim: #5b4fc4; --accent-soft: rgba(140, 123, 246, 0.14); --baseline: #4fd1c5; --wall: rgba(255, 255, 255, 0.14); --clamp: #f0a93a; --block: #f06a6a; --mono: "Roboto Mono", "Courier New", monospace; } .gradio-container { background: var(--bg) !important; color: var(--ink); } /* Lock the page itself - only the 3 columns below scroll internally. html/body get a real 100vh so Gradio's own flex-grow chain (.gradio-container > .main.app > .wrap > main.contain > outer .column) resolves to a definite height - but every flex item in that chain has the default min-height:auto (= its content's height), which blocks flex-grow from ever shrinking it below content size. Reset min-height to 0 at every level so the chain actually shrinks to the viewport, then #app-row fills what's left and each panel column scrolls on its own. */ html, body, gradio-app { height: 100% !important; overflow: hidden !important; margin: 0 !important; } gradio-app, .gradio-container, .gradio-container .app, .gradio-container .wrap, main.contain, main.contain > .column { min-height: 0 !important; } /* The header markdown blocks above #app-row must keep their natural size (not get squeezed to ~0 by #app-row's flex-grow); only #app-row grows. */ main.contain > .column > .block { flex: 0 0 auto !important; } #app-row { flex: 1 1 auto !important; min-height: 0 !important; overflow: hidden !important; flex-wrap: nowrap !important; } #app-row > .column { height: 100% !important; min-height: 0 !important; overflow-y: auto !important; overflow-x: hidden !important; flex-wrap: nowrap !important; } #app-row > .column > .block { flex: 0 0 auto !important; width: 100% !important; } /* State vector - 10 layer cards */ #layers { display: flex; flex-direction: column; gap: 0.6rem; } .layer-card { border: 1px solid var(--panel-border); border-radius: 10px; background: var(--panel-2); padding: 0.6rem 0.75rem; display: flex; flex-direction: column; gap: 0.4rem; margin-bottom: 0.6rem; } .layer-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; } .layer-title { font-family: var(--mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink); font-weight: 700; } .layer-badge { font-family: var(--mono); font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-dim); border: 1px solid var(--panel-border); border-radius: 999px; padding: 0.1em 0.6em; white-space: nowrap; } .layer-badge.edit-human { border-color: var(--block); color: var(--block); } .layer-badge.edit-gov { border-color: var(--accent); color: var(--accent); } .layer-badge.edit-review { border-color: var(--clamp); color: var(--clamp); } .layer-lines { margin: 0; padding-left: 1.1rem; font-size: 0.78rem; color: var(--ink-dim); display: flex; flex-direction: column; gap: 0.25rem; } .bar-row { display: grid; grid-template-columns: 9rem 1fr 3.5rem; align-items: center; gap: 0.5rem; font-family: var(--mono); font-size: 0.75rem; } .bar-track { position: relative; height: 10px; border-radius: 999px; background: rgba(255, 255, 255, 0.05); border: 1px solid var(--wall); } .bar-fill { position: absolute; top: 1px; bottom: 1px; left: 0; width: 3px; border-radius: 999px; background: var(--accent); } .bar-fill.clamped { background: var(--clamp); box-shadow: 0 0 8px var(--clamp); } .bar-baseline { position: absolute; top: -2px; bottom: -2px; width: 1px; background: var(--baseline); opacity: 0.7; } .bar-value { text-align: right; color: var(--ink-dim); } /* GitHub-style diffs - per-layer (state vector) and audit log */ .layer-diffs, #audit-log { display: flex; flex-direction: column; gap: 0.35rem; } .diff-block { border: 1px solid var(--panel-border); border-radius: 8px; overflow: hidden; } .diff-block.clamped { border-color: var(--clamp); } .diff-block.blocked { border-color: var(--block); } .diff-meta { padding: 0.2rem 0.5rem; font-size: 0.62rem; color: var(--ink-dim); background: var(--panel-2); border-bottom: 1px dashed var(--panel-border); } .diff-line { padding: 0.12rem 0.5rem; font-family: var(--mono); font-size: 0.7rem; white-space: pre-wrap; } .diff-line.removed { background: rgba(240, 106, 106, 0.12); color: #ffb4b4; } .diff-line.removed::before { content: "- "; } .diff-line.added { background: rgba(94, 226, 150, 0.12); color: #a3f0c2; } .diff-line.added::before { content: "+ "; } /* Governance demo */ .gov-card { border: 1px solid var(--panel-border); border-radius: 10px; padding: 0.6rem 0.75rem; background: var(--panel-2); margin-bottom: 0.5rem; font-size: 0.8rem; } .gov-card.rejected { border-color: var(--block); } .gov-card.clamped { border-color: var(--clamp); } .gov-card .gov-title { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.65rem; color: var(--ink-dim); margin-bottom: 0.25rem; } .gov-card > div + div { margin-top: 0.25rem; } .gov-card .gov-raw { margin-top: 0.35rem; padding-top: 0.35rem; border-top: 1px dashed var(--panel-border); color: var(--ink-dim); font-size: 0.65rem; word-break: break-all; } .legend-dot { display: inline-block; width: 0.6em; height: 0.6em; border-radius: 50%; margin: 0 0.15em; } .legend-dot.edit-human { background: var(--block); } .legend-dot.edit-gov { background: var(--accent); } .legend-dot.edit-review { background: var(--clamp); } """ # ── Server-side ports of app/frontend/brain.js's render helpers ──────────── def _pct(value: float, range_: list[float]) -> float: lo, hi = range_ span = max(hi - lo, 1e-9) return max(0.0, min(100.0, (value - lo) / span * 100.0)) def _edit_class(policy: str | None) -> str: if not policy: return "" if "human" in policy: return "edit-human" if "review" in policy: return "edit-review" return "edit-gov" def _relative_word_baseline(value: Any, mean: float, range_: list[float]) -> str | None: if not isinstance(value, (int, float)): return None word = recompile._relative_word(value, mean, range_) return "at baseline" if word == "at" else f"{word} baseline" def _build_bar_row(f: dict) -> str: return ( '
" ) def _build_diff_block(entry: dict, envs: dict[str, dict]) -> str: env = envs.get(entry["field"]) classes = "diff-block" if entry.get("governance_blocked"): classes += " blocked" elif entry.get("clamped"): classes += " clamped" from_v, to_v = entry["from"], entry["to"] from_s = f"{from_v:.2f}" if isinstance(from_v, (int, float)) else str(from_v) to_s = f"{to_v:.2f}" if isinstance(to_v, (int, float)) else str(to_v) before_word = _relative_word_baseline(from_v, env["mean"], env["range"]) if env else None after_word = _relative_word_baseline(to_v, env["mean"], env["range"]) if env else None added_extra = "" if entry.get("clamped"): added_extra += " [clamped to wall]" if entry.get("governance_blocked"): added_extra += " [blocked]" field = html.escape(entry["field"]) removed = f"{field}: {from_s}" + (f" ({before_word})" if before_word else "") added = f"{field}: {to_s}" + (f" ({after_word})" if after_word else "") + added_extra return ( f'(no mutations yet)
{html.escape(identity['field'])} directly.state.json - "
"identity.* has no declared range in personaxis.md, so the spec engine doesn't "
"know how to mutate it at all.{html.escape(overflow['field'])} "
f"{overflow['before']:.2f} + 5.0 (way outside its declared range).mood.tone {r['from']:.2f} → {r['to']:.2f}, "
"logged as a normal audited mutation (actor: human-operator).