Files
Cursor Agent c7ee891a90 fix(flow): improve tail capture and final chunk ASR recovery
- Add FlowUtteranceEndCoordinator with 350ms silence drain and 150ms post-roll
- Extend FinalChunkRecovery for short/empty final chunks in chunked pipeline
- Snapshot partial at mic stop and guard final transcript in FlowSessionManager
- Unify tail drain presets (iosFlow/macMLX) and expand diagnostics

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-26 10:13:40 +00:00

48 lines
1.7 KiB
Swift

// FinalChunkRecovery.swift
// OSGKeyboard · Shared
//
// Recovery plans for pipelined utterance ASR when the final chunk is short,
// empty, or straddles a chunk boundary.
import Foundation
public enum FinalChunkRecovery {
/// Samples and stitch index when the final chunk should be merged with
/// prior overlap *before* the first ASR pass.
public static func preMergePlan(
chunk: UtteranceAudioChunk,
processedChunks: Int,
previousChunkSamples: [Float],
config: FlowUtteranceChunkConfig
) -> (samples: [Float], stitchIndex: Int)? {
guard chunk.isLast, !chunk.samples.isEmpty, !previousChunkSamples.isEmpty else {
return nil
}
guard chunk.samples.count < config.minFinalChunkSamples else { return nil }
let merged = Array(previousChunkSamples.suffix(config.overlapSamples)) + chunk.samples
return (merged, max(0, chunk.index - 1))
}
/// Retry plan when the final chunk had audio but ASR returned empty text.
public static func emptyResultRetryPlan(
chunk: UtteranceAudioChunk,
previousChunkSamples: [Float],
config: FlowUtteranceChunkConfig,
asrText: String
) -> (samples: [Float], stitchIndex: Int)? {
guard chunk.isLast, !chunk.samples.isEmpty else { return nil }
guard asrText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
return nil
}
if previousChunkSamples.isEmpty {
return (chunk.samples, chunk.index)
}
let merged = Array(previousChunkSamples.suffix(config.overlapSamples)) + chunk.samples
return (merged, max(0, chunk.index - 1))
}
}