feat(kon): wire Tauri shell — thin commands, ProviderRegistry, tray

- lib.rs rewritten from scaffold to full app setup (~70 lines)
- AppState holds Arc<LocalEngine> for Whisper and Parakeet
- commands/models.rs: download, check, list, load for both engines
  Maps legacy size strings (Tiny/Base/Small/Medium) to ModelId
- commands/transcription.rs: transcribe_pcm, transcribe_file, transcribe_pcm_parakeet
  Delegates to LocalEngine.transcribe_sync() + post_process_segments()
- commands/audio.rs: save_audio via kon_audio::write_wav
- commands/windows.rs: open_task_window, open_viewer_window
- commands/llm.rs: 8 stub commands preserved for frontend compatibility
- tray.rs: extracted system tray setup (show, status, quit, click-to-show)
- Close-to-tray behaviour preserved
- All 25 v0.2 command names preserved for Svelte frontend compatibility
- clippy clean

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jake
2026-03-16 21:15:16 +00:00
parent 0646c75de7
commit 29ff91d3f6
9 changed files with 638 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
// 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 tauri::Emitter;
use crate::AppState;
use kon_ai_formatting::{post_process_segments, FormatMode, PostProcessOptions};
use kon_core::types::{Segment, TranscriptionOptions};
/// 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 result = tokio::task::spawn_blocking(move || {
let transcript = engine.transcribe_sync(&audio, &options)
.map_err(|e| e.to_string())?;
Ok::<_, String>(transcript)
})
.await
.map_err(|e| e.to_string())??;
let mut segments: Vec<Segment> = result.segments().to_vec();
post_process_segments(
&mut segments,
&PostProcessOptions {
remove_fillers,
british_english,
anti_hallucination,
format_mode: FormatMode::parse(&format_mode),
},
);
app.emit(
"transcription-result",
serde_json::json!({
"status": "transcription",
"segments": segments,
"language": result.language(),
"duration": result.duration(),
"chunk_id": chunk_id,
}),
)
.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,
language: String,
initial_prompt: String,
remove_fillers: bool,
british_english: bool,
anti_hallucination: bool,
format_mode: String,
) -> Result<serde_json::Value, String> {
let engine = state.whisper_engine.clone();
let options = TranscriptionOptions {
language: Some(language),
initial_prompt: Some(initial_prompt),
};
let result = 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())?;
let transcript = engine
.transcribe_sync(&resampled, &options)
.map_err(|e| e.to_string())?;
Ok::<_, String>(transcript)
})
.await
.map_err(|e| e.to_string())??;
let mut segments: Vec<Segment> = result.segments().to_vec();
post_process_segments(
&mut segments,
&PostProcessOptions {
remove_fillers,
british_english,
anti_hallucination,
format_mode: FormatMode::parse(&format_mode),
},
);
Ok(serde_json::json!({
"segments": segments,
"language": result.language(),
"duration": result.duration(),
}))
}
/// 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,
) -> Result<(), String> {
let engine = state.parakeet_engine.clone();
let audio = kon_core::AudioSamples::mono_16khz(samples);
let options = TranscriptionOptions::default();
let result = tokio::task::spawn_blocking(move || {
let transcript = engine
.transcribe_sync(&audio, &options)
.map_err(|e| e.to_string())?;
Ok::<_, String>(transcript)
})
.await
.map_err(|e| e.to_string())??;
let mut segments: Vec<Segment> = result.segments().to_vec();
post_process_segments(
&mut segments,
&PostProcessOptions {
remove_fillers,
british_english,
anti_hallucination,
format_mode: FormatMode::parse("Clean"),
},
);
app.emit(
"transcription-result",
serde_json::json!({
"status": "transcription",
"segments": segments,
"language": result.language(),
"duration": result.duration(),
"chunk_id": chunk_id,
}),
)
.map_err(|e| format!("Failed to emit result: {e}"))?;
Ok(())
}