refactor(chat): extract render item helpers
This commit is contained in:
@@ -5,7 +5,7 @@ import type { UiMessage } from "@/data/dto/chat";
|
|||||||
import {
|
import {
|
||||||
buildChatRenderItems,
|
buildChatRenderItems,
|
||||||
createChatMessageKeyResolver,
|
createChatMessageKeyResolver,
|
||||||
} from "../components/chat-area";
|
} from "../chat-render-items";
|
||||||
|
|
||||||
describe("chat area render items", () => {
|
describe("chat area render items", () => {
|
||||||
it("keeps local message keys stable when history is prepended", () => {
|
it("keeps local message keys stable when history is prepended", () => {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -22,6 +22,11 @@ import {
|
|||||||
listenChatScrollSnapshotSave,
|
listenChatScrollSnapshotSave,
|
||||||
saveChatScrollSnapshot,
|
saveChatScrollSnapshot,
|
||||||
} from "../chat-scroll-session";
|
} from "../chat-scroll-session";
|
||||||
|
import {
|
||||||
|
buildChatRenderItems,
|
||||||
|
createChatMessageKeyResolver,
|
||||||
|
type ChatMessageKeyResolver,
|
||||||
|
} from "../chat-render-items";
|
||||||
import { AiDisclosureBanner } from "./ai-disclosure-banner";
|
import { AiDisclosureBanner } from "./ai-disclosure-banner";
|
||||||
import { DateHeader } from "./date-header";
|
import { DateHeader } from "./date-header";
|
||||||
import { LottieMessageBubble } from "./lottie-message-bubble";
|
import { LottieMessageBubble } from "./lottie-message-bubble";
|
||||||
@@ -31,10 +36,6 @@ import styles from "./chat-area.module.css";
|
|||||||
const CHAT_AUTO_SCROLL_BOTTOM_THRESHOLD = 96;
|
const CHAT_AUTO_SCROLL_BOTTOM_THRESHOLD = 96;
|
||||||
|
|
||||||
type RestoreState = "idle" | "checking" | "restoring" | "settlingBottom" | "done";
|
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 {
|
export interface ChatAreaProps {
|
||||||
messages: readonly UiMessage[];
|
messages: readonly UiMessage[];
|
||||||
@@ -226,7 +227,7 @@ function startStableScrollTask({
|
|||||||
/** 渲染消息列表(按日期分组插入分隔条) */
|
/** 渲染消息列表(按日期分组插入分隔条) */
|
||||||
function renderMessagesWithDateHeaders(
|
function renderMessagesWithDateHeaders(
|
||||||
messages: readonly UiMessage[],
|
messages: readonly UiMessage[],
|
||||||
getMessageKey: (message: UiMessage) => string,
|
getMessageKey: ChatMessageKeyResolver,
|
||||||
isUnlockingMessage?: boolean,
|
isUnlockingMessage?: boolean,
|
||||||
unlockingMessageId?: string | null,
|
unlockingMessageId?: string | null,
|
||||||
onUnlockPrivateMessage?: (messageId: string) => void,
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user