206 lines
6.9 KiB
Rust
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())
|
|
}
|
|
}
|