mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:10:45 +00:00
fix(webchat): drop stale optimistic assistant tails
This commit is contained in:
54
ui/src/ui/chat/history-merge.test.ts
Normal file
54
ui/src/ui/chat/history-merge.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { preserveOptimisticTailMessages } from "../controllers/chat.ts";
|
||||
|
||||
describe("preserveOptimisticTailMessages", () => {
|
||||
it("keeps optimistic tail messages while history is stale", () => {
|
||||
const persistedUser = {
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "first" }],
|
||||
__openclaw: { seq: 1 },
|
||||
};
|
||||
const optimisticUser = {
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "latest ask" }],
|
||||
timestamp: 10,
|
||||
};
|
||||
const optimisticAssistant = {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "latest answer" }],
|
||||
timestamp: 11,
|
||||
};
|
||||
|
||||
expect(
|
||||
preserveOptimisticTailMessages(
|
||||
[persistedUser],
|
||||
[persistedUser, optimisticUser, optimisticAssistant],
|
||||
),
|
||||
).toEqual([persistedUser, optimisticUser, optimisticAssistant]);
|
||||
});
|
||||
|
||||
it("drops streamed assistant tail when final history has caught up past the shared user", () => {
|
||||
const persistedUser = {
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "latest ask" }],
|
||||
__openclaw: { seq: 1 },
|
||||
};
|
||||
const streamedAssistant = {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "partial streamed answer" }],
|
||||
timestamp: 10,
|
||||
};
|
||||
const historyAssistant = {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "complete persisted answer" }],
|
||||
__openclaw: { seq: 2 },
|
||||
};
|
||||
|
||||
expect(
|
||||
preserveOptimisticTailMessages(
|
||||
[persistedUser, historyAssistant],
|
||||
[persistedUser, streamedAssistant],
|
||||
),
|
||||
).toEqual([persistedUser, historyAssistant]);
|
||||
});
|
||||
});
|
||||
@@ -264,7 +264,7 @@ function messageDisplaySignature(message: unknown): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
function preserveOptimisticTailMessages(
|
||||
export function preserveOptimisticTailMessages(
|
||||
historyMessages: unknown[],
|
||||
previousMessages: unknown[],
|
||||
): unknown[] {
|
||||
@@ -279,29 +279,37 @@ function preserveOptimisticTailMessages(
|
||||
? previousMessages
|
||||
: historyMessages;
|
||||
}
|
||||
const historySignatures = new Set(
|
||||
historyMessages
|
||||
.map((message) => messageDisplaySignature(message))
|
||||
.filter((signature): signature is string => Boolean(signature)),
|
||||
);
|
||||
const historySignatureIndexes = new Map<string, number>();
|
||||
historyMessages.forEach((message, index) => {
|
||||
const signature = messageDisplaySignature(message);
|
||||
if (signature) {
|
||||
historySignatureIndexes.set(signature, index);
|
||||
}
|
||||
});
|
||||
let sharedPreviousIndex = -1;
|
||||
let sharedHistoryIndex = -1;
|
||||
for (let index = previousMessages.length - 1; index >= 0; index--) {
|
||||
const signature = messageDisplaySignature(previousMessages[index]);
|
||||
if (signature && historySignatures.has(signature)) {
|
||||
const historyIndex = signature ? historySignatureIndexes.get(signature) : undefined;
|
||||
if (typeof historyIndex === "number") {
|
||||
sharedPreviousIndex = index;
|
||||
sharedHistoryIndex = historyIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sharedPreviousIndex < 0) {
|
||||
return historyMessages;
|
||||
}
|
||||
if (sharedHistoryIndex < historyMessages.length - 1) {
|
||||
return historyMessages;
|
||||
}
|
||||
const optimisticTail: unknown[] = [];
|
||||
for (const message of previousMessages.slice(sharedPreviousIndex + 1)) {
|
||||
if (!isLocallyOptimisticHistoryMessage(message) || shouldHideHistoryMessage(message)) {
|
||||
return historyMessages;
|
||||
}
|
||||
const signature = messageDisplaySignature(message);
|
||||
if (!signature || historySignatures.has(signature)) {
|
||||
if (!signature || historySignatureIndexes.has(signature)) {
|
||||
return historyMessages;
|
||||
}
|
||||
optimisticTail.push(message);
|
||||
@@ -544,6 +552,9 @@ export async function sendChatMessage(
|
||||
if (!msg && !hasAttachments) {
|
||||
return null;
|
||||
}
|
||||
if (state.chatSending) {
|
||||
return state.chatRunId;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user