perf: throttle message list rebuild to once per animation frame during streaming (#21885)
Messages.svelte rebuilds the message list by walking the parent chain and creating spread copies on every history.messages change. During streaming, this runs on every token — hundreds of times per second — even though each ResponseMessage already has its own reactive binding for content updates. Throttle the rebuild to once per animation frame (~60Hz) during content-only updates, while keeping immediate rebuilds for structural changes (currentId changes like chat switches, navigation, or new messages). Adds onDestroy cleanup for the pending rAF.
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
currentChatPage,
|
||||
temporaryChatEnabled
|
||||
} from '$lib/stores';
|
||||
import { tick, getContext, onMount, createEventDispatcher } from 'svelte';
|
||||
import { tick, getContext, onMount, onDestroy, createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
@@ -73,7 +73,10 @@
|
||||
messagesLoading = false;
|
||||
};
|
||||
|
||||
$: if (history.currentId) {
|
||||
let pendingRebuild = null;
|
||||
let lastCurrentId = null;
|
||||
|
||||
const buildMessages = () => {
|
||||
let _messages = [];
|
||||
|
||||
let message = history.messages[history.currentId];
|
||||
@@ -91,6 +94,28 @@
|
||||
}
|
||||
|
||||
messages = _messages;
|
||||
};
|
||||
|
||||
// Throttle message list rebuilds to once per animation frame during streaming.
|
||||
// Structural changes (currentId change) always rebuild immediately.
|
||||
$: if (history.currentId) {
|
||||
const currentIdChanged = history.currentId !== lastCurrentId;
|
||||
lastCurrentId = history.currentId;
|
||||
|
||||
if (currentIdChanged) {
|
||||
// Structural change: new chat, navigation, new message — rebuild immediately
|
||||
cancelAnimationFrame(pendingRebuild);
|
||||
pendingRebuild = null;
|
||||
buildMessages();
|
||||
} else if (history.messages) {
|
||||
// Content update (streaming) — throttle to once per frame
|
||||
if (!pendingRebuild) {
|
||||
pendingRebuild = requestAnimationFrame(() => {
|
||||
pendingRebuild = null;
|
||||
buildMessages();
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
messages = [];
|
||||
}
|
||||
@@ -395,6 +420,10 @@
|
||||
showMessage({ id: parentMessageId }, false);
|
||||
};
|
||||
|
||||
onDestroy(() => {
|
||||
cancelAnimationFrame(pendingRebuild);
|
||||
});
|
||||
|
||||
const triggerScroll = () => {
|
||||
if (autoScroll) {
|
||||
const element = document.getElementById('messages-container');
|
||||
|
||||
Reference in New Issue
Block a user