Migrate to transcribe-rs-0.3.1 and add Canary support (#1023)

* refactor: migrate transcribe-rs from v0.2.8 to v0.3.0

Breaking API migration:
- engines::* → onnx::* module paths, whisper_cpp::* for Whisper
- TranscriptionEngine trait → SpeechModel trait
- Two-step new()+load_model() → one-step Model::load()
- transcribe_samples(Vec<f32>) → transcribe(&[f32])
- Explicit unload_model() → RAII drop
- Canary engine now from transcribe-rs instead of local crate

Removes canary-engine local crate dependency.
Uses path dependency to local transcribe-rs clone (temporary).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: upgrade transcribe-rs to v0.3.1, add Canary models

Update transcribe-rs dependency from v0.2.8 to v0.3.1 with
restructured features (whisper + onnx). Add Canary 180M Flash
(4 languages, 146MB) and Canary 1B v2 (25 EU languages, 692MB)
with translation support. Add i18n translations for all 17 locales.

Languages verified against HuggingFace model cards:
- Canary 180M Flash: en, de, es, fr
- Canary 1B v2: 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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* make sure when changing models the language changes with it

* format

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: CJ Pais <cj@cjpais.com>
This commit is contained in:
Ivan Zhuravlev 2026-03-16 12:31:44 +04:00 committed by GitHub
parent f45ad97e51
commit f8bbcd791c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 369 additions and 94 deletions

4
src-tauri/Cargo.lock generated
View file

@ -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",

View file

@ -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"] }

View file

@ -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

View file

@ -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<String>, // 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<String> = 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<String> = 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,
},
);

View file

@ -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<transcribe_rs::TranscriptionResult> {
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, &params)
.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, &params)
.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, &params)
.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))
}
}
},
));

View file

@ -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"

View file

@ -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;

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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<SettingsStore>()(
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();
});
},
})),
);