From fda6a7acc1cf360f132e1cee230aea2935cf6055 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 3 Apr 2026 13:09:52 -0400 Subject: [PATCH] feat(tts): wire useStreamingTts into ChatView --- frontend/src/views/ChatView.vue | 47 ++++++++------------------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/frontend/src/views/ChatView.vue b/frontend/src/views/ChatView.vue index fdc6cee..1e6dba7 100644 --- a/frontend/src/views/ChatView.vue +++ b/frontend/src/views/ChatView.vue @@ -3,7 +3,8 @@ import { onMounted, onUnmounted, ref, computed, watch, nextTick } from "vue"; import { useRoute, useRouter } from "vue-router"; import { useChatStore } from "@/stores/chat"; import { useSettingsStore } from "@/stores/settings"; -import { apiGet, transcribeAudio, synthesiseSpeech } from "@/api/client"; +import { apiGet, transcribeAudio } from "@/api/client"; +import { useStreamingTts } from "@/composables/useStreamingTts"; import { renderMarkdown } from "@/utils/markdown"; import { useVoiceRecorder } from "@/composables/useVoiceRecorder"; import { useVoiceAudio, setVoiceVolume } from "@/composables/useVoiceAudio"; @@ -32,37 +33,11 @@ const audio = useVoiceAudio(); const voiceEnabled = computed(() => settingsStore.voiceSttReady); const voiceTtsEnabled = computed(() => settingsStore.voiceTtsReady); const listenMode = useListenMode(); -const synthesising = ref(false); const showVolumeSlider = ref(false); - -async function speakLastAssistantMessage() { - const messages = store.currentConversation?.messages ?? []; - const last = [...messages].reverse().find((m) => m.role === "assistant"); - if (!last?.content) return; - const plain = last.content - .replace(/```[\s\S]*?```/g, "") - .replace(/`[^`]+`/g, (m: string) => 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 non-critical */ } - finally { synthesising.value = false; } -} - -watch(() => store.streaming, async (streaming) => { - if (!streaming && listenMode.value && voiceTtsEnabled.value) { - await new Promise((r) => setTimeout(r, 200)); - await speakLastAssistantMessage(); - } +const tts = useStreamingTts({ + streamingContent: computed(() => store.streamingContent), + streaming: computed(() => store.streaming), + enabled: computed(() => listenMode.value && voiceTtsEnabled.value), }); async function startPtt() { @@ -916,12 +891,12 @@ onUnmounted(() => {