use crate::managers::model::ModelManager; use crate::settings::get_settings; use anyhow::Result; use serde::Serialize; use std::sync::{Arc, Mutex}; use tauri::{App, AppHandle, Emitter, Manager}; use whisper_rs::install_whisper_log_trampoline; use whisper_rs::{ FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters, WhisperState, }; #[derive(Clone, Debug, Serialize)] pub struct ModelStateEvent { pub event_type: String, pub model_id: Option, pub model_name: Option, pub error: Option, } pub struct TranscriptionManager { state: Mutex>, context: Mutex>, model_manager: Arc, app_handle: AppHandle, current_model_id: Mutex>, } impl TranscriptionManager { pub fn new(app: &App, model_manager: Arc) -> Result { let app_handle = app.app_handle().clone(); let manager = Self { state: Mutex::new(None), context: Mutex::new(None), model_manager, app_handle: app_handle.clone(), current_model_id: Mutex::new(None), }; // Try to load the default model from settings, but don't fail if no models are available let settings = get_settings(&app_handle); let _ = manager.load_model(&settings.selected_model); Ok(manager) } pub fn load_model(&self, model_id: &str) -> Result<()> { // Emit loading started event let _ = self.app_handle.emit( "model-state-changed", ModelStateEvent { event_type: "loading_started".to_string(), model_id: Some(model_id.to_string()), model_name: None, error: None, }, ); let model_info = self .model_manager .get_model_info(model_id) .ok_or_else(|| anyhow::anyhow!("Model not found: {}", model_id))?; if !model_info.is_downloaded { let error_msg = "Model not downloaded"; 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.to_string()), }, ); return Err(anyhow::anyhow!(error_msg)); } let model_path = self.model_manager.get_model_path(model_id)?; let path_str = model_path .to_str() .ok_or_else(|| anyhow::anyhow!("Invalid path for model: {}", model_id))?; println!( "Loading transcription model {} from: {}", model_id, path_str ); // Install log trampoline once per model load (safe to call multiple times) install_whisper_log_trampoline(); // Create new context let context = WhisperContext::new_with_params(path_str, WhisperContextParameters::default()) .map_err(|e| { let error_msg = format!("Failed to load whisper 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) })?; // Create new state let state = context.create_state().map_err(|e| { let error_msg = format!("Failed to create state for 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) })?; // Update the current context and state { let mut current_context = self.context.lock().unwrap(); *current_context = Some(context); } { let mut current_state = self.state.lock().unwrap(); *current_state = Some(state); } { let mut current_model = self.current_model_id.lock().unwrap(); *current_model = Some(model_id.to_string()); } // Emit loading completed event let _ = self.app_handle.emit( "model-state-changed", ModelStateEvent { event_type: "loading_completed".to_string(), model_id: Some(model_id.to_string()), model_name: Some(model_info.name.clone()), error: None, }, ); println!("Successfully loaded transcription model: {}", model_id); Ok(()) } pub fn get_current_model(&self) -> Option { let current_model = self.current_model_id.lock().unwrap(); current_model.clone() } pub fn transcribe(&self, audio: Vec) -> Result { let st = std::time::Instant::now(); let mut result = String::new(); println!("Audio vector length: {}", audio.len()); if audio.len() == 0 { println!("Empty audio vector"); return Ok(result); } let mut state_guard = self.state.lock().unwrap(); let state = state_guard.as_mut().ok_or_else(|| { anyhow::anyhow!( "No model loaded. Please download and select a model from settings first." ) })?; // Initialize parameters let mut params = FullParams::new(SamplingStrategy::default()); params.set_language(Some("auto")); params.set_print_special(false); params.set_print_progress(false); params.set_print_realtime(false); params.set_print_timestamps(false); params.set_suppress_blank(true); params.set_suppress_non_speech_tokens(true); state .full(params, &audio) .expect("failed to convert samples"); let num_segments = state .full_n_segments() .expect("failed to get number of segments"); for i in 0..num_segments { let segment = state .full_get_segment_text(i) .expect("failed to get segment"); result.push_str(&segment); } let et = std::time::Instant::now(); println!("\ntook {}ms", (et - st).as_millis()); Ok(result.trim().to_string()) } }