refactor(chat): extract render item helpers

This commit is contained in:
2026-07-08 15:49:49 +08:00
parent 21b6d346bd
commit cfd1c31c8c
3 changed files with 52 additions and 44 deletions
@@ -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", () => {
+45
View File
@@ -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<UiMessage, string>();
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;
}
+6 -43
View File
@@ -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<UiMessage, string>();
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;
}