diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a7761d4..6ea57b9 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -187,6 +187,7 @@ pub fn run() { shortcut::change_selected_language_setting, shortcut::change_overlay_position_setting, shortcut::change_debug_mode_setting, + shortcut::change_word_correction_threshold_setting, shortcut::update_custom_words, shortcut::suspend_binding, shortcut::resume_binding, diff --git a/src-tauri/src/managers/transcription.rs b/src-tauri/src/managers/transcription.rs index 51ea000..bd68c41 100644 --- a/src-tauri/src/managers/transcription.rs +++ b/src-tauri/src/managers/transcription.rs @@ -27,7 +27,7 @@ pub struct TranscriptionManager { current_model_id: Mutex>, } -fn apply_custom_words(text: &str, custom_words: &[String]) -> String { +fn apply_custom_words(text: &str, custom_words: &[String], threshold: f64) -> String { if custom_words.is_empty() { return text.to_string(); } @@ -83,8 +83,8 @@ fn apply_custom_words(text: &str, custom_words: &[String]) -> String { levenshtein_score }; - // Accept if the score is good enough (threshold: 0.4 for good matches) - if combined_score < 0.4 && combined_score < best_score { + // Accept if the score is good enough (configurable threshold) + if combined_score < threshold && combined_score < best_score { best_match = Some(&custom_words[i]); best_score = combined_score; } @@ -319,7 +319,11 @@ impl TranscriptionManager { // Apply word correction if custom words are configured let corrected_result = if !settings.custom_words.is_empty() { - apply_custom_words(&result, &settings.custom_words) + apply_custom_words( + &result, + &settings.custom_words, + settings.word_correction_threshold, + ) } else { result }; diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index 6b08cf8..9398d66 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -44,6 +44,8 @@ pub struct AppSettings { pub debug_mode: bool, #[serde(default)] pub custom_words: Vec, + #[serde(default = "default_word_correction_threshold")] + pub word_correction_threshold: f64, } fn default_model() -> String { @@ -70,13 +72,17 @@ fn default_debug_mode() -> bool { false } +fn default_word_correction_threshold() -> f64 { + 0.2 +} + pub const SETTINGS_STORE_PATH: &str = "settings_store.json"; pub fn get_default_settings() -> AppSettings { #[cfg(target_os = "windows")] let default_shortcut = "ctrl+space"; #[cfg(target_os = "macos")] - let default_shortcut = "alt+space"; + let default_shortcut = "option+space"; #[cfg(target_os = "linux")] let default_shortcut = "ctrl+space"; #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] @@ -107,6 +113,7 @@ pub fn get_default_settings() -> AppSettings { overlay_position: OverlayPosition::Bottom, debug_mode: false, custom_words: Vec::new(), + word_correction_threshold: default_word_correction_threshold(), } } diff --git a/src-tauri/src/shortcut.rs b/src-tauri/src/shortcut.rs index d1db091..5da780e 100644 --- a/src-tauri/src/shortcut.rs +++ b/src-tauri/src/shortcut.rs @@ -171,6 +171,17 @@ pub fn update_custom_words(app: AppHandle, words: Vec) -> Result<(), Str Ok(()) } +#[tauri::command] +pub fn change_word_correction_threshold_setting( + app: AppHandle, + threshold: f64, +) -> Result<(), String> { + let mut settings = settings::get_settings(&app); + settings.word_correction_threshold = threshold; + settings::write_settings(&app, settings); + Ok(()) +} + /// Determine whether a shortcut string contains at least one non-modifier key. /// We allow single non-modifier keys (e.g. "f5" or "space") but disallow /// modifier-only combos (e.g. "ctrl" or "ctrl+shift"). diff --git a/src/components/settings/debug/DebugPaths.tsx b/src/components/settings/AppDataDirectory.tsx similarity index 67% rename from src/components/settings/debug/DebugPaths.tsx rename to src/components/settings/AppDataDirectory.tsx index 094db3f..4af03a0 100644 --- a/src/components/settings/debug/DebugPaths.tsx +++ b/src/components/settings/AppDataDirectory.tsx @@ -1,13 +1,13 @@ import React, { useState, useEffect } from "react"; import { invoke } from "@tauri-apps/api/core"; -import { TextDisplay, SettingsGroup } from "../../ui"; +import { TextDisplay } from "../ui"; -interface DebugPathsProps { +interface AppDataDirectoryProps { descriptionMode?: "tooltip" | "inline"; grouped?: boolean; } -export const DebugPaths: React.FC = ({ +export const AppDataDirectory: React.FC = ({ descriptionMode = "inline", grouped = false, }) => { @@ -16,18 +16,20 @@ export const DebugPaths: React.FC = ({ const [error, setError] = useState(null); useEffect(() => { - const loadPaths = async () => { + const loadAppDirectory = async () => { try { const result = await invoke("get_app_dir_path"); setAppDirPath(result); } catch (err) { - setError(err instanceof Error ? err.message : "Failed to load paths"); + setError( + err instanceof Error ? err.message : "Failed to load app directory", + ); } finally { setLoading(false); } }; - loadPaths(); + loadAppDirectory(); }, []); const handleCopy = (value: string) => { @@ -37,11 +39,9 @@ export const DebugPaths: React.FC = ({ if (loading) { return ( -
-
-
-
-
+
+
+
); } @@ -49,7 +49,9 @@ export const DebugPaths: React.FC = ({ if (error) { return (
-

Error loading paths: {error}

+

+ Error loading app directory: {error} +

); } @@ -60,7 +62,7 @@ export const DebugPaths: React.FC = ({ description="Main directory where application data, settings, and models are stored" value={appDirPath} descriptionMode={descriptionMode} - grouped={true} + grouped={grouped} copyable={true} monospace={true} onCopy={handleCopy} diff --git a/src/components/settings/Settings.tsx b/src/components/settings/Settings.tsx index ca83a8d..4c6b42e 100644 --- a/src/components/settings/Settings.tsx +++ b/src/components/settings/Settings.tsx @@ -10,7 +10,8 @@ import { TranslateToEnglish } from "./TranslateToEnglish"; import { LanguageSelector } from "./LanguageSelector"; import { CustomWords } from "./CustomWords"; import { SettingsGroup } from "../ui/SettingsGroup"; -import { DebugSettings } from "./debug"; +import { WordCorrectionThreshold } from "./debug/WordCorrectionThreshold"; +import { AppDataDirectory } from "./AppDataDirectory"; import { useSettings } from "../../hooks/useSettings"; export const Settings: React.FC = () => { @@ -61,7 +62,8 @@ export const Settings: React.FC = () => { {settings?.debug_mode && ( - + + )}
diff --git a/src/components/settings/debug/DebugSettings.tsx b/src/components/settings/debug/DebugSettings.tsx deleted file mode 100644 index 5688afc..0000000 --- a/src/components/settings/debug/DebugSettings.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import { DebugPaths } from "./DebugPaths"; - -interface DebugSettingsProps { - descriptionMode?: "tooltip" | "inline"; - grouped?: boolean; -} - -export const DebugSettings: React.FC = ({ - descriptionMode = "inline", - grouped = false, -}) => { - return ( -
- {descriptionMode === "inline" && !grouped && ( -
-
-

Debug

-
-

- Debug information and developer tools. These settings are only - visible when debug mode is enabled. -

-
- )} - - -
- ); -}; diff --git a/src/components/settings/debug/WordCorrectionThreshold.tsx b/src/components/settings/debug/WordCorrectionThreshold.tsx new file mode 100644 index 0000000..8816689 --- /dev/null +++ b/src/components/settings/debug/WordCorrectionThreshold.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { Slider } from "../../ui/Slider"; +import { useSettings } from "../../../hooks/useSettings"; + +interface WordCorrectionThresholdProps { + descriptionMode?: "tooltip" | "inline"; + grouped?: boolean; +} + +export const WordCorrectionThreshold: React.FC< + WordCorrectionThresholdProps +> = ({ descriptionMode = "tooltip", grouped = false }) => { + const { settings, updateSetting } = useSettings(); + + const handleThresholdChange = (value: number) => { + updateSetting("word_correction_threshold", value); + }; + + return ( + + ); +}; diff --git a/src/components/settings/debug/index.ts b/src/components/settings/debug/index.ts index db08089..8c257be 100644 --- a/src/components/settings/debug/index.ts +++ b/src/components/settings/debug/index.ts @@ -1,2 +1 @@ -export { DebugSettings } from "./DebugSettings"; -export { DebugPaths } from "./DebugPaths"; +export { WordCorrectionThreshold } from "./WordCorrectionThreshold"; diff --git a/src/components/settings/index.ts b/src/components/settings/index.ts index 4838851..ff8c3ab 100644 --- a/src/components/settings/index.ts +++ b/src/components/settings/index.ts @@ -8,3 +8,4 @@ export { ShowOverlay } from "./ShowOverlay"; export { HandyShortcut } from "./HandyShortcut"; export { TranslateToEnglish } from "./TranslateToEnglish"; export { CustomWords } from "./CustomWords"; +export { AppDataDirectory } from "./AppDataDirectory"; diff --git a/src/components/ui/Slider.tsx b/src/components/ui/Slider.tsx new file mode 100644 index 0000000..005bd18 --- /dev/null +++ b/src/components/ui/Slider.tsx @@ -0,0 +1,73 @@ +import React from "react"; +import { SettingContainer } from "./SettingContainer"; + +interface SliderProps { + value: number; + onChange: (value: number) => void; + min: number; + max: number; + step?: number; + disabled?: boolean; + label: string; + description: string; + descriptionMode?: "inline" | "tooltip"; + grouped?: boolean; + showValue?: boolean; + formatValue?: (value: number) => string; +} + +export const Slider: React.FC = ({ + value, + onChange, + min, + max, + step = 0.01, + disabled = false, + label, + description, + descriptionMode = "tooltip", + grouped = false, + showValue = true, + formatValue = (v) => v.toFixed(2), +}) => { + const handleChange = (e: React.ChangeEvent) => { + onChange(parseFloat(e.target.value)); + }; + + return ( + +
+
+ + {showValue && ( + + {formatValue(value)} + + )} +
+
+
+ ); +}; diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index 4216f04..0ba978d 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -1,4 +1,5 @@ export { Dropdown } from "./Dropdown"; +export { Slider } from "./Slider"; export { ToggleSwitch } from "./ToggleSwitch"; export { SettingContainer } from "./SettingContainer"; export { SettingsGroup } from "./SettingsGroup"; diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 7b64811..63ce38b 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -210,6 +210,11 @@ export const useSettings = (): UseSettingsReturn => { case "custom_words": await invoke("update_custom_words", { words: value }); break; + case "word_correction_threshold": + await invoke("change_word_correction_threshold_setting", { + threshold: value, + }); + break; case "bindings": // Handle bindings separately - they use their own invoke methods break; @@ -220,7 +225,7 @@ export const useSettings = (): UseSettingsReturn => { console.warn(`No handler for setting: ${String(key)}`); } - console.log(`Setting ${String(key)} updated to:`, value); + // console.log(`Setting ${String(key)} updated to:`, value); } catch (error) { console.error(`Failed to update setting ${String(key)}:`, error); diff --git a/src/lib/types.ts b/src/lib/types.ts index cde2622..366cd75 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -35,6 +35,7 @@ export const SettingsSchema = z.object({ overlay_position: OverlayPositionSchema, debug_mode: z.boolean(), custom_words: z.array(z.string()).optional().default([]), + word_correction_threshold: z.number().optional().default(0.2), }); export const BindingResponseSchema = z.object({ diff --git a/src/overlay/RecordingOverlay.tsx b/src/overlay/RecordingOverlay.tsx index 16fc4b9..0ae0ba2 100644 --- a/src/overlay/RecordingOverlay.tsx +++ b/src/overlay/RecordingOverlay.tsx @@ -41,7 +41,6 @@ const RecordingOverlay: React.FC = () => { }); smoothedLevelsRef.current = smoothed; - console.log(smoothed.length); setLevels(smoothed.slice(0, 9)); });