chore: remove 11 orphan Tauri commands and downstream dead code

Sweep of registered-but-never-invoked commands surfaced by an audit
against the frontend invoke() call sites. Each was confirmed dead via
grep across src-tauri, src, and crates: no caller anywhere.

Removed commands:
- check_model, count_transcripts_command, get_profile_cmd,
  install_update, list_feedback_examples_cmd (utility/CRUD shapes
  never wired)
- save_audio, start_native_capture, stop_native_capture (native-capture
  path superseded by the live transcription session)
- transcribe_pcm, transcribe_pcm_parakeet (PCM commands superseded by
  live session; no frontend caller)
- close_preview_window (preview window is hidden via the
  core:window:allow-hide capability, not the command)

Cascade in audio.rs (~430 lines removed):
- CaptureWorker, NativeCaptureState struct + impl, stop_worker,
  append_recorded_chunk, MAX_NATIVE_CAPTURE_RETURN_SAMPLES,
  persist_audio_samples
- The two cfg(test) tests that exercised stop_worker (the
  recording_filename tests stay, supporting resolve_recording_path
  which the live session uses)

Cascade elsewhere:
- FeedbackDto struct and its From<FeedbackRow> impl
- Stale storage imports in feedback.rs, profiles.rs, transcripts.rs
- tauri::Emitter import in transcription.rs
- app.manage(NativeCaptureState::new()) in lib.rs setup

generate_handler entries removed for all 11 commands. cargo check passes
cleanly with zero warnings; no tests reference any deleted symbols.

Net: 709 deletions, 17 insertions across 9 files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jars
2026-05-09 16:03:19 +01:00
parent 2a45cb8033
commit b16fc179b3
9 changed files with 17 additions and 709 deletions

View File

@@ -1,17 +1,10 @@
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicU64, Ordering};
use tauri::{Emitter, Manager};
use tokio::sync::{mpsc as tokio_mpsc, Mutex as AsyncMutex};
use tokio::task::JoinHandle;
use tauri::Manager;
use crate::commands::security::ensure_main_window;
use magnotia_audio::{DeviceInfo, MicrophoneCapture, WavWriter};
use magnotia_core::constants::WHISPER_SAMPLE_RATE;
use magnotia_core::types::AudioSamples;
const MAX_NATIVE_CAPTURE_RETURN_SAMPLES: usize = WHISPER_SAMPLE_RATE as usize * 60 * 10;
use magnotia_audio::{DeviceInfo, MicrophoneCapture};
/// Enumerate every input device available to cpal, with metadata for the
/// Settings device-picker UI. Includes a flag for likely PulseAudio /
@@ -25,311 +18,10 @@ pub async fn list_audio_devices(window: tauri::WebviewWindow) -> Result<Vec<Devi
.map_err(|e| e.to_string())
}
/// A running native-capture accumulator worker, held so the command
/// layer can both signal it to stop and `await` its termination. RB-06
/// replaced a fire-and-forget `tokio::spawn` that let the previous
/// worker keep flushing and appending samples after `stop_native_capture`
/// returned — a rapid start → stop → start could contaminate the new
/// session's samples vector with tail writes from the old one.
struct CaptureWorker {
stop_tx: tokio_mpsc::Sender<()>,
join: JoinHandle<()>,
}
/// Send the stop signal and await full worker termination. Consumes
/// `CaptureWorker` because the contained handles are single-use.
/// Errors from `join.await` (task panicked or was cancelled) are
/// logged and swallowed — the caller only needs the synchronisation
/// barrier, not the worker's return value.
async fn stop_worker(worker: CaptureWorker) {
let _ = worker.stop_tx.send(()).await;
drop(worker.stop_tx);
if let Err(e) = worker.join.await {
eprintln!("[native-capture] worker task did not terminate cleanly: {e}");
}
}
/// Shared state for native microphone capture.
pub struct NativeCaptureState {
/// The running accumulator worker, if any. `tokio::sync::Mutex`
/// because the fastest-moving consumer (`stop_worker`) awaits while
/// holding the lock — a `std::sync::Mutex` would have to be released
/// and reacquired around each await point.
worker: AsyncMutex<Option<CaptureWorker>>,
/// Compatibility buffer returned by stop_native_capture. Capped; the
/// authoritative capture is streamed to temp WAV on disk.
all_samples: Arc<Mutex<Vec<f32>>>,
wav_writer: Arc<Mutex<Option<WavWriter>>>,
temp_audio_path: Arc<Mutex<Option<PathBuf>>>,
capture_truncated: Arc<AtomicBool>,
}
impl NativeCaptureState {
pub fn new() -> Self {
Self {
worker: AsyncMutex::new(None),
all_samples: Arc::new(Mutex::new(Vec::new())),
wav_writer: Arc::new(Mutex::new(None)),
temp_audio_path: Arc::new(Mutex::new(None)),
capture_truncated: Arc::new(AtomicBool::new(false)),
}
}
}
fn append_recorded_chunk(
all_samples: &Arc<Mutex<Vec<f32>>>,
wav_writer: &Arc<Mutex<Option<WavWriter>>>,
truncated: &Arc<AtomicBool>,
chunk: &[f32],
) {
if let Ok(mut writer) = wav_writer.lock() {
if let Some(writer) = writer.as_mut() {
if let Err(e) = writer.append(chunk) {
eprintln!("[native-capture] temp WAV append failed: {e}");
}
}
}
if let Ok(mut all) = all_samples.lock() {
let remaining = MAX_NATIVE_CAPTURE_RETURN_SAMPLES.saturating_sub(all.len());
if remaining >= chunk.len() {
all.extend_from_slice(chunk);
} else {
if remaining > 0 {
all.extend_from_slice(&chunk[..remaining]);
}
truncated.store(true, Ordering::Relaxed);
}
}
}
/// Start native microphone capture via cpal.
/// Streams 16kHz mono PCM chunks to the frontend via `native-pcm` events.
///
/// `device_name`: explicit device name (from `list_audio_devices`) or None / ""
/// to auto-select. The frontend passes `settings.microphoneDevice` here so the
/// user's pick from Settings → Audio → Microphone takes effect.
#[tauri::command]
pub async fn start_native_capture(
window: tauri::WebviewWindow,
app: tauri::AppHandle,
state: tauri::State<'_, NativeCaptureState>,
device_name: Option<String>,
) -> Result<(), String> {
ensure_main_window(&window)?;
eprintln!(
"[native-capture] start_native_capture called (device='{}')",
device_name.as_deref().unwrap_or("<auto>")
);
// Stop any in-flight worker and AWAIT its termination before opening
// a new capture. Without the join we would race a draining worker
// against the `all_samples.clear()` below, leaving old-session
// samples in the new-session vector (RB-06).
if let Some(existing) = state.worker.lock().await.take() {
stop_worker(existing).await;
}
// `MicrophoneCapture::start()` is synchronous and may spend up to
// DEVICE_VALIDATION_MS per device per pass (350ms × N devices × 2 passes
// 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() {
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()
})?;
eprintln!(
"[native-capture] cpal capture started successfully on '{}'",
capture.device_name
);
// Wrap capture in Arc<Mutex> so it can be moved into the blocking task
let capture = Arc::new(Mutex::new(Some(capture)));
let capture_clone = capture.clone();
let all_samples = state.all_samples.clone();
all_samples.lock().unwrap().clear();
state.capture_truncated.store(false, Ordering::Relaxed);
let temp_path = std::env::temp_dir().join(recording_filename());
let writer = WavWriter::create(&temp_path, WHISPER_SAMPLE_RATE, 1)
.map_err(|e| format!("Failed to create temp capture WAV: {e}"))?;
*state.wav_writer.lock().unwrap() = Some(writer);
*state.temp_audio_path.lock().unwrap() = Some(temp_path);
let (stop_tx, mut stop_rx) = tokio_mpsc::channel::<()>(1);
let all_samples_clone = all_samples.clone();
let wav_writer_clone = state.wav_writer.clone();
let truncated_clone = state.capture_truncated.clone();
// Spawn a task that reads cpal chunks, downsamples to 16kHz mono,
// and emits events to the frontend. The JoinHandle is retained in
// `state.worker` so `stop_native_capture` can await full termination.
let join = tokio::spawn(async move {
let mut pcm_buffer: Vec<f32> = Vec::new();
let chunk_size = 8000_usize; // ~0.5s at 16kHz
loop {
// Check for stop signal (non-blocking)
if stop_rx.try_recv().is_ok() {
break;
}
// Drain available audio chunks from cpal (non-blocking).
// Distinguish Empty (try again) from Disconnected (capture stream
// is dead — exit the loop, don't spin forever).
// (Codex review 2026/04/17 M3)
let mut got_data = false;
let mut capture_dead = false;
loop {
match rx.try_recv() {
Ok(chunk) => {
got_data = true;
let sample_rate = chunk.sample_rate;
let channels = chunk.channels as usize;
// Downmix to mono if stereo
let mono: Vec<f32> = if channels > 1 {
chunk
.samples
.chunks(channels)
.map(|frame| frame.iter().sum::<f32>() / channels as f32)
.collect()
} else {
chunk.samples
};
// Downsample to 16kHz using simple decimation
// (acceptable quality for speech — same approach as pcm-processor.js)
let ratio = sample_rate as f64 / WHISPER_SAMPLE_RATE as f64;
if (ratio - 1.0).abs() < 0.01 {
pcm_buffer.extend_from_slice(&mono);
} else {
let mut pos: f64 = 0.0;
for &s in &mono {
pos += 1.0;
if pos >= ratio {
pcm_buffer.push(s);
pos -= ratio;
}
}
}
}
Err(std::sync::mpsc::TryRecvError::Empty) => break,
Err(std::sync::mpsc::TryRecvError::Disconnected) => {
eprintln!(
"[native-capture] capture stream disconnected; accumulator exiting"
);
capture_dead = true;
break;
}
}
}
// Emit chunks to frontend when we have enough
while pcm_buffer.len() >= chunk_size {
let chunk: Vec<f32> = pcm_buffer.drain(..chunk_size).collect();
append_recorded_chunk(
&all_samples_clone,
&wav_writer_clone,
&truncated_clone,
&chunk,
);
let _ = app.emit(
"native-pcm",
serde_json::json!({
"samples": chunk,
}),
);
}
if capture_dead {
break;
}
if !got_data {
// Avoid busy-spinning when no audio data is available
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
}
// Emit any remaining samples
if !pcm_buffer.is_empty() {
append_recorded_chunk(
&all_samples_clone,
&wav_writer_clone,
&truncated_clone,
&pcm_buffer,
);
let _ = app.emit(
"native-pcm",
serde_json::json!({
"samples": pcm_buffer,
}),
);
}
// Drop the capture to stop the cpal stream
if let Ok(mut cap) = capture_clone.lock() {
cap.take();
}
if let Ok(mut writer) = wav_writer_clone.lock() {
if let Some(writer) = writer.take() {
if let Err(e) = writer.finalize() {
eprintln!("[native-capture] temp WAV finalize failed: {e}");
}
}
}
});
*state.worker.lock().await = Some(CaptureWorker { stop_tx, join });
Ok(())
}
/// Stop native microphone capture. Returns all captured samples (16kHz mono).
///
/// Awaits full worker termination before reading `all_samples`, so the
/// returned vector contains every sample the worker flushed — and
/// nothing from a worker that technically outlived the call (RB-06).
#[tauri::command]
pub async fn stop_native_capture(
window: tauri::WebviewWindow,
state: tauri::State<'_, NativeCaptureState>,
) -> Result<Vec<f32>, String> {
ensure_main_window(&window)?;
if let Some(worker) = state.worker.lock().await.take() {
stop_worker(worker).await;
}
let samples = {
let mut all = state.all_samples.lock().unwrap();
std::mem::take(&mut *all)
};
if state.capture_truncated.load(Ordering::Relaxed) {
eprintln!(
"[native-capture] stop_native_capture returned a capped compatibility buffer; temp WAV contains the full capture"
);
}
Ok(samples)
}
/// Resolve the destination path for a new live-capture recording,
/// ensuring the parent directory exists. Extracted from
/// `persist_audio_samples` so `start_live_transcription_session` can
/// hand the path to the progressive WAV writer before any samples
/// arrive (brief item #19).
/// ensuring the parent directory exists. Used by
/// `start_live_transcription_session` to hand the path to the progressive
/// WAV writer before any samples arrive (brief item #19).
pub fn resolve_recording_path(
app: &tauri::AppHandle,
output_folder: Option<&str>,
@@ -368,7 +60,7 @@ fn recording_filename() -> String {
.unwrap_or_default();
let secs = duration.as_secs();
let nanos = duration.subsec_nanos();
let counter = RECORDING_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let counter = RECORDING_COUNTER.fetch_add(1, Ordering::Relaxed);
format!("magnotia-{secs}-{nanos:09}-{counter:04}.wav")
}
@@ -377,14 +69,11 @@ fn recording_filename() -> String {
/// restarts, so cross-launch collisions are already impossible — the
/// counter is the last-mile guarantee against within-launch same-tick
/// collisions.
static RECORDING_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
static RECORDING_COUNTER: AtomicU64 = AtomicU64::new(0);
#[cfg(test)]
mod tests {
use super::{recording_filename, stop_worker, CaptureWorker};
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use tokio::sync::mpsc;
use super::recording_filename;
#[test]
fn recording_filenames_are_unique_across_rapid_calls() {
@@ -437,104 +126,4 @@ mod tests {
);
assert!(parts[2].chars().all(|c| c.is_ascii_digit()));
}
// RB-06 regression: after `stop_worker(worker).await` completes, the
// underlying task must have exited — no lingering writes to shared
// state can leak past the stop point. The real native-capture
// worker drains a capture queue and appends to `all_samples`; this
// test swaps that for a synthetic worker that bumps an atomic
// counter in a loop and applies a distinct "flush" marker at exit.
// The assertions mirror the real-world invariant a caller needs:
// (a) after stop_worker returns, the worker has run its flush;
// (b) subsequent sleeps see the counter frozen — no writes occur
// after the join barrier.
// Pre-fix behaviour (fire-and-forget `tokio::spawn`) failed both:
// a start→stop→start cycle could observe tail writes from the
// previous worker in the new session's vector.
#[tokio::test]
async fn stop_worker_awaits_full_termination_no_writes_after_join() {
let counter = Arc::new(AtomicU32::new(0));
let counter_task = counter.clone();
let (stop_tx, mut stop_rx) = mpsc::channel::<()>(1);
let join = tokio::spawn(async move {
loop {
if stop_rx.try_recv().is_ok() {
break;
}
counter_task.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
}
// Flush marker — mirrors the final pcm_buffer drain in the
// real worker. Setting a value with a distinctive high bit
// so the test can prove the flush ran.
counter_task.fetch_or(0x8000_0000, Ordering::SeqCst);
});
// Let the worker accumulate a few bumps before we signal stop.
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
stop_worker(CaptureWorker { stop_tx, join }).await;
let after_stop = counter.load(Ordering::SeqCst);
assert!(
after_stop & 0x8000_0000 != 0,
"flush marker must be set post-stop (got {after_stop:#x})"
);
// Post-join, no further writes are possible because the task
// has ended. Sleep briefly and re-read to confirm.
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
let later = counter.load(Ordering::SeqCst);
assert_eq!(
later, after_stop,
"no writes must happen after stop_worker returns"
);
}
#[tokio::test]
async fn stop_worker_is_idempotent_on_a_worker_that_has_already_exited() {
// A worker that stops itself (channel disconnected, capture
// dead, etc.) must still be join-able cleanly by stop_worker —
// the helper should swallow any expected "task already done"
// condition without panicking.
let (stop_tx, _stop_rx) = mpsc::channel::<()>(1);
let join = tokio::spawn(async { /* exit immediately */ });
// Give the task a tick to finish.
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
// This must not hang or panic.
stop_worker(CaptureWorker { stop_tx, join }).await;
}
}
pub async fn persist_audio_samples(
app: &tauri::AppHandle,
samples: Vec<f32>,
output_folder: Option<String>,
) -> Result<String, String> {
let path = resolve_recording_path(app, output_folder.as_deref())?;
let path_clone = path.clone();
tokio::task::spawn_blocking(move || {
let audio = AudioSamples::mono_16khz(samples);
magnotia_audio::write_wav(&path_clone, &audio).map_err(|e| e.to_string())
})
.await
.map_err(|e| e.to_string())??;
Ok(path.to_string_lossy().to_string())
}
/// Save PCM f32 samples as a WAV file. Returns the file path.
#[tauri::command]
pub async fn save_audio(
window: tauri::WebviewWindow,
app: tauri::AppHandle,
samples: Vec<f32>,
output_folder: Option<String>,
) -> Result<String, String> {
ensure_main_window(&window)?;
persist_audio_samples(&app, samples, output_folder).await
}

View File

@@ -3,11 +3,10 @@
// on AI-generated output feeds a few-shot loop that conditions future
// prompts on the user's preferred style.
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use magnotia_storage::{
list_feedback_examples as db_list_feedback_examples, record_feedback as db_record_feedback,
FeedbackRow, FeedbackTargetType, RecordFeedbackParams,
record_feedback as db_record_feedback, FeedbackTargetType, RecordFeedbackParams,
};
use crate::AppState;
@@ -35,36 +34,6 @@ pub struct RecordFeedbackInput {
pub profile_id: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FeedbackDto {
pub id: i64,
pub target_type: String,
pub target_id: Option<String>,
pub rating: i64,
pub original_text: Option<String>,
pub corrected_text: Option<String>,
pub context_json: Option<String>,
pub profile_id: String,
pub created_at: String,
}
impl From<FeedbackRow> for FeedbackDto {
fn from(r: FeedbackRow) -> Self {
Self {
id: r.id,
target_type: r.target_type,
target_id: r.target_id,
rating: r.rating,
original_text: r.original_text,
corrected_text: r.corrected_text,
context_json: r.context_json,
profile_id: r.profile_id,
created_at: r.created_at,
}
}
}
fn parse_target_type(raw: &str) -> Result<FeedbackTargetType, String> {
FeedbackTargetType::parse(raw).ok_or_else(|| format!("unknown feedback target_type: {raw}"))
}
@@ -91,20 +60,3 @@ pub async fn record_feedback(
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn list_feedback_examples_cmd(
state: tauri::State<'_, AppState>,
target_type: String,
limit: Option<i64>,
min_rating: Option<i8>,
profile_id: Option<String>,
) -> Result<Vec<FeedbackDto>, String> {
let target = parse_target_type(&target_type)?;
let limit = limit.unwrap_or(8).clamp(1, 64);
let min_rating = min_rating.unwrap_or(0).clamp(-1, 1);
let rows =
db_list_feedback_examples(&state.db, target, limit, min_rating, profile_id.as_deref())
.await
.map_err(|e| e.to_string())?;
Ok(rows.into_iter().map(FeedbackDto::from).collect())
}

View File

@@ -529,12 +529,6 @@ pub async fn download_model(
Ok(format!("Model {} downloaded", size))
}
#[tauri::command]
pub fn check_model(size: String) -> Result<bool, String> {
let id = whisper_model_id(&size);
Ok(model_manager::is_downloaded(&id))
}
#[tauri::command]
pub fn list_models() -> Result<Vec<String>, String> {
let ids = model_manager::list_downloaded();

View File

@@ -15,9 +15,8 @@ use magnotia_ai_formatting::extract_corrections;
use magnotia_storage::{
add_profile_term as db_add_profile_term, create_profile as db_create_profile,
delete_profile as db_delete_profile, delete_profile_term as db_delete_profile_term,
get_profile as db_get_profile, list_profile_terms as db_list_profile_terms,
list_profiles as db_list_profiles, update_profile as db_update_profile, ProfileRow,
ProfileTermRow,
list_profile_terms as db_list_profile_terms, list_profiles as db_list_profiles,
update_profile as db_update_profile, ProfileRow, ProfileTermRow,
};
use crate::AppState;
@@ -79,17 +78,6 @@ pub async fn list_profiles_cmd(
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn get_profile_cmd(
state: tauri::State<'_, AppState>,
id: String,
) -> Result<Option<ProfileDto>, String> {
db_get_profile(&state.db, &id)
.await
.map(|opt| opt.map(ProfileDto::from))
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn create_profile_cmd(
state: tauri::State<'_, AppState>,

View File

@@ -5,8 +5,6 @@
use std::path::Path;
use std::sync::Arc;
use tauri::Emitter;
use crate::commands::build_initial_prompt;
use crate::commands::models::{default_model_id_for_engine, ensure_model_loaded};
use crate::commands::security::ensure_main_window;
@@ -137,96 +135,6 @@ fn transcribe_samples_sync(
})
}
/// Transcribe raw PCM f32 samples (Whisper). Emits "transcription-result" event.
#[tauri::command]
pub async fn transcribe_pcm(
window: tauri::WebviewWindow,
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,
profile_id: Option<String>,
) -> Result<(), String> {
ensure_main_window(&window)?;
if !state.whisper_engine.is_loaded() {
return Err(
"Whisper model not loaded. Download a Whisper model in Settings.".to_string(),
);
}
let resolved_profile_id =
profile_id.unwrap_or_else(|| magnotia_storage::DEFAULT_PROFILE_ID.to_string());
let profile = magnotia_storage::database::get_profile(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Profile {resolved_profile_id} not found"))?;
let profile_terms: Vec<String> =
magnotia_storage::database::list_profile_terms(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.into_iter()
.map(|t| t.term)
.collect();
let engine = state.whisper_engine.clone();
let options = TranscriptionOptions {
language: Some(language),
initial_prompt: build_initial_prompt(
&initial_prompt,
&profile.initial_prompt,
&profile_terms,
),
};
let timed = tokio::task::spawn_blocking(move || {
let audio = AudioSamples::mono_16khz(samples);
engine
.transcribe_sync(&audio, &options)
.map_err(|e| e.to_string())
})
.await
.map_err(|e| e.to_string())??;
let dictionary_terms = profile_terms.clone();
let mut segments: Vec<Segment> = timed.transcript.segments().to_vec();
let raw_text = join_segment_text(&segments);
post_process_segments(
&mut segments,
&PostProcessOptions {
remove_fillers,
british_english,
anti_hallucination,
format_mode: FormatMode::parse(&format_mode),
dictionary_terms,
},
Some(state.llm_engine.as_ref()),
);
app.emit(
"transcription-result",
serde_json::json!({
"status": "transcription",
"segments": segments,
"language": timed.transcript.language(),
"duration": timed.transcript.duration(),
"chunk_id": chunk_id,
"inference_ms": timed.inference_ms,
"raw_text": raw_text,
}),
)
.map_err(|e| format!("Failed to emit result: {e}"))?;
Ok(())
}
fn join_segment_text(segments: &[Segment]) -> String {
segments
.iter()
@@ -342,82 +250,3 @@ pub async fn transcribe_file(
}))
}
/// Transcribe raw PCM f32 samples (Parakeet). Emits "transcription-result" event.
#[tauri::command]
pub async fn transcribe_pcm_parakeet(
window: tauri::WebviewWindow,
state: tauri::State<'_, AppState>,
app: tauri::AppHandle,
samples: Vec<f32>,
chunk_id: u32,
remove_fillers: bool,
british_english: bool,
anti_hallucination: bool,
format_mode: String,
profile_id: Option<String>,
) -> Result<(), String> {
ensure_main_window(&window)?;
if !state.parakeet_engine.is_loaded() {
return Err(
"Parakeet model not loaded. Enable Parakeet in Settings.".to_string(),
);
}
let resolved_profile_id =
profile_id.unwrap_or_else(|| magnotia_storage::DEFAULT_PROFILE_ID.to_string());
// Validate the profile exists so parakeet and whisper behave identically
// when a bogus id slips through from the frontend.
magnotia_storage::database::get_profile(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Profile {resolved_profile_id} not found"))?;
let profile_terms: Vec<String> =
magnotia_storage::database::list_profile_terms(&state.db, &resolved_profile_id)
.await
.map_err(|e| e.to_string())?
.into_iter()
.map(|t| t.term)
.collect();
let engine = state.parakeet_engine.clone();
let options = TranscriptionOptions::default();
let timed = tokio::task::spawn_blocking(move || {
transcribe_samples_sync(engine, "parakeet", samples, options)
})
.await
.map_err(|e| e.to_string())??;
let dictionary_terms = profile_terms.clone();
let mut segments: Vec<Segment> = timed.transcript.segments().to_vec();
let raw_text = join_segment_text(&segments);
post_process_segments(
&mut segments,
&PostProcessOptions {
remove_fillers,
british_english,
anti_hallucination,
format_mode: FormatMode::parse(&format_mode),
dictionary_terms,
},
Some(state.llm_engine.as_ref()),
);
app.emit(
"transcription-result",
serde_json::json!({
"status": "transcription",
"segments": segments,
"language": timed.transcript.language(),
"duration": timed.transcript.duration(),
"chunk_id": chunk_id,
"inference_ms": timed.inference_ms,
"raw_text": raw_text,
}),
)
.map_err(|e| format!("Failed to emit result: {e}"))?;
Ok(())
}

View File

@@ -14,11 +14,10 @@
use serde::{Deserialize, Serialize};
use magnotia_storage::{
count_transcripts, delete_transcript as db_delete_transcript,
get_transcript as db_get_transcript, insert_transcript as db_insert_transcript,
list_transcripts_paged, search_transcripts as db_search_transcripts,
update_transcript as db_update_transcript, update_transcript_meta as db_update_transcript_meta,
InsertTranscriptParams, TranscriptRow,
delete_transcript as db_delete_transcript, get_transcript as db_get_transcript,
insert_transcript as db_insert_transcript, list_transcripts_paged,
search_transcripts as db_search_transcripts, update_transcript as db_update_transcript,
update_transcript_meta as db_update_transcript_meta, InsertTranscriptParams, TranscriptRow,
};
use crate::AppState;
@@ -145,14 +144,6 @@ pub async fn list_transcripts(
.map_err(|e| e.to_string())
}
/// Total count of transcripts (for "showing X of N" UI).
#[tauri::command]
pub async fn count_transcripts_command(state: tauri::State<'_, AppState>) -> Result<i64, String> {
count_transcripts(&state.db)
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn get_transcript(
state: tauri::State<'_, AppState>,

View File

@@ -8,9 +8,3 @@ pub async fn check_for_update(window: tauri::WebviewWindow) -> Result<Option<Str
Ok(None)
}
/// Download and stage the update. The app will install it on next launch.
#[tauri::command]
pub async fn install_update(window: tauri::WebviewWindow) -> Result<(), String> {
ensure_main_window(&window)?;
Err("Updates are disabled until release signing is configured.".to_string())
}

View File

@@ -27,12 +27,6 @@ pub async fn open_preview_window(_app: tauri::AppHandle) -> Result<(), String> {
Err(ANDROID_MULTIWINDOW_ERR.into())
}
#[cfg(target_os = "android")]
#[tauri::command]
pub async fn close_preview_window(_app: tauri::AppHandle) -> Result<(), String> {
Err(ANDROID_MULTIWINDOW_ERR.into())
}
#[cfg(target_os = "android")]
#[tauri::command]
pub async fn open_viewer_window(_app: tauri::AppHandle) -> Result<(), String> {
@@ -152,17 +146,6 @@ pub async fn open_preview_window(app: tauri::AppHandle) -> Result<(), String> {
Ok(())
}
/// Hide the transcription preview window without destroying it so the next
/// open is instant. Returns Ok even when no preview window exists.
#[cfg(not(target_os = "android"))]
#[tauri::command]
pub async fn close_preview_window(app: tauri::AppHandle) -> Result<(), String> {
if let Some(window) = app.get_webview_window("transcription-preview") {
window.hide().map_err(|e| e.to_string())?;
}
Ok(())
}
/// Open the transcript viewer window.
#[cfg(not(target_os = "android"))]
#[tauri::command]

View File

@@ -294,7 +294,6 @@ pub fn run() {
app.manage(PreferencesScript(init_script));
app.manage(commands::hotkey::HotkeyState::new());
app.manage(commands::audio::NativeCaptureState::new());
app.manage(commands::live::LiveTranscriptionState::default());
app.manage(commands::tts::TtsState::new());
app.manage(commands::meeting::MeetingState::new());
@@ -323,7 +322,6 @@ pub fn run() {
save_preferences,
// Whisper model management
commands::models::download_model,
commands::models::check_model,
commands::models::list_models,
commands::models::load_model,
commands::models::prewarm_default_model_cmd,
@@ -346,13 +344,8 @@ pub fn run() {
commands::models::load_parakeet_model,
commands::models::check_parakeet_engine,
// Transcription
commands::transcription::transcribe_pcm,
commands::transcription::transcribe_file,
commands::transcription::transcribe_pcm_parakeet,
// Audio
commands::audio::save_audio,
commands::audio::start_native_capture,
commands::audio::stop_native_capture,
commands::audio::list_audio_devices,
// Tasks (canonical SQLite-backed task CRUD)
commands::tasks::create_task_cmd,
@@ -369,7 +362,6 @@ pub fn run() {
commands::tasks::list_recent_completions_cmd,
// HITL feedback (Phase 2 roadmap)
commands::feedback::record_feedback,
commands::feedback::list_feedback_examples_cmd,
// Read aloud (Phase 4 roadmap)
commands::tts::tts_speak,
commands::tts::tts_stop,
@@ -387,7 +379,6 @@ pub fn run() {
commands::intentions::delete_implementation_rule,
// Profiles + profile terms (canonical SQLite-backed profile CRUD) — Task 12
commands::profiles::list_profiles_cmd,
commands::profiles::get_profile_cmd,
commands::profiles::create_profile_cmd,
commands::profiles::update_profile_cmd,
commands::profiles::delete_profile_cmd,
@@ -398,7 +389,6 @@ pub fn run() {
// Transcripts (canonical SQLite-backed history) — Day 4
commands::transcripts::add_transcript,
commands::transcripts::list_transcripts,
commands::transcripts::count_transcripts_command,
commands::transcripts::get_transcript,
commands::transcripts::update_transcript,
commands::transcripts::update_transcript_meta_cmd,
@@ -417,7 +407,6 @@ pub fn run() {
commands::windows::open_task_window,
commands::windows::open_viewer_window,
commands::windows::open_preview_window,
commands::windows::close_preview_window,
// Clipboard
commands::clipboard::copy_to_clipboard,
// Filesystem (Phase 9 save-dialog path)
@@ -441,7 +430,6 @@ pub fn run() {
commands::hotkey::stop_evdev_hotkey,
// Updater
commands::update::check_for_update,
commands::update::install_update,
])
.run(tauri::generate_context!())
.expect("error while running Magnotia");