feat(trace): finish run-aware demo verification

This commit is contained in:
zhuyongxin
2026-07-10 21:37:43 +08:00
parent 78c1477198
commit f9df94377b
28 changed files with 779 additions and 459 deletions
+75 -19
View File
@@ -9,6 +9,7 @@ class SuperBizAgentApp {
this.chatHistories = this.loadChatHistories(); // 所有历史对话
this.isCurrentChatFromHistory = false; // 标记当前对话是否是从历史记录加载的
this.lastTraceSessionId = this.loadLastTraceSessionId();
this.lastTraceRunId = this.loadLastTraceRunId();
this.initializeElements();
this.bindEvents();
@@ -310,13 +311,14 @@ class SuperBizAgentApp {
id: this.sessionId,
title: title,
messages: [...this.currentChatHistory],
lastRunId: this.sessionId === this.lastTraceSessionId ? this.lastTraceRunId : '',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
// 添加到历史记录列表的开头
this.chatHistories.unshift(chatHistory);
this.rememberTraceSessionId(this.sessionId);
this.rememberTraceTarget(this.sessionId, null);
// 限制历史记录数量(最多保存50条)
if (this.chatHistories.length > 50) {
@@ -344,6 +346,9 @@ class SuperBizAgentApp {
const history = this.chatHistories[existingIndex];
history.messages = [...this.currentChatHistory];
history.updatedAt = new Date().toISOString();
if (this.sessionId === this.lastTraceSessionId && this.lastTraceRunId) {
history.lastRunId = this.lastTraceRunId;
}
// 如果标题需要更新(第一条消息改变了)
const firstUserMessage = this.currentChatHistory.find(msg => msg.type === 'user');
@@ -444,7 +449,7 @@ class SuperBizAgentApp {
// 加载历史对话
this.sessionId = history.id;
this.rememberTraceSessionId(this.sessionId);
this.rememberTraceTarget(this.sessionId, history.lastRunId || null);
this.currentChatHistory = [...history.messages];
this.isCurrentChatFromHistory = true; // 标记为从历史记录加载
@@ -487,16 +492,39 @@ class SuperBizAgentApp {
}
}
loadLastTraceRunId() {
try {
return localStorage.getItem('lastTraceRunId') || '';
} catch (e) {
return '';
}
}
rememberTraceSessionId(sessionId) {
this.rememberTraceTarget(sessionId, null);
}
rememberTraceTarget(sessionId, runId) {
if (!sessionId) {
return;
}
this.lastTraceSessionId = sessionId;
this.lastTraceRunId = runId || '';
try {
localStorage.setItem('lastTraceSessionId', sessionId);
if (runId) {
localStorage.setItem('lastTraceRunId', runId);
} else {
localStorage.removeItem('lastTraceRunId');
}
} catch (e) {
// localStorage may be unavailable in private or restricted contexts.
}
const history = this.chatHistories.find(item => item && item.id === sessionId);
if (history && runId) {
history.lastRunId = runId;
this.saveChatHistories();
}
this.updateTraceWorkbenchLink();
}
@@ -508,9 +536,30 @@ class SuperBizAgentApp {
const sessionId = this.lastTraceSessionId
|| (this.currentChatHistory.length > 0 ? this.sessionId : '')
|| (recentHistory ? recentHistory.id : '');
this.traceWorkbenchLink.href = sessionId
? `trace.html?sessionId=${encodeURIComponent(sessionId)}`
: 'trace.html';
if (!sessionId) {
this.traceWorkbenchLink.href = 'trace.html';
return;
}
const params = new URLSearchParams({ sessionId });
if (this.lastTraceRunId && sessionId === this.lastTraceSessionId) {
params.set('runId', this.lastTraceRunId);
}
this.traceWorkbenchLink.href = `trace.html?${params.toString()}`;
}
rememberRunMetadata(sseMessage) {
if (!sseMessage) {
return;
}
const payload = sseMessage.data && typeof sseMessage.data === 'object' ? sseMessage.data : {};
const sessionId = sseMessage.sessionId || payload.sessionId;
const runId = sseMessage.runId || payload.runId;
if (!sessionId) {
return;
}
this.lastSessionId = sessionId;
this.lastRunId = runId || '';
this.rememberTraceTarget(sessionId, runId || null);
}
// 切换模式下拉菜单
@@ -675,10 +724,11 @@ class SuperBizAgentApp {
const chatResponse = data.data;
if (chatResponse && chatResponse.success) {
// 保存后端返回的 sessionId,用于 feedback 提交
// 保存后端返回的 sessionId/runId,用于 feedback 提交和 Trace 精确定位
if (chatResponse.sessionId) {
this.lastSessionId = chatResponse.sessionId;
this.rememberTraceSessionId(chatResponse.sessionId);
this.lastRunId = chatResponse.runId || '';
this.rememberTraceTarget(chatResponse.sessionId, chatResponse.runId || null);
}
// 成功:添加实际响应消息(即使 answer 为空也显示)
const answer = chatResponse.answer || '(无回复内容)';
@@ -784,7 +834,9 @@ class SuperBizAgentApp {
console.log('[SSE调试] 解析JSON成功:', sseMessage);
if (sseMessage && typeof sseMessage.type === 'string') {
if (sseMessage.type === 'content') {
if (sseMessage.type === 'metadata') {
this.rememberRunMetadata(sseMessage);
} else if (sseMessage.type === 'content') {
const content = sseMessage.data || '';
fullResponse += content;
console.log('[SSE调试] 添加内容:', content);
@@ -897,7 +949,7 @@ class SuperBizAgentApp {
// assistant 消息末尾加反馈栏(流式消息完成后由 handleStreamComplete 添加)
if (type === 'assistant' && !isStreaming) {
messageContentWrapper.appendChild(this.createFeedbackBar(this.lastSessionId));
messageContentWrapper.appendChild(this.createFeedbackBar(this.lastSessionId, this.lastRunId || ''));
}
messageDiv.appendChild(messageContentWrapper);
@@ -919,7 +971,7 @@ class SuperBizAgentApp {
}
// 创建反馈栏,sessionId 闭包绑定,避免多轮对话时错位
createFeedbackBar(sessionId) {
createFeedbackBar(sessionId, runId = '') {
const bar = document.createElement('div');
bar.className = 'feedback-bar';
@@ -933,8 +985,8 @@ class SuperBizAgentApp {
notUsefulBtn.title = '无用';
notUsefulBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3H10z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M17 2h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
usefulBtn.addEventListener('click', () => this.submitFeedback('useful', bar, sessionId));
notUsefulBtn.addEventListener('click', () => this.submitFeedback('not_useful', bar, sessionId));
usefulBtn.addEventListener('click', () => this.submitFeedback('useful', bar, sessionId, runId));
notUsefulBtn.addEventListener('click', () => this.submitFeedback('not_useful', bar, sessionId, runId));
bar.appendChild(usefulBtn);
bar.appendChild(notUsefulBtn);
@@ -942,7 +994,7 @@ class SuperBizAgentApp {
}
// 提交反馈
async submitFeedback(feedback, barElement, sessionId) {
async submitFeedback(feedback, barElement, sessionId, runId = '') {
if (!sessionId) return;
barElement.querySelectorAll('.feedback-btn').forEach(btn => btn.disabled = true);
@@ -951,7 +1003,7 @@ class SuperBizAgentApp {
const response = await fetch(`${this.apiBaseUrl}/feedback`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId, feedback })
body: JSON.stringify({ sessionId, runId: runId || undefined, feedback })
});
const data = await response.json();
if (data.success) {
@@ -1055,7 +1107,7 @@ class SuperBizAgentApp {
}
// 流式完成后追加反馈栏
if (messageContentWrapper && !messageContentWrapper.querySelector('.feedback-bar')) {
messageContentWrapper.appendChild(this.createFeedbackBar(this.lastSessionId));
messageContentWrapper.appendChild(this.createFeedbackBar(this.lastSessionId, this.lastRunId || ''));
}
}
// 保存流式消息到历史记录
@@ -1273,9 +1325,11 @@ class SuperBizAgentApp {
for (const jsonStr of matches) {
try {
const sseMessage = JSON.parse(jsonStr);
if (sseMessage.type === 'session') {
if (sseMessage.type === 'metadata') {
this.rememberRunMetadata(sseMessage);
} else if (sseMessage.type === 'session') {
this.lastSessionId = sseMessage.data;
this.rememberTraceSessionId(sseMessage.data);
this.rememberTraceTarget(sseMessage.data, null);
} else if (sseMessage.type === 'content') {
fullResponse += sseMessage.data || '';
} else if (sseMessage.type === 'done') {
@@ -1306,9 +1360,11 @@ class SuperBizAgentApp {
try {
const sseMessage = JSON.parse(rawData);
if (sseMessage && sseMessage.type) {
if (sseMessage.type === 'session') {
if (sseMessage.type === 'metadata') {
this.rememberRunMetadata(sseMessage);
} else if (sseMessage.type === 'session') {
this.lastSessionId = sseMessage.data;
this.rememberTraceSessionId(sseMessage.data);
this.rememberTraceTarget(sseMessage.data, null);
} else if (sseMessage.type === 'content') {
fullResponse += sseMessage.data || '';
if (loadingMessageElement) {
+27 -9
View File
@@ -36,7 +36,7 @@ class TraceWorkbench {
bindEvents() {
this.sessionForm.addEventListener('submit', (event) => {
event.preventDefault();
this.loadTrace(this.sessionIdInput.value.trim());
this.loadTrace(this.sessionIdInput.value.trim(), '');
});
this.sessionIdInput.addEventListener('focus', () => {
@@ -66,9 +66,10 @@ class TraceWorkbench {
bootstrapFromUrl() {
const params = new URLSearchParams(window.location.search);
const sessionId = params.get('sessionId') || this.getLastTraceSessionId();
const runId = params.get('runId') || '';
if (sessionId) {
this.sessionIdInput.value = sessionId;
this.loadTrace(sessionId);
this.loadTrace(sessionId, runId);
return;
}
this.renderEmpty();
@@ -107,6 +108,7 @@ class TraceWorkbench {
id: history.id,
title: history.title || '未命名对话',
updatedAt: history.updatedAt || history.createdAt || '',
runId: history.lastRunId || '',
source: 'history'
}));
}
@@ -237,7 +239,7 @@ class TraceWorkbench {
chooseSession(sessionId) {
this.sessionIdInput.value = sessionId;
this.closeSessionOptions();
this.loadTrace(sessionId);
this.loadTrace(sessionId, '');
}
shortSessionId(sessionId) {
@@ -264,18 +266,23 @@ class TraceWorkbench {
});
}
async loadTrace(sessionId) {
async loadTrace(sessionId, runId = '') {
if (!sessionId) {
this.setState('请先输入会话 ID。', 'error');
return;
}
this.setState(`正在加载 ${sessionId}...`, 'loading');
this.updateUrl(sessionId);
const runSuffix = runId ? ` / ${runId}` : '';
this.setState(`正在加载 ${sessionId}${runSuffix}...`, 'loading');
this.updateUrl(sessionId, runId);
this.loadButton.disabled = true;
try {
const response = await fetch(`/api/diagnosis/${encodeURIComponent(sessionId)}/trace`);
const url = new URL(`/api/diagnosis/${encodeURIComponent(sessionId)}/trace`, window.location.origin);
if (runId) {
url.searchParams.set('runId', runId);
}
const response = await fetch(url.toString());
const data = await this.handleResponse(response);
this.trace = data;
this.selectedToolId = data.toolInvocations && data.toolInvocations.length
@@ -284,11 +291,14 @@ class TraceWorkbench {
this.activeFilter = 'all';
try {
localStorage.setItem('lastTraceSessionId', sessionId);
if (data.runId) {
localStorage.setItem('lastTraceRunId', data.runId);
}
} catch (error) {
// ignore storage failures
}
this.renderTrace();
this.setState(`已加载 ${sessionId}。`, '');
this.setState(`已加载 ${sessionId}${data.runId ? ` / ${data.runId}` : ''}。`, '');
} catch (error) {
this.trace = null;
this.renderEmpty();
@@ -324,9 +334,14 @@ class TraceWorkbench {
return value;
}
updateUrl(sessionId) {
updateUrl(sessionId, runId = '') {
const url = new URL(window.location.href);
url.searchParams.set('sessionId', sessionId);
if (runId) {
url.searchParams.set('runId', runId);
} else {
url.searchParams.delete('runId');
}
window.history.replaceState({}, '', url.toString());
}
@@ -399,6 +414,9 @@ class TraceWorkbench {
if (session.sessionId) {
subtitleParts.push(`会话 ${session.sessionId}`);
}
if (this.trace && this.trace.runId) {
subtitleParts.push(`运行 ${this.trace.runId}`);
}
if (session.status) {
subtitleParts.push(`状态 ${this.displayStatus(session.status)}`);
}