checkpoint before checking out cursor/flow-abcd-host-return-2b89

This commit is contained in:
Rocky
2026-07-06 15:58:20 +08:00
parent 537a68552a
commit e6e75eda31
3 changed files with 162 additions and 5 deletions
@@ -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
}
}