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:
@@ -43,10 +43,7 @@ impl EvdevHotkeyListener {
|
||||
/// The listener spawns:
|
||||
/// 1. One async task per input device that has the target key
|
||||
/// 2. A watcher task that detects new devices via inotify on `/dev/input/`
|
||||
pub fn start(
|
||||
combo: HotkeyCombo,
|
||||
event_tx: mpsc::Sender<HotkeyEvent>,
|
||||
) -> Self {
|
||||
pub fn start(combo: HotkeyCombo, event_tx: mpsc::Sender<HotkeyEvent>) -> Self {
|
||||
let (hotkey_tx, hotkey_rx) = watch::channel(Some(combo));
|
||||
let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1);
|
||||
|
||||
@@ -57,12 +54,7 @@ impl EvdevHotkeyListener {
|
||||
let event_tx_clone = event_tx.clone();
|
||||
let tracked_clone = tracked.clone();
|
||||
tokio::spawn(async move {
|
||||
scan_and_attach(
|
||||
&hotkey_rx_clone,
|
||||
&event_tx_clone,
|
||||
&tracked_clone,
|
||||
)
|
||||
.await;
|
||||
scan_and_attach(&hotkey_rx_clone, &event_tx_clone, &tracked_clone).await;
|
||||
});
|
||||
|
||||
// Spawn hotplug watcher
|
||||
@@ -92,17 +84,19 @@ impl EvdevHotkeyListener {
|
||||
}
|
||||
});
|
||||
match watcher {
|
||||
Ok(mut w) => match w.watch(Path::new("/dev/input"), RecursiveMode::NonRecursive) {
|
||||
Ok(()) => Some(w),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"[kon-hotkey] cannot watch /dev/input ({e}); \
|
||||
Ok(mut w) => {
|
||||
match w.watch(Path::new("/dev/input"), RecursiveMode::NonRecursive) {
|
||||
Ok(()) => Some(w),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"[kon-hotkey] cannot watch /dev/input ({e}); \
|
||||
hotplug detection disabled, devices present \
|
||||
at startup still work",
|
||||
);
|
||||
None
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"[kon-hotkey] cannot create inotify watcher ({e}); \
|
||||
@@ -168,8 +162,8 @@ pub fn check_access() -> Result<(), String> {
|
||||
}
|
||||
|
||||
// Try to open any event device
|
||||
let entries = std::fs::read_dir(input_dir)
|
||||
.map_err(|e| format!("Cannot read /dev/input: {e}"))?;
|
||||
let entries =
|
||||
std::fs::read_dir(input_dir).map_err(|e| format!("Cannot read /dev/input: {e}"))?;
|
||||
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
@@ -250,11 +244,12 @@ async fn try_attach_device(
|
||||
return false;
|
||||
}
|
||||
|
||||
let device_name = device
|
||||
.name()
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
log::info!("Attached hotkey listener to: {} ({})", device_name, path.display());
|
||||
let device_name = device.name().unwrap_or("unknown").to_string();
|
||||
log::info!(
|
||||
"Attached hotkey listener to: {} ({})",
|
||||
device_name,
|
||||
path.display()
|
||||
);
|
||||
|
||||
tracked_set.insert(path.to_path_buf());
|
||||
drop(tracked_set);
|
||||
|
||||
Reference in New Issue
Block a user