fix(web): abort the stream when its in-flight question is deleted (#18214)

This commit is contained in:
chanx
2026-08-13 16:15:24 +08:00
committed by GitHub
parent 6d061b359e
commit a79a6aaa51

View File

@@ -240,6 +240,12 @@ export const useChatStreamStore = create<ChatStreamState>()(
(state) => {
const previous = state.sessions[conversationId];
if (!previous) return state;
// A session that is no longer streaming has no trailing placeholder
// to fill in. The reader's final flush runs before endStream, so
// this also guards the case where the in-flight question (and its
// placeholder) were just deleted: without it, the flush would slice
// off whatever message is now last.
if (!previous.isStreaming) return state;
return {
sessions: {
...state.sessions,
@@ -304,6 +310,20 @@ export const useChatStreamStore = create<ChatStreamState>()(
removeMessageById: (conversationId, messageId) => {
if (!conversationId) return;
// Deleting the question that is currently being answered has to stop
// that answer too: the stream lives at module level, so it would
// otherwise keep producing tokens for a question that is gone. The
// question and its assistant placeholder are appended as a pair (and
// share an id), so the in-flight question sits second to last.
const session = get().sessions[conversationId];
const removingStreamedQuestion = Boolean(
session?.isStreaming && session.messages.at(-2)?.id === messageId,
);
if (removingStreamedQuestion) {
session?.abortController?.abort();
}
set(
(state) => {
const previous = state.sessions[conversationId];
@@ -314,6 +334,15 @@ export const useChatStreamStore = create<ChatStreamState>()(
[conversationId]: {
...previous,
messages: previous.messages.filter((x) => x.id !== messageId),
// Close the session right here instead of waiting for the
// reader's endStream, so the abort's trailing flush can't
// write the deleted answer back into the list.
isStreaming: removingStreamedQuestion
? false
: previous.isStreaming,
abortController: removingStreamedQuestion
? undefined
: previous.abortController,
},
},
};