feat(tts): wire useStreamingTts into ChatView
This commit is contained in:
@@ -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(() => {
|
||||
<div class="volume-wrapper">
|
||||
<button
|
||||
class="btn-attach btn-listen"
|
||||
:class="{ 'btn-listen--active': listenMode, 'btn-listen--busy': synthesising || audio.playing.value }"
|
||||
@click="listenMode = !listenMode; if (listenMode) speakLastAssistantMessage()"
|
||||
:class="{ 'btn-listen--active': listenMode, 'btn-listen--busy': tts.speaking.value }"
|
||||
@click="listenMode = !listenMode; if (!listenMode) tts.stop()"
|
||||
:title="listenMode ? 'Stop auto-read' : 'Read responses aloud'"
|
||||
aria-label="Toggle listen mode"
|
||||
>
|
||||
<svg v-if="!synthesising && !audio.playing.value" width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
|
||||
<svg v-if="!tts.speaking.value" width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
|
||||
</svg>
|
||||
<svg v-else width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
|
||||
@@ -951,9 +926,9 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-if="synthesising || audio.playing.value"
|
||||
v-if="tts.speaking.value"
|
||||
class="btn-attach"
|
||||
@click="audio.stop(); synthesising = false"
|
||||
@click="tts.stop()"
|
||||
title="Stop playback"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor"><rect width="14" height="14" rx="2"/></svg>
|
||||
|
||||
Reference in New Issue
Block a user