Merge pull request #79 from cjpais/tweak-correct-thresh
new default correction threshold + debug menu
This commit is contained in:
commit
b074aad19a
15 changed files with 161 additions and 54 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ pub struct TranscriptionManager {
|
|||
current_model_id: Mutex<Option<String>>,
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ pub struct AppSettings {
|
|||
pub debug_mode: bool,
|
||||
#[serde(default)]
|
||||
pub custom_words: Vec<String>,
|
||||
#[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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -171,6 +171,17 @@ pub fn update_custom_words(app: AppHandle, words: Vec<String>) -> 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").
|
||||
|
|
|
|||
|
|
@ -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<DebugPathsProps> = ({
|
||||
export const AppDataDirectory: React.FC<AppDataDirectoryProps> = ({
|
||||
descriptionMode = "inline",
|
||||
grouped = false,
|
||||
}) => {
|
||||
|
|
@ -16,18 +16,20 @@ export const DebugPaths: React.FC<DebugPathsProps> = ({
|
|||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadPaths = async () => {
|
||||
const loadAppDirectory = async () => {
|
||||
try {
|
||||
const result = await invoke<string>("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<DebugPathsProps> = ({
|
|||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="animate-pulse">
|
||||
<div className="h-4 bg-gray-200 rounded w-1/3 mb-2"></div>
|
||||
<div className="h-8 bg-gray-100 rounded"></div>
|
||||
</div>
|
||||
<div className="animate-pulse">
|
||||
<div className="h-4 bg-gray-200 rounded w-1/3 mb-2"></div>
|
||||
<div className="h-8 bg-gray-100 rounded"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -49,7 +49,9 @@ export const DebugPaths: React.FC<DebugPathsProps> = ({
|
|||
if (error) {
|
||||
return (
|
||||
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p className="text-red-600 text-sm">Error loading paths: {error}</p>
|
||||
<p className="text-red-600 text-sm">
|
||||
Error loading app directory: {error}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -60,7 +62,7 @@ export const DebugPaths: React.FC<DebugPathsProps> = ({
|
|||
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}
|
||||
|
|
@ -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 && (
|
||||
<SettingsGroup title="Debug">
|
||||
<DebugSettings descriptionMode="tooltip" grouped={true} />
|
||||
<WordCorrectionThreshold descriptionMode="tooltip" grouped={true} />
|
||||
<AppDataDirectory descriptionMode="tooltip" grouped={true} />
|
||||
</SettingsGroup>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
import React from "react";
|
||||
import { DebugPaths } from "./DebugPaths";
|
||||
|
||||
interface DebugSettingsProps {
|
||||
descriptionMode?: "tooltip" | "inline";
|
||||
grouped?: boolean;
|
||||
}
|
||||
|
||||
export const DebugSettings: React.FC<DebugSettingsProps> = ({
|
||||
descriptionMode = "inline",
|
||||
grouped = false,
|
||||
}) => {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{descriptionMode === "inline" && !grouped && (
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
<h2 className="text-xl font-semibold text-gray-900">Debug</h2>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">
|
||||
Debug information and developer tools. These settings are only
|
||||
visible when debug mode is enabled.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DebugPaths descriptionMode={descriptionMode} grouped={true} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
31
src/components/settings/debug/WordCorrectionThreshold.tsx
Normal file
31
src/components/settings/debug/WordCorrectionThreshold.tsx
Normal file
|
|
@ -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 (
|
||||
<Slider
|
||||
value={settings?.word_correction_threshold ?? 0.2}
|
||||
onChange={handleThresholdChange}
|
||||
min={0.0}
|
||||
max={1.0}
|
||||
label="Word Correction Threshold"
|
||||
description="Controls how aggressively custom words are applied. Lower values mean fewer corrections will be made, higher values mean more corrections. Range: 0 (least aggressive) to 1 (most aggressive)."
|
||||
descriptionMode={descriptionMode}
|
||||
grouped={grouped}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,2 +1 @@
|
|||
export { DebugSettings } from "./DebugSettings";
|
||||
export { DebugPaths } from "./DebugPaths";
|
||||
export { WordCorrectionThreshold } from "./WordCorrectionThreshold";
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@ export { ShowOverlay } from "./ShowOverlay";
|
|||
export { HandyShortcut } from "./HandyShortcut";
|
||||
export { TranslateToEnglish } from "./TranslateToEnglish";
|
||||
export { CustomWords } from "./CustomWords";
|
||||
export { AppDataDirectory } from "./AppDataDirectory";
|
||||
|
|
|
|||
73
src/components/ui/Slider.tsx
Normal file
73
src/components/ui/Slider.tsx
Normal file
|
|
@ -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<SliderProps> = ({
|
||||
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<HTMLInputElement>) => {
|
||||
onChange(parseFloat(e.target.value));
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingContainer
|
||||
title={label}
|
||||
description={description}
|
||||
descriptionMode={descriptionMode}
|
||||
grouped={grouped}
|
||||
layout="horizontal"
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="flex items-center space-x-1 h-6">
|
||||
<input
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
disabled={disabled}
|
||||
className="flex-grow h-2 rounded-lg appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-logo-primary disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
style={{
|
||||
background: `linear-gradient(to right, #FAA2CA 0%, #FAA2CA ${
|
||||
((value - min) / (max - min)) * 100
|
||||
}%, rgba(128, 128, 128, 0.2) ${
|
||||
((value - min) / (max - min)) * 100
|
||||
}%, rgba(128, 128, 128, 0.2) 100%)`,
|
||||
}}
|
||||
/>
|
||||
{showValue && (
|
||||
<span className="text-sm font-medium text-text/90 min-w-10 text-right">
|
||||
{formatValue(value)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
export { Dropdown } from "./Dropdown";
|
||||
export { Slider } from "./Slider";
|
||||
export { ToggleSwitch } from "./ToggleSwitch";
|
||||
export { SettingContainer } from "./SettingContainer";
|
||||
export { SettingsGroup } from "./SettingsGroup";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ const RecordingOverlay: React.FC = () => {
|
|||
});
|
||||
|
||||
smoothedLevelsRef.current = smoothed;
|
||||
console.log(smoothed.length);
|
||||
setLevels(smoothed.slice(0, 9));
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue