Skip to content

Commit ce54b1d

Browse files
authored
perf: guard TTS sentence parsing behind showCallOverlay check (open-webui#22195)
The chatCompletionEventHandler runs getMessageContentParts() and removeAllDetails() on every streaming token to extract sentences for real-time TTS dispatch via CustomEvent('chat'). These functions perform multiple O(n) regex passes over the full accumulated message content, resulting in O(n^2) total work over a streaming response. The only consumer of these events is CallOverlay.svelte, which is only mounted when showCallOverlay is true. Without the overlay open, the parsing runs but the dispatched events have no listeners. Wrap all three TTS parsing blocks in an if () guard so the expensive regex work is skipped entirely for the vast majority of users who are not using the voice call feature.
1 parent 16701be commit ce54b1d

1 file changed

Lines changed: 58 additions & 52 deletions

File tree

src/lib/components/chat/Chat.svelte

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1567,27 +1567,29 @@
15671567
navigator.vibrate(5);
15681568
}
15691569
1570-
// Emit chat event for TTS
1571-
const messageContentParts = getMessageContentParts(
1572-
removeAllDetails(message.content),
1573-
$config?.audio?.tts?.split_on ?? 'punctuation'
1574-
);
1575-
messageContentParts.pop();
1576-
1577-
// dispatch only last sentence and make sure it hasn't been dispatched before
1578-
if (
1579-
messageContentParts.length > 0 &&
1580-
messageContentParts[messageContentParts.length - 1] !== message.lastSentence
1581-
) {
1582-
message.lastSentence = messageContentParts[messageContentParts.length - 1];
1583-
eventTarget.dispatchEvent(
1584-
new CustomEvent('chat', {
1585-
detail: {
1586-
id: message.id,
1587-
content: messageContentParts[messageContentParts.length - 1]
1588-
}
1589-
})
1570+
// Emit chat event for TTS (only when call overlay is active)
1571+
if ($showCallOverlay) {
1572+
const messageContentParts = getMessageContentParts(
1573+
removeAllDetails(message.content),
1574+
$config?.audio?.tts?.split_on ?? 'punctuation'
15901575
);
1576+
messageContentParts.pop();
1577+
1578+
// dispatch only last sentence and make sure it hasn't been dispatched before
1579+
if (
1580+
messageContentParts.length > 0 &&
1581+
messageContentParts[messageContentParts.length - 1] !== message.lastSentence
1582+
) {
1583+
message.lastSentence = messageContentParts[messageContentParts.length - 1];
1584+
eventTarget.dispatchEvent(
1585+
new CustomEvent('chat', {
1586+
detail: {
1587+
id: message.id,
1588+
content: messageContentParts[messageContentParts.length - 1]
1589+
}
1590+
})
1591+
);
1592+
}
15911593
}
15921594
}
15931595
}
@@ -1601,27 +1603,29 @@
16011603
navigator.vibrate(5);
16021604
}
16031605
1604-
// Emit chat event for TTS
1605-
const messageContentParts = getMessageContentParts(
1606-
removeAllDetails(message.content),
1607-
$config?.audio?.tts?.split_on ?? 'punctuation'
1608-
);
1609-
messageContentParts.pop();
1610-
1611-
// dispatch only last sentence and make sure it hasn't been dispatched before
1612-
if (
1613-
messageContentParts.length > 0 &&
1614-
messageContentParts[messageContentParts.length - 1] !== message.lastSentence
1615-
) {
1616-
message.lastSentence = messageContentParts[messageContentParts.length - 1];
1617-
eventTarget.dispatchEvent(
1618-
new CustomEvent('chat', {
1619-
detail: {
1620-
id: message.id,
1621-
content: messageContentParts[messageContentParts.length - 1]
1622-
}
1623-
})
1606+
// Emit chat event for TTS (only when call overlay is active)
1607+
if ($showCallOverlay) {
1608+
const messageContentParts = getMessageContentParts(
1609+
removeAllDetails(message.content),
1610+
$config?.audio?.tts?.split_on ?? 'punctuation'
16241611
);
1612+
messageContentParts.pop();
1613+
1614+
// dispatch only last sentence and make sure it hasn't been dispatched before
1615+
if (
1616+
messageContentParts.length > 0 &&
1617+
messageContentParts[messageContentParts.length - 1] !== message.lastSentence
1618+
) {
1619+
message.lastSentence = messageContentParts[messageContentParts.length - 1];
1620+
eventTarget.dispatchEvent(
1621+
new CustomEvent('chat', {
1622+
detail: {
1623+
id: message.id,
1624+
content: messageContentParts[messageContentParts.length - 1]
1625+
}
1626+
})
1627+
);
1628+
}
16251629
}
16261630
}
16271631
@@ -1648,18 +1652,20 @@
16481652
document.getElementById(`speak-button-${message.id}`)?.click();
16491653
}
16501654
1651-
// Emit chat event for TTS
1652-
let lastMessageContentPart =
1653-
getMessageContentParts(
1654-
removeAllDetails(message.content),
1655-
$config?.audio?.tts?.split_on ?? 'punctuation'
1656-
)?.at(-1) ?? '';
1657-
if (lastMessageContentPart) {
1658-
eventTarget.dispatchEvent(
1659-
new CustomEvent('chat', {
1660-
detail: { id: message.id, content: lastMessageContentPart }
1661-
})
1662-
);
1655+
// Emit chat event for TTS (only when call overlay is active)
1656+
if ($showCallOverlay) {
1657+
let lastMessageContentPart =
1658+
getMessageContentParts(
1659+
removeAllDetails(message.content),
1660+
$config?.audio?.tts?.split_on ?? 'punctuation'
1661+
)?.at(-1) ?? '';
1662+
if (lastMessageContentPart) {
1663+
eventTarget.dispatchEvent(
1664+
new CustomEvent('chat', {
1665+
detail: { id: message.id, content: lastMessageContentPart }
1666+
})
1667+
);
1668+
}
16631669
}
16641670
eventTarget.dispatchEvent(
16651671
new CustomEvent('chat:finish', {

0 commit comments

Comments
 (0)