This commit is contained in:
2026-06-09 20:55:16 +08:00
parent 721caccc58
commit 1e793ce814
2 changed files with 168 additions and 35 deletions
+90 -4
View File
@@ -259,6 +259,40 @@
}
@keyframes blink { 50% { opacity: 0; } }
/* 执行过程 */
.trace-panel {
display: flex;
flex-direction: column;
gap: 5px;
margin-bottom: 8px;
color: var(--text-dim);
font-size: 0.78rem;
line-height: 1.45;
white-space: normal;
}
.trace-panel:empty { display: none; }
.trace-item {
border-left: 2px solid var(--accent-border);
padding-left: 8px;
}
.trace-item.running { color: var(--text-dim); }
.trace-item.success { opacity: .86; }
.trace-item.error {
color: var(--danger);
border-left-color: var(--danger);
}
.trace-detail {
margin-top: 4px;
font-family: Consolas, 'Fira Code', monospace;
background: var(--code-bg);
border: 1px solid var(--border);
border-radius: 6px;
padding: 6px 8px;
white-space: pre-wrap;
overflow-x: auto;
}
.answer-text { display: inline; }
/* 错误消息 */
.error-msg {
color: var(--danger);
@@ -830,13 +864,49 @@ function addAIBubble() {
const bub = document.createElement('div');
bub.className = 'bubble typing-cursor';
const trace = document.createElement('div');
trace.className = 'trace-panel';
const txt = document.createElement('span');
txt.className = 'answer-text';
bub.appendChild(trace);
bub.appendChild(txt);
row.appendChild(av);
row.appendChild(bub);
msgBox.appendChild(row);
scrollToBottom();
return { bub, txt };
return { bub, txt, trace };
}
function appendTrace(aiBubble, frame) {
if (!aiBubble.trace) return;
const item = document.createElement('div');
item.className = `trace-item ${frame.status || ''}`;
const label = frame.message || [frame.tool, frame.stage, frame.status].filter(Boolean).join(' ');
item.textContent = label;
const data = frame.data || {};
const details = [];
if (data.sql) details.push(data.sql);
const stats = [];
if (data.database) stats.push(`数据库: ${data.database}`);
if (typeof data.rows === 'number') stats.push(`行数: ${data.rows}`);
if (typeof data.columns === 'number') stats.push(`列数: ${data.columns}`);
if (typeof data.count === 'number') stats.push(`结果数: ${data.count}`);
if (data.truncated) stats.push(`已截断,最多 ${data.max_rows || ''}`);
if (data.reason) stats.push(`原因: ${data.reason}`);
if (data.error) stats.push(`错误: ${data.error}`);
if (stats.length) details.push(stats.join(' '));
if (details.length) {
const detail = document.createElement('div');
detail.className = 'trace-detail';
detail.textContent = details.join('\n');
item.appendChild(detail);
}
aiBubble.trace.appendChild(item);
scrollToBottom();
}
async function streamChat(messages, aiBubble, webSearch = false) {
@@ -880,13 +950,29 @@ async function streamChat(messages, aiBubble, webSearch = false) {
}
try {
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === 'object' && parsed.error) {
throw new Error(parsed.error);
}
if (typeof parsed === 'string') {
full += parsed;
txtEl.innerHTML = renderMarkdown(full);
scrollToBottom();
continue;
}
if (parsed && typeof parsed === 'object') {
if (parsed.type === 'error' || parsed.error) {
throw new Error(parsed.error || parsed.message || '流式响应错误');
}
if (parsed.type === 'delta') {
const delta = parsed.text || '';
if (delta) {
full += delta;
txtEl.innerHTML = renderMarkdown(full);
scrollToBottom();
}
continue;
}
if (parsed.type === 'trace') {
appendTrace(aiBubble, parsed);
continue;
}
}
} catch (e) {
if (e instanceof SyntaxError) continue;