checkpoint before checking out cursor/flow-abcd-host-return-2b89
This commit is contained in:
@@ -34,10 +34,15 @@ public struct ProgressiveDictationTranscriptAccumulator: Sendable {
|
||||
// Same audio window — volatile refinement of the current segment.
|
||||
segments[idx].text = trimmed
|
||||
} else if let last = segments.last,
|
||||
trimmed.hasPrefix(last.text) || last.text.hasPrefix(trimmed) {
|
||||
// Cumulative progressive update without a range change.
|
||||
let longer = trimmed.count >= last.text.count ? trimmed : last.text
|
||||
segments[segments.count - 1].text = longer
|
||||
let revision = Self.revisionText(
|
||||
previous: last.text,
|
||||
candidate: trimmed,
|
||||
startDelta: abs(last.startSeconds - start)
|
||||
) {
|
||||
// Cumulative progressive update with a small range drift. SpeechAnalyzer
|
||||
// may add punctuation while nudging the range start; treat that as a
|
||||
// refinement, not a new sentence.
|
||||
segments[segments.count - 1].text = revision
|
||||
} else {
|
||||
// New time range — append instead of replacing earlier speech.
|
||||
segments.append(Segment(startSeconds: start, text: trimmed))
|
||||
@@ -61,4 +66,54 @@ public struct ProgressiveDictationTranscriptAccumulator: Sendable {
|
||||
partial = DictationTextComposer.compose(anchor: partial, live: segment.text)
|
||||
}
|
||||
}
|
||||
|
||||
private static func revisionText(
|
||||
previous: String,
|
||||
candidate: String,
|
||||
startDelta: Double
|
||||
) -> String? {
|
||||
let normalizedPrevious = DictationTextComposer.normalizeForOverlap(previous)
|
||||
let normalizedCandidate = DictationTextComposer.normalizeForOverlap(candidate)
|
||||
guard !normalizedPrevious.isEmpty, !normalizedCandidate.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if candidate.hasPrefix(previous) || previous.hasPrefix(candidate) {
|
||||
return candidate.count >= previous.count ? candidate : previous
|
||||
}
|
||||
|
||||
// Avoid collapsing genuinely separate long ranges; this only handles
|
||||
// volatile corrections whose time range start drifted slightly.
|
||||
guard startDelta <= 1.0 else { return nil }
|
||||
|
||||
if normalizedCandidate.hasPrefix(normalizedPrevious)
|
||||
|| normalizedPrevious.hasPrefix(normalizedCandidate)
|
||||
|| normalizedCandidate.contains(normalizedPrevious)
|
||||
|| normalizedPrevious.contains(normalizedCandidate) {
|
||||
return normalizedCandidate.count >= normalizedPrevious.count ? candidate : previous
|
||||
}
|
||||
|
||||
let overlap = longestNormalizedOverlap(
|
||||
previous: normalizedPrevious,
|
||||
candidate: normalizedCandidate
|
||||
)
|
||||
let shorter = min(normalizedPrevious.count, normalizedCandidate.count)
|
||||
guard shorter >= 4, Double(overlap) / Double(shorter) >= 0.8 else {
|
||||
return nil
|
||||
}
|
||||
return normalizedCandidate.count >= normalizedPrevious.count ? candidate : previous
|
||||
}
|
||||
|
||||
private static func longestNormalizedOverlap(previous: String, candidate: String) -> Int {
|
||||
let previousChars = Array(previous)
|
||||
let candidateChars = Array(candidate)
|
||||
let maxProbe = min(64, previousChars.count, candidateChars.count)
|
||||
guard maxProbe > 0 else { return 0 }
|
||||
for length in stride(from: maxProbe, through: 1, by: -1) {
|
||||
if previousChars.suffix(length).elementsEqual(candidateChars.prefix(length)) {
|
||||
return length
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user