fix(frontend): avoid duplicate optimistic user message (#3002)

This commit is contained in:
Admire
2026-05-23 17:02:23 +08:00
committed by GitHub
parent 604fcbb9d2
commit d0fa37e71d
2 changed files with 115 additions and 2 deletions
+21 -1
View File
@@ -135,6 +135,20 @@ function getMessagesAfterBaseline(
}); });
} }
export function getVisibleOptimisticMessages(
optimisticMessages: Message[],
previousHumanMessageCount: number,
currentHumanMessageCount: number,
): Message[] {
if (
optimisticMessages.some((message) => message.type === "human") &&
currentHumanMessageCount > previousHumanMessageCount
) {
return [];
}
return optimisticMessages;
}
function getStreamErrorMessage(error: unknown): string { function getStreamErrorMessage(error: unknown): string {
if (typeof error === "string" && error.trim()) { if (typeof error === "string" && error.trim()) {
return error; return error;
@@ -627,10 +641,16 @@ export function useThreadStream({
messagesRef.current = thread.messages; messagesRef.current = thread.messages;
} }
const visibleOptimisticMessages = getVisibleOptimisticMessages(
optimisticMessages,
prevHumanMsgCountRef.current,
humanMessageCount,
);
const mergedMessages = mergeMessages( const mergedMessages = mergeMessages(
history, history,
thread.messages, thread.messages,
optimisticMessages, visibleOptimisticMessages,
); );
const pendingUsageMessages = thread.isLoading const pendingUsageMessages = thread.isLoading
? getMessagesAfterBaseline( ? getMessagesAfterBaseline(
@@ -1,7 +1,10 @@
import type { Message } from "@langchain/langgraph-sdk"; import type { Message } from "@langchain/langgraph-sdk";
import { expect, test } from "vitest"; import { expect, test } from "vitest";
import { mergeMessages } from "@/core/threads/hooks"; import {
getVisibleOptimisticMessages,
mergeMessages,
} from "@/core/threads/hooks";
test("mergeMessages removes duplicate messages already present in history", () => { test("mergeMessages removes duplicate messages already present in history", () => {
const human = { const human = {
@@ -62,3 +65,93 @@ test("mergeMessages deduplicates tool messages by tool_call_id", () => {
expect(mergeMessages([oldTool], [liveTool], [])).toEqual([liveTool]); expect(mergeMessages([oldTool], [liveTool], [])).toEqual([liveTool]);
}); });
test("getVisibleOptimisticMessages hides optimistic user input after server human arrives", () => {
const optimisticHuman = {
id: "opt-human-1",
type: "human",
content: "hello",
} as Message;
expect(getVisibleOptimisticMessages([optimisticHuman], 0, 1)).toEqual([]);
});
test("mergeMessages shows server human instead of optimistic duplicate after first response", () => {
const serverHuman = {
id: "server-human-1",
type: "human",
content: "hello",
} as Message;
const optimisticHuman = {
id: "opt-human-1",
type: "human",
content: "hello",
} as Message;
const visibleOptimistic = getVisibleOptimisticMessages(
[optimisticHuman],
0,
1,
);
expect(mergeMessages([], [serverHuman], visibleOptimistic)).toEqual([
serverHuman,
]);
});
test("getVisibleOptimisticMessages keeps optimistic user input until server human arrives", () => {
const optimisticHuman = {
id: "opt-human-1",
type: "human",
content: "hello",
} as Message;
expect(getVisibleOptimisticMessages([optimisticHuman], 0, 0)).toEqual([
optimisticHuman,
]);
});
test("getVisibleOptimisticMessages keeps non-human optimistic status messages", () => {
const optimisticAi = {
id: "opt-ai-1",
type: "ai",
content: "Uploading files...",
} as Message;
expect(getVisibleOptimisticMessages([optimisticAi], 0, 1)).toEqual([
optimisticAi,
]);
});
test("getVisibleOptimisticMessages hides the upload optimistic pair after server human arrives", () => {
const optimisticHuman = {
id: "opt-human-1",
type: "human",
content: "upload this",
} as Message;
const optimisticUploadingAi = {
id: "opt-ai-uploading",
type: "ai",
content: "Uploading files...",
} as Message;
expect(
getVisibleOptimisticMessages(
[optimisticHuman, optimisticUploadingAi],
0,
1,
),
).toEqual([]);
});
test("getVisibleOptimisticMessages hides optimistic user input after later server turns", () => {
const optimisticHuman = {
id: "opt-human-2",
type: "human",
content: "follow up",
} as Message;
expect(getVisibleOptimisticMessages([optimisticHuman], 3, 4)).toEqual([]);
expect(getVisibleOptimisticMessages([optimisticHuman], 3, 3)).toEqual([
optimisticHuman,
]);
});