perf: reduce chat rerenders during streaming
Stabilizes unchanged chat turns while messages stream Keeps static chat history from rerendering unnecessarily Adds coverage for turn record reuse
This commit is contained in:
@@ -19,6 +19,9 @@ import { normalizeParts } from './message/partUtils';
|
||||
|
||||
const MESSAGE_LIST_VIRTUALIZE_THRESHOLD = 5;
|
||||
const MESSAGE_LIST_OVERSCAN = 6;
|
||||
const EMPTY_STATIC_ENTRY_MESSAGES: ChatMessageEntry[] = [];
|
||||
const EMPTY_UNGROUPED_MESSAGE_IDS = new Set<string>();
|
||||
const EMPTY_VIRTUAL_ROWS: VirtualItem[] = [];
|
||||
|
||||
const estimateHistoryEntryHeight = (entry: RenderEntry | undefined): number => {
|
||||
if (!entry) {
|
||||
@@ -936,7 +939,7 @@ const MessageListEntry = React.memo(({
|
||||
MessageListEntry.displayName = 'MessageListEntry';
|
||||
|
||||
// Inner component that renders staged turn entries.
|
||||
const StaticHistoryList: React.FC<{
|
||||
type StaticHistoryListProps = {
|
||||
entries: RenderEntry[];
|
||||
shouldVirtualize: boolean;
|
||||
virtualRows: VirtualItem[];
|
||||
@@ -954,7 +957,9 @@ const StaticHistoryList: React.FC<{
|
||||
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
||||
onUserAnimationConsumed: (messageId: string) => void;
|
||||
activeStreamingPhase?: StreamPhase | null;
|
||||
}> = ({ entries, shouldVirtualize, virtualRows, totalSize, measureElement, contentRef, onMessageContentChange, getAnimationHandlers, scrollToBottom, stickyUserHeader, defaultActivityExpanded, turnUiStates, onToggleTurnGroup, chatRenderMode, shouldAnimateUserMessage, onUserAnimationConsumed, activeStreamingPhase }) => {
|
||||
};
|
||||
|
||||
const StaticHistoryList = React.memo(({ entries, shouldVirtualize, virtualRows, totalSize, measureElement, contentRef, onMessageContentChange, getAnimationHandlers, scrollToBottom, stickyUserHeader, defaultActivityExpanded, turnUiStates, onToggleTurnGroup, chatRenderMode, shouldAnimateUserMessage, onUserAnimationConsumed, activeStreamingPhase }: StaticHistoryListProps) => {
|
||||
const renderEntry = React.useCallback((entry: RenderEntry) => {
|
||||
return (
|
||||
<MessageListEntry
|
||||
@@ -1043,7 +1048,7 @@ const StaticHistoryList: React.FC<{
|
||||
{paddingBottom > 0 ? <div aria-hidden="true" style={{ height: `${paddingBottom}px` }} /> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
StaticHistoryList.displayName = 'StaticHistoryList';
|
||||
|
||||
@@ -1222,6 +1227,9 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
||||
sessionKey,
|
||||
showTextJustificationActivity: chatRenderMode === 'sorted',
|
||||
});
|
||||
const hasUngroupedStaticEntries = projection.ungroupedMessageIds.size > 0;
|
||||
const staticEntryMessages = hasUngroupedStaticEntries ? displayMessages : EMPTY_STATIC_ENTRY_MESSAGES;
|
||||
const staticEntryUngroupedIds = hasUngroupedStaticEntries ? projection.ungroupedMessageIds : EMPTY_UNGROUPED_MESSAGE_IDS;
|
||||
const staticRenderEntries = React.useMemo<RenderEntry[]>(() => streamPerfMeasure('ui.message_list.render_entries_ms', () => {
|
||||
const turnEntries = staticTurns.map((turn) => ({
|
||||
kind: 'turn' as const,
|
||||
@@ -1230,7 +1238,7 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
||||
isLastTurn: turn.turnId === projection.lastTurnId,
|
||||
}));
|
||||
|
||||
if (projection.ungroupedMessageIds.size === 0) {
|
||||
if (staticEntryUngroupedIds.size === 0) {
|
||||
return turnEntries;
|
||||
}
|
||||
|
||||
@@ -1240,14 +1248,14 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
||||
});
|
||||
|
||||
const orderedEntries: RenderEntry[] = [];
|
||||
displayMessages.forEach((message, index) => {
|
||||
staticEntryMessages.forEach((message, index) => {
|
||||
const turnEntry = turnEntryByUserMessageId.get(message.info.id);
|
||||
if (turnEntry) {
|
||||
orderedEntries.push(turnEntry);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!projection.ungroupedMessageIds.has(message.info.id)) {
|
||||
if (!staticEntryUngroupedIds.has(message.info.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1255,13 +1263,13 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
||||
kind: 'ungrouped',
|
||||
key: `msg:${message.info.id}`,
|
||||
message,
|
||||
previousMessage: index > 0 ? displayMessages[index - 1] : undefined,
|
||||
nextMessage: index < displayMessages.length - 1 ? displayMessages[index + 1] : undefined,
|
||||
previousMessage: index > 0 ? staticEntryMessages[index - 1] : undefined,
|
||||
nextMessage: index < staticEntryMessages.length - 1 ? staticEntryMessages[index + 1] : undefined,
|
||||
});
|
||||
});
|
||||
|
||||
return orderedEntries;
|
||||
}), [displayMessages, projection.lastTurnId, projection.ungroupedMessageIds, staticTurns]);
|
||||
}), [projection.lastTurnId, staticEntryMessages, staticEntryUngroupedIds, staticTurns]);
|
||||
|
||||
const trailingStreamingEntry = React.useMemo<RenderEntry | undefined>(() => {
|
||||
if (streamingTurn) {
|
||||
@@ -1428,7 +1436,7 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
||||
}, []);
|
||||
|
||||
const historyVirtualRows = React.useMemo(
|
||||
() => (shouldVirtualizeHistory ? historyVirtualizer.getVirtualItems() : []),
|
||||
() => (shouldVirtualizeHistory ? historyVirtualizer.getVirtualItems() : EMPTY_VIRTUAL_ROWS),
|
||||
[historyVirtualizer, shouldVirtualizeHistory],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user