Handy/src-tauri/src/managers/transcription.rs

206 lines
6.9 KiB
Rust

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<String>,
pub model_name: Option<String>,
pub error: Option<String>,
}
pub struct TranscriptionManager {
state: Mutex<Option<WhisperState>>,
context: Mutex<Option<WhisperContext>>,
model_manager: Arc<ModelManager>,
app_handle: AppHandle,
current_model_id: Mutex<Option<String>>,
}
impl TranscriptionManager {
pub fn new(app: &App, model_manager: Arc<ModelManager>) -> Result<Self> {
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<String> {
let current_model = self.current_model_id.lock().unwrap();
current_model.clone()
}
pub fn transcribe(&self, audio: Vec<f32>) -> Result<String> {
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())
}
}