Skip to content

Commit 2fa790a

Browse files
committed
fix: throttle streaming renders via rAF batching
- Batch Svelte reactivity triggers during streaming to ~60fps using requestAnimationFrame, reducing rendering overhead for fast models - Throttle TTS content parsing to every 150ms instead of every token - Remove per-token console.log, await tick(), and scrollToBottom calls - Keep immediate rendering for done events and non-streaming events
1 parent 4251eaf commit 2fa790a

1 file changed

Lines changed: 68 additions & 50 deletions

File tree

src/lib/components/chat/Chat.svelte

Lines changed: 68 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@
154154
155155
let taskIds = null;
156156
157+
// Streaming render state
158+
let renderRafId = null;
159+
let lastTtsProcessTime = 0;
160+
157161
// Chat Input
158162
let prompt = '';
159163
let chatFiles = [];
@@ -382,8 +386,6 @@
382386
};
383387
384388
const chatEventHandler = async (event, cb) => {
385-
console.log(event);
386-
387389
if (event.chat_id === $chatId) {
388390
await tick();
389391
let message = history.messages[event.message_id];
@@ -508,7 +510,24 @@
508510
console.log('Unknown message type', data);
509511
}
510512
511-
history.messages[event.message_id] = message;
513+
// Batch reactivity updates during streaming to reduce rendering overhead.
514+
// During active streaming, content changes accumulate on the message reference
515+
// and a single render is scheduled per animation frame (~60fps).
516+
// Non-streaming events (done, status changes, etc.) render immediately.
517+
if (type === 'chat:completion' && !data?.done) {
518+
if (!renderRafId) {
519+
const msgId = event.message_id;
520+
renderRafId = requestAnimationFrame(() => {
521+
renderRafId = null;
522+
history.messages[msgId] = history.messages[msgId];
523+
if (autoScroll) {
524+
scrollToBottom();
525+
}
526+
});
527+
}
528+
} else {
529+
history.messages[event.message_id] = message;
530+
}
512531
}
513532
}
514533
};
@@ -1479,27 +1498,31 @@
14791498
navigator.vibrate(5);
14801499
}
14811500
1482-
// Emit chat event for TTS
1483-
const messageContentParts = getMessageContentParts(
1484-
removeAllDetails(message.content),
1485-
$config?.audio?.tts?.split_on ?? 'punctuation'
1486-
);
1487-
messageContentParts.pop();
1501+
// Throttle TTS processing to avoid re-parsing content on every token
1502+
const now = Date.now();
1503+
if (now - lastTtsProcessTime >= 150) {
1504+
lastTtsProcessTime = now;
14881505
1489-
// dispatch only last sentence and make sure it hasn't been dispatched before
1490-
if (
1491-
messageContentParts.length > 0 &&
1492-
messageContentParts[messageContentParts.length - 1] !== message.lastSentence
1493-
) {
1494-
message.lastSentence = messageContentParts[messageContentParts.length - 1];
1495-
eventTarget.dispatchEvent(
1496-
new CustomEvent('chat', {
1497-
detail: {
1498-
id: message.id,
1499-
content: messageContentParts[messageContentParts.length - 1]
1500-
}
1501-
})
1506+
const messageContentParts = getMessageContentParts(
1507+
removeAllDetails(message.content),
1508+
$config?.audio?.tts?.split_on ?? 'punctuation'
15021509
);
1510+
messageContentParts.pop();
1511+
1512+
if (
1513+
messageContentParts.length > 0 &&
1514+
messageContentParts[messageContentParts.length - 1] !== message.lastSentence
1515+
) {
1516+
message.lastSentence = messageContentParts[messageContentParts.length - 1];
1517+
eventTarget.dispatchEvent(
1518+
new CustomEvent('chat', {
1519+
detail: {
1520+
id: message.id,
1521+
content: messageContentParts[messageContentParts.length - 1]
1522+
}
1523+
})
1524+
);
1525+
}
15031526
}
15041527
}
15051528
}
@@ -1513,27 +1536,31 @@
15131536
navigator.vibrate(5);
15141537
}
15151538
1516-
// Emit chat event for TTS
1517-
const messageContentParts = getMessageContentParts(
1518-
removeAllDetails(message.content),
1519-
$config?.audio?.tts?.split_on ?? 'punctuation'
1520-
);
1521-
messageContentParts.pop();
1539+
// Throttle TTS processing to avoid re-parsing content on every token
1540+
const now = Date.now();
1541+
if (now - lastTtsProcessTime >= 150) {
1542+
lastTtsProcessTime = now;
15221543
1523-
// dispatch only last sentence and make sure it hasn't been dispatched before
1524-
if (
1525-
messageContentParts.length > 0 &&
1526-
messageContentParts[messageContentParts.length - 1] !== message.lastSentence
1527-
) {
1528-
message.lastSentence = messageContentParts[messageContentParts.length - 1];
1529-
eventTarget.dispatchEvent(
1530-
new CustomEvent('chat', {
1531-
detail: {
1532-
id: message.id,
1533-
content: messageContentParts[messageContentParts.length - 1]
1534-
}
1535-
})
1544+
const messageContentParts = getMessageContentParts(
1545+
removeAllDetails(message.content),
1546+
$config?.audio?.tts?.split_on ?? 'punctuation'
15361547
);
1548+
messageContentParts.pop();
1549+
1550+
if (
1551+
messageContentParts.length > 0 &&
1552+
messageContentParts[messageContentParts.length - 1] !== message.lastSentence
1553+
) {
1554+
message.lastSentence = messageContentParts[messageContentParts.length - 1];
1555+
eventTarget.dispatchEvent(
1556+
new CustomEvent('chat', {
1557+
detail: {
1558+
id: message.id,
1559+
content: messageContentParts[messageContentParts.length - 1]
1560+
}
1561+
})
1562+
);
1563+
}
15371564
}
15381565
}
15391566
@@ -1546,8 +1573,6 @@
15461573
message.usage = usage;
15471574
}
15481575
1549-
history.messages[message.id] = message;
1550-
15511576
if (done) {
15521577
message.done = true;
15531578
@@ -1596,13 +1621,6 @@
15961621
createMessagesList(history, message.id)
15971622
);
15981623
}
1599-
1600-
console.log(data);
1601-
await tick();
1602-
1603-
if (autoScroll) {
1604-
scrollToBottom();
1605-
}
16061624
};
16071625
16081626
//////////////////////////

0 commit comments

Comments
 (0)