mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-11 01:37:31 +00:00
fix(web): dedupe optimistic user message against snapshot resync (#1620)
* fix(web): dedupe optimistic user message against snapshot resync * fix(web): match snapshot messages by identity
This commit is contained in:
parent
3c0e368cbd
commit
e91a616f21
3 changed files with 54 additions and 1 deletions
5
.changeset/fix-web-duplicate-user-message.md
Normal file
5
.changeset/fix-web-duplicate-user-message.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
web: Fix duplicate user message bubbles after a session snapshot resync.
|
||||
|
|
@ -18,9 +18,23 @@ export function mergeSnapshotMessages(
|
|||
const earliestSnapshotMs = Date.parse(snapshot[0]!.createdAt);
|
||||
if (Number.isNaN(earliestSnapshotMs)) return snapshot;
|
||||
|
||||
// The optimistic bubble keeps its client-side id to avoid remounting, while
|
||||
// submitPrompt stamps the authoritative v2 user-message id into promptId.
|
||||
// Match that identity against the snapshot instead of guessing from content:
|
||||
// repeated prompts are distinct messages even when their text/media is equal.
|
||||
const snapshotIds = new Set(snapshot.map((m) => m.id));
|
||||
const snapshotUserIds = new Set(snapshot.filter((m) => m.role === 'user').map((m) => m.id));
|
||||
|
||||
const older = loaded.filter((message) => {
|
||||
const createdAtMs = Date.parse(message.createdAt);
|
||||
return !Number.isNaN(createdAtMs) && createdAtMs < earliestSnapshotMs;
|
||||
if (Number.isNaN(createdAtMs) || createdAtMs >= earliestSnapshotMs) return false;
|
||||
if (snapshotIds.has(message.id)) return false;
|
||||
if (
|
||||
message.role === 'user' &&
|
||||
message.promptId !== undefined &&
|
||||
snapshotUserIds.has(message.promptId)
|
||||
) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
return older.length > 0 ? [...older, ...snapshot] : snapshot;
|
||||
|
|
|
|||
|
|
@ -534,6 +534,40 @@ describe('mergeSnapshotMessages', () => {
|
|||
expect(mergeSnapshotMessages([], snapshot)).toBe(snapshot);
|
||||
expect(mergeSnapshotMessages(snapshot, [])).toEqual([]);
|
||||
});
|
||||
|
||||
function optimisticUser(id: string, createdAt: string, text: string, promptId: string): AppMessage {
|
||||
return {
|
||||
id,
|
||||
sessionId: 's1',
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text }],
|
||||
createdAt,
|
||||
promptId,
|
||||
metadata: { 'kimiWeb.optimisticUserMessage': true },
|
||||
};
|
||||
}
|
||||
|
||||
function realUser(id: string, createdAt: string, text: string): AppMessage {
|
||||
return {
|
||||
id,
|
||||
sessionId: 's1',
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text }],
|
||||
createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
it('drops an optimistic user message when its promptId is the snapshot message id', () => {
|
||||
const loaded = [optimisticUser('msg_opt_1', '2026-01-02T23:59:59.000Z', 'hello', 'msg_9')];
|
||||
const snapshot = [realUser('msg_9', '2026-01-03T00:00:00.000Z', 'hello')];
|
||||
expect(mergeSnapshotMessages(loaded, snapshot).map((m) => m.id)).toEqual(['msg_9']);
|
||||
});
|
||||
|
||||
it('keeps an optimistic user message when a different snapshot message repeats its content', () => {
|
||||
const loaded = [optimisticUser('msg_opt_1', '2026-01-02T23:59:59.000Z', 'hello', 'msg_8')];
|
||||
const snapshot = [realUser('msg_9', '2026-01-03T00:00:00.000Z', 'hello')];
|
||||
expect(mergeSnapshotMessages(loaded, snapshot).map((m) => m.id)).toEqual(['msg_opt_1', 'msg_9']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeSnapshotSubagents', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue