diff --git a/hive-ag3nt/assets/agent.css b/hive-ag3nt/assets/agent.css new file mode 100644 index 0000000..1943405 --- /dev/null +++ b/hive-ag3nt/assets/agent.css @@ -0,0 +1,140 @@ +: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); } diff --git a/hive-ag3nt/assets/live.js b/hive-ag3nt/assets/live.js new file mode 100644 index 0000000..28200e4 --- /dev/null +++ b/hive-ag3nt/assets/live.js @@ -0,0 +1,103 @@ +(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]'); + }; +})(); diff --git a/hive-ag3nt/src/web_ui.rs b/hive-ag3nt/src/web_ui.rs index d6cd09f..df88324 100644 --- a/hive-ag3nt/src/web_ui.rs +++ b/hive-ag3nt/src/web_ui.rs @@ -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#" -
▓█▓▒░ NEEDS L0G1N ▓█▓▒░
\nNo Claude session in ~/.claude/. The harness is up but the turn loop is paused until you log in.