From c2fd48bee053060f96172aa1ed4be257862a242d Mon Sep 17 00:00:00 2001 From: Grzegorz <19194188+farce1@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:07:07 +0200 Subject: [PATCH 1/4] feat(transcription): add check_parakeet_model_ready command Mirror check_model_ready for the Parakeet engine (parakeet assets + VAD) so the settings UI can show per-engine model readiness. Expose as a Tauri command and register it. --- src-tauri/src/commands.rs | 9 +++++++++ src-tauri/src/lib.rs | 1 + src-tauri/src/transcription/model.rs | 30 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index a7a7543..b65cf55 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -583,6 +583,15 @@ pub async fn check_model_ready( Ok(transcription::model::check_model_ready(data_dir.inner().0.as_path())) } +#[tauri::command] +pub async fn check_parakeet_model_ready( + data_dir: tauri::State<'_, DataDir>, +) -> Result { + Ok(transcription::model::check_parakeet_model_ready( + data_dir.inner().0.as_path(), + )) +} + #[tauri::command] pub async fn check_diarization_model_ready( data_dir: tauri::State<'_, DataDir>, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5708657..74c8667 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -256,6 +256,7 @@ pub fn run() { commands::start_transcription, commands::stop_transcription, commands::check_model_ready, + commands::check_parakeet_model_ready, commands::download_model, commands::start_diarization, commands::rename_speaker, diff --git a/src-tauri/src/transcription/model.rs b/src-tauri/src/transcription/model.rs index 4fe6746..22af5de 100644 --- a/src-tauri/src/transcription/model.rs +++ b/src-tauri/src/transcription/model.rs @@ -48,6 +48,10 @@ pub fn check_model_ready(data_dir: &Path) -> bool { check_transcription_assets_ready(data_dir) && vad_model_path(data_dir).exists() } +pub fn check_parakeet_model_ready(data_dir: &Path) -> bool { + check_parakeet_assets_ready(data_dir) && vad_model_path(data_dir).exists() +} + #[cfg(test)] mod tests { use super::*; @@ -73,4 +77,30 @@ mod tests { std::fs::remove_dir_all(&tmp).ok(); } + + #[test] + fn parakeet_model_ready_requires_assets_and_vad() { + let tmp = + std::env::temp_dir().join(format!("on-parakeet-model-ready-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&tmp); + + let dir = parakeet_model_dir(&tmp); + std::fs::create_dir_all(&dir).unwrap(); + for name in [ + "encoder.int8.onnx", + "decoder.int8.onnx", + "joiner.int8.onnx", + "tokens.txt", + ] { + std::fs::write(dir.join(name), b"x").unwrap(); + } + + // Assets present but VAD missing -> not ready. + assert!(!check_parakeet_model_ready(&tmp)); + + std::fs::write(vad_model_path(&tmp), b"x").unwrap(); + assert!(check_parakeet_model_ready(&tmp)); + + std::fs::remove_dir_all(&tmp).ok(); + } } From 346175421073b2243ef21923ee1b116564604a63 Mon Sep 17 00:00:00 2001 From: Grzegorz <19194188+farce1@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:30:45 +0200 Subject: [PATCH 2/4] feat(settings): add asrEngine setting and per-engine command mapping Introduce the AsrEngine type ('whisper' | 'parakeet'), persist the choice via the asrEngine setting (default whisper), and add asrEngineInfo() mapping each engine to its readiness/download Tauri commands and model directory. --- src/lib/asr.test.ts | 21 +++++++++++++++++++++ src/lib/asr.ts | 24 ++++++++++++++++++++++++ src/lib/constants.ts | 1 + src/types/index.ts | 2 ++ 4 files changed, 48 insertions(+) create mode 100644 src/lib/asr.test.ts create mode 100644 src/lib/asr.ts diff --git a/src/lib/asr.test.ts b/src/lib/asr.test.ts new file mode 100644 index 0000000..eb2da25 --- /dev/null +++ b/src/lib/asr.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect } from 'vitest'; + +import { asrEngineInfo } from './asr'; + +describe('asrEngineInfo', () => { + it('maps whisper to its readiness/download commands and model dir', () => { + expect(asrEngineInfo('whisper')).toEqual({ + readyCommand: 'check_model_ready', + downloadCommand: 'download_model', + modelDirName: 'sherpa-onnx-whisper-turbo', + }); + }); + + it('maps parakeet to its readiness/download commands and model dir', () => { + expect(asrEngineInfo('parakeet')).toEqual({ + readyCommand: 'check_parakeet_model_ready', + downloadCommand: 'download_parakeet_model', + modelDirName: 'sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8', + }); + }); +}); diff --git a/src/lib/asr.ts b/src/lib/asr.ts new file mode 100644 index 0000000..198adbd --- /dev/null +++ b/src/lib/asr.ts @@ -0,0 +1,24 @@ +import type { AsrEngine } from '../types'; + +export interface AsrEngineInfo { + readyCommand: string; + downloadCommand: string; + modelDirName: string; +} + +const ASR_ENGINE_INFO: Record = { + whisper: { + readyCommand: 'check_model_ready', + downloadCommand: 'download_model', + modelDirName: 'sherpa-onnx-whisper-turbo', + }, + parakeet: { + readyCommand: 'check_parakeet_model_ready', + downloadCommand: 'download_parakeet_model', + modelDirName: 'sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8', + }, +}; + +export function asrEngineInfo(engine: AsrEngine): AsrEngineInfo { + return ASR_ENGINE_INFO[engine]; +} diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 35e7eea..74fe0c4 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -19,6 +19,7 @@ export const DEFAULT_SETTINGS: AppSettings = { ollamaServerUrl: 'http://localhost:11434', autoSummary: true, autoDiarize: false, + asrEngine: 'whisper', }; let cachedDataDir: string | null = null; diff --git a/src/types/index.ts b/src/types/index.ts index 1dd6b61..c49d9d7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -25,6 +25,7 @@ export type SortField = 'date' | 'duration' | 'title'; export type SortDirection = 'asc' | 'desc'; export type ViewMode = 'card' | 'compact'; export type SettingsTab = 'general' | 'recording' | 'transcription' | 'summary' | 'data' | 'about'; +export type AsrEngine = 'whisper' | 'parakeet'; export interface LibraryFilters { search: string; @@ -70,6 +71,7 @@ export interface AppSettings { ollamaServerUrl: string; autoSummary: boolean; autoDiarize: boolean; + asrEngine: AsrEngine; } export interface TranscriptSegment { From 62f66084f473e9958116d0e19a5d7774f89b17a3 Mon Sep 17 00:00:00 2001 From: Grzegorz <19194188+farce1@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:38:59 +0200 Subject: [PATCH 3/4] feat(settings): add ASR model picker to TranscriptionSection Add an engine dropdown (Whisper/Parakeet) that persists the asrEngine setting and drives readiness checks, downloads (download_parakeet_model with the existing progress UI), and per-engine model deletion via asrEngineInfo(). Covered by an RTL test. --- .../settings/TranscriptionSection.test.tsx | 104 ++++++++++++++++++ .../settings/TranscriptionSection.tsx | 63 ++++++++--- src/i18n/locales/en/settings.json | 5 +- src/i18n/locales/pl/settings.json | 5 +- 4 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 src/components/settings/TranscriptionSection.test.tsx diff --git a/src/components/settings/TranscriptionSection.test.tsx b/src/components/settings/TranscriptionSection.test.tsx new file mode 100644 index 0000000..6fc3cc5 --- /dev/null +++ b/src/components/settings/TranscriptionSection.test.tsx @@ -0,0 +1,104 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react'; + +// --- Mocks (must precede importing the component under test) + +const mockInvoke = vi.fn(); +vi.mock('@tauri-apps/api/core', () => ({ + invoke: (...args: unknown[]) => mockInvoke(...args), + Channel: class MockChannel { + onmessage: ((e: unknown) => void) | null = null; + }, +})); + +vi.mock('@tauri-apps/api/path', () => ({ + join: (...parts: string[]) => Promise.resolve(parts.join('/')), + appLocalDataDir: () => Promise.resolve('/data'), +})); + +const mockRemove = vi.fn(); +vi.mock('@tauri-apps/plugin-fs', () => ({ + readDir: vi.fn().mockResolvedValue([]), + remove: (...args: unknown[]) => mockRemove(...args), +})); + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string, opts?: { defaultValue?: string }) => opts?.defaultValue ?? key, + i18n: { language: 'en' }, + }), +})); + +let mockEngine: 'whisper' | 'parakeet' = 'whisper'; +const mockUpdateEngine = vi.fn(); +vi.mock('../../hooks/useSettings', () => ({ + useSetting: () => [mockEngine, mockUpdateEngine], +})); + +import { TranscriptionSection } from './TranscriptionSection'; + +describe('TranscriptionSection ASR picker', () => { + beforeEach(() => { + mockInvoke.mockReset(); + mockUpdateEngine.mockReset(); + mockRemove.mockReset(); + mockRemove.mockResolvedValue(undefined); + mockEngine = 'whisper'; + mockInvoke.mockResolvedValue(false); + }); + + afterEach(() => { + cleanup(); + }); + + it('checks parakeet readiness when the parakeet engine is selected', async () => { + mockEngine = 'parakeet'; + render(); + + await waitFor(() => { + expect(mockInvoke).toHaveBeenCalledWith('check_parakeet_model_ready'); + }); + }); + + it('persists the chosen engine when picked from the dropdown', async () => { + mockEngine = 'whisper'; + render(); + + fireEvent.click(await screen.findByText('Whisper', { exact: true })); + fireEvent.click(await screen.findByText('Parakeet', { exact: true })); + + await waitFor(() => { + expect(mockUpdateEngine).toHaveBeenCalledWith('parakeet'); + }); + }); + + it('downloads the parakeet model via the parakeet command', async () => { + mockEngine = 'parakeet'; + render(); + + fireEvent.click(await screen.findByText('transModel_btnDownload', { exact: true })); + + await waitFor(() => { + expect(mockInvoke).toHaveBeenCalledWith( + 'download_parakeet_model', + expect.objectContaining({ onEvent: expect.anything() }), + ); + }); + }); + + it('deletes only the selected engine model directory, preserving shared VAD', async () => { + mockEngine = 'parakeet'; + mockInvoke.mockResolvedValue(true); // model ready -> delete button shows + vi.spyOn(window, 'confirm').mockReturnValue(true); + render(); + + fireEvent.click(await screen.findByText('transModel_btnDelete', { exact: true })); + + await waitFor(() => { + expect(mockRemove).toHaveBeenCalledWith( + '/data/models/sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8', + { recursive: true }, + ); + }); + }); +}); diff --git a/src/components/settings/TranscriptionSection.tsx b/src/components/settings/TranscriptionSection.tsx index c9d984f..acbed9c 100644 --- a/src/components/settings/TranscriptionSection.tsx +++ b/src/components/settings/TranscriptionSection.tsx @@ -1,12 +1,15 @@ import { Channel, invoke } from '@tauri-apps/api/core'; import { join } from '@tauri-apps/api/path'; -import { readDir, remove } from '@tauri-apps/plugin-fs'; +import { remove } from '@tauri-apps/plugin-fs'; import { FileText } from 'lucide-react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { getDataDirectory } from '../../lib/constants'; -import type { DownloadEvent } from '../../types'; +import { useSetting } from '../../hooks/useSettings'; +import { asrEngineInfo } from '../../lib/asr'; +import { DEFAULT_SETTINGS, getDataDirectory } from '../../lib/constants'; +import { Dropdown } from '../ui/Dropdown'; +import type { AsrEngine, DownloadEvent } from '../../types'; type DownloadProgress = { downloadedBytes: number; @@ -14,33 +17,48 @@ type DownloadProgress = { extracting: boolean; }; +const ENGINE_MODELS: Record = { + whisper: { name: 'Whisper Large V3 Turbo', size: '1.6 GB' }, + parakeet: { name: 'Parakeet TDT 0.6B v3', size: '~0.6 GB' }, +}; + const panelClasses = 'relative z-0 rounded-2xl border border-gray-200/80 bg-white/75 p-4 shadow-sm backdrop-blur-sm focus-within:z-20 dark:border-gray-700/80 dark:bg-gray-900/45'; export function TranscriptionSection() { const { t } = useTranslation('settings'); + const [asrEngine, updateAsrEngine] = useSetting('asrEngine'); const [modelReady, setModelReady] = useState(false); const [loadingModelState, setLoadingModelState] = useState(false); const [working, setWorking] = useState(false); const [errorMessage, setErrorMessage] = useState(null); const [downloadProgress, setDownloadProgress] = useState(null); - const activeModel = 'Whisper Large V3 Turbo'; - const activeModelSize = '1.6 GB'; + const engine = asrEngine ?? DEFAULT_SETTINGS.asrEngine; + const activeModel = ENGINE_MODELS[engine].name; + const activeModelSize = ENGINE_MODELS[engine].size; + + const engineOptions = useMemo( + () => [ + { value: 'whisper' as const, label: 'Whisper' }, + { value: 'parakeet' as const, label: 'Parakeet' }, + ], + [], + ); const refreshModelState = useCallback(async () => { setLoadingModelState(true); setErrorMessage(null); try { - const ready = await invoke('check_model_ready'); + const ready = await invoke(asrEngineInfo(engine).readyCommand); setModelReady(ready); } catch { setErrorMessage(t('transModel_errorCheck')); } finally { setLoadingModelState(false); } - }, [t]); + }, [engine, t]); useEffect(() => { void refreshModelState(); @@ -57,13 +75,8 @@ export function TranscriptionSection() { try { const dataDir = await getDataDirectory(); - const modelsPath = await join(dataDir, 'models'); - const entries = await readDir(modelsPath); - - for (const entry of entries) { - const targetPath = await join(modelsPath, entry.name); - await remove(targetPath, { recursive: true }); - } + const modelPath = await join(dataDir, 'models', asrEngineInfo(engine).modelDirName); + await remove(modelPath, { recursive: true }); setModelReady(false); await refreshModelState(); @@ -72,7 +85,7 @@ export function TranscriptionSection() { } finally { setWorking(false); } - }, [refreshModelState, t]); + }, [engine, refreshModelState, t]); const handleDownloadModel = useCallback(async () => { setWorking(true); @@ -115,7 +128,7 @@ export function TranscriptionSection() { }; try { - await invoke('download_model', { + await invoke(asrEngineInfo(engine).downloadCommand, { onEvent: channel, }); await refreshModelState(); @@ -125,7 +138,7 @@ export function TranscriptionSection() { setWorking(false); setDownloadProgress(null); } - }, [refreshModelState, t]); + }, [engine, refreshModelState, t]); const progressPercent = useMemo(() => { if (!downloadProgress || !downloadProgress.totalBytes) { @@ -150,6 +163,22 @@ export function TranscriptionSection() { +
+

{t('asrEngine_title')}

+

{t('asrEngine_description')}

+ +
+ void updateAsrEngine(value)} + size="regular" + fullWidth + className="w-full" + /> +
+
+

{t('transModel_title')}

{t('transModel_description', { model: activeModel })}

diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index 9d76410..cd44f8b 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -46,7 +46,10 @@ "audioSource_hint": "Maximum recording duration remains capped at 4 hours.", "transcription_title": "Transcription", - "transcription_description": "Manage local Whisper model availability. Recording language is auto-detected.", + "transcription_description": "Manage local speech-to-text model availability. Recording language is auto-detected.", + + "asrEngine_title": "Speech Recognition Engine", + "asrEngine_description": "Choose which local model transcribes your recordings.", "transModel_title": "Transcription Model", "transModel_description": "{{model}} runs fully local and powers transcript generation.", diff --git a/src/i18n/locales/pl/settings.json b/src/i18n/locales/pl/settings.json index ae60118..e1c1771 100644 --- a/src/i18n/locales/pl/settings.json +++ b/src/i18n/locales/pl/settings.json @@ -46,7 +46,10 @@ "audioSource_hint": "Maksymalny czas nagrywania jest ograniczony do 4 godzin.", "transcription_title": "Transkrypcja", - "transcription_description": "Zarządzaj dostępnością lokalnego modelu Whisper. Język nagrania jest wykrywany automatycznie.", + "transcription_description": "Zarządzaj dostępnością lokalnego modelu transkrypcji mowy. Język nagrania jest wykrywany automatycznie.", + + "asrEngine_title": "Silnik rozpoznawania mowy", + "asrEngine_description": "Wybierz, który lokalny model transkrybuje Twoje nagrania.", "transModel_title": "Model transkrypcji", "transModel_description": "{{model}} działa w pełni lokalnie i generuje transkrypcje.", From 814b080f25399dc80db019fab3c401f978e83e6a Mon Sep 17 00:00:00 2001 From: Grzegorz <19194188+farce1@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:00:33 +0200 Subject: [PATCH 4/4] feat(transcription): honor explicit ASR engine selection from settings resolve_asr_engine now takes an optional preference: an explicit Whisper choice always wins, an explicit Parakeet choice is used when its model is present (else falls back to availability), and no preference keeps the availability-based behavior. The persisted asrEngine setting is threaded from start_session through SessionStartArgs and StartWorkerArgs to the worker. --- src-tauri/src/commands.rs | 3 ++ src-tauri/src/session.rs | 3 ++ src-tauri/src/transcription/mod.rs | 44 +++++++++++++++++++++--------- src/hooks/useSession.ts | 2 ++ 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index b65cf55..986b0d0 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -266,6 +266,7 @@ pub async fn start_session( on_segment: Channel, audio_source: Option, preferred_mic_device: Option, + asr_engine: Option, ) -> Result { let session_handle = app.state::().inner().clone(); let session_handle_for_start = session_handle.clone(); @@ -286,6 +287,7 @@ pub async fn start_session( on_segment, audio_source, preferred_mic_device, + asr_engine, }) }) .await @@ -523,6 +525,7 @@ pub async fn start_transcription( db_pool: None, meeting_id: None, on_worker_disconnected: None, + asr_engine_preference: None, })?; let _ = app.emit("transcribing-active", ()); diff --git a/src-tauri/src/session.rs b/src-tauri/src/session.rs index 4c4724b..e7ebb22 100644 --- a/src-tauri/src/session.rs +++ b/src-tauri/src/session.rs @@ -55,6 +55,7 @@ pub struct SessionStartArgs { pub on_segment: Channel, pub audio_source: Option, pub preferred_mic_device: Option, + pub asr_engine: Option, } impl SessionCoordinator { @@ -95,6 +96,7 @@ impl SessionCoordinator { on_segment, audio_source, preferred_mic_device, + asr_engine, } = args; if !matches!(self.phase, SessionPhase::Idle | SessionPhase::Processing) { @@ -174,6 +176,7 @@ impl SessionCoordinator { db_pool: Some(pool.clone()), meeting_id: Some(meeting_id), on_worker_disconnected: Some(degraded_callback), + asr_engine_preference: asr_engine, }) }; diff --git a/src-tauri/src/transcription/mod.rs b/src-tauri/src/transcription/mod.rs index 74c2176..062fa1f 100644 --- a/src-tauri/src/transcription/mod.rs +++ b/src-tauri/src/transcription/mod.rs @@ -29,6 +29,7 @@ pub struct StartWorkerArgs { pub db_pool: Option, pub meeting_id: Option, pub on_worker_disconnected: Option>, + pub asr_engine_preference: Option, } #[derive(Debug)] @@ -80,9 +81,11 @@ fn join_with_timeout(handle: JoinHandle<()>, timeout: Duration) -> bool { joined_rx.recv_timeout(timeout).is_ok() } -fn resolve_asr_engine(data_dir: &Path) -> (String, PathBuf) { - // Prefer Parakeet when its model is present; otherwise fall back to Whisper. - if model::check_parakeet_assets_ready(data_dir) { +fn resolve_asr_engine(preference: Option<&str>, data_dir: &Path) -> (String, PathBuf) { + // Explicit Whisper always wins; otherwise prefer Parakeet when its model is present. + let use_parakeet = + preference != Some("whisper") && model::check_parakeet_assets_ready(data_dir); + if use_parakeet { ("parakeet".to_string(), model::parakeet_model_dir(data_dir)) } else { ("whisper".to_string(), model::whisper_turbo_model_dir(data_dir)) @@ -101,9 +104,11 @@ pub fn start_transcription_worker( db_pool, meeting_id, on_worker_disconnected, + asr_engine_preference, } = args; - let (asr_engine, model_dir) = resolve_asr_engine(data_dir.as_path()); + let (asr_engine, model_dir) = + resolve_asr_engine(asr_engine_preference.as_deref(), data_dir.as_path()); let assets_ready = match asr_engine.as_str() { "parakeet" => model::check_parakeet_assets_ready(data_dir.as_path()), @@ -318,28 +323,41 @@ mod tests { use super::*; #[test] - fn resolve_prefers_parakeet_when_present_else_whisper() { + fn resolve_honors_explicit_preference_then_availability() { let tmp = std::env::temp_dir().join(format!("on-engine-sel-{}", std::process::id())); let _ = std::fs::remove_dir_all(&tmp); - let (engine, dir) = resolve_asr_engine(&tmp); - assert_eq!(engine, "whisper"); - assert_eq!(dir, model::whisper_turbo_model_dir(&tmp)); + let whisper_dir = model::whisper_turbo_model_dir(&tmp); + let parakeet_dir = model::parakeet_model_dir(&tmp); + + // Nothing downloaded: availability and explicit whisper both pick whisper; + // explicit parakeet falls back to availability (whisper) since its model is absent. + assert_eq!(resolve_asr_engine(None, &tmp).0, "whisper"); + assert_eq!(resolve_asr_engine(Some("whisper"), &tmp).0, "whisper"); + assert_eq!(resolve_asr_engine(Some("parakeet"), &tmp).0, "whisper"); - let pdir = model::parakeet_model_dir(&tmp); - std::fs::create_dir_all(&pdir).unwrap(); + std::fs::create_dir_all(¶keet_dir).unwrap(); for f in [ "encoder.int8.onnx", "decoder.int8.onnx", "joiner.int8.onnx", "tokens.txt", ] { - std::fs::write(pdir.join(f), b"x").unwrap(); + std::fs::write(parakeet_dir.join(f), b"x").unwrap(); } - let (engine, dir) = resolve_asr_engine(&tmp); + // No preference -> availability prefers parakeet when present. + let (engine, dir) = resolve_asr_engine(None, &tmp); assert_eq!(engine, "parakeet"); - assert_eq!(dir, model::parakeet_model_dir(&tmp)); + assert_eq!(dir, parakeet_dir); + + // Explicit whisper wins even though parakeet is present. + let (engine, dir) = resolve_asr_engine(Some("whisper"), &tmp); + assert_eq!(engine, "whisper"); + assert_eq!(dir, whisper_dir); + + // Explicit parakeet with its model present. + assert_eq!(resolve_asr_engine(Some("parakeet"), &tmp).0, "parakeet"); std::fs::remove_dir_all(&tmp).ok(); } diff --git a/src/hooks/useSession.ts b/src/hooks/useSession.ts index 2b686af..766e1dc 100644 --- a/src/hooks/useSession.ts +++ b/src/hooks/useSession.ts @@ -40,11 +40,13 @@ export function useSession() { channelRef.current = channel; const audioSource = await getSetting('defaultAudioSource'); const preferredMicDevice = await getSetting('preferredMicDevice'); + const asrEngine = await getSetting('asrEngine'); const nextMeetingId = await invoke('start_session', { onSegment: channel, audioSource: audioSource || undefined, preferredMicDevice: preferredMicDevice || undefined, + asrEngine: asrEngine || undefined, }); setMeetingId(nextMeetingId);