extract dashboard + agent CSS/JS to assets/ (include_str!)

This commit is contained in:
müde 2026-05-15 16:32:35 +02:00
parent e9b213690e
commit edf42b7e93
7 changed files with 547 additions and 534 deletions

View file

@ -132,115 +132,13 @@ fn render_online(label: &str) -> String {
/// Live event tail rendered into every `/` response when the agent is online.
/// JS opens an `EventSource` on `/events/stream` and appends rows; no full-page
/// reload, so the login flow and other forms aren't clobbered.
const LIVE_PANEL: &str = r#"
<h3>live</h3>
<div id="live" class="live"><div class="meta">connecting</div></div>
<script>
(function() {
const log = document.getElementById('live');
let placeholder = log.firstChild;
function setPlaceholder(text) {
log.innerHTML = '';
const span = document.createElement('div');
span.className = 'meta';
span.textContent = text;
log.appendChild(span);
placeholder = span;
}
function clearPlaceholder() {
if (placeholder) { log.innerHTML = ''; placeholder = null; }
}
function row(cls, text) {
clearPlaceholder();
const el = document.createElement('div');
el.className = 'row ' + (cls || '');
el.textContent = text;
log.appendChild(el);
log.scrollTop = log.scrollHeight;
return el;
}
function trim(s, n) {
return s.length > n ? s.slice(0, n) + '…' : s;
}
function renderStream(v) {
if (v.type === 'system' && v.subtype === 'init') {
row('sys', '· session init · tools=' + (v.tools||[]).length + ' model=' + (v.model || '?'));
return;
}
if (v.type === 'rate_limit_event') {
const u = Math.round((v.rate_limit_info?.utilization || 0) * 100);
const s = v.rate_limit_info?.status || '';
row('sys', '· rate-limit util=' + u + '% (' + s + ')');
return;
}
if (v.type === 'assistant' && v.message && v.message.content) {
for (const c of v.message.content) {
if (c.type === 'text' && c.text && c.text.trim())
row('text', c.text);
else if (c.type === 'thinking')
row('thinking', '· thinking ');
else if (c.type === 'tool_use')
row('tool-use', ' ' + c.name + ' ' + trim(JSON.stringify(c.input || {}), 240));
}
return;
}
if (v.type === 'user' && v.message && v.message.content) {
for (const c of v.message.content) {
if (c.type === 'tool_result') {
const txt = Array.isArray(c.content)
? c.content.map(p => p.text || '').join(' ')
: (c.content || '');
row('tool-result', ' ' + trim(txt, 300));
}
}
return;
}
if (v.type === 'result') {
row('result', ' done · ' + (v.subtype || '') + (v.is_error ? ' [error]' : ''));
return;
}
// Fallback: small one-liner for unknown events; don't spam.
row('sys', '· ' + trim(JSON.stringify(v), 200));
}
function handle(ev) {
if (ev.kind === 'turn_start') {
const block = row('turn-start', ' TURN ' + ev.from);
const body = document.createElement('div');
body.className = 'turn-body';
body.textContent = ev.body;
block.appendChild(body);
return;
}
if (ev.kind === 'turn_end') {
const cls = ev.ok ? 'turn-end-ok' : 'turn-end-fail';
const sym = ev.ok ? '✓' : '';
row(cls, sym + ' turn ' + (ev.ok ? 'ok' : 'fail') + (ev.note ? ' ' + ev.note : ''));
return;
}
if (ev.kind === 'note') {
row('note', '· ' + ev.text);
return;
}
if (ev.kind === 'stream') {
const v = Object.assign({}, ev); delete v.kind;
renderStream(v);
return;
}
row('note', JSON.stringify(ev));
}
const es = new EventSource('/events/stream');
es.onopen = function() { setPlaceholder('(connected waiting for events)'); };
es.onmessage = function(e) {
try { handle(JSON.parse(e.data)); }
catch (err) { row('note', '[parse err] ' + e.data); }
};
es.onerror = function() {
if (es.readyState === EventSource.CONNECTING) setPlaceholder('(reconnecting)');
else row('note', '[disconnected]');
};
})();
</script>
"#;
const LIVE_PANEL: &str = concat!(
"<h3>live</h3>\n",
"<div id=\"live\" class=\"live\"><div class=\"meta\">connecting…</div></div>\n",
"<script>\n",
include_str!("../assets/live.js"),
"</script>",
);
fn render_needs_login_idle() -> String {
"<p class=\"status-needs-login\">▓█▓▒░ NEEDS L0G1N ▓█▓▒░</p>\n<p>No Claude session in <code>~/.claude/</code>. The harness is up but the turn loop is paused until you log in.</p>\n<form method=\"POST\" action=\"/login/start\">\n <button type=\"submit\" class=\"btn btn-login\">◆ ST4RT L0G1N</button>\n</form>\n<p class=\"meta\">Spawns <code>claude auth login</code> over plain stdio pipes. The OAuth URL will appear here when claude emits it; paste the resulting code back into the form below.</p>".into()
@ -394,147 +292,8 @@ fn html_escape(s: &str) -> String {
.replace('"', "&quot;")
}
const STYLE: &str = r#"
<style>
:root {
--bg: #0a0014;
--fg: #e0d4ff;
--muted: #6c5c8c;
--purple: #cc66ff;
--purple-dim: #4a1a6a;
--amber: #ffb84d;
--green: #66ff99;
}
body {
background: var(--bg);
color: var(--fg);
font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", "Source Code Pro", monospace;
max-width: 70em;
margin: 1.5em auto;
padding: 0 1.5em;
line-height: 1.6;
}
.banner {
color: var(--purple);
text-align: center;
margin: 0 0 1em 0;
font-size: 0.95em;
text-shadow: 0 0 6px rgba(204, 102, 255, 0.5);
overflow-x: auto;
}
h2, h3 {
color: var(--purple);
text-transform: uppercase;
letter-spacing: 0.15em;
text-shadow: 0 0 8px rgba(204, 102, 255, 0.4);
}
.divider {
color: var(--purple-dim);
overflow: hidden;
white-space: nowrap;
margin-bottom: 0.5em;
}
.meta { color: var(--muted); font-size: 0.85em; }
.status-online { color: var(--green); text-shadow: 0 0 6px rgba(102, 255, 153, 0.5); }
.status-needs-login { color: var(--amber); text-shadow: 0 0 6px rgba(255, 184, 77, 0.6); }
code { background: rgba(204, 102, 255, 0.1); padding: 0.05em 0.3em; border-radius: 2px; }
a { color: #66e0ff; }
.btn {
font-family: inherit;
font-size: 1em;
background: var(--bg);
border: 1px solid var(--purple);
color: var(--purple);
padding: 0.25em 0.8em;
cursor: pointer;
letter-spacing: 0.1em;
}
.btn:hover { background: rgba(204, 102, 255, 0.1); }
.btn-login { color: var(--amber); border-color: var(--amber); }
.btn-cancel { color: #ff6b6b; border-color: #ff6b6b; font-size: 0.85em; padding: 0.15em 0.6em; }
.btn-rebuild {
color: var(--amber);
border: 1px solid var(--amber);
padding: 0.15em 0.6em;
font-size: 0.55em;
font-family: inherit;
text-decoration: none;
letter-spacing: 0.1em;
margin-left: 0.6em;
vertical-align: middle;
cursor: pointer;
}
.btn-rebuild:hover { background: rgba(255, 184, 77, 0.1); }
.btn-send { color: var(--green); border-color: var(--green); }
.sendform { display: flex; gap: 0.6em; margin-top: 0.5em; }
.sendform input {
font-family: inherit; font-size: 1em;
background: rgba(255, 255, 255, 0.04);
color: var(--fg);
border: 1px solid var(--purple-dim);
padding: 0.4em 0.6em;
flex: 1;
}
.sendform input:focus { outline: 1px solid var(--purple); }
.loginform { display: flex; gap: 0.6em; margin-top: 0.5em; }
.loginform input {
font-family: inherit; font-size: 1em;
background: rgba(255, 255, 255, 0.04);
color: var(--fg);
border: 1px solid var(--purple-dim);
padding: 0.4em 0.6em;
flex: 1;
}
.loginform input:focus { outline: 1px solid var(--purple); }
pre.diff {
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--purple-dim);
padding: 0.6em 0.8em;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-all;
max-height: 30em;
}
.live {
background: rgba(255, 255, 255, 0.02);
border: 1px solid var(--purple-dim);
padding: 0.4em 0.6em;
overflow-y: auto;
max-height: 32em;
font-family: inherit;
}
.live .row {
white-space: pre-wrap;
word-break: break-word;
padding: 0.05em 0;
line-height: 1.45;
border-left: 2px solid transparent;
padding-left: 0.5em;
margin: 0.1em 0;
}
.live .row + .row { border-top: 0; }
.live .turn-start {
color: var(--amber);
font-weight: bold;
margin-top: 1em;
border-left-color: var(--amber);
padding-top: 0.3em;
}
.live .turn-start:first-child { margin-top: 0; }
.live .turn-body {
color: var(--fg);
font-weight: normal;
margin-top: 0.15em;
padding-left: 1.2em;
opacity: 0.85;
}
.live .turn-end-ok { color: #66ff99; border-left-color: #66ff99; margin-bottom: 0.4em; }
.live .turn-end-fail { color: #ff6b6b; border-left-color: #ff6b6b; margin-bottom: 0.4em; }
.live .text { color: var(--fg); padding-left: 1.2em; }
.live .thinking { color: var(--muted); font-style: italic; padding-left: 1.2em; }
.live .tool-use { color: #66e0ff; padding-left: 1.2em; }
.live .tool-result { color: var(--muted); padding-left: 1.2em; }
.live .result { color: var(--green); padding-left: 0.5em; }
.live .sys, .live .note { color: var(--muted); }
</style>
"#;
const STYLE: &str = concat!(
"<style>\n",
include_str!("../assets/agent.css"),
"</style>",
);