feat(kon): add full transcript provenance — enriched schema, metadata pipeline, error log
Schema enrichment: - transcripts table: +engine, +model_id, +inference_ms, +sample_rate, +audio_channels, +format_mode, +remove_fillers, +british_english, +anti_hallucination - New error_log table: context, error_code, message, metadata (JSON) - 5 indices on frequently queried columns Metadata pipeline: - TranscriptMetadata struct in kon-core for full provenance capture - LocalEngine tracks loaded ModelId, returns TimedTranscript with inference_ms - std::time::Instant timing around transcribe-rs inference calls - Tauri transcription commands emit inference_ms in event payloads - InsertTranscriptParams accepts all provenance fields - log_error() for structured error logging to SQLite Every transcript is now fully reproducible from its stored metadata. 23 tests passing, clippy clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -84,10 +84,11 @@ pub async fn load_model(
|
||||
}
|
||||
|
||||
let engine = state.whisper_engine.clone();
|
||||
let mid = id.clone();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let model = kon_transcription::load_whisper(&model_file)
|
||||
.map_err(|e| e.to_string())?;
|
||||
engine.load(model);
|
||||
engine.load(model, mid);
|
||||
Ok::<_, String>(())
|
||||
})
|
||||
.await
|
||||
@@ -152,10 +153,11 @@ pub async fn load_parakeet_model(
|
||||
}
|
||||
|
||||
let engine = state.parakeet_engine.clone();
|
||||
let mid = id.clone();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let model = kon_transcription::load_parakeet(&dir)
|
||||
.map_err(|e| e.to_string())?;
|
||||
engine.load(model);
|
||||
engine.load(model, mid);
|
||||
Ok::<_, String>(())
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -31,15 +31,13 @@ pub async fn transcribe_pcm(
|
||||
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)
|
||||
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 mut segments: Vec<Segment> = result.segments().to_vec();
|
||||
let mut segments: Vec<Segment> = timed.transcript.segments().to_vec();
|
||||
post_process_segments(
|
||||
&mut segments,
|
||||
&PostProcessOptions {
|
||||
@@ -55,9 +53,10 @@ pub async fn transcribe_pcm(
|
||||
serde_json::json!({
|
||||
"status": "transcription",
|
||||
"segments": segments,
|
||||
"language": result.language(),
|
||||
"duration": result.duration(),
|
||||
"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}"))?;
|
||||
@@ -83,20 +82,19 @@ pub async fn transcribe_file(
|
||||
initial_prompt: Some(initial_prompt),
|
||||
};
|
||||
|
||||
let result = tokio::task::spawn_blocking(move || {
|
||||
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())?;
|
||||
let transcript = engine
|
||||
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())?;
|
||||
Ok::<_, String>(transcript)
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())??;
|
||||
|
||||
let mut segments: Vec<Segment> = result.segments().to_vec();
|
||||
let mut segments: Vec<Segment> = timed.transcript.segments().to_vec();
|
||||
post_process_segments(
|
||||
&mut segments,
|
||||
&PostProcessOptions {
|
||||
@@ -109,8 +107,9 @@ pub async fn transcribe_file(
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"segments": segments,
|
||||
"language": result.language(),
|
||||
"duration": result.duration(),
|
||||
"language": timed.transcript.language(),
|
||||
"duration": timed.transcript.duration(),
|
||||
"inference_ms": timed.inference_ms,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -129,16 +128,13 @@ pub async fn transcribe_pcm_parakeet(
|
||||
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)
|
||||
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 mut segments: Vec<Segment> = result.segments().to_vec();
|
||||
let mut segments: Vec<Segment> = timed.transcript.segments().to_vec();
|
||||
post_process_segments(
|
||||
&mut segments,
|
||||
&PostProcessOptions {
|
||||
@@ -154,9 +150,10 @@ pub async fn transcribe_pcm_parakeet(
|
||||
serde_json::json!({
|
||||
"status": "transcription",
|
||||
"segments": segments,
|
||||
"language": result.language(),
|
||||
"duration": result.duration(),
|
||||
"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}"))?;
|
||||
|
||||
Reference in New Issue
Block a user