Skip to content

Commit 3942e87

Browse files
committed
perf: batch streaming history updates to animation frames
Decouple data ingestion from rendering during streaming. Socket events still accumulate content on every token (cheap mutation), but the Svelte reactivity trigger (history.messages assignment) now fires at most once per requestAnimationFrame (~60fps). This prevents main thread saturation on high-throughput models (400+ tok/s) and adapts automatically to device capability. Non-streaming events (done, error, status) flush immediately.
1 parent a7bc7a0 commit 3942e87

1 file changed

Lines changed: 36 additions & 3 deletions

File tree

src/lib/components/chat/Chat.svelte

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,40 @@
381381
saveChatHandler(_chatId, history);
382382
};
383383
384+
// Batch reactive history updates to animation frames during streaming.
385+
// Data is accumulated on every socket event (cheap mutation), but the
386+
// Svelte reactivity trigger fires at most once per frame.
387+
let _pendingFlushFrame = null;
388+
let _pendingFlushIds = new Set();
389+
390+
const scheduleHistoryFlush = (messageId) => {
391+
_pendingFlushIds.add(messageId);
392+
if (!_pendingFlushFrame) {
393+
_pendingFlushFrame = requestAnimationFrame(() => {
394+
for (const id of _pendingFlushIds) {
395+
history.messages[id] = history.messages[id];
396+
}
397+
_pendingFlushIds.clear();
398+
_pendingFlushFrame = null;
399+
400+
if (autoScroll) {
401+
scrollToBottom();
402+
}
403+
});
404+
}
405+
};
406+
407+
const flushHistoryNow = () => {
408+
if (_pendingFlushFrame) {
409+
cancelAnimationFrame(_pendingFlushFrame);
410+
_pendingFlushFrame = null;
411+
}
412+
for (const id of _pendingFlushIds) {
413+
history.messages[id] = history.messages[id];
414+
}
415+
_pendingFlushIds.clear();
416+
};
417+
384418
const chatEventHandler = async (event, cb) => {
385419
console.log(event);
386420
@@ -508,7 +542,7 @@
508542
console.log('Unknown message type', data);
509543
}
510544
511-
history.messages[event.message_id] = message;
545+
scheduleHistoryFlush(event.message_id);
512546
}
513547
}
514548
};
@@ -1546,8 +1580,6 @@
15461580
message.usage = usage;
15471581
}
15481582
1549-
history.messages[message.id] = message;
1550-
15511583
if (done) {
15521584
message.done = true;
15531585
@@ -1582,6 +1614,7 @@
15821614
})
15831615
);
15841616
1617+
flushHistoryNow();
15851618
history.messages[message.id] = message;
15861619
15871620
await tick();

0 commit comments

Comments
 (0)