fix(chat): stabilize message identities
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user