Phase 9 of the rebrand cascade. Sweep covers everything the Phase 8
frontend pass deliberately skipped: docs/, root markdown, scripts,
Cargo.toml descriptions, code comments that survived earlier
word-boundary sed, plus a handful of identifiers caught on the final
verify pass.
transcription-app changes:
- README.md, HANDOVER.md, KNOWN-ISSUES.md, run.sh — magnotia/Magnotia
-> lumotia/Lumotia.
- docs/ — sweep across all subdirs except docs/handovers/ (preserved
as immutable audit trail). Includes architecture-map references
to magnotia_core::*, magnotia_storage::*, etc. now pointing at
lumotia_*; dev-setup.md tracing output examples (lumotia_startup
target); brief/ + superpowers/ + issues/ + whisper-ecosystem/ +
audit/.
- Cargo.toml descriptions on 9 crates (core, audio, cloud-providers,
hotkey, llm, mcp, plus referenced others).
- crates/core/src/{error,hardware,recommendation,paths}.rs +
crates/audio/src/wav.rs + crates/llm/src/model_manager.rs +
crates/cloud-providers/src/keystore.rs + crates/mcp/src/lib.rs —
doc comments and a model-manager user-agent string.
- Caught on final pass: BroadcastChannel("magnotia_task_sync") -> ...
("lumotia_task_sync"); magnotia_locale i18n localStorage key
renamed + migration shim added; CSS keyframe names
magnotiaPulse / magnotiaBar / magnotiaFade renamed in the design-
system kit; magnotia_viewer_item / magnotia_viewer_mode handoff
keys renamed in HistoryPage + viewer/+page.svelte; src/assets/
wordmark.svg text.
- src-tauri/src/lib.rs comment cleanup ("magnotia era" was sed'd
to "lumotia era" earlier — restored).
Preserved (intentional):
- crates/core/src/paths.rs — keeps "magnotia" / "Magnotia" / ".magnotia"
legacy detection strings in legacy_and_target_paths() so the
migration shim can still find user data from the magnotia era.
- src/lib/stores/{page,focusTimer}.svelte.ts + src/lib/i18n/index.ts
— migration call sites reference the legacy magnotia keys
deliberately.
- docs/handovers/ — historical audit trail.
cargo build --workspace passes. npm run check: 0 errors / 0 warnings
(3958 files). cargo test --workspace: 339 pass / 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.2 KiB
name, type, slice, last_verified
| name | type | slice | last_verified |
|---|---|---|---|
| Transcription commands | architecture-map-page | 02-tauri-runtime | 2026/05/09 |
commands::transcription
Where you are: Architecture map → Tauri runtime → Commands → Transcription
Plain English summary. The non-live transcription paths. Three commands: transcribe a Vec of PCM samples through Whisper, transcribe the same shape through Parakeet, and transcribe a file from disk by decoding + resampling to 16 kHz first. Both PCM commands emit transcription-result events; the file command returns the result inline. All three run inference on a blocking thread, run the formatting pipeline against the output, and respect profile prompt + vocabulary precedence via the shared build_initial_prompt helper.
At a glance
- Path:
src-tauri/src/commands/transcription.rs. - LOC: 413.
- Tauri commands exposed:
transcribe_pcm(window, state, app, samples: Vec<f32>, chunk_id: u32, language, initial_prompt, remove_fillers, british_english, anti_hallucination, format_mode, profile_id) -> Result<(), String>— main-window only. Whisper PCM. Emitstranscription-result.transcribe_file(window, state, path, engine: Option<String>, model_id: Option<String>, language, initial_prompt, remove_fillers, british_english, anti_hallucination, format_mode, profile_id) -> Result<serde_json::Value, String>— main-window only. Decodes the file, picks engine (default whisper), returns the result inline.transcribe_pcm_parakeet(window, state, app, samples, chunk_id, remove_fillers, british_english, anti_hallucination, format_mode, profile_id) -> Result<(), String>— main-window only. Parakeet PCM. Emitstranscription-result.
- Events emitted:
transcription-result(payload:{ status: "transcription", segments, language, duration, chunk_id, inference_ms, raw_text }) — fires fromtranscribe_pcm(src-tauri/src/commands/transcription.rs:208) andtranscribe_pcm_parakeet(:398). - Depends on:
lumotia_audio::{decode_audio_file_limited, resample_to_16khz, probe_audio_duration_secs},lumotia_core::types::{AudioSamples, Segment, Transcript, TranscriptionOptions},lumotia_transcription::{LocalEngine, TimedTranscript},lumotia_ai_formatting::{post_process_segments, FormatMode, PostProcessOptions},lumotia_storage::{database, DEFAULT_PROFILE_ID}. Pluscommands::build_initial_prompt,commands::models::{default_model_id_for_engine, ensure_model_loaded},commands::security::ensure_main_window. - Called from frontend at: dictation page (PCM commands when not live), file-import flow (transcribe_file), Settings test page (file command for QA).
What's in here
Constants (src-tauri/src/commands/transcription.rs:18)
Chunking thresholds:
- Parakeet: chunk anything over 18 s into 15 s windows with 1 s overlap.
- Generic file: chunk anything over 8 minutes into 3-minute windows with 2 s overlap.
- Hard cap:
MAX_FILE_TRANSCRIPTION_SECS = 2 * 60 * 60(2 hours).
pick_engine (:31)
Maps "whisper" and "parakeet" to the relevant Arc<LocalEngine> from AppState.
pick_chunking_strategy (:42)
Returns the ChunkingStrategy to use based on engine + sample count, or None for "no chunking needed".
trim_overlap_segments (:61)
For chunks past the first, drops segments that end before trim_before_secs and clamps remaining starts. The same logic appears in commands::live for the live-mode path.
transcribe_samples_sync (:74)
The shared inner that the file path uses. If no chunking is needed, calls engine.transcribe_sync once. Otherwise loops over chunks, runs each through inference, trims overlap, offsets timestamps by the chunk start, accumulates segments and inference_ms. Returns a synthesised TimedTranscript.
transcribe_pcm (:142)
Whisper-specific PCM path (no chunking — frontend is expected to keep the buffer reasonable, e.g. ≤ 30 s for the Whisper context window):
ensure_main_window.- Resolve
profile_id(defaultDEFAULT_PROFILE_ID). - Fetch
ProfileRowand profile term list fromlumotia_storage::database. - Build effective Whisper prompt via
build_initial_prompt(&caller_prompt, &profile.initial_prompt, &profile_terms). spawn_blockingrunsengine.transcribe_syncon the samples.- Run
post_process_segments(filler removal, British English conversion, anti-hallucination, format mode, dictionary terms, optional LLM cleanup viastate.llm_engine). app.emit("transcription-result", ...)with raw_text (pre-post-process) plus the post-processed segments.
transcribe_file (:236)
ensure_main_window.- Resolve profile + terms (same as PCM path).
- Default engine to
"whisper", default model id todefault_model_id_for_engine(&engine_name). ensure_model_loaded(state, engine, model_id, None)— None = no sequential-GPU guard.- Probe audio duration via
lumotia_audio::probe_audio_duration_secs. If > 2 hours, return a friendly error. spawn_blockingdecodes the file (decode_audio_file_limited(path, Some(MAX_FILE_TRANSCRIPTION_SECS))), resamples to 16 kHz mono, then runstranscribe_samples_sync.- Run
post_process_segments. - Return a JSON value with
engine,modelId,segments,language,duration,inference_ms,raw_text.
transcribe_pcm_parakeet (:342)
Parakeet PCM path. Skips the initial_prompt construction (Parakeet doesn't have a Whisper-style prompt) but still validates the profile exists and gathers the dictionary terms for post-processing. Calls transcribe_samples_sync(parakeet_engine, "parakeet", samples, options) so the > 18 s chunking kicks in if relevant. Emits transcription-result like the Whisper path.
join_segment_text (:225)
Concatenates segment text with whitespace stripping for the raw_text field on the emitted event.
Data flow
frontend invoke('transcribe_pcm', { samples, chunk_id, ..., profile_id })
-> ensure_main_window
-> get_profile + list_profile_terms (DB)
-> build_initial_prompt
-> spawn_blocking(engine.transcribe_sync(samples, options)) -> TimedTranscript
-> post_process_segments(segments, opts, llm_engine) -- in-place
-> app.emit("transcription-result", { segments, raw_text, ... })
transcribe_file is the same shape with decode_audio_file_limited + resample_to_16khz + transcribe_samples_sync substituted for the inline transcribe_sync.
Watch-outs
transcribe_pcmdoes NOT callensure_model_loaded. It assumes the model is already loaded (which is true when the frontend has driven the Settings → load flow, and is true afterprewarm_default_model_cmd). If a third caller invokestranscribe_pcmwithout a load step, you'll get a runtime panic fromengine.transcribe_syncwith no clear message. The file path does callensure_model_loaded. Consider adding the same guard totranscribe_pcmfor symmetry.transcribe_filereturns aserde_json::Valuerather than a typed DTO. The shape is fully ad-hoc (engine,modelId,segments,language,duration,inference_ms,raw_text). The frontend has to keep this shape in sync by hand. Consider promoting it to a typed struct.- Chunking does not emit per-chunk events from this command. The file path returns the entire concatenated result at the end. If a 90-minute file fails three quarters of the way through, the user gets nothing. Live mode (
commands::live) is the streaming alternative. - The 2-hour cap is a hard constant. Document it in the frontend "import audio" flow.
- The Parakeet PCM path does not respect
languageorinitial_promptbecause Parakeet has no equivalent. The frontend should disable those Settings widgets when the engine is Parakeet, or this command will silently ignore them.
See also
- Models —
ensure_model_loaded,default_model_id_for_engine, theLocalEnginecache thatstate.whisper_engineandstate.parakeet_enginewrap. - Live transcription — the streaming sibling.
- Profiles — feeds
profile.initial_promptand the term list. commands::mod—build_initial_promptis the prompt assembler.- Audio capture — supplies the
Vec<f32>thattranscribe_pcmconsumes.