diff --git a/src-tauri/src/managers/transcription.rs b/src-tauri/src/managers/transcription.rs index 73e4e5b..928ef06 100644 --- a/src-tauri/src/managers/transcription.rs +++ b/src-tauri/src/managers/transcription.rs @@ -1,4 +1,5 @@ use crate::audio_toolkit::{apply_custom_words, filter_transcription_output}; +use crate::managers::audio::AudioRecordingManager; use crate::managers::model::{EngineType, ModelManager}; use crate::settings::{ get_settings, ModelUnloadTimeout, OrtAcceleratorSetting, WhisperAcceleratorSetting, @@ -12,7 +13,7 @@ use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, Condvar, Mutex, MutexGuard}; use std::thread; use std::time::{Duration, SystemTime}; -use tauri::{AppHandle, Emitter}; +use tauri::{AppHandle, Emitter, Manager}; use transcribe_rs::{ onnx::{ canary::CanaryModel, @@ -79,12 +80,7 @@ impl TranscriptionManager { model_manager, app_handle: app_handle.clone(), current_model_id: Arc::new(Mutex::new(None)), - last_activity: Arc::new(AtomicU64::new( - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_millis() as u64, - )), + last_activity: Arc::new(AtomicU64::new(Self::now_ms())), shutdown_signal: Arc::new(AtomicBool::new(false)), watcher_handle: Arc::new(Mutex::new(None)), is_loading: Arc::new(Mutex::new(false)), @@ -107,14 +103,28 @@ impl TranscriptionManager { } let settings = get_settings(&app_handle_cloned); - let timeout_seconds = settings.model_unload_timeout.to_seconds(); + let timeout = settings.model_unload_timeout; - if let Some(limit_seconds) = timeout_seconds { + // Skip Immediately — that variant is handled by + // maybe_unload_immediately() after each transcription. + // Treating it as 0s here would unload the model mid-recording. + if timeout == ModelUnloadTimeout::Immediately { + continue; + } + + // While recording, keep the idle timer fresh so the + // model is never unloaded mid-session. + let is_recording = app_handle_cloned + .try_state::>() + .map_or(false, |a| a.is_recording()); + if is_recording { + manager_cloned.touch_activity(); + continue; + } + + if let Some(limit_seconds) = timeout.to_seconds() { let last = manager_cloned.last_activity.load(Ordering::Relaxed); - let now_ms = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_millis() as u64; + let now_ms = TranscriptionManager::now_ms(); let idle_ms = now_ms.saturating_sub(last); let limit_ms = limit_seconds * 1000; @@ -213,6 +223,18 @@ impl TranscriptionManager { Ok(()) } + fn now_ms() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as u64 + } + + /// Reset the idle timer to now. + fn touch_activity(&self) { + self.last_activity.store(Self::now_ms(), Ordering::Relaxed); + } + /// Unloads the model immediately if the setting is enabled and the model is loaded pub fn maybe_unload_immediately(&self, context: &str) { let settings = get_settings(&self.app_handle); @@ -402,13 +424,7 @@ impl TranscriptionManager { } // Reset idle timer so the watcher doesn't immediately unload a just-loaded model - self.last_activity.store( - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_millis() as u64, - Ordering::Relaxed, - ); + self.touch_activity(); // Emit loading completed event let _ = self.app_handle.emit( @@ -457,13 +473,7 @@ impl TranscriptionManager { pub fn transcribe(&self, audio: Vec) -> Result { // Update last activity timestamp - self.last_activity.store( - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_millis() as u64, - Ordering::Relaxed, - ); + self.touch_activity(); let st = std::time::Instant::now(); diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index d7beb8c..4b1942c 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -123,7 +123,7 @@ pub enum ModelUnloadTimeout { Min10, Min15, Hour1, - Sec5, // Debug mode only + Sec15, // Debug mode only } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Type)] @@ -216,7 +216,7 @@ impl ModelUnloadTimeout { ModelUnloadTimeout::Min10 => Some(10), ModelUnloadTimeout::Min15 => Some(15), ModelUnloadTimeout::Hour1 => Some(60), - ModelUnloadTimeout::Sec5 => Some(0), // Special case for debug - handled separately + ModelUnloadTimeout::Sec15 => Some(0), // Special case for debug - handled separately } } @@ -224,7 +224,7 @@ impl ModelUnloadTimeout { match self { ModelUnloadTimeout::Never => None, ModelUnloadTimeout::Immediately => Some(0), // Special case for immediate unloading - ModelUnloadTimeout::Sec5 => Some(5), + ModelUnloadTimeout::Sec15 => Some(15), _ => self.to_minutes().map(|m| m * 60), } } diff --git a/src/bindings.ts b/src/bindings.ts index 8dd24cd..49bbe2b 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -809,7 +809,7 @@ 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[]; 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 ModelUnloadTimeout = "never" | "immediately" | "min_2" | "min_5" | "min_10" | "min_15" | "hour_1" | "sec_15" export type OrtAcceleratorSetting = "auto" | "cpu" | "cuda" | "directml" | "rocm" export type OverlayPosition = "none" | "top" | "bottom" export type PasteMethod = "ctrl_v" | "direct" | "none" | "shift_insert" | "ctrl_shift_v" | "external_script" diff --git a/src/components/settings/ModelUnloadTimeout.tsx b/src/components/settings/ModelUnloadTimeout.tsx index 4ff9f48..a6a5f89 100644 --- a/src/components/settings/ModelUnloadTimeout.tsx +++ b/src/components/settings/ModelUnloadTimeout.tsx @@ -51,8 +51,8 @@ export const ModelUnloadTimeoutSetting: React.FC = ({ const debugTimeoutOptions = [ ...timeoutOptions, { - value: "sec5" as ModelUnloadTimeout, - label: t("settings.advanced.modelUnload.options.sec5"), + value: "sec15" as ModelUnloadTimeout, + label: t("settings.advanced.modelUnload.options.sec15"), }, ]; diff --git a/src/i18n/locales/ar/translation.json b/src/i18n/locales/ar/translation.json index 711a50b..ca54dfb 100644 --- a/src/i18n/locales/ar/translation.json +++ b/src/i18n/locales/ar/translation.json @@ -309,7 +309,7 @@ "min10": "بعد 10 دقائق", "min15": "بعد 15 دقيقة", "hour1": "بعد ساعة واحدة", - "sec5": "بعد 5 ثوانٍ (تصحيح أخطاء)" + "sec15": "بعد 15 ثوانٍ (تصحيح أخطاء)" } }, "customWords": { diff --git a/src/i18n/locales/cs/translation.json b/src/i18n/locales/cs/translation.json index b5cb99c..f4e6712 100644 --- a/src/i18n/locales/cs/translation.json +++ b/src/i18n/locales/cs/translation.json @@ -331,7 +331,7 @@ "min10": "Po 10 minutách", "min15": "Po 15 minutách", "hour1": "Po 1 hodině", - "sec5": "Po 5 sekundách (Debug)" + "sec15": "Po 15 sekundách (Debug)" } }, "customWords": { diff --git a/src/i18n/locales/de/translation.json b/src/i18n/locales/de/translation.json index d567949..9e9e60e 100644 --- a/src/i18n/locales/de/translation.json +++ b/src/i18n/locales/de/translation.json @@ -331,7 +331,7 @@ "min10": "Nach 10 Minuten", "min15": "Nach 15 Minuten", "hour1": "Nach 1 Stunde", - "sec5": "Nach 5 Sekunden (Debug)" + "sec15": "Nach 15 Sekunden (Debug)" } }, "customWords": { diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 21a5ab1..30e1440 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -331,7 +331,7 @@ "min10": "After 10 minutes", "min15": "After 15 minutes", "hour1": "After 1 hour", - "sec5": "After 5 seconds (Debug)" + "sec15": "After 15 seconds (Debug)" } }, "customWords": { diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json index 941a729..2c74bcc 100644 --- a/src/i18n/locales/es/translation.json +++ b/src/i18n/locales/es/translation.json @@ -331,7 +331,7 @@ "min10": "Después de 10 minutos", "min15": "Después de 15 minutos", "hour1": "Después de 1 hora", - "sec5": "Después de 5 segundos (Depuración)" + "sec15": "Después de 15 segundos (Depuración)" } }, "customWords": { diff --git a/src/i18n/locales/fr/translation.json b/src/i18n/locales/fr/translation.json index 0cd2694..8224ea3 100644 --- a/src/i18n/locales/fr/translation.json +++ b/src/i18n/locales/fr/translation.json @@ -331,7 +331,7 @@ "min10": "Après 10 minutes", "min15": "Après 15 minutes", "hour1": "Après 1 heure", - "sec5": "Après 5 secondes (Débogage)" + "sec15": "Après 15 secondes (Débogage)" } }, "customWords": { diff --git a/src/i18n/locales/it/translation.json b/src/i18n/locales/it/translation.json index 2b0d9b1..95a3523 100644 --- a/src/i18n/locales/it/translation.json +++ b/src/i18n/locales/it/translation.json @@ -331,7 +331,7 @@ "min10": "Dopo 10 minuti", "min15": "Dopo 15 minuti", "hour1": "Dopo 1 ora", - "sec5": "Dopo 5 secondi (Debug)" + "sec15": "Dopo 15 secondi (Debug)" } }, "customWords": { diff --git a/src/i18n/locales/ja/translation.json b/src/i18n/locales/ja/translation.json index 7adeef3..e72dc0f 100644 --- a/src/i18n/locales/ja/translation.json +++ b/src/i18n/locales/ja/translation.json @@ -331,7 +331,7 @@ "min10": "10分後", "min15": "15分後", "hour1": "1時間後", - "sec5": "5秒後(デバッグ)" + "sec15": "15秒後(デバッグ)" } }, "customWords": { diff --git a/src/i18n/locales/ko/translation.json b/src/i18n/locales/ko/translation.json index b2a5a87..afef760 100644 --- a/src/i18n/locales/ko/translation.json +++ b/src/i18n/locales/ko/translation.json @@ -331,7 +331,7 @@ "min10": "10분 후", "min15": "15분 후", "hour1": "1시간 후", - "sec5": "5초 후 (디버그)" + "sec15": "15초 후 (디버그)" } }, "customWords": { diff --git a/src/i18n/locales/pl/translation.json b/src/i18n/locales/pl/translation.json index 3efd9d4..baa137b 100644 --- a/src/i18n/locales/pl/translation.json +++ b/src/i18n/locales/pl/translation.json @@ -331,7 +331,7 @@ "min10": "Po 10 minutach", "min15": "Po 15 minutach", "hour1": "Po 1 godzinie", - "sec5": "Po 5 sekundach (Debug)" + "sec15": "Po 15 sekundach (Debug)" } }, "customWords": { diff --git a/src/i18n/locales/pt/translation.json b/src/i18n/locales/pt/translation.json index cc28c27..cc4cfca 100644 --- a/src/i18n/locales/pt/translation.json +++ b/src/i18n/locales/pt/translation.json @@ -331,7 +331,7 @@ "min10": "Após 10 minutos", "min15": "Após 15 minutos", "hour1": "Após 1 hora", - "sec5": "Após 5 segundos (Depuração)" + "sec15": "Após 15 segundos (Depuração)" } }, "customWords": { diff --git a/src/i18n/locales/ru/translation.json b/src/i18n/locales/ru/translation.json index 56e9f6e..51f6968 100644 --- a/src/i18n/locales/ru/translation.json +++ b/src/i18n/locales/ru/translation.json @@ -331,7 +331,7 @@ "min10": "Через 10 минут", "min15": "Через 15 минут", "hour1": "Через 1 час", - "sec5": "Через 5 секунд (отладка)" + "sec15": "Через 15 секунд (отладка)" } }, "customWords": { diff --git a/src/i18n/locales/tr/translation.json b/src/i18n/locales/tr/translation.json index 69e273a..3ea7944 100644 --- a/src/i18n/locales/tr/translation.json +++ b/src/i18n/locales/tr/translation.json @@ -331,7 +331,7 @@ "min10": "10 dakika sonra", "min15": "15 dakika sonra", "hour1": "1 saat sonra", - "sec5": "5 saniye sonra (Debug)" + "sec15": "15 saniye sonra (Debug)" } }, "customWords": { diff --git a/src/i18n/locales/uk/translation.json b/src/i18n/locales/uk/translation.json index bc450e9..b965a5a 100644 --- a/src/i18n/locales/uk/translation.json +++ b/src/i18n/locales/uk/translation.json @@ -331,7 +331,7 @@ "min10": "Через 10 хвилин", "min15": "Через 15 хвилин", "hour1": "Через 1 годину", - "sec5": "Через 5 секунд (Дебаг)" + "sec15": "Через 15 секунд (Дебаг)" } }, "customWords": { diff --git a/src/i18n/locales/vi/translation.json b/src/i18n/locales/vi/translation.json index 6d3ecc2..b31211b 100644 --- a/src/i18n/locales/vi/translation.json +++ b/src/i18n/locales/vi/translation.json @@ -331,7 +331,7 @@ "min10": "Sau 10 phút", "min15": "Sau 15 phút", "hour1": "Sau 1 giờ", - "sec5": "Sau 5 giây (Gỡ lỗi)" + "sec15": "Sau 15 giây (Gỡ lỗi)" } }, "customWords": { diff --git a/src/i18n/locales/zh-TW/translation.json b/src/i18n/locales/zh-TW/translation.json index 04ed20a..14f0cd8 100644 --- a/src/i18n/locales/zh-TW/translation.json +++ b/src/i18n/locales/zh-TW/translation.json @@ -331,7 +331,7 @@ "min10": "10 分鐘後", "min15": "15 分鐘後", "hour1": "1 小時後", - "sec5": "5 秒後(偵錯)" + "sec15": "15 秒後(偵錯)" } }, "customWords": { diff --git a/src/i18n/locales/zh/translation.json b/src/i18n/locales/zh/translation.json index e53e8d6..3020cae 100644 --- a/src/i18n/locales/zh/translation.json +++ b/src/i18n/locales/zh/translation.json @@ -331,7 +331,7 @@ "min10": "10 分钟后", "min15": "15 分钟后", "hour1": "1 小时后", - "sec5": "5 秒后(调试)" + "sec15": "15 秒后(调试)" } }, "customWords": {