Skip to content

Commit 7cdbd73

Browse files
authored
Update index.html
1 parent 335ef9e commit 7cdbd73

1 file changed

Lines changed: 114 additions & 29 deletions

File tree

‎app/views/index.html‎

Lines changed: 114 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,13 @@
9595
#messages { padding:6px 0 20px; }
9696
.msg { margin:14px 0; padding:12px 16px; border-radius:12px; white-space:pre-wrap; word-break:break-word; max-width:85%; }
9797
.msg.user { background:var(--accent-soft); color:var(--fg); margin-left:auto; border:1px solid var(--line); }
98-
.msg.agent { background:var(--panel); border:1px solid var(--line); box-shadow:var(--shadow); }
98+
.msg.agent { background:transparent; border:0; box-shadow:none; max-width:100%; padding:2px 0; }
9999
.msg.tool { background:transparent; border:1px dashed var(--line); color:var(--muted); font-family:ui-monospace, monospace; font-size:12px; max-width:100%; }
100100
.msg.err { background:transparent; border:1px solid var(--err); color:var(--err); max-width:100%; }
101101
.msg .who { font-size:12px; color:var(--muted); margin-bottom:4px; }
102102
.msg pre { background:rgba(0,0,0,.05); border-radius:8px; padding:10px; overflow:auto; font-size:12px; }
103+
.progress { display:flex; align-items:center; gap:8px; margin:10px 0; font-size:13px; color:var(--muted); }
104+
.progress .spin { display:inline-block; animation:spin 1s linear infinite; }
103105
.artifact { display:inline-flex; align-items:center; gap:6px; background:var(--panel); border:1px solid var(--line); border-radius:999px; padding:5px 14px; font-size:13px; text-decoration:none; color:var(--fg); box-shadow:var(--shadow); }
104106
.artifact:hover { border-color:var(--accent); color:var(--accent); }
105107
.artifact .size { color:var(--muted); font-size:11px; }
@@ -334,7 +336,12 @@ <h1>一句话处理 Excel 表格</h1>
334336
const box = $('messages'); box.innerHTML = '';
335337
for (const run of detail.runs) {
336338
addMsg('user', run.prompt);
337-
if (run.answer) addMsg('agent', run.answer);
339+
if (run.events && run.events.length) {
340+
let agentDiv = null;
341+
for (const e of run.events) agentDiv = renderEvent(e, agentDiv, false);
342+
} else if (run.answer) {
343+
addMsg('agent', run.answer);
344+
}
338345
if (run.error) addMsg('err', run.error);
339346
}
340347
loadConversations();
@@ -432,7 +439,6 @@ <h1>一句话处理 Excel 表格</h1>
432439
const div = document.createElement('div');
433440
div.className = 'msg ' + kind;
434441
if (kind === 'agent') {
435-
div.innerHTML = `<div class="who">AI</div>`;
436442
div.appendChild(renderAnswer(text));
437443
} else {
438444
div.textContent = text;
@@ -441,6 +447,29 @@ <h1>一句话处理 Excel 表格</h1>
441447
$('messages').scrollTop = $('messages').scrollHeight;
442448
return div;
443449
}
450+
function addAgentText() {
451+
const div = document.createElement('div');
452+
div.className = 'msg agent';
453+
const span = document.createElement('span');
454+
div.appendChild(span);
455+
$('messages').appendChild(div);
456+
$('messages').scrollTop = $('messages').scrollHeight;
457+
return span;
458+
}
459+
// One live progress row per tool: tool_running updates it in place,
460+
// tool_start (a new tool / message boundary) starts a fresh row.
461+
let liveProgress = null;
462+
function addProgress(text, busy, fresh = false) {
463+
if (!liveProgress || fresh) {
464+
const div = document.createElement('div');
465+
div.className = 'progress';
466+
$('messages').appendChild(div);
467+
liveProgress = div;
468+
}
469+
liveProgress.innerHTML = (busy ? '<span class="spin">⚙️</span>' : '<span>✅</span>') +
470+
'<span>' + escapeHtml(text) + '</span>';
471+
$('messages').scrollTop = $('messages').scrollHeight;
472+
}
444473
function renderAnswer(text) {
445474
// minimal markdown: code fences + bold
446475
const parts = String(text).split(/```/);
@@ -511,6 +540,86 @@ <h1>一句话处理 Excel 表格</h1>
511540
if (Array.isArray(d)) return d.map(strData).join(', ');
512541
return JSON.stringify(d);
513542
}
543+
// The harness's "tool_calls" payload -> "Bash(command='ls -la')".
544+
// Each entry is {name, args}; args values arrive pre-formatted and
545+
// pre-truncated by the harness (repr strings), so they are safe to show
546+
// verbatim -- addProgress escapes them before they reach the DOM.
547+
function toolCallLabel(calls) {
548+
if (!Array.isArray(calls)) return '';
549+
return calls.map(c => toolOneLabel(c)).filter(Boolean).join(', ');
550+
}
551+
function toolOneLabel(c) {
552+
const name = (c && c.name) || 'tool';
553+
const args = (c && typeof c.args === 'object' && c.args) || {};
554+
const params = Object.keys(args).map(k => k + '=' + args[k]).join(' ');
555+
return params ? name + '(' + params + ')' : name;
556+
}
557+
// A tool round reuses ONE progress row (tool_start opens it,
558+
// tool_running/tool then update it in place), so the command has to be
559+
// remembered for the row's later states -- otherwise the finished row
560+
// would read a bare "工具执行完成" and the command would be lost from the
561+
// transcript. Reset per round by tool_start.
562+
let roundLabel = '';
563+
let roundArgsByName = {};
564+
// Harness log lines that are bookkeeping rather than progress: the
565+
// session's generated title says nothing about what the agent is doing.
566+
const LOG_SKIP = [/^session titled\b/];
567+
// One harness event (live SSE or stored replay) -> message-flow DOM.
568+
// Returns the current agent-text span, or null when the event starts a
569+
// new assistant message (tool_start / retry are message boundaries).
570+
// live=false (history replay) skips ask/confirm: the run is over, so
571+
// the answer box would be dead.
572+
function renderEvent(e, agentDiv, live = true) {
573+
if (e.type === 'delta') {
574+
if (!agentDiv) agentDiv = addAgentText();
575+
agentDiv.textContent += (e.data?.text ?? '');
576+
$('messages').scrollTop = $('messages').scrollHeight;
577+
} else if (e.type === 'notify' && e.data) {
578+
const kind = e.data.kind, data = e.data.data;
579+
if (kind === 'tool_start') {
580+
agentDiv = null;
581+
roundLabel = strData(data); // names only until tool_calls lands
582+
roundArgsByName = {};
583+
addProgress('准备执行: ' + roundLabel, true, true);
584+
} else if (kind === 'tool_calls') {
585+
// Arrives right behind tool_start, naming the same round but with
586+
// arguments: upgrade that row in place (no `fresh`) so the user
587+
// sees the actual command instead of a bare tool name.
588+
const label = toolCallLabel(data);
589+
if (label) {
590+
roundLabel = label;
591+
for (const c of data) {
592+
if (c && c.name) roundArgsByName[c.name] = toolOneLabel(c);
593+
}
594+
addProgress('准备执行: ' + label, true);
595+
}
596+
} else if (kind === 'tool_running') {
597+
const name = strData(data);
598+
addProgress('正在执行: ' + (roundArgsByName[name] || name), true);
599+
} else if (kind === 'tool') {
600+
addProgress(roundLabel ? '完成: ' + roundLabel : '工具执行完成', false);
601+
} else if (kind === 'todos') {
602+
renderTodos(data);
603+
} else if (kind === 'error') {
604+
addMsg('err', typeof data === 'string' ? data : JSON.stringify(data));
605+
} else if (kind === 'retry') {
606+
agentDiv = null;
607+
addProgress('模型调用重试中…', true, true);
608+
} else if (kind === 'compact') {
609+
addProgress('正在压缩上下文…', true);
610+
} else if (live && (kind === 'ask' || kind === 'confirm')) {
611+
addAsk(data, e.run_id || currentRun);
612+
}
613+
} else if (e.type === 'log' && e.data && e.data.message) {
614+
const msg = String(e.data.message);
615+
// Own row (`fresh`): a log line would otherwise overwrite the live
616+
// tool row and erase the command the user just saw. Harness logs
617+
// are rare and noteworthy (compaction, MCP, auto-save/agent errors),
618+
// so one row each is proportionate.
619+
if (!LOG_SKIP.some(re => re.test(msg))) addProgress(msg, true, true);
620+
}
621+
return agentDiv;
622+
}
514623
function setRunStatus(text, busy) {
515624
const box = $('runStatus');
516625
if (!text) { box.style.display = 'none'; return; }
@@ -538,32 +647,8 @@ <h1>一句话处理 Excel 表格</h1>
538647
let agentDiv = null;
539648
es.onmessage = ev => {
540649
const e = JSON.parse(ev.data);
541-
if (e.type === 'delta') {
542-
if (!agentDiv) agentDiv = addMsg('agent', '');
543-
agentDiv.lastChild.textContent = (agentDiv.lastChild.textContent || '') + (e.data?.text ?? '');
544-
$('messages').scrollTop = $('messages').scrollHeight;
545-
} else if (e.type === 'notify' && e.data) {
546-
const kind = e.data.kind, data = e.data.data;
547-
if (kind === 'tool_start') {
548-
setRunStatus('准备执行: ' + strData(data), true);
549-
} else if (kind === 'tool_running') {
550-
setRunStatus('正在执行: ' + strData(data), true);
551-
} else if (kind === 'tool') {
552-
setRunStatus('工具执行完成', true);
553-
} else if (kind === 'todos') {
554-
renderTodos(data);
555-
} else if (kind === 'error') {
556-
addMsg('err', typeof data === 'string' ? data : JSON.stringify(data));
557-
} else if (kind === 'retry') {
558-
setRunStatus('模型调用重试中…', true);
559-
} else if (kind === 'compact') {
560-
setRunStatus('正在压缩上下文…', true);
561-
} else if (kind === 'ask' || kind === 'confirm') {
562-
addAsk(data, runId);
563-
}
564-
} else if (e.type === 'log' && e.data && e.data.message) {
565-
setRunStatus(e.data.message, true);
566-
} else if (e.type === 'run') {
650+
agentDiv = renderEvent(e, agentDiv);
651+
if (e.type === 'run') {
567652
es.close(); es = null; currentRun = null;
568653
$('stopBtn2').style.display = 'none';
569654
setRunStatus('', false);

0 commit comments

Comments
 (0)