mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-23 16:35:59 +00:00
fix(frontend): avoid duplicate optimistic user message (#3002)
This commit is contained in:
@@ -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,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user