|
|
@@ -50,9 +50,11 @@ export const useFlowChartData = (traceId: string | null, refreshTrigger?: number
|
|
|
const [sinceEventId, setSinceEventId] = useState(0);
|
|
|
const [readyToConnect, setReadyToConnect] = useState(false);
|
|
|
const currentEventIdRef = useRef(0);
|
|
|
+ const maxSequenceRef = useRef(0);
|
|
|
const restReloadingRef = useRef(false);
|
|
|
const [reloading, setReloading] = useState(false);
|
|
|
const [invalidBranches, setInvalidBranches] = useState<Message[][]>([]);
|
|
|
+ const [traceCompleted, setTraceCompleted] = useState(false);
|
|
|
|
|
|
const messageComparator = useCallback((a: Message, b: Message): number => {
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
@@ -94,7 +96,9 @@ export const useFlowChartData = (traceId: string | null, refreshTrigger?: number
|
|
|
setSinceEventId(0);
|
|
|
setReadyToConnect(false);
|
|
|
currentEventIdRef.current = 0;
|
|
|
+ maxSequenceRef.current = 0;
|
|
|
restReloadingRef.current = false;
|
|
|
+ setTraceCompleted(false);
|
|
|
}, [traceId]);
|
|
|
|
|
|
const reloadViaRest = useCallback(async () => {
|
|
|
@@ -153,6 +157,13 @@ export const useFlowChartData = (traceId: string | null, refreshTrigger?: number
|
|
|
|
|
|
const { availableData: finalMessages, invalidBranches: invalidBranchesTemp } = processRetryLogic(nextMessages);
|
|
|
|
|
|
+ // Update max sequence
|
|
|
+ const maxSeq = finalMessages.reduce((max, msg) => {
|
|
|
+ const seq = typeof msg.sequence === "number" ? msg.sequence : -1;
|
|
|
+ return Math.max(max, seq);
|
|
|
+ }, 0);
|
|
|
+ maxSequenceRef.current = maxSeq;
|
|
|
+
|
|
|
setMessages(finalMessages);
|
|
|
setInvalidBranches(invalidBranchesTemp);
|
|
|
const grouped: Record<string, Message[]> = {};
|
|
|
@@ -250,32 +261,41 @@ export const useFlowChartData = (traceId: string | null, refreshTrigger?: number
|
|
|
if (typeof currentEventId === "number") {
|
|
|
currentEventIdRef.current = Math.max(currentEventIdRef.current, currentEventId);
|
|
|
}
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- const trace = isRecord(data.trace) ? data.trace : undefined;
|
|
|
- const rawTrace = isRecord(raw.trace) ? raw.trace : undefined;
|
|
|
- const goalTree =
|
|
|
- (isRecord(data.goal_tree) ? data.goal_tree : undefined) ||
|
|
|
- (trace && isRecord(trace.goal_tree) ? trace.goal_tree : undefined) ||
|
|
|
- (isRecord(raw.goal_tree) ? raw.goal_tree : undefined) ||
|
|
|
- (rawTrace && isRecord(rawTrace.goal_tree) ? rawTrace.goal_tree : undefined) ||
|
|
|
- {};
|
|
|
- const goalList = isRecord(goalTree) ? goalTree.goals : undefined;
|
|
|
- const nextGoals = Array.isArray(goalList) ? (goalList as Goal[]) : [];
|
|
|
- setGoals((prev) => {
|
|
|
- const mergedFlat = nextGoals.map((ng) => {
|
|
|
- const existing = prev.find((p) => p.id === ng.id);
|
|
|
- if (!existing) return ng;
|
|
|
- const merged: Goal = { ...existing, ...ng };
|
|
|
- if (existing.sub_trace_ids && !merged.sub_trace_ids) {
|
|
|
- merged.sub_trace_ids = existing.sub_trace_ids;
|
|
|
- }
|
|
|
- if (existing.agent_call_mode && !merged.agent_call_mode) {
|
|
|
- merged.agent_call_mode = existing.agent_call_mode;
|
|
|
- }
|
|
|
- return merged;
|
|
|
+ if (event === "rewind") {
|
|
|
+ console.log("Processing rewind event:", data);
|
|
|
+ const afterSequence =
|
|
|
+ (typeof data.after_sequence === "number" ? data.after_sequence : undefined) ||
|
|
|
+ (typeof raw.after_sequence === "number" ? raw.after_sequence : undefined);
|
|
|
+
|
|
|
+ if (typeof afterSequence === "number") {
|
|
|
+ maxSequenceRef.current = afterSequence;
|
|
|
+ setMessages((prev) =>
|
|
|
+ prev.filter((msg) => (typeof msg.sequence === "number" ? msg.sequence : -1) <= afterSequence),
|
|
|
+ );
|
|
|
+
|
|
|
+ setMsgGroups((prev) => {
|
|
|
+ const next: Record<string, Message[]> = {};
|
|
|
+ Object.entries(prev).forEach(([k, v]) => {
|
|
|
+ const filtered = v.filter(
|
|
|
+ (msg) => (typeof msg.sequence === "number" ? msg.sequence : -1) <= afterSequence,
|
|
|
+ );
|
|
|
+ if (filtered.length > 0) next[k] = filtered;
|
|
|
+ });
|
|
|
+ return next;
|
|
|
});
|
|
|
- return buildSubGoals(mergedFlat);
|
|
|
- });
|
|
|
+
|
|
|
+ // 如果有 goal_tree_snapshot,直接更新 Goals
|
|
|
+ const snapshot = isRecord(data.goal_tree_snapshot) ? data.goal_tree_snapshot : undefined;
|
|
|
+ if (snapshot && Array.isArray(snapshot.goals)) {
|
|
|
+ setGoals(buildSubGoals(snapshot.goals as Goal[]));
|
|
|
+ } else {
|
|
|
+ // 否则触发重载
|
|
|
+ void reloadViaRest();
|
|
|
+ }
|
|
|
+ }
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -333,9 +353,27 @@ export const useFlowChartData = (traceId: string | null, refreshTrigger?: number
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ if (event === "trace_completed") {
|
|
|
+ setTraceCompleted(true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
if (event === "message_added") {
|
|
|
const message = isMessage(data.message) ? data.message : isMessage(raw.message) ? raw.message : null;
|
|
|
if (message) {
|
|
|
+ // Check sequence continuity
|
|
|
+ const seq = typeof message.sequence === "number" ? message.sequence : -1;
|
|
|
+ if (seq > 0) {
|
|
|
+ if (maxSequenceRef.current > 0 && seq > maxSequenceRef.current + 1) {
|
|
|
+ console.warn(
|
|
|
+ `Message sequence gap detected: current max ${maxSequenceRef.current}, received ${seq}. Triggering reload.`,
|
|
|
+ );
|
|
|
+ void reloadViaRest();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ maxSequenceRef.current = Math.max(maxSequenceRef.current, seq);
|
|
|
+ }
|
|
|
+
|
|
|
setMessages((prev) => {
|
|
|
const next = [...prev, message];
|
|
|
next.sort(messageComparator);
|
|
|
@@ -356,5 +394,5 @@ export const useFlowChartData = (traceId: string | null, refreshTrigger?: number
|
|
|
// 只有当 traceId 存在且 REST 加载完成 (readyToConnect) 后才连接 WebSocket
|
|
|
const { connected } = useWebSocket(readyToConnect ? traceId : null, wsOptions);
|
|
|
|
|
|
- return { goals, messages, msgGroups, connected, reloading, invalidBranches };
|
|
|
+ return { goals, messages, msgGroups, connected, reloading, invalidBranches, traceCompleted };
|
|
|
};
|