fix(tts): add speak() for complete responses; fix briefing message commit race
This commit is contained in:
@@ -52,6 +52,8 @@ export interface UseStreamingTtsReturn {
|
|||||||
speaking: ComputedRef<boolean>
|
speaking: ComputedRef<boolean>
|
||||||
/** Cancel all in-flight synthesis/playback and clear the queue. */
|
/** Cancel all in-flight synthesis/playback and clear the queue. */
|
||||||
stop: () => void
|
stop: () => void
|
||||||
|
/** Speak a complete text through the same sentence-chunking pipeline. */
|
||||||
|
speak: (text: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useStreamingTts(options: UseStreamingTtsOptions): UseStreamingTtsReturn {
|
export function useStreamingTts(options: UseStreamingTtsOptions): UseStreamingTtsReturn {
|
||||||
@@ -139,5 +141,18 @@ export function useStreamingTts(options: UseStreamingTtsOptions): UseStreamingTt
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return { speaking, stop }
|
function speak(text: string): void {
|
||||||
|
stop()
|
||||||
|
if (!text.trim()) return
|
||||||
|
const myAbortId = abortId
|
||||||
|
const { sentences, remainder } = extractSentences(text)
|
||||||
|
for (const sentence of sentences) {
|
||||||
|
enqueueSentence(sentence, myAbortId)
|
||||||
|
}
|
||||||
|
if (remainder.trim().length >= MIN_CHARS) {
|
||||||
|
enqueueSentence(remainder.trim(), myAbortId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { speaking, stop, speak }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,11 +148,14 @@ watch(selectedConvId, async (id) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Refresh messages after streaming ends
|
// Refresh messages after streaming ends — read from store directly (already updated before streaming=false)
|
||||||
watch(() => chatStore.streaming, async (streaming) => {
|
watch(() => chatStore.streaming, async (streaming) => {
|
||||||
if (!streaming && _mounted && selectedConvId.value === todayConvId.value && todayConvId.value) {
|
if (!streaming && _mounted && selectedConvId.value === todayConvId.value) {
|
||||||
const today = await getBriefingToday().catch(() => null)
|
const storeMessages = chatStore.currentConversation?.messages
|
||||||
if (_mounted && today) messages.value = today.messages
|
if (_mounted && storeMessages?.length) {
|
||||||
|
messages.value = storeMessages as unknown as BriefingMessage[]
|
||||||
|
}
|
||||||
|
await nextTick()
|
||||||
if (_mounted) scrollToBottom()
|
if (_mounted) scrollToBottom()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -420,7 +423,7 @@ onMounted(async () => {
|
|||||||
v-if="voiceTtsEnabled"
|
v-if="voiceTtsEnabled"
|
||||||
class="btn-icon"
|
class="btn-icon"
|
||||||
:class="{ 'btn-icon-active': listenMode, 'btn-icon-busy': tts.speaking.value }"
|
:class="{ 'btn-icon-active': listenMode, 'btn-icon-busy': tts.speaking.value }"
|
||||||
@click="listenMode = !listenMode; if (!listenMode) tts.stop()"
|
@click="listenMode = !listenMode; if (listenMode) tts.speak([...messages].reverse().find(m => m.role === 'assistant')?.content ?? ''); else tts.stop()"
|
||||||
:title="listenMode ? 'Stop auto-read' : 'Read replies aloud'"
|
:title="listenMode ? 'Stop auto-read' : 'Read replies aloud'"
|
||||||
aria-label="Toggle listen mode"
|
aria-label="Toggle listen mode"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -892,7 +892,7 @@ onUnmounted(() => {
|
|||||||
<button
|
<button
|
||||||
class="btn-attach btn-listen"
|
class="btn-attach btn-listen"
|
||||||
:class="{ 'btn-listen--active': listenMode, 'btn-listen--busy': tts.speaking.value }"
|
:class="{ 'btn-listen--active': listenMode, 'btn-listen--busy': tts.speaking.value }"
|
||||||
@click="listenMode = !listenMode; if (!listenMode) tts.stop()"
|
@click="listenMode = !listenMode; if (listenMode) tts.speak([...store.currentConversation?.messages ?? []].reverse().find(m => m.role === 'assistant')?.content ?? ''); else tts.stop()"
|
||||||
:title="listenMode ? 'Stop auto-read' : 'Read responses aloud'"
|
:title="listenMode ? 'Stop auto-read' : 'Read responses aloud'"
|
||||||
aria-label="Toggle listen mode"
|
aria-label="Toggle listen mode"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ onUnmounted(async () => {
|
|||||||
:class="{ 'btn-listen-ws--active': listenMode, 'btn-listen-ws--busy': tts.speaking.value }"
|
:class="{ 'btn-listen-ws--active': listenMode, 'btn-listen-ws--busy': tts.speaking.value }"
|
||||||
:title="listenMode ? 'Stop auto-read' : 'Read responses aloud'"
|
:title="listenMode ? 'Stop auto-read' : 'Read responses aloud'"
|
||||||
aria-label="Toggle listen mode"
|
aria-label="Toggle listen mode"
|
||||||
@click="listenMode = !listenMode; if (!listenMode) tts.stop()"
|
@click="listenMode = !listenMode; if (listenMode) tts.speak([...chatStore.currentConversation?.messages ?? []].reverse().find(m => m.role === 'assistant')?.content ?? ''); else tts.stop()"
|
||||||
>
|
>
|
||||||
<svg v-if="!tts.speaking.value" width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
<svg v-if="!tts.speaking.value" width="16" height="16" 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"/>
|
<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"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user