103 lines
3.4 KiB
JavaScript
103 lines
3.4 KiB
JavaScript
(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]');
|
|
};
|
|
})();
|