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,
|
currentChatPage,
|
||||||
temporaryChatEnabled
|
temporaryChatEnabled
|
||||||
} from '$lib/stores';
|
} from '$lib/stores';
|
||||||
import { tick, getContext, onMount, createEventDispatcher } from 'svelte';
|
import { tick, getContext, onMount, onDestroy, createEventDispatcher } from 'svelte';
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
@@ -73,7 +73,10 @@
|
|||||||
messagesLoading = false;
|
messagesLoading = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
$: if (history.currentId) {
|
let pendingRebuild = null;
|
||||||
|
let lastCurrentId = null;
|
||||||
|
|
||||||
|
const buildMessages = () => {
|
||||||
let _messages = [];
|
let _messages = [];
|
||||||
|
|
||||||
let message = history.messages[history.currentId];
|
let message = history.messages[history.currentId];
|
||||||
@@ -91,6 +94,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
messages = _messages;
|
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 {
|
} else {
|
||||||
messages = [];
|
messages = [];
|
||||||
}
|
}
|
||||||
@@ -395,6 +420,10 @@
|
|||||||
showMessage({ id: parentMessageId }, false);
|
showMessage({ id: parentMessageId }, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
cancelAnimationFrame(pendingRebuild);
|
||||||
|
});
|
||||||
|
|
||||||
const triggerScroll = () => {
|
const triggerScroll = () => {
|
||||||
if (autoScroll) {
|
if (autoScroll) {
|
||||||
const element = document.getElementById('messages-container');
|
const element = document.getElementById('messages-container');
|
||||||
|
|||||||
Reference in New Issue
Block a user