fix: deduplicate messages and UI cards when streamSubgraphs is true
With streamSubgraphs:true, subgraph values events push messages that the parent Supervisor later merges, causing the same message.id to appear multiple times in thread.messages. Similarly, the same card_id can appear in both subgraph and parent values snapshots. - Add deduplicateMessages() at module level: keeps the last (most up-to-date) copy per message.id, eliminating duplicate AI messages caused by subgraph-then-parent merge events. - Apply deduplicateUiItems() to the full allUi array before per-message card matching, so loading→done card pairs pushed by subgraphs and parent are collapsed globally before orphan attribution runs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
09a0021007
commit
e9808cc44f
+21
-5
@@ -73,6 +73,16 @@ function deduplicateUiItems(items: UIMsgLocal[]): UIMsgLocal[] {
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Message deduplication by id ────────────────────────────────────────────
|
||||
// With streamSubgraphs:true, subgraph values events push messages that the
|
||||
// parent graph later merges. The same message.id can appear multiple times in
|
||||
// thread.messages. Keep only the last (most up-to-date) copy per id.
|
||||
function deduplicateMessages(msgs: Message[]): Message[] {
|
||||
const seen = new Map<string, number>();
|
||||
msgs.forEach((m, i) => { if (m.id) seen.set(m.id, i); });
|
||||
return msgs.filter((m, i) => !m.id || seen.get(m.id) === i);
|
||||
}
|
||||
|
||||
// ─── Tool groups ────────────────────────────────────────────────────────────
|
||||
const TOOL_GROUPS = [
|
||||
{ key: "knowledge", label: "知识库", icon: BookOpen, tools: ["kb_search"] },
|
||||
@@ -551,12 +561,13 @@ function App() {
|
||||
if (thread.messages.length > 0) {
|
||||
lastMessagesRef.current = thread.messages;
|
||||
}
|
||||
const activeMessages: Message[] =
|
||||
const activeMessages: Message[] = deduplicateMessages(
|
||||
thread.messages.length > 0
|
||||
? thread.messages
|
||||
: lastMessagesRef.current.length > 0
|
||||
? lastMessagesRef.current
|
||||
: historicalMessages;
|
||||
: historicalMessages
|
||||
);
|
||||
|
||||
// ── Collect completed tool call IDs ─────────────────────────────────────
|
||||
const completedToolIds = new Set(
|
||||
@@ -677,11 +688,16 @@ function App() {
|
||||
)}
|
||||
|
||||
{activeMessages.map((message, idx) => {
|
||||
// Render UI cards attached to this message
|
||||
// Render UI cards attached to this message.
|
||||
// Apply deduplicateUiItems to the full ui array first so that cards
|
||||
// pushed by both subgraph and parent-graph values events (same card_id)
|
||||
// are collapsed before any per-message matching logic runs.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const allUi: UIMsgLocal[] = thread.messages.length > 0
|
||||
const allUi: UIMsgLocal[] = deduplicateUiItems(
|
||||
thread.messages.length > 0
|
||||
? ((thread.values as any)?.ui ?? [])
|
||||
: historicalUi;
|
||||
: historicalUi
|
||||
);
|
||||
// Match UI items by message_id; for subgraph messages where IDs
|
||||
// may not align, fallback: distribute orphaned cards by tool name.
|
||||
let matchedUi = allUi.filter(
|
||||
|
||||
Reference in New Issue
Block a user