|
1800 | 1800 | const chatCompletionEventHandler = async (data, message, chatId) => { |
1801 | 1801 | const { id, done, choices, content, output, sources, selected_model_id, error, usage } = data; |
1802 | 1802 |
|
| 1803 | + const extractTextFromOutput = (o) => |
| 1804 | + o |
| 1805 | + .filter((i) => i.type === 'message') |
| 1806 | + .flatMap((i) => (i.content ?? []).map((p) => (p.text ?? '').trim())) |
| 1807 | + .filter(Boolean) |
| 1808 | + .join('\n'); |
| 1809 | +
|
| 1810 | + const dispatchStreamingTTS = (text) => { |
| 1811 | + if (!$showCallOverlay) return; |
| 1812 | + const parts = getMessageContentParts( |
| 1813 | + text, |
| 1814 | + $config?.audio?.tts?.split_on ?? 'punctuation' |
| 1815 | + ); |
| 1816 | + parts.pop(); |
| 1817 | + if (parts.length > 0 && parts[parts.length - 1] !== message.lastSentence) { |
| 1818 | + message.lastSentence = parts[parts.length - 1]; |
| 1819 | + eventTarget.dispatchEvent( |
| 1820 | + new CustomEvent('chat', { |
| 1821 | + detail: { id: message.id, content: parts[parts.length - 1] } |
| 1822 | + }) |
| 1823 | + ); |
| 1824 | + } |
| 1825 | + }; |
| 1826 | +
|
1803 | 1827 | // Store raw OR-aligned output items from backend |
1804 | 1828 | if (output) { |
1805 | 1829 | message.output = output; |
| 1830 | + dispatchStreamingTTS(extractTextFromOutput(output)); |
1806 | 1831 | } |
1807 | 1832 |
|
1808 | 1833 | if (error) { |
|
1813 | 1838 | message.sources = sources; |
1814 | 1839 | } |
1815 | 1840 |
|
1816 | | - if (choices) { |
| 1841 | + // Only accumulate content from choices when there's no structured output |
| 1842 | + // (output-based rendering takes priority — avoids redundant serialize/parse round-trip) |
| 1843 | + if (choices && !output) { |
1817 | 1844 | if (choices[0]?.message?.content) { |
1818 | 1845 | // Non-stream response |
1819 | 1846 | message.content += choices[0]?.message?.content; |
|
1829 | 1856 | navigator.vibrate(5); |
1830 | 1857 | } |
1831 | 1858 |
|
1832 | | - // Emit chat event for TTS (only when call overlay is active) |
1833 | | - if ($showCallOverlay) { |
1834 | | - const messageContentParts = getMessageContentParts( |
1835 | | - removeAllDetails(message.content), |
1836 | | - $config?.audio?.tts?.split_on ?? 'punctuation' |
1837 | | - ); |
1838 | | - messageContentParts.pop(); |
1839 | | -
|
1840 | | - // dispatch only last sentence and make sure it hasn't been dispatched before |
1841 | | - if ( |
1842 | | - messageContentParts.length > 0 && |
1843 | | - messageContentParts[messageContentParts.length - 1] !== message.lastSentence |
1844 | | - ) { |
1845 | | - message.lastSentence = messageContentParts[messageContentParts.length - 1]; |
1846 | | - eventTarget.dispatchEvent( |
1847 | | - new CustomEvent('chat', { |
1848 | | - detail: { |
1849 | | - id: message.id, |
1850 | | - content: messageContentParts[messageContentParts.length - 1] |
1851 | | - } |
1852 | | - }) |
1853 | | - ); |
1854 | | - } |
1855 | | - } |
| 1859 | + dispatchStreamingTTS(removeAllDetails(message.content)); |
1856 | 1860 | } |
1857 | 1861 | } |
1858 | 1862 | } |
|
1865 | 1869 | navigator.vibrate(5); |
1866 | 1870 | } |
1867 | 1871 |
|
1868 | | - // Emit chat event for TTS (only when call overlay is active) |
1869 | | - if ($showCallOverlay) { |
1870 | | - const messageContentParts = getMessageContentParts( |
1871 | | - removeAllDetails(message.content), |
1872 | | - $config?.audio?.tts?.split_on ?? 'punctuation' |
1873 | | - ); |
1874 | | - messageContentParts.pop(); |
1875 | | -
|
1876 | | - // dispatch only last sentence and make sure it hasn't been dispatched before |
1877 | | - if ( |
1878 | | - messageContentParts.length > 0 && |
1879 | | - messageContentParts[messageContentParts.length - 1] !== message.lastSentence |
1880 | | - ) { |
1881 | | - message.lastSentence = messageContentParts[messageContentParts.length - 1]; |
1882 | | - eventTarget.dispatchEvent( |
1883 | | - new CustomEvent('chat', { |
1884 | | - detail: { |
1885 | | - id: message.id, |
1886 | | - content: messageContentParts[messageContentParts.length - 1] |
1887 | | - } |
1888 | | - }) |
1889 | | - ); |
1890 | | - } |
1891 | | - } |
| 1872 | + dispatchStreamingTTS(removeAllDetails(message.content)); |
1892 | 1873 | } |
1893 | 1874 |
|
1894 | 1875 | if (selected_model_id) { |
|
0 commit comments