|
111 | 111 | let autoScroll = true; |
112 | 112 | let processing = ''; |
113 | 113 | let messagesContainerElement: HTMLDivElement; |
| 114 | + let scrollRafId = null; |
| 115 | + let lastTtsProcessTime = 0; |
114 | 116 |
|
115 | 117 | let navbarElement; |
116 | 118 |
|
|
671 | 673 |
|
672 | 674 | onDestroy(() => { |
673 | 675 | try { |
| 676 | + if (scrollRafId) { |
| 677 | + cancelAnimationFrame(scrollRafId); |
| 678 | + } |
674 | 679 | pageSubscribe(); |
675 | 680 | showControlsSubscribe(); |
676 | 681 | selectedFolderSubscribe(); |
|
1175 | 1180 | } |
1176 | 1181 | }; |
1177 | 1182 |
|
1178 | | - const scrollToBottom = async (behavior = 'auto') => { |
1179 | | - await tick(); |
1180 | | - if (messagesContainerElement) { |
1181 | | - messagesContainerElement.scrollTo({ |
1182 | | - top: messagesContainerElement.scrollHeight, |
1183 | | - behavior |
1184 | | - }); |
| 1183 | + const scrollToBottom = (behavior = 'auto') => { |
| 1184 | + if (scrollRafId) { |
| 1185 | + return; |
1185 | 1186 | } |
| 1187 | + scrollRafId = requestAnimationFrame(async () => { |
| 1188 | + scrollRafId = null; |
| 1189 | + await tick(); |
| 1190 | + if (messagesContainerElement) { |
| 1191 | + messagesContainerElement.scrollTo({ |
| 1192 | + top: messagesContainerElement.scrollHeight, |
| 1193 | + behavior |
| 1194 | + }); |
| 1195 | + } |
| 1196 | + }); |
1186 | 1197 | }; |
1187 | 1198 | const chatCompletedHandler = async (_chatId, modelId, responseMessageId, messages) => { |
1188 | 1199 | const res = await chatCompleted(localStorage.token, { |
|
1471 | 1482 | // Stream response |
1472 | 1483 | let value = choices[0]?.delta?.content ?? ''; |
1473 | 1484 | if (message.content == '' && value == '\n') { |
1474 | | - console.log('Empty response'); |
| 1485 | + // Skip leading newlines |
1475 | 1486 | } else { |
1476 | 1487 | message.content += value; |
1477 | 1488 |
|
1478 | 1489 | if (navigator.vibrate && ($settings?.hapticFeedback ?? false)) { |
1479 | 1490 | navigator.vibrate(5); |
1480 | 1491 | } |
1481 | 1492 |
|
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(); |
| 1493 | + // Throttle TTS processing to avoid re-parsing entire content on every token |
| 1494 | + const now = Date.now(); |
| 1495 | + if (now - lastTtsProcessTime >= 150) { |
| 1496 | + lastTtsProcessTime = now; |
1488 | 1497 |
|
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 | | - }) |
| 1498 | + const messageContentParts = getMessageContentParts( |
| 1499 | + removeAllDetails(message.content), |
| 1500 | + $config?.audio?.tts?.split_on ?? 'punctuation' |
1502 | 1501 | ); |
| 1502 | + messageContentParts.pop(); |
| 1503 | +
|
| 1504 | + if ( |
| 1505 | + messageContentParts.length > 0 && |
| 1506 | + messageContentParts[messageContentParts.length - 1] !== message.lastSentence |
| 1507 | + ) { |
| 1508 | + message.lastSentence = messageContentParts[messageContentParts.length - 1]; |
| 1509 | + eventTarget.dispatchEvent( |
| 1510 | + new CustomEvent('chat', { |
| 1511 | + detail: { |
| 1512 | + id: message.id, |
| 1513 | + content: messageContentParts[messageContentParts.length - 1] |
| 1514 | + } |
| 1515 | + }) |
| 1516 | + ); |
| 1517 | + } |
1503 | 1518 | } |
1504 | 1519 | } |
1505 | 1520 | } |
|
1513 | 1528 | navigator.vibrate(5); |
1514 | 1529 | } |
1515 | 1530 |
|
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(); |
| 1531 | + // Throttle TTS processing |
| 1532 | + const now = Date.now(); |
| 1533 | + if (now - lastTtsProcessTime >= 150) { |
| 1534 | + lastTtsProcessTime = now; |
1522 | 1535 |
|
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 | | - }) |
| 1536 | + const messageContentParts = getMessageContentParts( |
| 1537 | + removeAllDetails(message.content), |
| 1538 | + $config?.audio?.tts?.split_on ?? 'punctuation' |
1536 | 1539 | ); |
| 1540 | + messageContentParts.pop(); |
| 1541 | +
|
| 1542 | + if ( |
| 1543 | + messageContentParts.length > 0 && |
| 1544 | + messageContentParts[messageContentParts.length - 1] !== message.lastSentence |
| 1545 | + ) { |
| 1546 | + message.lastSentence = messageContentParts[messageContentParts.length - 1]; |
| 1547 | + eventTarget.dispatchEvent( |
| 1548 | + new CustomEvent('chat', { |
| 1549 | + detail: { |
| 1550 | + id: message.id, |
| 1551 | + content: messageContentParts[messageContentParts.length - 1] |
| 1552 | + } |
| 1553 | + }) |
| 1554 | + ); |
| 1555 | + } |
1537 | 1556 | } |
1538 | 1557 | } |
1539 | 1558 |
|
|
1597 | 1616 | ); |
1598 | 1617 | } |
1599 | 1618 |
|
1600 | | - console.log(data); |
1601 | | - await tick(); |
1602 | | -
|
1603 | | - if (autoScroll) { |
1604 | | - scrollToBottom(); |
| 1619 | + if (!done) { |
| 1620 | + if (autoScroll) { |
| 1621 | + scrollToBottom(); |
| 1622 | + } |
1605 | 1623 | } |
1606 | 1624 | }; |
1607 | 1625 |
|
|
0 commit comments