feat(trace): finish run-aware demo verification
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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)}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user