fix(web): 保留 trace REST 权威事件
This commit is contained in:
@@ -14,7 +14,10 @@ const expanded = ref(false);
|
||||
const events = computed(() => Array.isArray(props.trace?.events) ? props.trace.events : []);
|
||||
const eventCount = computed(() => props.trace?.eventCount ?? events.value.length);
|
||||
const readableRows = computed(() => traceDisplayRows(traceRecord(props.trace), events.value as Record<string, unknown>[]));
|
||||
const emptyTraceLabel = computed(() => props.trace?.fullTraceLoaded === true ? "思考中..." : "加载中...");
|
||||
const emptyTraceLabel = computed(() => {
|
||||
if (events.value.length > 0 || (props.trace?.fullTraceLoaded === true && eventCount.value > 0)) return "暂无可读 Trace";
|
||||
return props.trace?.fullTraceLoaded === true ? "思考中..." : "加载中...";
|
||||
});
|
||||
const toolPreviewLimit = 140;
|
||||
|
||||
watch(() => [eventCount.value, props.trace?.status, following.value, expanded.value], async () => {
|
||||
|
||||
@@ -89,11 +89,13 @@ export function mergeRunnerTrace(previous: ChatMessage["runnerTrace"], next: Non
|
||||
if (!previous) return next;
|
||||
const previousEvents = Array.isArray(previous.events) ? previous.events : [];
|
||||
const nextEvents = Array.isArray(next.events) ? next.events : [];
|
||||
const nextAuthoritative = next.fullTraceLoaded === true || next.eventSource === "trace-api";
|
||||
const previousAuthoritative = previous.fullTraceLoaded === true || previous.eventSource === "trace-api";
|
||||
const keepPreviousEvents = previousAuthoritative && !nextAuthoritative && (next.eventsCompacted === true || nextEvents.length < previousEvents.length);
|
||||
const nextAuthoritative = next.eventSource === "trace-api";
|
||||
const previousAuthoritative = previous.eventSource === "trace-api";
|
||||
const keepPreviousEvents = previousEvents.length > 0 && !nextAuthoritative && (previousAuthoritative || next.eventsCompacted === true || nextEvents.length <= previousEvents.length);
|
||||
const mergeAuthoritativeEvents = nextAuthoritative && previousEvents.length > 0 && (next.hasMore === true || next.fullTraceLoaded !== true || previous.fullTraceLoaded !== true || nextEvents.length < previousEvents.length);
|
||||
const events = nextAuthoritative ? mergeAuthoritativeEvents ? mergeTraceEvents(previousEvents, nextEvents) : nextEvents : keepPreviousEvents ? previousEvents : nextEvents.length >= previousEvents.length ? nextEvents : mergeTraceEvents(previousEvents, nextEvents);
|
||||
const events = nextAuthoritative
|
||||
? mergeAuthoritativeEvents ? mergeTraceEvents(previousEvents, nextEvents) : nextEvents
|
||||
: keepPreviousEvents ? mergeTraceEvents(previousEvents, nextEvents) : nextEvents.length > previousEvents.length ? mergeTraceEvents(previousEvents, nextEvents) : nextEvents;
|
||||
const eventCount = keepPreviousEvents ? previous.eventCount ?? events.length : next.eventCount ?? previous.eventCount ?? events.length;
|
||||
return { ...previous, ...next, events, eventCount, updatedAt: next.updatedAt ?? previous.updatedAt ?? new Date().toISOString() };
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
const workspaceSelectionEpoch = ref(0);
|
||||
const sessionStatusAuthority = ref<Record<string, SessionStatusAuthority>>({});
|
||||
const turnStatusAuthority = ref<Record<string, TurnStatusAuthority>>({});
|
||||
const traceAuthorityById = ref<Record<string, NonNullable<ChatMessage["runnerTrace"]>>>({});
|
||||
const traceHydrationInFlight = new Set<string>();
|
||||
|
||||
const visibleConversations = computed(() => conversations.value);
|
||||
@@ -103,7 +104,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
workspace.value = workspaceSnapshot;
|
||||
providerProfile.value = firstNonEmptyString(workspace.value?.providerProfile, workspace.value?.workspace?.providerProfile, providerProfile.value) ?? providerProfile.value;
|
||||
rememberWorkbenchProjectId(workspaceProjectId(workspace.value, nextProjectId));
|
||||
messages.value = messagesFromSelectedConversation(selectedConversation, selectedConversationId, previousConversationId, messages.value);
|
||||
messages.value = restoreMessagesTraceAuthority(messagesFromSelectedConversation(selectedConversation, selectedConversationId, previousConversationId, messages.value), messages.value);
|
||||
void hydrateTurnStatusAuthority(messages.value);
|
||||
void hydrateTerminalMessageDiagnostics();
|
||||
void hydrateTraceEvents(messages.value);
|
||||
@@ -133,7 +134,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
loading.value = false;
|
||||
if (response.ok && isCurrentWorkspaceSelection(requestEpoch)) {
|
||||
workspace.value = response.data?.workspace ?? current;
|
||||
messages.value = messagesFromWorkspace(workspace.value);
|
||||
messages.value = restoreMessagesTraceAuthority(messagesFromWorkspace(workspace.value), messages.value);
|
||||
currentRequest.value = null;
|
||||
void hydrateTurnStatusAuthority(messages.value);
|
||||
void hydrateTraceEvents(messages.value);
|
||||
@@ -165,7 +166,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
if (!selectedConversation) error.value = detailResponse.error ?? "conversation unavailable";
|
||||
const selectedProjectId = conversationProjectId(selectedConversation ?? conversation, tabProjectId);
|
||||
workspace.value = workspaceWithSelectedConversation(response.data?.workspace ?? current, selectedConversation ?? conversation, selectedProjectId);
|
||||
messages.value = messagesFromSelectedConversation(selectedConversation, conversation.conversationId, null, []);
|
||||
messages.value = restoreMessagesTraceAuthority(messagesFromSelectedConversation(selectedConversation, conversation.conversationId, null, []), messages.value);
|
||||
if (selectedConversation) conversations.value = mergeConversationIntoList(conversations.value, selectedConversation);
|
||||
void hydrateTurnStatusAuthority(messages.value);
|
||||
void hydrateTerminalMessageDiagnostics();
|
||||
@@ -214,7 +215,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
const response = await api.workbench.deleteConversation(conversationId, { projectId: activeProjectId.value, workspaceId: workspace.value?.workspaceId, updatedByClient: "cloud-web-vue" });
|
||||
if (response.ok) {
|
||||
workspace.value = response.data?.workspace ?? workspace.value;
|
||||
messages.value = messagesFromWorkspace(workspace.value);
|
||||
messages.value = restoreMessagesTraceAuthority(messagesFromWorkspace(workspace.value), messages.value);
|
||||
currentRequest.value = null;
|
||||
void hydrateTurnStatusAuthority(messages.value);
|
||||
await refreshConversations(selectedConversationIdFromWorkspace(workspace.value));
|
||||
@@ -321,6 +322,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
messages.value = messages.value.map((message) => {
|
||||
if (message.traceId !== traceId || message.role !== "agent") return message;
|
||||
const runnerTrace = mergeTerminalResultTrace(message.runnerTrace, result as AgentChatResultResponse);
|
||||
rememberTraceAuthority(runnerTrace);
|
||||
const error = normalizeAgentError((result as AgentChatResultResponse).error ?? runnerTrace?.error ?? message.error);
|
||||
const errorText = agentErrorDisplayText(error);
|
||||
const agentRun = agentRunFromResult(result as AgentChatResultResponse, runnerTrace) ?? agentRunFromMessage(message);
|
||||
@@ -446,6 +448,32 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
await Promise.all(source.filter(messageNeedsTraceHydration).slice(-12).map((message) => hydrateTraceEventsForMessage(message)));
|
||||
}
|
||||
|
||||
function restoreMessagesTraceAuthority(source: ChatMessage[], previous: ChatMessage[] = []): ChatMessage[] {
|
||||
const previousTraceById = new Map<string, NonNullable<ChatMessage["runnerTrace"]>>();
|
||||
for (const message of previous) {
|
||||
const trace = message.runnerTrace;
|
||||
const traceId = firstNonEmptyString(message.traceId, trace?.traceId);
|
||||
if (!traceId || !trace || !traceHasEvents(trace)) continue;
|
||||
previousTraceById.set(traceId, trace);
|
||||
}
|
||||
return source.map((message) => {
|
||||
if (message.role !== "agent") return message;
|
||||
const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
|
||||
const authority = traceId ? traceAuthorityById.value[traceId] ?? previousTraceById.get(traceId) ?? null : null;
|
||||
if (!authority) return message;
|
||||
return { ...message, runnerTrace: mergeRunnerTrace(message.runnerTrace, authority) };
|
||||
});
|
||||
}
|
||||
|
||||
function rememberTraceAuthority(trace: ChatMessage["runnerTrace"]): void {
|
||||
const traceId = firstNonEmptyString(trace?.traceId);
|
||||
if (!traceId || !trace) return;
|
||||
const existing = traceAuthorityById.value[traceId] ?? null;
|
||||
if (trace.eventSource !== "trace-api" && !traceHasEvents(trace) && !existing) return;
|
||||
const nextTrace = mergeRunnerTrace(existing, trace as NonNullable<ChatMessage["runnerTrace"]>);
|
||||
traceAuthorityById.value = { ...traceAuthorityById.value, [traceId]: nextTrace };
|
||||
}
|
||||
|
||||
function applyTraceHydrationResult(traceId: string, result: AgentChatResultResponse): void {
|
||||
const events = Array.isArray(result.events) ? result.events : Array.isArray(result.traceEvents) ? result.traceEvents : [];
|
||||
messages.value = messages.value.map((message) => {
|
||||
@@ -457,6 +485,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
sessionId: result.sessionId ?? message.runnerTrace?.sessionId,
|
||||
threadId: result.threadId ?? message.runnerTrace?.threadId,
|
||||
events,
|
||||
eventSource: "trace-api",
|
||||
eventCount: result.eventCount ?? events.length,
|
||||
eventsCompacted: result.runnerTrace?.eventsCompacted ?? false,
|
||||
fullTraceLoaded: result.fullTraceLoaded === true || result.hasMore === false,
|
||||
@@ -480,6 +509,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
const errorText = agentErrorDisplayText(error);
|
||||
const text = terminal ? firstNonEmptyString(result.assistantText, finalResponseText(result.finalResponse), agentReplyText(result.reply), errorText, result.text, result.summary, message.text) ?? message.text : message.text;
|
||||
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
|
||||
rememberTraceAuthority(runnerTrace);
|
||||
return { ...message, status, text, runnerTrace, error: error ?? message.error ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
|
||||
});
|
||||
}
|
||||
@@ -549,7 +579,12 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
const trace = snapshotToRunnerTrace(snapshot);
|
||||
rememberTurnStatus(traceId, snapshot);
|
||||
const status = statusFromResult(snapshot.status);
|
||||
messages.value = messages.value.map((message) => message.traceId === traceId ? { ...message, status, traceAutoLifecycle: "running", runnerTrace: mergeRunnerTrace(message.runnerTrace, trace), updatedAt: new Date().toISOString() } : message);
|
||||
messages.value = messages.value.map((message) => {
|
||||
if (message.traceId !== traceId) return message;
|
||||
const runnerTrace = mergeRunnerTrace(message.runnerTrace, trace);
|
||||
rememberTraceAuthority(runnerTrace);
|
||||
return { ...message, status, traceAutoLifecycle: "running", runnerTrace, updatedAt: new Date().toISOString() };
|
||||
});
|
||||
void refreshSessionStatusById(trace.sessionId ?? selectedSessionId.value);
|
||||
}
|
||||
|
||||
@@ -559,6 +594,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
messages.value = messages.value.map((message) => {
|
||||
if (message.traceId !== traceId || message.role !== "agent") return message;
|
||||
const runnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
|
||||
rememberTraceAuthority(runnerTrace);
|
||||
const error = normalizeAgentError(result.error ?? runnerTrace?.error ?? message.error);
|
||||
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
|
||||
return { ...message, status: terminalStatus, text, traceAutoLifecycle: "terminal", runnerTrace, error: error ?? message.error ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
|
||||
@@ -588,6 +624,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
messages.value = messages.value.map((message) => {
|
||||
if (message.traceId !== traceId || message.role !== "agent") return message;
|
||||
const runnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
|
||||
rememberTraceAuthority(runnerTrace);
|
||||
const error = normalizeAgentError(result.error ?? runnerTrace?.error ?? message.error);
|
||||
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
|
||||
const status = statusFromResult(result.status);
|
||||
@@ -870,10 +907,15 @@ function messageNeedsTraceHydration(message: ChatMessage): boolean {
|
||||
if (!traceId) return false;
|
||||
const trace = message.runnerTrace;
|
||||
const events = Array.isArray(trace?.events) ? trace.events : [];
|
||||
if (trace?.fullTraceLoaded === true && trace.eventsCompacted !== true) return false;
|
||||
const eventCount = firstFiniteNumber(trace?.eventCount) ?? events.length;
|
||||
if (trace?.fullTraceLoaded === true && trace.eventsCompacted !== true) return events.length === 0 && (eventCount > 0 || isTraceActiveStatus(trace?.status) || isTraceActiveStatus(message.status));
|
||||
return events.length === 0 || trace?.eventsCompacted === true || trace?.fullTraceLoaded !== true;
|
||||
}
|
||||
|
||||
function traceHasEvents(trace: ChatMessage["runnerTrace"]): boolean {
|
||||
return Array.isArray(trace?.events) && trace.events.length > 0;
|
||||
}
|
||||
|
||||
function agentRunFromResult(result: AgentChatResultResponse, runnerTrace: ChatMessage["runnerTrace"]): AgentRunProvenance | null {
|
||||
return asAgentRun(result.agentRun ?? runnerTrace?.agentRun);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user