fix(stage-ui): share streaming transcription sessions (#2260)

This commit is contained in:
Neko
2026-08-12 00:36:26 +08:00
committed by GitHub
parent 9f34877bab
commit 788a509940
9 changed files with 203 additions and 72 deletions
+6 -1
View File
@@ -48,13 +48,16 @@ const settingsAudioDeviceStore = useSettingsAudioDevice()
const { stream, enabled } = storeToRefs(settingsAudioDeviceStore)
const { startRecord, stopRecord, onStopRecord } = useAudioRecorder(stream)
const hearingPipeline = useHearingSpeechInputPipeline()
const { transcribeForRecording, transcribeForMediaStream, stopStreamingTranscription } = hearingPipeline
const { removeStreamingTranscriptionConsumer, transcribeForRecording, transcribeForMediaStream, stopStreamingTranscription } = hearingPipeline
const { supportsStreamInput } = storeToRefs(hearingPipeline)
const providersStore = useProviderStore()
const consciousnessStore = useConsciousnessStore()
const { activeProvider: activeChatProvider, activeModel: activeChatModel } = storeToRefs(consciousnessStore)
const chatStore = useChatStore()
/** Identifies this page in the shared streaming transcription session. */
const transcriptionConsumerId = 'stage-pocket:voice-input'
const shouldUseStreamInput = computed(() => supportsStreamInput.value && !!stream.value)
const {
@@ -104,6 +107,7 @@ async function handleSpeechStart() {
// Use both callbacks to support incremental updates and final transcript replacement.
// ChatArea uses only onSentenceEnd to avoid re-adding deleted text.
await transcribeForMediaStream(stream.value, {
consumerId: transcriptionConsumerId,
onSentenceEnd: (delta) => {
const finalText = delta
if (!finalText || !finalText.trim()) {
@@ -141,6 +145,7 @@ async function handleSpeechEnd() {
function stopAudioInteraction() {
try {
removeStreamingTranscriptionConsumer(transcriptionConsumerId)
stopOnStopRecord?.()
stopOnStopRecord = undefined
// Stop any active streaming transcription sessions to prevent session leakage
@@ -325,11 +325,13 @@ const { nowSpeaking } = storeToRefs(useSpeakingStore())
const hearingStore = useHearingStore()
const { activeTranscriptionModel, activeTranscriptionProvider } = storeToRefs(hearingStore)
const hearingPipeline = useHearingSpeechInputPipeline()
const { transcribeForMediaStream, stopStreamingTranscription } = hearingPipeline
const { removeStreamingTranscriptionConsumer, transcribeForMediaStream, stopStreamingTranscription } = hearingPipeline
const { error: transcriptionError, supportsStreamInput } = storeToRefs(hearingPipeline)
const chatStore = useChatStore()
const chatSession = useChatSessionStore()
const streamingTranscriptionUnavailable = ref(false)
/** Identifies this page in the shared streaming transcription session. */
const transcriptionConsumerId = 'stage-tamagotchi:voice-input'
const shouldUseStreamInput = computed(() => supportsStreamInput.value && !!stream.value && !streamingTranscriptionUnavailable.value)
const voiceTranscriptBuffer = createTranscriptBuffer({
flushDelayMs: 1200,
@@ -588,17 +590,20 @@ async function startAudioInteractionConsumers() {
return
await transcribeForMediaStream(currentStream, {
consumerId: transcriptionConsumerId,
onSentenceEnd: handleStreamingSentenceEnd,
onSpeechEnd: handleStreamingSpeechEnd,
})
if (inspectVoiceInputStreamingRequestGate().skip) {
removeStreamingTranscriptionConsumer(transcriptionConsumerId)
await stopStreamingTranscription(true)
return
}
if (transcriptionError.value) {
streamingTranscriptionUnavailable.value = true
removeStreamingTranscriptionConsumer(transcriptionConsumerId)
await stopStreamingTranscription(true)
console.warn('[Main Page] Streaming transcription unavailable; using recorder-backed fallback:', transcriptionError.value)
}
@@ -616,6 +621,7 @@ async function stopAudioInteractionConsumers(options: StopAudioInteractionOption
clearAssistantSpeechResumeTimer()
voiceInputGeneration += 1
removeStreamingTranscriptionConsumer(transcriptionConsumerId)
await Promise.all([
stopStreamingTranscription(true),
+6 -1
View File
@@ -45,13 +45,16 @@ const settingsAudioDeviceStore = useSettingsAudioDevice()
const { stream, enabled } = storeToRefs(settingsAudioDeviceStore)
const { startRecord, stopRecord, onStopRecord } = useAudioRecorder(stream)
const hearingPipeline = useHearingSpeechInputPipeline()
const { stopStreamingTranscription, transcribeForMediaStream, transcribeForRecording } = hearingPipeline
const { removeStreamingTranscriptionConsumer, stopStreamingTranscription, transcribeForMediaStream, transcribeForRecording } = hearingPipeline
const { supportsStreamInput } = storeToRefs(hearingPipeline)
const providersStore = useProviderStore()
const consciousnessStore = useConsciousnessStore()
const { activeProvider: activeChatProvider, activeModel: activeChatModel } = storeToRefs(consciousnessStore)
const chatStore = useChatStore()
/** Identifies this page in the shared streaming transcription session. */
const transcriptionConsumerId = 'stage-web:voice-input'
const shouldUseStreamInput = computed(() => supportsStreamInput.value && !!stream.value)
const {
@@ -91,6 +94,7 @@ async function startAudioInteraction() {
if (shouldUseStreamInput.value && stream.value) {
await transcribeForMediaStream(stream.value, {
consumerId: transcriptionConsumerId,
onSentenceEnd: text => void sendVoiceInputTextToChat(text),
})
return
@@ -128,6 +132,7 @@ async function handleSpeechEnd() {
function stopAudioInteraction() {
try {
removeStreamingTranscriptionConsumer(transcriptionConsumerId)
stopOnStopRecord?.()
stopOnStopRecord = undefined
void stopStreamingTranscription(true)