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:
Classic298
2026-02-28 18:09:43 -05:00
committed by GitHub
parent 499ca282e5
commit 30ae519226
+31 -2
View File
@@ -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');