feat: OpenWhispr-inspired transcription polish pass
Major quality pass on top of Phase 2. Five substantive changes plus
cross-cutting touches across audio, hotkey, transcription, and Tauri
command layers.
Transcription quality
- Long-audio chunking in commands/transcription.rs: Parakeet and large
file transcription now chunk-and-recompose with overlap trimming, so
the live-path chunking advantage extends to file-based workflows.
- Stateful live speech gate in commands/live.rs on top of the earlier
duplicate-boundary filtering — distinguishes start-of-speech from
mid-speech and holds state across chunks.
Auto-learning corrections
- New crates/ai-formatting/src/correction_learning.rs: extracts user
text corrections from viewer edits and proposes additions to the
active profile's vocabulary.
- src-tauri/src/commands/profiles.rs bridge for frontend-driven
confirmation of learned terms.
- src/routes/viewer/+page.svelte hooks the learning path into the
segment-edit flow so corrections feed profile_terms without a
separate 'train this profile' UX.
Transcript profile provenance
- Migration v8 (crates/storage/src/migrations.rs) adds profile_id to
transcripts, defaulting to DEFAULT_PROFILE_ID so existing rows stay
valid.
- crates/storage/src/database.rs: TranscriptRow + CRUD carry profile_id.
- src-tauri/src/commands/transcripts.rs: add_transcript accepts and
persists profile_id.
- DictationPage.svelte + FilesPage.svelte send activeProfileId on
capture so learned corrections are attributed to the right profile.
Cleanup prompt contract
- crates/ai-formatting/src/llm_client.rs hardened: the CLEANUP_PROMPT
now specifies concrete do/do-not rules, ready for a real model-backed
cleanup pass. The llm_client is still a stub — kon-llm remains unwired
— but the prompt shape is final.
Cross-cutting polish
- Minor touches in audio (capture/decode/resample), hotkey (lib/linux/stub),
core, transcription (concurrency/model_manager/local_engine/whisper_rs),
and the rest of src-tauri/src/commands/*: error-path tightening, log
clarity, TS-migration follow-ups (@ts-nocheck additions for incremental
typing).
Verified locally: npm run check, cargo test -p kon-ai-formatting,
cargo test -p kon-storage, cargo test -p kon --lib commands::live::tests,
cargo check — all green.
Scope boundary: kon-llm crate is still a stub; task extraction remains
rule-based. Bundled local-LLM runtime is the next clean step and is not
in this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -67,18 +67,17 @@ pub async fn start_native_capture(
|
||||
// worst case). Run on a blocking thread so the async runtime stays
|
||||
// responsive to other Tauri commands. (Codex review 2026/04/17 D2)
|
||||
let device_name_for_blocking = device_name.clone();
|
||||
let (capture, rx) = tokio::task::spawn_blocking(move || {
|
||||
match device_name_for_blocking.as_deref() {
|
||||
let (capture, rx) =
|
||||
tokio::task::spawn_blocking(move || match device_name_for_blocking.as_deref() {
|
||||
Some(name) if !name.is_empty() => MicrophoneCapture::start_with_device(name),
|
||||
_ => MicrophoneCapture::start(),
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("audio task join error: {e}"))?
|
||||
.map_err(|e| {
|
||||
eprintln!("[native-capture] MicrophoneCapture::start failed: {e}");
|
||||
e.to_string()
|
||||
})?;
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("audio task join error: {e}"))?
|
||||
.map_err(|e| {
|
||||
eprintln!("[native-capture] MicrophoneCapture::start failed: {e}");
|
||||
e.to_string()
|
||||
})?;
|
||||
eprintln!(
|
||||
"[native-capture] cpal capture started successfully on '{}'",
|
||||
capture.device_name
|
||||
@@ -150,7 +149,9 @@ pub async fn start_native_capture(
|
||||
}
|
||||
Err(std::sync::mpsc::TryRecvError::Empty) => break,
|
||||
Err(std::sync::mpsc::TryRecvError::Disconnected) => {
|
||||
eprintln!("[native-capture] capture stream disconnected; accumulator exiting");
|
||||
eprintln!(
|
||||
"[native-capture] capture stream disconnected; accumulator exiting"
|
||||
);
|
||||
capture_dead = true;
|
||||
break;
|
||||
}
|
||||
@@ -166,9 +167,12 @@ pub async fn start_native_capture(
|
||||
all.extend_from_slice(&chunk);
|
||||
}
|
||||
|
||||
let _ = app.emit("native-pcm", serde_json::json!({
|
||||
"samples": chunk,
|
||||
}));
|
||||
let _ = app.emit(
|
||||
"native-pcm",
|
||||
serde_json::json!({
|
||||
"samples": chunk,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if capture_dead {
|
||||
@@ -185,9 +189,12 @@ pub async fn start_native_capture(
|
||||
if let Ok(mut all) = all_samples_clone.lock() {
|
||||
all.extend_from_slice(&pcm_buffer);
|
||||
}
|
||||
let _ = app.emit("native-pcm", serde_json::json!({
|
||||
"samples": pcm_buffer,
|
||||
}));
|
||||
let _ = app.emit(
|
||||
"native-pcm",
|
||||
serde_json::json!({
|
||||
"samples": pcm_buffer,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Drop the capture to stop the cpal stream
|
||||
|
||||
Reference in New Issue
Block a user