feat: add vocabulary-aware Transcriber and shared correction/result types

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 14:52:33 +02:00
co-authored by Claude Sonnet 5.5
parent c815e07b25
commit aec4aa7a5b
2 changed files with 78 additions and 0 deletions
+57
View File
@@ -24,6 +24,63 @@ public protocol AudioRecording {
public protocol Transcriber {
func transcribe(_ samples: [Float]) async throws -> String
/// Biases decoding towards `vocabulary`. Engines without bias support ignore the list.
func transcribe(_ samples: [Float], vocabulary: [String]) async throws -> String
}
public extension Transcriber {
func transcribe(_ samples: [Float], vocabulary: [String]) async throws -> String {
try await transcribe(samples)
}
}
/// One replacement made by the dictionary correction pass.
/// `start`/`length` are UTF-16 offsets into the corrected text.
public struct AppliedCorrection: Equatable, Codable, Sendable {
public var heard: String
public var wrote: String
public var rule: String
public var start: Int
public var length: Int
public init(heard: String, wrote: String, rule: String, start: Int, length: Int) {
self.heard = heard
self.wrote = wrote
self.rule = rule
self.start = start
self.length = length
}
}
public struct CorrectionResult: Equatable, Sendable {
public var text: String
public var corrections: [AppliedCorrection]
public init(text: String, corrections: [AppliedCorrection]) {
self.text = text
self.corrections = corrections
}
}
public protocol TextCorrecting {
func correct(_ text: String) -> CorrectionResult
}
/// A finished dictation, emitted just before the text is inserted.
public struct DictationResult: Equatable, Sendable {
public var date: Date
public var duration: TimeInterval
public var rawText: String
public var finalText: String
public var corrections: [AppliedCorrection]
public init(date: Date, duration: TimeInterval, rawText: String, finalText: String, corrections: [AppliedCorrection]) {
self.date = date
self.duration = duration
self.rawText = rawText
self.finalText = finalText
self.corrections = corrections
}
}
public protocol Cleaner {
@@ -0,0 +1,21 @@
import Foundation
import Testing
@testable import SstCore
private struct PlainTranscriber: Transcriber {
func transcribe(_ samples: [Float]) async throws -> String { "plain \(samples.count)" }
}
struct TranscriberVocabularyTests {
@Test func vocabularyVariantFallsBackToPlainTranscribe() async throws {
let result = try await PlainTranscriber().transcribe([0, 0, 0], vocabulary: ["Anthropic"])
#expect(result == "plain 3")
}
@Test func appliedCorrectionRoundTripsThroughJSON() throws {
let original = AppliedCorrection(heard: "cloud code", wrote: "Claude Code", rule: "cloud code", start: 12, length: 11)
let data = try JSONEncoder().encode(original)
let decoded = try JSONDecoder().decode(AppliedCorrection.self, from: data)
#expect(decoded == original)
}
}