Add dictation-locale transcription (incl. Russian), rework list capture UI
- TranscriptionSettings/Service route per-locale to SpeechTranscriber or DictationTranscriber (union of both supported sets, speech preferred); neutral-confidence ranking fallback keeps mixed-engine auto-pick working - NotesListView: text + voice capture buttons above the list (glassProminent), search moved to the iOS 26 toolbar search accessory (.minimize) - Changelog entries for languages, capture buttons, search placement
This commit is contained in:
@@ -36,6 +36,71 @@ struct LanguagePickerTests {
|
||||
#expect(LanguagePicker.meanConfidence(of: transcript([("no attributes", nil)])) == 0)
|
||||
}
|
||||
|
||||
// MARK: - Ranking confidence (mixed-engine graceful degradation)
|
||||
|
||||
@Test func hasConfidenceDetectsAttributedRuns() {
|
||||
#expect(LanguagePicker.hasConfidence(of: transcript([("sure", 0.9)])))
|
||||
#expect(LanguagePicker.hasConfidence(of: transcript([("a", nil), ("b", 0.2)])))
|
||||
#expect(!LanguagePicker.hasConfidence(of: transcript([("none", nil)])))
|
||||
#expect(!LanguagePicker.hasConfidence(of: AttributedString("")))
|
||||
}
|
||||
|
||||
@Test func rankingConfidenceUsesRealMeanWhenPresent() {
|
||||
// With any confidence attribute, ranking == the character-weighted mean.
|
||||
let attributed = transcript([("hello", 0.9), (" world", 0.4)])
|
||||
#expect(abs(LanguagePicker.rankingConfidence(of: attributed)
|
||||
- LanguagePicker.meanConfidence(of: attributed)) < 0.0001)
|
||||
}
|
||||
|
||||
@Test func rankingConfidenceFallsBackToNeutralForUnattributedText() {
|
||||
// A DictationTranscriber transcript with text but no confidence attribute
|
||||
// scores the documented neutral value, not 0, so it can still compete.
|
||||
let dictation = transcript([("привет мир", nil)])
|
||||
#expect(LanguagePicker.rankingConfidence(of: dictation) == LanguagePicker.neutralConfidence)
|
||||
}
|
||||
|
||||
@Test func rankingConfidenceOfEmptyIsZero() {
|
||||
#expect(LanguagePicker.rankingConfidence(of: AttributedString("")) == 0)
|
||||
}
|
||||
|
||||
@Test func neutralDictationBeatsLowConfidenceSpeech() {
|
||||
// Audio is Russian: the dictation locale produces good (neutral-scored)
|
||||
// text; the mismatched speech locale produces low-confidence garbage.
|
||||
let winner = LanguagePicker.pick(from: [
|
||||
LanguageCandidate(localeID: "en-US", meanConfidence: 0.2, transcriptLength: 12),
|
||||
LanguageCandidate(localeID: "ru-RU",
|
||||
meanConfidence: LanguagePicker.neutralConfidence,
|
||||
transcriptLength: 18),
|
||||
])
|
||||
#expect(winner?.localeID == "ru-RU")
|
||||
}
|
||||
|
||||
@Test func highConfidenceSpeechBeatsNeutralDictation() {
|
||||
// Audio is English: the speech locale's real high confidence outranks the
|
||||
// mismatched dictation locale's neutral score.
|
||||
let winner = LanguagePicker.pick(from: [
|
||||
LanguageCandidate(localeID: "en-US", meanConfidence: 0.85, transcriptLength: 20),
|
||||
LanguageCandidate(localeID: "ru-RU",
|
||||
meanConfidence: LanguagePicker.neutralConfidence,
|
||||
transcriptLength: 22),
|
||||
])
|
||||
#expect(winner?.localeID == "en-US")
|
||||
}
|
||||
|
||||
@Test func twoNeutralDictationCandidatesBreakByLength() {
|
||||
// Both engines dictation-only (e.g. ru-RU vs uk-UA), both neutral-scored:
|
||||
// the longer transcript wins the tie-break.
|
||||
let winner = LanguagePicker.pick(from: [
|
||||
LanguageCandidate(localeID: "uk-UA",
|
||||
meanConfidence: LanguagePicker.neutralConfidence,
|
||||
transcriptLength: 8),
|
||||
LanguageCandidate(localeID: "ru-RU",
|
||||
meanConfidence: LanguagePicker.neutralConfidence,
|
||||
transcriptLength: 24),
|
||||
])
|
||||
#expect(winner?.localeID == "ru-RU")
|
||||
}
|
||||
|
||||
// MARK: - Argmax
|
||||
|
||||
@Test func pickChoosesHighestConfidence() {
|
||||
|
||||
Reference in New Issue
Block a user