From b4f5a935b289e6dffe7ec8cac715cd5dc43cebb8 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 3 Apr 2026 13:11:12 -0400 Subject: [PATCH] feat(tts): wire useStreamingTts into BriefingView --- frontend/src/views/BriefingView.vue | 58 ++++++----------------------- 1 file changed, 11 insertions(+), 47 deletions(-) diff --git a/frontend/src/views/BriefingView.vue b/frontend/src/views/BriefingView.vue index 1a9836f..661a5ec 100644 --- a/frontend/src/views/BriefingView.vue +++ b/frontend/src/views/BriefingView.vue @@ -4,6 +4,7 @@ import { useBackgroundRefresh } from '@/composables/useBackgroundRefresh' import { useVoiceRecorder } from '@/composables/useVoiceRecorder' import { useVoiceAudio } from '@/composables/useVoiceAudio' import { useListenMode } from '@/composables/useListenMode' +import { useStreamingTts } from '@/composables/useStreamingTts' import { useChatStore } from '@/stores/chat' import { useSettingsStore } from '@/stores/settings' import ChatMessage from '@/components/ChatMessage.vue' @@ -20,7 +21,6 @@ import { deleteRssReaction, getNewsItems, transcribeAudio, - synthesiseSpeech, type BriefingConversation, type BriefingMessage, } from '@/api/client' @@ -262,43 +262,15 @@ const voiceEnabled = computed(() => settingsStore.voiceSttReady) const voiceTtsEnabled = computed(() => settingsStore.voiceTtsReady) const listenMode = useListenMode() const transcribing = ref(false) -const synthesising = ref(false) +const tts = useStreamingTts({ + streamingContent: computed(() => chatStore.streamingContent), + streaming: computed(() => chatStore.streaming), + enabled: computed(() => listenMode.value && voiceTtsEnabled.value), +}) const recorder = useVoiceRecorder() const audio = useVoiceAudio() -// Read the latest assistant message aloud -async function listenToLatest() { - const lastAssistant = [...messages.value].reverse().find((m) => m.role === 'assistant') - if (!lastAssistant?.content) return - await speakText(lastAssistant.content) -} - -async function speakText(text: string) { - if (!voiceTtsEnabled.value || synthesising.value) return - // Strip markdown for cleaner TTS output - const plain = text - .replace(/```[\s\S]*?```/g, '') - .replace(/`[^`]+`/g, (m) => m.slice(1, -1)) - .replace(/#{1,6}\s+/g, '') - .replace(/\*\*([^*]+)\*\*/g, '$1') - .replace(/\*([^*]+)\*/g, '$1') - .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') - .replace(/^\s*[-*+]\s+/gm, '') - .replace(/\n{2,}/g, ' ') - .trim() - if (!plain) return - synthesising.value = true - try { - const blob = await synthesiseSpeech(plain) - await audio.play(blob) - } catch { - // TTS failure is non-critical - } finally { - synthesising.value = false - } -} - // PTT: hold to record, release to transcribe and send async function startPtt() { if (!voiceEnabled.value || recorder.recording.value) return @@ -323,14 +295,6 @@ async function stopPtt() { } } -// Auto-TTS when listen mode is on and streaming ends -watch(() => chatStore.streaming, async (streaming) => { - if (!streaming && listenMode.value && voiceTtsEnabled.value) { - // Small delay to let messages update after stream - await new Promise((r) => setTimeout(r, 200)) - await listenToLatest() - } -}) // Convert BriefingMessage to Message for ChatMessage component function toMsg(m: BriefingMessage): Message { @@ -455,12 +419,12 @@ onMounted(async () => {