diff --git a/src/app/chat/__tests__/chat-area.helpers.test.ts b/src/app/chat/__tests__/chat-area.helpers.test.ts index 584c8037..bcc56a9f 100644 --- a/src/app/chat/__tests__/chat-area.helpers.test.ts +++ b/src/app/chat/__tests__/chat-area.helpers.test.ts @@ -5,7 +5,7 @@ import type { UiMessage } from "@/data/dto/chat"; import { buildChatRenderItems, createChatMessageKeyResolver, -} from "../components/chat-area"; +} from "../chat-render-items"; describe("chat area render items", () => { it("keeps local message keys stable when history is prepended", () => { diff --git a/src/app/chat/chat-render-items.ts b/src/app/chat/chat-render-items.ts new file mode 100644 index 00000000..1edc93ea --- /dev/null +++ b/src/app/chat/chat-render-items.ts @@ -0,0 +1,45 @@ +import type { UiMessage } from "@/data/dto/chat"; + +export type ChatMessageKeyResolver = (message: UiMessage) => string; + +export type ChatRenderItem = + | { type: "date"; date: string; key: string } + | { type: "msg"; message: UiMessage; key: string }; + +export function createChatMessageKeyResolver(): ChatMessageKeyResolver { + const localMessageKeys = new WeakMap(); + let nextLocalMessageKey = 0; + + return (message) => { + if (message.id && message.id.length > 0) return `msg-${message.id}`; + + const existing = localMessageKeys.get(message); + if (existing) return existing; + + nextLocalMessageKey += 1; + const next = `local-msg-${nextLocalMessageKey}`; + localMessageKeys.set(message, next); + return next; + }; +} + +export function buildChatRenderItems( + messages: readonly UiMessage[], + getMessageKey: ChatMessageKeyResolver, +): ChatRenderItem[] { + const items: ChatRenderItem[] = []; + + messages.forEach((message, index) => { + const messageKey = getMessageKey(message); + if (index === 0 || message.date !== messages[index - 1].date) { + items.push({ + type: "date", + date: message.date, + key: `date-${message.date}-${messageKey}`, + }); + } + items.push({ type: "msg", message, key: messageKey }); + }); + + return items; +} diff --git a/src/app/chat/components/chat-area.tsx b/src/app/chat/components/chat-area.tsx index 8cfb0953..2c13d681 100644 --- a/src/app/chat/components/chat-area.tsx +++ b/src/app/chat/components/chat-area.tsx @@ -22,6 +22,11 @@ import { listenChatScrollSnapshotSave, saveChatScrollSnapshot, } from "../chat-scroll-session"; +import { + buildChatRenderItems, + createChatMessageKeyResolver, + type ChatMessageKeyResolver, +} from "../chat-render-items"; import { AiDisclosureBanner } from "./ai-disclosure-banner"; import { DateHeader } from "./date-header"; import { LottieMessageBubble } from "./lottie-message-bubble"; @@ -31,10 +36,6 @@ import styles from "./chat-area.module.css"; const CHAT_AUTO_SCROLL_BOTTOM_THRESHOLD = 96; type RestoreState = "idle" | "checking" | "restoring" | "settlingBottom" | "done"; -type ChatMessageKeyResolver = (message: UiMessage) => string; -type ChatRenderItem = - | { type: "date"; date: string; key: string } - | { type: "msg"; message: UiMessage; key: string }; export interface ChatAreaProps { messages: readonly UiMessage[]; @@ -226,7 +227,7 @@ function startStableScrollTask({ /** 渲染消息列表(按日期分组插入分隔条) */ function renderMessagesWithDateHeaders( messages: readonly UiMessage[], - getMessageKey: (message: UiMessage) => string, + getMessageKey: ChatMessageKeyResolver, isUnlockingMessage?: boolean, unlockingMessageId?: string | null, onUnlockPrivateMessage?: (messageId: string) => void, @@ -258,41 +259,3 @@ function renderMessagesWithDateHeaders( ), ); } - -export function createChatMessageKeyResolver(): ChatMessageKeyResolver { - const localMessageKeys = new WeakMap(); - let nextLocalMessageKey = 0; - - return (message) => { - if (message.id && message.id.length > 0) return `msg-${message.id}`; - - const existing = localMessageKeys.get(message); - if (existing) return existing; - - nextLocalMessageKey += 1; - const next = `local-msg-${nextLocalMessageKey}`; - localMessageKeys.set(message, next); - return next; - }; -} - -export function buildChatRenderItems( - messages: readonly UiMessage[], - getMessageKey: ChatMessageKeyResolver, -): ChatRenderItem[] { - const items: ChatRenderItem[] = []; - - messages.forEach((message, index) => { - const messageKey = getMessageKey(message); - if (index === 0 || message.date !== messages[index - 1].date) { - items.push({ - type: "date", - date: message.date, - key: `date-${message.date}-${messageKey}`, - }); - } - items.push({ type: "msg", message, key: messageKey }); - }); - - return items; -}