Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 44 additions & 17 deletions src-tauri/src/commands/models.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::managers::model::{ModelInfo, ModelManager};
use crate::managers::transcription::TranscriptionManager;
use crate::settings::{get_settings, write_settings};
use crate::settings::{get_settings, write_settings, ModelUnloadTimeout};
use std::sync::Arc;
use tauri::{AppHandle, State};
use tauri::{AppHandle, Manager, State};

#[tauri::command]
#[specta::specta]
Expand Down Expand Up @@ -58,36 +58,63 @@ pub async fn delete_model(
.map_err(|e| e.to_string())
}

#[tauri::command]
#[specta::specta]
pub async fn set_active_model(
app_handle: AppHandle,
model_manager: State<'_, Arc<ModelManager>>,
transcription_manager: State<'_, Arc<TranscriptionManager>>,
model_id: String,
) -> Result<(), String> {
/// Shared logic for switching the active model, used by both the Tauri command
/// and the tray menu handler.
///
/// Validates the model, updates the persisted setting, and loads the model
/// unless the unload timeout is set to "Immediately" (in which case the model
/// will be loaded on-demand during the next transcription).
pub fn switch_active_model(app: &AppHandle, model_id: &str) -> Result<(), String> {
let model_manager = app.state::<Arc<ModelManager>>();
let transcription_manager = app.state::<Arc<TranscriptionManager>>();

// Check if model exists and is available
let model_info = model_manager
.get_model_info(&model_id)
.get_model_info(model_id)
.ok_or_else(|| format!("Model not found: {}", model_id))?;

if !model_info.is_downloaded {
return Err(format!("Model not downloaded: {}", model_id));
}

// Update settings
let settings = get_settings(app);
let unload_timeout = settings.model_unload_timeout;
let mut settings = settings;
settings.selected_model = model_id.to_string();
write_settings(app, settings);

// Skip eager loading if unload is set to "Immediately" — the model
// will be loaded on-demand during the next transcription.
if unload_timeout == ModelUnloadTimeout::Immediately {
log::info!(
"Model selection changed to {} (not loading — unload set to Immediately).",
model_id
);
return Ok(());
}

// Load the model in the transcription manager
transcription_manager
.load_model(&model_id)
.load_model(model_id)
.map_err(|e| e.to_string())?;

// Update settings
let mut settings = get_settings(&app_handle);
settings.selected_model = model_id.clone();
write_settings(&app_handle, settings);

Ok(())
}

#[tauri::command]
#[specta::specta]
pub async fn set_active_model(
app_handle: AppHandle,
model_manager: State<'_, Arc<ModelManager>>,
transcription_manager: State<'_, Arc<TranscriptionManager>>,
model_id: String,
) -> Result<(), String> {
// State params kept for specta binding generation
let _ = (&model_manager, &transcription_manager);
switch_active_model(&app_handle, &model_id)
}

#[tauri::command]
#[specta::specta]
pub async fn get_current_model(app_handle: AppHandle) -> Result<String, String> {
Expand Down
19 changes: 19 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,25 @@ fn initialize_core_logic(app_handle: &AppHandle) {
"quit" => {
app.exit(0);
}
id if id.starts_with("model_select:") => {
let model_id = id.strip_prefix("model_select:").unwrap().to_string();
let current_model = settings::get_settings(app).selected_model;
if model_id == current_model {
return;
}
let app_clone = app.clone();
std::thread::spawn(move || {
match commands::models::switch_active_model(&app_clone, &model_id) {
Ok(()) => {
log::info!("Model switched to {} via tray.", model_id);
}
Err(e) => {
log::error!("Failed to switch model via tray: {}", e);
}
}
tray::update_tray_menu(&app_clone, &tray::TrayIconState::Idle, None);
});
}
_ => {}
})
.build(app_handle)
Expand Down
42 changes: 38 additions & 4 deletions src-tauri/src/tray.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::managers::history::{HistoryEntry, HistoryManager};
use crate::managers::model::ModelManager;
use crate::managers::transcription::TranscriptionManager;
use crate::settings;
use crate::tray_i18n::get_tray_translations;
use log::{error, info, warn};
use std::sync::Arc;
use tauri::image::Image;
use tauri::menu::{Menu, MenuItem, PredefinedMenuItem};
use tauri::menu::{CheckMenuItem, Menu, MenuItem, PredefinedMenuItem, Submenu};
use tauri::tray::TrayIcon;
use tauri::{AppHandle, Manager, Theme};
use tauri_plugin_clipboard_manager::ClipboardExt;
Expand Down Expand Up @@ -125,6 +126,40 @@ pub fn update_tray_menu(app: &AppHandle, state: &TrayIconState, locale: Option<&
)
.expect("failed to create copy last transcript item");
let model_loaded = app.state::<Arc<TranscriptionManager>>().is_model_loaded();
let quit_i = MenuItem::with_id(app, "quit", &strings.quit, true, quit_accelerator)
.expect("failed to create quit item");
let separator = || PredefinedMenuItem::separator(app).expect("failed to create separator");

// Build model submenu — label is the active model name
let model_manager = app.state::<Arc<ModelManager>>();
let models = model_manager.get_available_models();
let current_model_id = &settings.selected_model;

let mut downloaded: Vec<_> = models.into_iter().filter(|m| m.is_downloaded).collect();
downloaded.sort_by(|a, b| a.name.cmp(&b.name));

let submenu_label = downloaded
.iter()
.find(|m| m.id == *current_model_id)
.map(|m| m.name.clone())
.unwrap_or_else(|| strings.model.clone());

let model_submenu = {
let submenu = Submenu::with_id(app, "model_submenu", &submenu_label, true)
.expect("failed to create model submenu");

for model in &downloaded {
let is_active = model.id == *current_model_id;
let item_id = format!("model_select:{}", model.id);
let item =
CheckMenuItem::with_id(app, &item_id, &model.name, true, is_active, None::<&str>)
.expect("failed to create model item");
let _ = submenu.append(&item);
}

submenu
};

let unload_model_i = MenuItem::with_id(
app,
"unload_model",
Expand All @@ -133,9 +168,6 @@ pub fn update_tray_menu(app: &AppHandle, state: &TrayIconState, locale: Option<&
None::<&str>,
)
.expect("failed to create unload model item");
let quit_i = MenuItem::with_id(app, "quit", &strings.quit, true, quit_accelerator)
.expect("failed to create quit item");
let separator = || PredefinedMenuItem::separator(app).expect("failed to create separator");

let menu = match state {
TrayIconState::Recording | TrayIconState::Transcribing => {
Expand Down Expand Up @@ -164,6 +196,8 @@ pub fn update_tray_menu(app: &AppHandle, state: &TrayIconState, locale: Option<&
&version_i,
&separator(),
&copy_last_transcript_i,
&separator(),
&model_submenu,
&unload_model_i,
&separator(),
&settings_i,
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/ar/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "...التحقق من وجود تحديثات",
"copyLastTranscript": "نسخ آخر نص تم تفريغه",
"unloadModel": "تفريغ النموذج",
"model": "النموذج",
"quit": "إنهاء",
"cancel": "إلغاء"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/cs/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "Zkontrolovat aktualizace...",
"copyLastTranscript": "Zkopírovat poslední přepis",
"unloadModel": "Uvolnit model",
"model": "Model",
"quit": "Ukončit",
"cancel": "Zrušit"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "Nach Updates suchen...",
"copyLastTranscript": "Letzte Transkription kopieren",
"unloadModel": "Modell entladen",
"model": "Modell",
"quit": "Beenden",
"cancel": "Abbrechen"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "Check for Updates...",
"copyLastTranscript": "Copy Last Transcript",
"unloadModel": "Unload Model",
"model": "Model",
"quit": "Quit",
"cancel": "Cancel"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "Buscar actualizaciones...",
"copyLastTranscript": "Copiar la última transcripción",
"unloadModel": "Descargar modelo",
"model": "Modelo",
"quit": "Salir",
"cancel": "Cancelar"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "Rechercher des mises à jour...",
"copyLastTranscript": "Copier la dernière transcription",
"unloadModel": "Décharger le modèle",
"model": "Modèle",
"quit": "Quitter",
"cancel": "Annuler"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/it/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "Verifica aggiornamenti...",
"copyLastTranscript": "Copia l'ultima trascrizione",
"unloadModel": "Scarica modello",
"model": "Modello",
"quit": "Esci",
"cancel": "Annulla"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/ja/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "アップデートを確認...",
"copyLastTranscript": "最新の文字起こしをコピー",
"unloadModel": "モデルをアンロード",
"model": "モデル",
"quit": "終了",
"cancel": "キャンセル"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/ko/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "업데이트 확인...",
"copyLastTranscript": "마지막 녹음 내용 복사",
"unloadModel": "모델 언로드",
"model": "모델",
"quit": "종료",
"cancel": "취소"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/pl/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "Sprawdź aktualizacje...",
"copyLastTranscript": "Kopiuj ostatnią transkrypcję",
"unloadModel": "Zwolnij model",
"model": "Model",
"quit": "Zamknij",
"cancel": "Anuluj"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/pt/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "Verificar Atualizações...",
"copyLastTranscript": "Copiar última transcrição",
"unloadModel": "Descarregar modelo",
"model": "Modelo",
"quit": "Sair",
"cancel": "Cancelar"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/ru/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "Проверить обновления...",
"copyLastTranscript": "Скопировать последнюю транскрипцию",
"unloadModel": "Выгрузить модель",
"model": "Модель",
"quit": "Выход",
"cancel": "Отмена"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/tr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "Güncellemeleri Kontrol Et...",
"copyLastTranscript": "Son transkripti kopyala",
"unloadModel": "Modeli boşalt",
"model": "Model",
"quit": "Çıkış",
"cancel": "İptal"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/uk/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "Перевірити оновлення...",
"copyLastTranscript": "Скопіювати останню транскрипцію",
"unloadModel": "Вивантажити модель",
"model": "Модель",
"quit": "Вийти",
"cancel": "Скасувати"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/vi/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "Kiểm tra cập nhật...",
"copyLastTranscript": "Sao chép bản chép lời mới nhất",
"unloadModel": "Dỡ mô hình",
"model": "Mô hình",
"quit": "Thoát",
"cancel": "Hủy"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/zh-TW/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "檢查更新...",
"copyLastTranscript": "複製最新轉錄",
"unloadModel": "卸載模型",
"model": "模型",
"quit": "結束",
"cancel": "取消"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/zh/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"checkUpdates": "检查更新...",
"copyLastTranscript": "复制最新转录",
"unloadModel": "卸载模型",
"model": "模型",
"quit": "退出",
"cancel": "取消"
},
Expand Down
Loading