fix(chat): stabilize message identities

This commit is contained in:
2026-07-20 18:30:21 +08:00
parent 159e8bfd59
commit 1e13f94b5d
33 changed files with 718 additions and 212 deletions
+58 -16
View File
@@ -2,7 +2,12 @@ import type {
ChatLockDetailData,
ChatSendResponse,
} from "@/data/schemas/chat";
import type { UiMessage } from "@/stores/chat/ui-message";
import {
createClientUiMessageIdentity,
createLegacyUiMessageIdentity,
createRemoteUiMessageIdentity,
type UiMessage,
} from "@/stores/chat/ui-message";
import { todayString } from "@/utils/date";
/**
@@ -21,20 +26,33 @@ export function localMessagesToUi(
lockDetail?: ChatLockDetailData;
}[],
): UiMessage[] {
return records.map((m) => ({
...(m.id ? { id: m.id } : {}),
content: getAiMessageDisplayContent({
content: m.content,
isFromAI: m.role === "assistant",
hasImage: Boolean(m.image?.url),
}),
isFromAI: m.role === "assistant",
date: messageDateFromCreatedAt(m.createdAt),
...(m.audioUrl && m.lockDetail?.locked !== true
? { audioUrl: m.audioUrl }
: {}),
...deriveUiLockFields(m.lockDetail, m.image?.url),
}));
const occurrences = new Map<string, number>();
return records.map((m) => {
const isFromAI = m.role === "assistant";
const identityBase = m.id
? `remote:${m.id}:${m.role}`
: `legacy:${createLegacyFingerprint(m)}`;
const occurrence = occurrences.get(identityBase) ?? 0;
occurrences.set(identityBase, occurrence + 1);
const identity = m.id
? createRemoteUiMessageIdentity(m.id, isFromAI, occurrence)
: createLegacyUiMessageIdentity(identityBase, occurrence);
return {
...identity,
content: getAiMessageDisplayContent({
content: m.content,
isFromAI,
hasImage: Boolean(m.image?.url),
}),
isFromAI,
date: messageDateFromCreatedAt(m.createdAt),
...(m.audioUrl && m.lockDetail?.locked !== true
? { audioUrl: m.audioUrl }
: {}),
...deriveUiLockFields(m.lockDetail, m.image?.url),
};
});
}
/**
@@ -43,7 +61,9 @@ export function localMessagesToUi(
*/
export function sendResponseToUiMessage(response: ChatSendResponse): UiMessage {
return {
...(response.messageId ? { id: response.messageId } : {}),
...(response.messageId
? createRemoteUiMessageIdentity(response.messageId, true)
: createClientUiMessageIdentity("reply")),
content: getAiMessageDisplayContent({
content: response.reply,
isFromAI: true,
@@ -58,6 +78,28 @@ export function sendResponseToUiMessage(response: ChatSendResponse): UiMessage {
};
}
function createLegacyFingerprint(record: {
type?: string;
content: string;
role: string;
createdAt: string;
audioUrl?: string | null;
image?: { type: string | null; url: string | null };
lockDetail?: ChatLockDetailData;
}): string {
return JSON.stringify([
record.role,
record.type ?? "text",
record.content,
record.createdAt,
record.audioUrl ?? null,
record.image?.type ?? null,
record.image?.url ?? null,
record.lockDetail?.locked ?? null,
record.lockDetail?.reason ?? null,
]);
}
function messageDateFromCreatedAt(createdAt: string): string {
const parsed = new Date(createdAt);
if (Number.isNaN(parsed.getTime())) return todayString();