fix(asr): keep recordings when streaming recognition fails or stalls
- Keep recording after a streaming ASR error and retry the captured PCM through batch ASR before reporting a failure. - Treat an expired ASR wait as a stall so batch fallback still runs. - Prime the managed Volcengine stream on mic touch and adopt it only while fresh and open. - Auto-scroll the live transcript to the newest recognized text.
This commit is contained in:
@@ -2109,6 +2109,13 @@ final class FlowSessionManager: ObservableObject {
|
||||
!hasUnacknowledgedTerminalResult() else {
|
||||
return
|
||||
}
|
||||
bindSessionASRIfNeeded()
|
||||
if let cloudASR = sessionASR as? CloudASRService {
|
||||
// Same locale source as `beginUtterance`, so the prime is adoptable.
|
||||
cloudASR.primeUtteranceStreaming(
|
||||
locale: SpeechLocaleResolver.resolve(store.localeId)
|
||||
)
|
||||
}
|
||||
startAudioPrime(id: command.utteranceId, origin: "micTouch")
|
||||
}
|
||||
|
||||
@@ -2171,6 +2178,7 @@ final class FlowSessionManager: ObservableObject {
|
||||
!isUtteranceProcessing else {
|
||||
return
|
||||
}
|
||||
(sessionASR as? CloudASRService)?.cancelPrimedUtteranceStreaming()
|
||||
audioPrimeCancellationRequested = true
|
||||
// `capture.start()` is not cancellation-safe. Never stop it mid-start;
|
||||
// the completion waiter performs cleanup unless a real utterance adopts
|
||||
@@ -2613,11 +2621,14 @@ final class FlowSessionManager: ObservableObject {
|
||||
]
|
||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||
.first(where: { !$0.isEmpty })
|
||||
// Without a partial, keep recording: capture is independent
|
||||
// of ASR, and finalize retries the full PCM through batch
|
||||
// ASR (`recognitionFailed`) before reporting the error.
|
||||
if let recovery {
|
||||
manager.lastFinal = recovery
|
||||
manager.debug("asr error recovered via partial len=\(recovery.count)")
|
||||
} else if manager.isUtteranceRecording {
|
||||
manager.failUtterance(message: message, kind: .asrFailed)
|
||||
manager.debug("asr error while recording — deferring to batch fallback")
|
||||
}
|
||||
case .cancelled:
|
||||
// A provider-side cancellation while the mic is still open
|
||||
@@ -2935,8 +2946,16 @@ final class FlowSessionManager: ObservableObject {
|
||||
try? await Task.sleep(nanoseconds: 100_000_000)
|
||||
}
|
||||
|
||||
// The recognizer never reported back within the wait: treat it as a
|
||||
// failure so batch ASR can still recover the captured audio.
|
||||
let asrStalled = lastFinal.isEmpty
|
||||
&& asrCompletedGeneration != finalizeGeneration
|
||||
&& !Task.isCancelled
|
||||
if lastFinal.isEmpty {
|
||||
FlowDiagnostics.log("ASR wait elapsed — cancelling ASR task and using best available transcript")
|
||||
FlowDiagnostics.log(
|
||||
"ASR wait elapsed — cancelling ASR task and using best available transcript "
|
||||
+ "stalled=\(asrStalled ? 1 : 0)"
|
||||
)
|
||||
asrTask?.cancel()
|
||||
Task { await chunkedPipeline?.cancel() }
|
||||
}
|
||||
@@ -2973,11 +2992,13 @@ final class FlowSessionManager: ObservableObject {
|
||||
let wantsBatchFallback = UtteranceBatchFallbackPolicy.shouldRunBatchFallback(
|
||||
stitchedFinal: lastFinal,
|
||||
partialSnapshot: bestPartialSnapshot,
|
||||
recognitionFailed: asrFailureMessage != nil
|
||||
recognitionFailed: asrFailureMessage != nil,
|
||||
recognitionStalled: asrStalled
|
||||
)
|
||||
FlowTrace.pipeline(
|
||||
"batchFallback.decision",
|
||||
"wanted=\(wantsBatchFallback ? 1 : 0) pcmSamples=\(utterancePCMSamples.count) "
|
||||
"wanted=\(wantsBatchFallback ? 1 : 0) stalled=\(asrStalled ? 1 : 0) "
|
||||
+ "pcmSamples=\(utterancePCMSamples.count) "
|
||||
+ "pcmRms=\(FlowTrace.rms(utterancePCMSamples)) "
|
||||
+ "stitchedLen=\(lastFinal.count) partialLen=\(bestPartialSnapshot.count) "
|
||||
+ "resolvedLen=\(text.count)"
|
||||
|
||||
Reference in New Issue
Block a user