210 lines
6.3 KiB
Rust
210 lines
6.3 KiB
Rust
// Tauri command handlers must match the frontend's invoke() parameter lists,
|
|
// so the argument counts are dictated by the Svelte code.
|
|
#![allow(clippy::too_many_arguments)]
|
|
|
|
use std::path::Path;
|
|
use std::sync::Arc;
|
|
|
|
use tauri::Emitter;
|
|
|
|
use crate::commands::models::{default_model_id_for_engine, ensure_model_loaded};
|
|
use crate::AppState;
|
|
use kon_ai_formatting::{post_process_segments, FormatMode, PostProcessOptions};
|
|
use kon_core::types::{Segment, TranscriptionOptions};
|
|
|
|
fn pick_engine(
|
|
state: &AppState,
|
|
engine: &str,
|
|
) -> Result<Arc<kon_transcription::LocalEngine>, String> {
|
|
match engine {
|
|
"whisper" => Ok(state.whisper_engine.clone()),
|
|
"parakeet" => Ok(state.parakeet_engine.clone()),
|
|
other => Err(format!("Unknown engine: {other}")),
|
|
}
|
|
}
|
|
|
|
/// Transcribe raw PCM f32 samples (Whisper). Emits "transcription-result" event.
|
|
#[tauri::command]
|
|
pub async fn transcribe_pcm(
|
|
state: tauri::State<'_, AppState>,
|
|
app: tauri::AppHandle,
|
|
samples: Vec<f32>,
|
|
chunk_id: u32,
|
|
language: String,
|
|
initial_prompt: String,
|
|
remove_fillers: bool,
|
|
british_english: bool,
|
|
anti_hallucination: bool,
|
|
format_mode: String,
|
|
) -> Result<(), String> {
|
|
let engine = state.whisper_engine.clone();
|
|
let audio = kon_core::AudioSamples::mono_16khz(samples);
|
|
let options = TranscriptionOptions {
|
|
language: Some(language),
|
|
initial_prompt: Some(initial_prompt),
|
|
};
|
|
|
|
let timed = tokio::task::spawn_blocking(move || {
|
|
engine.transcribe_sync(&audio, &options).map_err(|e| e.to_string())
|
|
})
|
|
.await
|
|
.map_err(|e| e.to_string())??;
|
|
|
|
let dictionary_terms: Vec<String> = kon_storage::database::list_dictionary(&state.db)
|
|
.await
|
|
.unwrap_or_default()
|
|
.into_iter()
|
|
.map(|e| e.term)
|
|
.collect();
|
|
|
|
let mut segments: Vec<Segment> = timed.transcript.segments().to_vec();
|
|
post_process_segments(
|
|
&mut segments,
|
|
&PostProcessOptions {
|
|
remove_fillers,
|
|
british_english,
|
|
anti_hallucination,
|
|
format_mode: FormatMode::parse(&format_mode),
|
|
dictionary_terms,
|
|
},
|
|
);
|
|
|
|
app.emit(
|
|
"transcription-result",
|
|
serde_json::json!({
|
|
"status": "transcription",
|
|
"segments": segments,
|
|
"language": timed.transcript.language(),
|
|
"duration": timed.transcript.duration(),
|
|
"chunk_id": chunk_id,
|
|
"inference_ms": timed.inference_ms,
|
|
}),
|
|
)
|
|
.map_err(|e| format!("Failed to emit result: {e}"))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Transcribe an audio file by path. Decodes, resamples to 16kHz, runs Whisper.
|
|
#[tauri::command]
|
|
pub async fn transcribe_file(
|
|
state: tauri::State<'_, AppState>,
|
|
path: String,
|
|
engine: Option<String>,
|
|
model_id: Option<String>,
|
|
language: String,
|
|
initial_prompt: String,
|
|
remove_fillers: bool,
|
|
british_english: bool,
|
|
anti_hallucination: bool,
|
|
format_mode: String,
|
|
) -> Result<serde_json::Value, String> {
|
|
let engine_name = engine.unwrap_or_else(|| "whisper".to_string());
|
|
let model_id = model_id
|
|
.unwrap_or_else(|| default_model_id_for_engine(&engine_name).to_string());
|
|
ensure_model_loaded(&state, &engine_name, &model_id).await?;
|
|
|
|
let engine = pick_engine(&state, &engine_name)?;
|
|
let options = TranscriptionOptions {
|
|
language: Some(language),
|
|
initial_prompt: Some(initial_prompt),
|
|
};
|
|
|
|
let timed = tokio::task::spawn_blocking(move || {
|
|
let audio = kon_audio::decode_audio_file(Path::new(&path))
|
|
.map_err(|e| e.to_string())?;
|
|
let resampled =
|
|
kon_audio::resample_to_16khz(&audio).map_err(|e| e.to_string())?;
|
|
engine
|
|
.transcribe_sync(&resampled, &options)
|
|
.map_err(|e| e.to_string())
|
|
})
|
|
.await
|
|
.map_err(|e| e.to_string())??;
|
|
|
|
let dictionary_terms: Vec<String> = kon_storage::database::list_dictionary(&state.db)
|
|
.await
|
|
.unwrap_or_default()
|
|
.into_iter()
|
|
.map(|e| e.term)
|
|
.collect();
|
|
|
|
let mut segments: Vec<Segment> = timed.transcript.segments().to_vec();
|
|
post_process_segments(
|
|
&mut segments,
|
|
&PostProcessOptions {
|
|
remove_fillers,
|
|
british_english,
|
|
anti_hallucination,
|
|
format_mode: FormatMode::parse(&format_mode),
|
|
dictionary_terms,
|
|
},
|
|
);
|
|
|
|
Ok(serde_json::json!({
|
|
"engine": engine_name,
|
|
"modelId": model_id,
|
|
"segments": segments,
|
|
"language": timed.transcript.language(),
|
|
"duration": timed.transcript.duration(),
|
|
"inference_ms": timed.inference_ms,
|
|
}))
|
|
}
|
|
|
|
/// Transcribe raw PCM f32 samples (Parakeet). Emits "transcription-result" event.
|
|
#[tauri::command]
|
|
pub async fn transcribe_pcm_parakeet(
|
|
state: tauri::State<'_, AppState>,
|
|
app: tauri::AppHandle,
|
|
samples: Vec<f32>,
|
|
chunk_id: u32,
|
|
remove_fillers: bool,
|
|
british_english: bool,
|
|
anti_hallucination: bool,
|
|
format_mode: String,
|
|
) -> Result<(), String> {
|
|
let engine = state.parakeet_engine.clone();
|
|
let audio = kon_core::AudioSamples::mono_16khz(samples);
|
|
let options = TranscriptionOptions::default();
|
|
|
|
let timed = tokio::task::spawn_blocking(move || {
|
|
engine.transcribe_sync(&audio, &options).map_err(|e| e.to_string())
|
|
})
|
|
.await
|
|
.map_err(|e| e.to_string())??;
|
|
|
|
let dictionary_terms: Vec<String> = kon_storage::database::list_dictionary(&state.db)
|
|
.await
|
|
.unwrap_or_default()
|
|
.into_iter()
|
|
.map(|e| e.term)
|
|
.collect();
|
|
|
|
let mut segments: Vec<Segment> = timed.transcript.segments().to_vec();
|
|
post_process_segments(
|
|
&mut segments,
|
|
&PostProcessOptions {
|
|
remove_fillers,
|
|
british_english,
|
|
anti_hallucination,
|
|
format_mode: FormatMode::parse(&format_mode),
|
|
dictionary_terms,
|
|
},
|
|
);
|
|
|
|
app.emit(
|
|
"transcription-result",
|
|
serde_json::json!({
|
|
"status": "transcription",
|
|
"segments": segments,
|
|
"language": timed.transcript.language(),
|
|
"duration": timed.transcript.duration(),
|
|
"chunk_id": chunk_id,
|
|
"inference_ms": timed.inference_ms,
|
|
}),
|
|
)
|
|
.map_err(|e| format!("Failed to emit result: {e}"))?;
|
|
|
|
Ok(())
|
|
}
|