diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 5384ca8..2c55c88 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -7076,9 +7076,9 @@ dependencies = [ [[package]] name = "transcribe-rs" -version = "0.2.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "811b3e12455e39ad39003be9a4b7043363a7082b50484a3eb6e3e862466520e4" +checksum = "a74103eb9d8148a277bcf0a4f0e8fb8c08a624e7daf39d1b255af190927c3040" dependencies = [ "base64 0.22.1", "derive_builder", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 5c3d80f..34878cd 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -68,7 +68,7 @@ chrono = "0.4" rusqlite = { version = "0.37", features = ["bundled"] } tar = "0.4.44" flate2 = "1.0" -transcribe-rs = { version = "0.2.8", features = ["whisper", "parakeet", "moonshine", "sense_voice", "gigaam"] } +transcribe-rs = { version = "0.3.1", features = ["whisper", "onnx"] } handy-keys = "0.2.4" ferrous-opencc = "0.2.3" clap = { version = "4", features = ["derive"] } diff --git a/src-tauri/src/commands/models.rs b/src-tauri/src/commands/models.rs index a3197e3..fca4f0a 100644 --- a/src-tauri/src/commands/models.rs +++ b/src-tauri/src/commands/models.rs @@ -92,6 +92,24 @@ pub fn switch_active_model(app: &AppHandle, model_id: &str) -> Result<(), String // when it reacts to events emitted by load_model. let mut settings = settings; settings.selected_model = model_id.to_string(); + + // Reset language to auto if the new model doesn't support the currently selected language. + // This prevents stale language settings from causing errors (e.g. Canary receiving zh-Hans) + // and stops downstream processing (e.g. OpenCC) from running on an irrelevant language. + if settings.selected_language != "auto" + && !model_info.supported_languages.is_empty() + && !model_info + .supported_languages + .contains(&settings.selected_language) + { + log::info!( + "Resetting language from '{}' to 'auto' (not supported by {})", + settings.selected_language, + model_id + ); + settings.selected_language = "auto".to_string(); + } + write_settings(app, settings); // Skip eager loading if unload is set to "Immediately" — the model diff --git a/src-tauri/src/managers/model.rs b/src-tauri/src/managers/model.rs index 59150c7..74f2515 100644 --- a/src-tauri/src/managers/model.rs +++ b/src-tauri/src/managers/model.rs @@ -24,6 +24,7 @@ pub enum EngineType { MoonshineStreaming, SenseVoice, GigaAM, + Canary, } #[derive(Debug, Clone, Serialize, Deserialize, Type)] @@ -44,6 +45,7 @@ pub struct ModelInfo { pub supports_translation: bool, // Whether the model supports translating to English pub is_recommended: bool, // Whether this is the recommended model for new users pub supported_languages: Vec, // Languages this model can transcribe + pub supports_language_selection: bool, // Whether the user can explicitly pick a language pub is_custom: bool, // Whether this is a user-provided custom model } @@ -112,6 +114,7 @@ impl ModelManager { supports_translation: true, is_recommended: false, supported_languages: whisper_languages.clone(), + supports_language_selection: true, is_custom: false, }, ); @@ -136,6 +139,7 @@ impl ModelManager { supports_translation: true, is_recommended: false, supported_languages: whisper_languages.clone(), + supports_language_selection: true, is_custom: false, }, ); @@ -159,6 +163,7 @@ impl ModelManager { supports_translation: false, // Turbo doesn't support translation is_recommended: false, supported_languages: whisper_languages.clone(), + supports_language_selection: true, is_custom: false, }, ); @@ -182,6 +187,7 @@ impl ModelManager { supports_translation: true, is_recommended: false, supported_languages: whisper_languages.clone(), + supports_language_selection: true, is_custom: false, }, ); @@ -206,6 +212,7 @@ impl ModelManager { supports_translation: false, is_recommended: false, supported_languages: whisper_languages, + supports_language_selection: true, is_custom: false, }, ); @@ -230,6 +237,7 @@ impl ModelManager { supports_translation: false, is_recommended: false, supported_languages: vec!["en".to_string()], + supports_language_selection: false, is_custom: false, }, ); @@ -263,6 +271,7 @@ impl ModelManager { supports_translation: false, is_recommended: true, supported_languages: parakeet_v3_languages, + supports_language_selection: false, is_custom: false, }, ); @@ -286,6 +295,7 @@ impl ModelManager { supports_translation: false, is_recommended: false, supported_languages: vec!["en".to_string()], + supports_language_selection: false, is_custom: false, }, ); @@ -311,6 +321,7 @@ impl ModelManager { supports_translation: false, is_recommended: false, supported_languages: vec!["en".to_string()], + supports_language_selection: false, is_custom: false, }, ); @@ -336,6 +347,7 @@ impl ModelManager { supports_translation: false, is_recommended: false, supported_languages: vec!["en".to_string()], + supports_language_selection: false, is_custom: false, }, ); @@ -361,6 +373,7 @@ impl ModelManager { supports_translation: false, is_recommended: false, supported_languages: vec!["en".to_string()], + supports_language_selection: false, is_custom: false, }, ); @@ -392,6 +405,7 @@ impl ModelManager { supports_translation: false, is_recommended: false, supported_languages: sense_voice_languages, + supports_language_selection: true, is_custom: false, }, ); @@ -418,6 +432,72 @@ impl ModelManager { supports_translation: false, is_recommended: false, supported_languages: gigaam_languages, + supports_language_selection: false, + is_custom: false, + }, + ); + + // Canary 180m Flash supported languages (4 languages) + let canary_flash_languages: Vec = vec!["en", "de", "es", "fr"] + .into_iter() + .map(String::from) + .collect(); + + available_models.insert( + "canary-180m-flash".to_string(), + ModelInfo { + id: "canary-180m-flash".to_string(), + name: "Canary 180M Flash".to_string(), + description: "Very fast. English, German, Spanish, French. Supports translation." + .to_string(), + filename: "canary-180m-flash".to_string(), + url: Some("https://blob.handy.computer/canary-180m-flash.tar.gz".to_string()), + size_mb: 146, + is_downloaded: false, + is_downloading: false, + partial_size: 0, + is_directory: true, + engine_type: EngineType::Canary, + accuracy_score: 0.75, + speed_score: 0.85, + supports_translation: true, + is_recommended: false, + supported_languages: canary_flash_languages, + supports_language_selection: true, + is_custom: false, + }, + ); + + // Canary 1B v2 supported languages (25 EU languages) + let canary_1b_languages: Vec = vec![ + "bg", "hr", "cs", "da", "nl", "en", "et", "fi", "fr", "de", "el", "hu", "it", "lv", + "lt", "mt", "pl", "pt", "ro", "sk", "sl", "es", "sv", "ru", "uk", + ] + .into_iter() + .map(String::from) + .collect(); + + available_models.insert( + "canary-1b-v2".to_string(), + ModelInfo { + id: "canary-1b-v2".to_string(), + name: "Canary 1B v2".to_string(), + description: "Accurate multilingual. 25 European languages. Supports translation." + .to_string(), + filename: "canary-1b-v2".to_string(), + url: Some("https://blob.handy.computer/canary-1b-v2.tar.gz".to_string()), + size_mb: 692, + is_downloaded: false, + is_downloading: false, + partial_size: 0, + is_directory: true, + engine_type: EngineType::Canary, + accuracy_score: 0.85, + speed_score: 0.70, + supports_translation: true, + is_recommended: false, + supported_languages: canary_1b_languages, + supports_language_selection: true, is_custom: false, }, ); @@ -689,6 +769,7 @@ impl ModelManager { supports_translation: false, is_recommended: false, supported_languages: vec![], + supports_language_selection: true, is_custom: true, }, ); @@ -1240,6 +1321,7 @@ mod tests { supports_translation: true, is_recommended: false, supported_languages: vec!["en".to_string()], + supports_language_selection: true, is_custom: false, }, ); diff --git a/src-tauri/src/managers/transcription.rs b/src-tauri/src/managers/transcription.rs index afedc49..90010de 100644 --- a/src-tauri/src/managers/transcription.rs +++ b/src-tauri/src/managers/transcription.rs @@ -11,22 +11,16 @@ use std::thread; use std::time::{Duration, SystemTime}; use tauri::{AppHandle, Emitter}; use transcribe_rs::{ - engines::{ - gigaam::GigaAMEngine, - moonshine::{ - ModelVariant, MoonshineEngine, MoonshineModelParams, MoonshineStreamingEngine, - StreamingModelParams, - }, - parakeet::{ - ParakeetEngine, ParakeetInferenceParams, ParakeetModelParams, TimestampGranularity, - }, - sense_voice::{ - Language as SenseVoiceLanguage, SenseVoiceEngine, SenseVoiceInferenceParams, - SenseVoiceModelParams, - }, - whisper::{WhisperEngine, WhisperInferenceParams}, + onnx::{ + canary::CanaryModel, + gigaam::GigaAMModel, + moonshine::{MoonshineModel, MoonshineVariant, StreamingModel}, + parakeet::{ParakeetModel, ParakeetParams, TimestampGranularity}, + sense_voice::{SenseVoiceModel, SenseVoiceParams}, + Quantization, }, - TranscriptionEngine, + whisper_cpp::{WhisperEngine, WhisperInferenceParams}, + SpeechModel, TranscribeOptions, }; #[derive(Clone, Debug, Serialize)] @@ -39,11 +33,12 @@ pub struct ModelStateEvent { enum LoadedEngine { Whisper(WhisperEngine), - Parakeet(ParakeetEngine), - Moonshine(MoonshineEngine), - MoonshineStreaming(MoonshineStreamingEngine), - SenseVoice(SenseVoiceEngine), - GigaAM(GigaAMEngine), + Parakeet(ParakeetModel), + Moonshine(MoonshineModel), + MoonshineStreaming(StreamingModel), + SenseVoice(SenseVoiceModel), + GigaAM(GigaAMModel), + Canary(CanaryModel), } /// RAII guard that clears the `is_loading` flag and notifies waiters on drop. @@ -186,17 +181,8 @@ impl TranscriptionManager { { let mut engine = self.lock_engine(); - if let Some(ref mut loaded_engine) = *engine { - match loaded_engine { - LoadedEngine::Whisper(ref mut e) => e.unload_model(), - LoadedEngine::Parakeet(ref mut e) => e.unload_model(), - LoadedEngine::Moonshine(ref mut e) => e.unload_model(), - LoadedEngine::MoonshineStreaming(ref mut e) => e.unload_model(), - LoadedEngine::SenseVoice(ref mut e) => e.unload_model(), - LoadedEngine::GigaAM(ref mut e) => e.unload_model(), - } - } - *engine = None; // Drop the engine to free memory + // v0.3.0: no explicit unload — dropping the engine frees all resources + *engine = None; } { let mut current_model = self.current_model_id.lock().unwrap(); @@ -274,8 +260,7 @@ impl TranscriptionManager { // Create appropriate engine based on model type let loaded_engine = match model_info.engine_type { EngineType::Whisper => { - let mut engine = WhisperEngine::new(); - engine.load_model(&model_path).map_err(|e| { + let engine = WhisperEngine::load(&model_path).map_err(|e| { let error_msg = format!("Failed to load whisper model {}: {}", model_id, e); let _ = self.app_handle.emit( "model-state-changed", @@ -291,10 +276,8 @@ impl TranscriptionManager { LoadedEngine::Whisper(engine) } EngineType::Parakeet => { - let mut engine = ParakeetEngine::new(); - engine - .load_model_with_params(&model_path, ParakeetModelParams::int8()) - .map_err(|e| { + let engine = + ParakeetModel::load(&model_path, &Quantization::Int8).map_err(|e| { let error_msg = format!("Failed to load parakeet model {}: {}", model_id, e); let _ = self.app_handle.emit( @@ -311,32 +294,28 @@ impl TranscriptionManager { LoadedEngine::Parakeet(engine) } EngineType::Moonshine => { - let mut engine = MoonshineEngine::new(); - engine - .load_model_with_params( - &model_path, - MoonshineModelParams::variant(ModelVariant::Base), - ) - .map_err(|e| { - let error_msg = - format!("Failed to load moonshine model {}: {}", model_id, e); - let _ = self.app_handle.emit( - "model-state-changed", - ModelStateEvent { - event_type: "loading_failed".to_string(), - model_id: Some(model_id.to_string()), - model_name: Some(model_info.name.clone()), - error: Some(error_msg.clone()), - }, - ); - anyhow::anyhow!(error_msg) - })?; + let engine = MoonshineModel::load( + &model_path, + MoonshineVariant::Base, + &Quantization::default(), + ) + .map_err(|e| { + let error_msg = format!("Failed to load moonshine model {}: {}", model_id, e); + let _ = self.app_handle.emit( + "model-state-changed", + ModelStateEvent { + event_type: "loading_failed".to_string(), + model_id: Some(model_id.to_string()), + model_name: Some(model_info.name.clone()), + error: Some(error_msg.clone()), + }, + ); + anyhow::anyhow!(error_msg) + })?; LoadedEngine::Moonshine(engine) } EngineType::MoonshineStreaming => { - let mut engine = MoonshineStreamingEngine::new(); - engine - .load_model_with_params(&model_path, StreamingModelParams::default()) + let engine = StreamingModel::load(&model_path, 0, &Quantization::default()) .map_err(|e| { let error_msg = format!( "Failed to load moonshine streaming model {}: {}", @@ -356,10 +335,8 @@ impl TranscriptionManager { LoadedEngine::MoonshineStreaming(engine) } EngineType::SenseVoice => { - let mut engine = SenseVoiceEngine::new(); - engine - .load_model_with_params(&model_path, SenseVoiceModelParams::int8()) - .map_err(|e| { + let engine = + SenseVoiceModel::load(&model_path, &Quantization::Int8).map_err(|e| { let error_msg = format!("Failed to load SenseVoice model {}: {}", model_id, e); let _ = self.app_handle.emit( @@ -376,8 +353,7 @@ impl TranscriptionManager { LoadedEngine::SenseVoice(engine) } EngineType::GigaAM => { - let mut engine = GigaAMEngine::new(); - engine.load_model(&model_path).map_err(|e| { + let engine = GigaAMModel::load(&model_path, &Quantization::Int8).map_err(|e| { let error_msg = format!("Failed to load gigaam model {}: {}", model_id, e); let _ = self.app_handle.emit( "model-state-changed", @@ -392,6 +368,22 @@ impl TranscriptionManager { })?; LoadedEngine::GigaAM(engine) } + EngineType::Canary => { + let engine = CanaryModel::load(&model_path, &Quantization::Int8).map_err(|e| { + let error_msg = format!("Failed to load canary model {}: {}", model_id, e); + let _ = self.app_handle.emit( + "model-state-changed", + ModelStateEvent { + event_type: "loading_failed".to_string(), + model_id: Some(model_id.to_string()), + model_name: Some(model_info.name.clone()), + error: Some(error_msg.clone()), + }, + ); + anyhow::anyhow!(error_msg) + })?; + LoadedEngine::Canary(engine) + } }; // Update the current engine and model ID @@ -486,6 +478,33 @@ impl TranscriptionManager { // Get current settings for configuration let settings = get_settings(&self.app_handle); + // Validate selected language against the model's supported languages. + // If the language isn't supported, fall back to "auto" to prevent errors. + let validated_language = if settings.selected_language == "auto" { + "auto".to_string() + } else { + let is_supported = self + .model_manager + .get_model_info(&settings.selected_model) + .map(|info| { + info.supported_languages.is_empty() + || info + .supported_languages + .contains(&settings.selected_language) + }) + .unwrap_or(true); + + if is_supported { + settings.selected_language.clone() + } else { + warn!( + "Language '{}' not supported by current model, falling back to auto-detect", + settings.selected_language + ); + "auto".to_string() + } + }; + // Perform transcription with the appropriate engine. // We use catch_unwind to prevent engine panics from poisoning the mutex, // which would make the app hang indefinitely on subsequent operations. @@ -511,15 +530,15 @@ impl TranscriptionManager { || -> Result { match &mut engine { LoadedEngine::Whisper(whisper_engine) => { - let whisper_language = if settings.selected_language == "auto" { + let whisper_language = if validated_language == "auto" { None } else { - let normalized = if settings.selected_language == "zh-Hans" - || settings.selected_language == "zh-Hant" + let normalized = if validated_language == "zh-Hans" + || validated_language == "zh-Hant" { "zh".to_string() } else { - settings.selected_language.clone() + validated_language.clone() }; Some(normalized) }; @@ -536,50 +555,64 @@ impl TranscriptionManager { }; whisper_engine - .transcribe_samples(audio, Some(params)) + .transcribe_with(&audio, ¶ms) .map_err(|e| anyhow::anyhow!("Whisper transcription failed: {}", e)) } LoadedEngine::Parakeet(parakeet_engine) => { - let params = ParakeetInferenceParams { - timestamp_granularity: TimestampGranularity::Segment, + let params = ParakeetParams { + timestamp_granularity: Some(TimestampGranularity::Segment), ..Default::default() }; parakeet_engine - .transcribe_samples(audio, Some(params)) + .transcribe_with(&audio, ¶ms) .map_err(|e| { anyhow::anyhow!("Parakeet transcription failed: {}", e) }) } LoadedEngine::Moonshine(moonshine_engine) => moonshine_engine - .transcribe_samples(audio, None) + .transcribe(&audio, &TranscribeOptions::default()) .map_err(|e| anyhow::anyhow!("Moonshine transcription failed: {}", e)), LoadedEngine::MoonshineStreaming(streaming_engine) => streaming_engine - .transcribe_samples(audio, None) + .transcribe(&audio, &TranscribeOptions::default()) .map_err(|e| { anyhow::anyhow!("Moonshine streaming transcription failed: {}", e) }), LoadedEngine::SenseVoice(sense_voice_engine) => { - let language = match settings.selected_language.as_str() { - "zh" | "zh-Hans" | "zh-Hant" => SenseVoiceLanguage::Chinese, - "en" => SenseVoiceLanguage::English, - "ja" => SenseVoiceLanguage::Japanese, - "ko" => SenseVoiceLanguage::Korean, - "yue" => SenseVoiceLanguage::Cantonese, - _ => SenseVoiceLanguage::Auto, + let language = match validated_language.as_str() { + "zh" | "zh-Hans" | "zh-Hant" => Some("zh".to_string()), + "en" => Some("en".to_string()), + "ja" => Some("ja".to_string()), + "ko" => Some("ko".to_string()), + "yue" => Some("yue".to_string()), + _ => None, }; - let params = SenseVoiceInferenceParams { + let params = SenseVoiceParams { language, - use_itn: true, + use_itn: Some(true), }; sense_voice_engine - .transcribe_samples(audio, Some(params)) + .transcribe_with(&audio, ¶ms) .map_err(|e| { anyhow::anyhow!("SenseVoice transcription failed: {}", e) }) } LoadedEngine::GigaAM(gigaam_engine) => gigaam_engine - .transcribe_samples(audio, None) + .transcribe(&audio, &TranscribeOptions::default()) .map_err(|e| anyhow::anyhow!("GigaAM transcription failed: {}", e)), + LoadedEngine::Canary(canary_engine) => { + let lang = if validated_language == "auto" { + None + } else { + Some(validated_language.clone()) + }; + let options = TranscribeOptions { + language: lang, + translate: settings.translate_to_english, + }; + canary_engine + .transcribe(&audio, &options) + .map_err(|e| anyhow::anyhow!("Canary transcription failed: {}", e)) + } } }, )); diff --git a/src/bindings.ts b/src/bindings.ts index f8a259f..1f89f3e 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -763,7 +763,7 @@ export type AutoSubmitKey = "enter" | "ctrl_enter" | "cmd_enter" export type BindingResponse = { success: boolean; binding: ShortcutBinding | null; error: string | null } export type ClipboardHandling = "dont_modify" | "copy_to_clipboard" export type CustomSounds = { start: boolean; stop: boolean } -export type EngineType = "Whisper" | "Parakeet" | "Moonshine" | "MoonshineStreaming" | "SenseVoice" | "GigaAM" +export type EngineType = "Whisper" | "Parakeet" | "Moonshine" | "MoonshineStreaming" | "SenseVoice" | "GigaAM" | "Canary" export type HistoryEntry = { id: number; file_name: string; timestamp: number; saved: boolean; title: string; transcription_text: string; post_processed_text: string | null; post_process_prompt: string | null } /** * Result of changing keyboard implementation @@ -776,7 +776,7 @@ reset_bindings: string[] } export type KeyboardImplementation = "tauri" | "handy_keys" export type LLMPrompt = { id: string; name: string; prompt: string } export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" -export type ModelInfo = { id: string; name: string; description: string; filename: string; url: string | null; size_mb: number; is_downloaded: boolean; is_downloading: boolean; partial_size: number; is_directory: boolean; engine_type: EngineType; accuracy_score: number; speed_score: number; supports_translation: boolean; is_recommended: boolean; supported_languages: string[]; is_custom: boolean } +export type ModelInfo = { id: string; name: string; description: string; filename: string; url: string | null; size_mb: number; is_downloaded: boolean; is_downloading: boolean; partial_size: number; is_directory: boolean; engine_type: EngineType; accuracy_score: number; speed_score: number; supports_translation: boolean; is_recommended: boolean; supported_languages: string[]; supports_language_selection: boolean; is_custom: boolean } export type ModelLoadStatus = { is_loaded: boolean; current_model: string | null } export type ModelUnloadTimeout = "never" | "immediately" | "min_2" | "min_5" | "min_10" | "min_15" | "hour_1" | "sec_5" export type OverlayPosition = "none" | "top" | "bottom" diff --git a/src/components/settings/general/ModelSettingsCard.tsx b/src/components/settings/general/ModelSettingsCard.tsx index 39b8df4..51ec8fc 100644 --- a/src/components/settings/general/ModelSettingsCard.tsx +++ b/src/components/settings/general/ModelSettingsCard.tsx @@ -13,8 +13,7 @@ export const ModelSettingsCard: React.FC = () => { const currentModelInfo = models.find((m: ModelInfo) => m.id === currentModel); const supportsLanguageSelection = - currentModelInfo?.engine_type === "Whisper" || - currentModelInfo?.engine_type === "SenseVoice"; + currentModelInfo?.supports_language_selection ?? false; const supportsTranslation = currentModelInfo?.supports_translation ?? false; const hasAnySettings = supportsLanguageSelection || supportsTranslation; diff --git a/src/i18n/locales/ar/translation.json b/src/i18n/locales/ar/translation.json index ae34e3f..251ee29 100644 --- a/src/i18n/locales/ar/translation.json +++ b/src/i18n/locales/ar/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "التعرف على الكلام الروسي. سريع ودقيق." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "سريع جداً. الإنجليزية، الألمانية، الإسبانية، الفرنسية. يدعم الترجمة." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "متعدد اللغات ودقيق. 25 لغة أوروبية. يدعم الترجمة." } }, "errors": { diff --git a/src/i18n/locales/cs/translation.json b/src/i18n/locales/cs/translation.json index 0b3e490..b671318 100644 --- a/src/i18n/locales/cs/translation.json +++ b/src/i18n/locales/cs/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "Rozpoznávání ruské řeči. Rychlé a přesné." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "Velmi rychlý. Angličtina, němčina, španělština, francouzština. Podporuje překlad." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "Přesný vícejazyčný. 25 evropských jazyků. Podporuje překlad." } }, "errors": { diff --git a/src/i18n/locales/de/translation.json b/src/i18n/locales/de/translation.json index 42d5f58..5451c49 100644 --- a/src/i18n/locales/de/translation.json +++ b/src/i18n/locales/de/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "Russische Spracherkennung. Schnell und präzise." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "Sehr schnell. Englisch, Deutsch, Spanisch, Französisch. Unterstützt Übersetzung." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "Genaue mehrsprachige Erkennung. 25 europäische Sprachen. Unterstützt Übersetzung." } }, "errors": { diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index b2667bd..66bfa24 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "Russian speech recognition. Fast and accurate." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "Very fast. English, German, Spanish, French. Supports translation." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "Accurate multilingual. 25 European languages. Supports translation." } }, "errors": { diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json index d18843b..18fae60 100644 --- a/src/i18n/locales/es/translation.json +++ b/src/i18n/locales/es/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "Reconocimiento de voz en ruso. Rápido y preciso." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "Muy rápido. Inglés, alemán, español, francés. Soporta traducción." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "Multilingüe preciso. 25 idiomas europeos. Soporta traducción." } }, "errors": { diff --git a/src/i18n/locales/fr/translation.json b/src/i18n/locales/fr/translation.json index 58aedec..2fa70e8 100644 --- a/src/i18n/locales/fr/translation.json +++ b/src/i18n/locales/fr/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "Reconnaissance vocale en russe. Rapide et précis." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "Très rapide. Anglais, allemand, espagnol, français. Supporte la traduction." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "Multilingue précis. 25 langues européennes. Supporte la traduction." } }, "errors": { diff --git a/src/i18n/locales/it/translation.json b/src/i18n/locales/it/translation.json index 6c5fc22..7cde7dc 100644 --- a/src/i18n/locales/it/translation.json +++ b/src/i18n/locales/it/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "Riconoscimento vocale in russo. Veloce e preciso." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "Molto veloce. Inglese, tedesco, spagnolo, francese. Supporta la traduzione." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "Multilingue accurato. 25 lingue europee. Supporta la traduzione." } }, "errors": { diff --git a/src/i18n/locales/ja/translation.json b/src/i18n/locales/ja/translation.json index f16c9ef..4b1f953 100644 --- a/src/i18n/locales/ja/translation.json +++ b/src/i18n/locales/ja/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "ロシア語音声認識。高速かつ高精度。" + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "非常に高速。英語、ドイツ語、スペイン語、フランス語。翻訳対応。" + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "高精度な多言語対応。25のヨーロッパ言語。翻訳対応。" } }, "errors": { diff --git a/src/i18n/locales/ko/translation.json b/src/i18n/locales/ko/translation.json index bda6629..6931315 100644 --- a/src/i18n/locales/ko/translation.json +++ b/src/i18n/locales/ko/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "러시아어 음성 인식. 빠르고 정확함." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "매우 빠름. 영어, 독일어, 스페인어, 프랑스어. 번역 지원." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "정확한 다국어 지원. 25개 유럽 언어. 번역 지원." } }, "errors": { diff --git a/src/i18n/locales/pl/translation.json b/src/i18n/locales/pl/translation.json index 0343dab..071dfea 100644 --- a/src/i18n/locales/pl/translation.json +++ b/src/i18n/locales/pl/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "Rozpoznawanie mowy rosyjskiej. Szybkie i dokładne." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "Bardzo szybki. Angielski, niemiecki, hiszpański, francuski. Obsługuje tłumaczenie." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "Dokładny wielojęzyczny. 25 języków europejskich. Obsługuje tłumaczenie." } }, "errors": { diff --git a/src/i18n/locales/pt/translation.json b/src/i18n/locales/pt/translation.json index 834b16a..fced01a 100644 --- a/src/i18n/locales/pt/translation.json +++ b/src/i18n/locales/pt/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "Reconhecimento de voz em russo. Rápido e preciso." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "Muito rápido. Inglês, alemão, espanhol, francês. Suporta tradução." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "Multilíngue preciso. 25 idiomas europeus. Suporta tradução." } }, "errors": { diff --git a/src/i18n/locales/ru/translation.json b/src/i18n/locales/ru/translation.json index a6cd534..1c90413 100644 --- a/src/i18n/locales/ru/translation.json +++ b/src/i18n/locales/ru/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "Распознавание русской речи. Быстро и точно." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "Очень быстрая. Английский, немецкий, испанский, французский. Поддержка перевода." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "Точная многоязычная. 25 европейских языков. Поддержка перевода." } }, "errors": { diff --git a/src/i18n/locales/tr/translation.json b/src/i18n/locales/tr/translation.json index 8c27716..3b33ebe 100644 --- a/src/i18n/locales/tr/translation.json +++ b/src/i18n/locales/tr/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "Rusça konuşma tanıma. Hızlı ve doğru." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "Çok hızlı. İngilizce, Almanca, İspanyolca, Fransızca. Çeviri desteği." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "Doğru çok dilli. 25 Avrupa dili. Çeviri desteği." } }, "errors": { diff --git a/src/i18n/locales/uk/translation.json b/src/i18n/locales/uk/translation.json index fac5675..2c5a6f9 100644 --- a/src/i18n/locales/uk/translation.json +++ b/src/i18n/locales/uk/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "Розпізнавання російського мовлення. Швидко і точно." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "Дуже швидка. Англійська, німецька, іспанська, французька. Підтримує переклад." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "Точна багатомовна. 25 європейських мов. Підтримує переклад." } }, "errors": { diff --git a/src/i18n/locales/vi/translation.json b/src/i18n/locales/vi/translation.json index e651d93..e72d8cc 100644 --- a/src/i18n/locales/vi/translation.json +++ b/src/i18n/locales/vi/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "Nhận dạng giọng nói tiếng Nga. Nhanh và chính xác." + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "Rất nhanh. Tiếng Anh, tiếng Đức, tiếng Tây Ban Nha, tiếng Pháp. Hỗ trợ dịch thuật." + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "Đa ngôn ngữ chính xác. 25 ngôn ngữ châu Âu. Hỗ trợ dịch thuật." } }, "errors": { diff --git a/src/i18n/locales/zh-TW/translation.json b/src/i18n/locales/zh-TW/translation.json index 49e5150..f2a315f 100644 --- a/src/i18n/locales/zh-TW/translation.json +++ b/src/i18n/locales/zh-TW/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "俄語語音辨識。快速且準確。" + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "速度極快。支援英語、德語、西班牙語、法語。支援翻譯。" + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "準確的多語言模型。25種歐洲語言。支援翻譯。" } }, "errors": { diff --git a/src/i18n/locales/zh/translation.json b/src/i18n/locales/zh/translation.json index 2dbc934..874e975 100644 --- a/src/i18n/locales/zh/translation.json +++ b/src/i18n/locales/zh/translation.json @@ -80,6 +80,14 @@ "gigaam-v3-e2e-ctc": { "name": "GigaAM v3", "description": "俄语语音识别。快速且准确。" + }, + "canary-180m-flash": { + "name": "Canary 180M Flash", + "description": "非常快速。支持英语、德语、西班牙语、法语。支持翻译。" + }, + "canary-1b-v2": { + "name": "Canary 1B v2", + "description": "准确的多语言模型。25种欧洲语言。支持翻译。" } }, "errors": { diff --git a/src/stores/settingsStore.ts b/src/stores/settingsStore.ts index 3de95f9..2ef36f7 100644 --- a/src/stores/settingsStore.ts +++ b/src/stores/settingsStore.ts @@ -1,5 +1,6 @@ import { create } from "zustand"; import { subscribeWithSelector } from "zustand/middleware"; +import { listen } from "@tauri-apps/api/event"; import type { AppSettings as Settings, AudioDevice } from "@/bindings"; import { commands } from "@/bindings"; @@ -565,6 +566,12 @@ export const useSettingsStore = create()( refreshSettings(), checkCustomSounds(), ]); + + // Re-fetch settings when the backend changes them (e.g. language + // reset during model switch). The backend is the source of truth. + listen("model-state-changed", () => { + get().refreshSettings(); + }); }, })), );