feat(transcription): multilingual model catalog entries (T8.7, M4.2)

This commit is contained in:
iamdoubz
2026-07-07 12:58:52 -05:00
parent 6bc0949e7e
commit 37a6dd172d
+77
View File
@@ -12,6 +12,12 @@ struct Catalog {
id: &'static str,
label: &'static str,
size_mb: u32,
/// `false` for the `.en` (English-only) ggml variants; `true` for the
/// multilingual variants, which whisper.cpp ships as the same filename
/// minus the `.en` infix (e.g. `ggml-base.bin` vs `ggml-base.en.bin`) —
/// same download/install machinery, just a different id/URL (T8.7,
/// FR-TRX-4, M4.2).
multilingual: bool,
}
const CATALOG: &[Catalog] = &[
@@ -19,21 +25,49 @@ const CATALOG: &[Catalog] = &[
id: "tiny.en-q5_1",
label: "Tiny (English, quantized) — fastest, least accurate",
size_mb: 32,
multilingual: false,
},
Catalog {
id: "base.en-q5_1",
label: "Base (English, quantized) — balanced default",
size_mb: 60,
multilingual: false,
},
Catalog {
id: "small.en-q5_1",
label: "Small (English, quantized) — more accurate, slower",
size_mb: 190,
multilingual: false,
},
Catalog {
id: "medium.en-q5_1",
label: "Medium (English, quantized) — best accuracy, slowest",
size_mb: 540,
multilingual: false,
},
Catalog {
id: "tiny-q5_1",
label: "Tiny (multilingual, quantized) — fastest, least accurate",
size_mb: 32,
multilingual: true,
},
Catalog {
id: "base-q5_1",
label: "Base (multilingual, quantized) — balanced default",
size_mb: 60,
multilingual: true,
},
Catalog {
id: "small-q5_1",
label: "Small (multilingual, quantized) — more accurate, slower",
size_mb: 190,
multilingual: true,
},
Catalog {
id: "medium-q5_1",
label: "Medium (multilingual, quantized) — best accuracy, slowest",
size_mb: 540,
multilingual: true,
},
];
@@ -50,10 +84,19 @@ pub fn list(active_id: &str) -> Vec<ModelInfo> {
size_mb: m.size_mb,
installed: whisper_model_file(m.id).exists(),
active: m.id == active_id,
multilingual: m.multilingual,
})
.collect()
}
/// Whether `id` names a multilingual (non-`.en`) catalog model — an unknown
/// id (shouldn't happen; callers validate against the catalog first) is
/// conservatively treated as English-only rather than granting language
/// selection it can't honor.
pub fn is_multilingual(id: &str) -> bool {
CATALOG.iter().any(|m| m.id == id && m.multilingual)
}
#[derive(Debug, thiserror::Error)]
pub enum ModelError {
#[error("unknown model id: {0}")]
@@ -147,4 +190,38 @@ mod tests {
let err = remove(active, active).unwrap_err();
assert!(matches!(err, ModelError::Invalid(_)));
}
#[test]
fn catalog_has_a_multilingual_counterpart_for_every_english_only_size() {
// T8.7/M4.2: every `.en` model must have a same-size multilingual
// sibling so the Settings picker always has a language-capable
// option at whatever accuracy/speed tier the user already chose.
let en_only: Vec<_> = CATALOG.iter().filter(|m| !m.multilingual).collect();
let multilingual: Vec<_> = CATALOG.iter().filter(|m| m.multilingual).collect();
assert_eq!(en_only.len(), multilingual.len());
for en in &en_only {
assert!(
multilingual.iter().any(|m| m.size_mb == en.size_mb),
"no multilingual sibling for {}",
en.id
);
}
}
#[test]
fn is_multilingual_matches_the_catalog_flag() {
assert!(!is_multilingual("base.en-q5_1"));
assert!(is_multilingual("base-q5_1"));
// Unknown ids are conservatively English-only (no model to check).
assert!(!is_multilingual("nonexistent-id"));
}
#[test]
fn list_reports_multilingual_flag_per_model() {
let models = list("base.en-q5_1");
let base_en = models.iter().find(|m| m.id == "base.en-q5_1").unwrap();
let base_multi = models.iter().find(|m| m.id == "base-q5_1").unwrap();
assert!(!base_en.multilingual);
assert!(base_multi.multilingual);
}
}