fix: prevent duplicate AI message when switching conversations during stream
Two bugs caused the same AI response to appear twice: 1. handleSelectConversation would fetch conversation history even while a stream was in progress (isLoading). If the fetch resolved mid-stream, fetchConversation overwrote the conversation messages unconditionally, writing the server-persisted AI message (with a real UUID) alongside the still-streaming client-side message (with a temp ID), producing a duplicate. Fix: guard the fetch with !isLoading, and add a second check inside the .then callback so setConversations only writes history if the messages array is still empty (protects against slow-resolving network requests). 2. onDone in streamChat could theoretically be called a second time if the ReadableStream completed without a "done" SSE event. Fix: call reader.cancel() immediately after onDone so the while loop exits cleanly. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
d86d9b8b32
commit
1ad75a3602
@@ -185,9 +185,9 @@ export function GeminiChat() {
|
||||
setActiveConvId(id);
|
||||
setInputValue("");
|
||||
|
||||
// Load messages if not yet loaded
|
||||
// Load messages if not yet loaded and no stream is in progress
|
||||
const conv = conversations.find((c) => c.id === id);
|
||||
if (conv && conv.messages.length === 0) {
|
||||
if (conv && conv.messages.length === 0 && !isLoading) {
|
||||
fetchConversation(id)
|
||||
.then((detail) => {
|
||||
const msgs: Message[] = detail.messages.map((m) => ({
|
||||
@@ -196,8 +196,14 @@ export function GeminiChat() {
|
||||
content: m.content,
|
||||
timestamp: new Date(m.created_at),
|
||||
}));
|
||||
// Only write history if the conversation still has no messages
|
||||
// (guards against a race where streaming already populated it)
|
||||
setConversations((prev) =>
|
||||
prev.map((c) => (c.id === id ? { ...c, messages: msgs } : c))
|
||||
prev.map((c) =>
|
||||
c.id === id && c.messages.length === 0
|
||||
? { ...c, messages: msgs }
|
||||
: c
|
||||
)
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -205,7 +211,7 @@ export function GeminiChat() {
|
||||
});
|
||||
}
|
||||
},
|
||||
[conversations]
|
||||
[conversations, isLoading]
|
||||
);
|
||||
|
||||
const handleRenameConversation = useCallback(
|
||||
|
||||
@@ -209,6 +209,7 @@ export function streamChat(
|
||||
onEvent(event);
|
||||
if (event.type === "done") {
|
||||
onDone();
|
||||
reader.cancel();
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
@@ -216,6 +217,7 @@ export function streamChat(
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only call onDone here if the stream ended without a "done" event
|
||||
onDone();
|
||||
})
|
||||
.catch((err) => {
|
||||
|
||||
Reference in New Issue
Block a user