Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions src-tauri/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,11 @@ pub trait ShortcutAction: Send + Sync {
}

// Transcribe Action
struct TranscribeAction;

async fn maybe_post_process_transcription(
settings: &AppSettings,
transcription: &str,
) -> Option<String> {
if !settings.post_process_enabled {
return None;
}
struct TranscribeAction {
post_process: bool,
}

async fn post_process_transcription(settings: &AppSettings, transcription: &str) -> Option<String> {
let provider = match settings.active_post_process_provider().cloned() {
Some(provider) => provider,
None => {
Expand Down Expand Up @@ -305,6 +300,7 @@ impl ShortcutAction for TranscribeAction {
play_feedback_sound(app, SoundType::Stop);

let binding_id = binding_id.to_string(); // Clone binding_id for the async task
let post_process = self.post_process;

tauri::async_runtime::spawn(async move {
let binding_id = binding_id.clone(); // Clone for the inner async task
Expand Down Expand Up @@ -343,11 +339,14 @@ impl ShortcutAction for TranscribeAction {
final_text = converted_text;
}

// Then apply regular post-processing if enabled
// Then apply LLM post-processing if this is the post-process hotkey
// Uses final_text which may already have Chinese conversion applied
if let Some(processed_text) =
maybe_post_process_transcription(&settings, &final_text).await
{
let processed = if post_process {
post_process_transcription(&settings, &final_text).await
} else {
None
};
if let Some(processed_text) = processed {
post_processed_text = Some(processed_text.clone());
final_text = processed_text;

Expand Down Expand Up @@ -474,7 +473,13 @@ pub static ACTION_MAP: Lazy<HashMap<String, Arc<dyn ShortcutAction>>> = Lazy::ne
let mut map = HashMap::new();
map.insert(
"transcribe".to_string(),
Arc::new(TranscribeAction) as Arc<dyn ShortcutAction>,
Arc::new(TranscribeAction {
post_process: false,
}) as Arc<dyn ShortcutAction>,
);
map.insert(
"transcribe_with_post_process".to_string(),
Arc::new(TranscribeAction { post_process: true }) as Arc<dyn ShortcutAction>,
);
map.insert(
"cancel".to_string(),
Expand Down
11 changes: 11 additions & 0 deletions src-tauri/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,17 @@ pub fn get_default_settings() -> AppSettings {
current_binding: default_shortcut.to_string(),
},
);
bindings.insert(
"transcribe_with_post_process".to_string(),
ShortcutBinding {
id: "transcribe_with_post_process".to_string(),
name: "Transcribe with Post-Processing".to_string(),
description: "Converts your speech into text and applies AI post-processing."
.to_string(),
default_binding: String::new(),
current_binding: String::new(),
},
);
bindings.insert(
"cancel".to_string(),
ShortcutBinding {
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/shortcut/handy_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,10 @@ pub fn init_shortcuts(app: &AppHandle) -> Result<(), String> {
if id == "cancel" {
continue;
}
// Skip post-processing shortcut when the feature is disabled
if id == "transcribe_with_post_process" && !user_settings.post_process_enabled {
continue;
}

let binding = user_settings
.bindings
Expand Down
53 changes: 44 additions & 9 deletions src-tauri/src/shortcut/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,30 @@ pub fn change_binding(
) -> Result<BindingResponse, String> {
let mut settings = settings::get_settings(&app);

// Get the binding to modify
// Get the binding to modify, or create it from defaults if it doesn't exist
let binding_to_modify = match settings.bindings.get(&id) {
Some(binding) => binding.clone(),
None => {
let error_msg = format!("Binding with id '{}' not found", id);
warn!("change_binding error: {}", error_msg);
return Ok(BindingResponse {
success: false,
binding: None,
error: Some(error_msg),
});
// Try to get the default binding for this id
let default_settings = settings::get_default_settings();
match default_settings.bindings.get(&id) {
Some(default_binding) => {
warn!(
"Binding '{}' not found in settings, creating from defaults",
id
);
default_binding.clone()
}
None => {
let error_msg = format!("Binding with id '{}' not found in defaults", id);
warn!("change_binding error: {}", error_msg);
return Ok(BindingResponse {
success: false,
binding: None,
error: Some(error_msg),
});
}
}
}
};

Expand Down Expand Up @@ -374,6 +387,11 @@ fn register_all_shortcuts_for_implementation(
continue;
}

// Skip post-processing shortcut when the feature is disabled
if id == "transcribe_with_post_process" && !current_settings.post_process_enabled {
continue;
}

let mut binding = current_settings
.bindings
.get(id)
Expand Down Expand Up @@ -680,7 +698,24 @@ pub fn change_clipboard_handling_setting(app: AppHandle, handling: String) -> Re
pub fn change_post_process_enabled_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
let mut settings = settings::get_settings(&app);
settings.post_process_enabled = enabled;
settings::write_settings(&app, settings);
settings::write_settings(&app, settings.clone());

// Register or unregister the post-processing shortcut
if let Some(binding) = settings
.bindings
.get("transcribe_with_post_process")
.cloned()
{
if enabled {
// Only register if the user has actually set a binding
if !binding.current_binding.is_empty() {
let _ = register_shortcut(&app, binding);
}
} else {
let _ = unregister_shortcut(&app, binding);
}
}

Ok(())
}

Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/shortcut/tauri_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ pub fn init_shortcuts(app: &AppHandle) {
if id == "cancel" {
continue; // Skip cancel shortcut, it will be registered dynamically
}
// Skip post-processing shortcut when the feature is disabled
if id == "transcribe_with_post_process" && !user_settings.post_process_enabled {
continue;
}
let binding = user_settings
.bindings
.get(&id)
Expand Down
10 changes: 8 additions & 2 deletions src/components/settings/GlobalShortcutInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,16 @@ export const GlobalShortcutInput: React.FC<GlobalShortcutInputProps> = ({
</div>
) : (
<div
className="px-2 py-1 text-sm font-semibold bg-mid-gray/10 border border-mid-gray/80 hover:bg-logo-primary/10 rounded cursor-pointer hover:border-logo-primary"
className={`px-2 py-1 text-sm font-semibold rounded cursor-pointer min-w-[120px] text-center ${
binding.current_binding
? "bg-mid-gray/10 border border-mid-gray/80 hover:bg-logo-primary/10 hover:border-logo-primary"
: "bg-mid-gray/5 border border-mid-gray/40 hover:bg-logo-primary/10 hover:border-logo-primary text-mid-gray/60"
}`}
onClick={() => startRecording(shortcutId)}
>
{formatKeyCombination(binding.current_binding, osType)}
{binding.current_binding
? formatKeyCombination(binding.current_binding, osType)
: t("settings.general.shortcut.notSet")}
</div>
)}
<ResetButton
Expand Down
10 changes: 8 additions & 2 deletions src/components/settings/HandyKeysShortcutInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,16 @@ export const HandyKeysShortcutInput: React.FC<HandyKeysShortcutInputProps> = ({
</div>
) : (
<div
className="px-2 py-1 text-sm font-semibold bg-mid-gray/10 border border-mid-gray/80 hover:bg-logo-primary/10 rounded cursor-pointer hover:border-logo-primary"
className={`px-2 py-1 text-sm font-semibold rounded cursor-pointer min-w-[120px] text-center ${
binding.current_binding
? "bg-mid-gray/10 border border-mid-gray/80 hover:bg-logo-primary/10 hover:border-logo-primary"
: "bg-mid-gray/5 border border-mid-gray/40 hover:bg-logo-primary/10 hover:border-logo-primary text-mid-gray/60"
}`}
onClick={startRecording}
>
{formatKeyCombination(binding.current_binding, osType)}
{binding.current_binding
? formatKeyCombination(binding.current_binding, osType)
: t("settings.general.shortcut.notSet")}
</div>
)}
<ResetButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type { ModelOption } from "./types";
import type { DropdownOption } from "../../ui/Dropdown";

type PostProcessProviderState = {
enabled: boolean;
providerOptions: DropdownOption[];
selectedProviderId: string;
selectedProvider: PostProcessProvider | undefined;
Expand Down Expand Up @@ -43,8 +42,6 @@ export const usePostProcessProviderState = (): PostProcessProviderState => {
postProcessModelOptions,
} = useSettings();

const enabled = settings?.post_process_enabled || false;

// Settings are guaranteed to have providers after migration
const providers = settings?.post_process_providers || [];

Expand Down Expand Up @@ -191,7 +188,6 @@ export const usePostProcessProviderState = (): PostProcessProviderState => {
// No automatic fetching - user must click refresh button

return {
enabled,
providerOptions,
selectedProviderId,
selectedProvider,
Expand Down
34 changes: 9 additions & 25 deletions src/components/settings/post-processing/PostProcessingSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,13 @@ import { BaseUrlField } from "../PostProcessingSettingsApi/BaseUrlField";
import { ApiKeyField } from "../PostProcessingSettingsApi/ApiKeyField";
import { ModelSelect } from "../PostProcessingSettingsApi/ModelSelect";
import { usePostProcessProviderState } from "../PostProcessingSettingsApi/usePostProcessProviderState";
import { ShortcutInput } from "../ShortcutInput";
import { useSettings } from "../../../hooks/useSettings";

const DisabledNotice: React.FC<{ children: React.ReactNode }> = ({
children,
}) => (
<div className="p-4 bg-mid-gray/5 rounded-lg border border-mid-gray/20">
<p className="text-sm text-mid-gray">{children}</p>
</div>
);

const PostProcessingSettingsApiComponent: React.FC = () => {
const { t } = useTranslation();
const state = usePostProcessProviderState();

if (!state.enabled) {
return (
<DisabledNotice>
{t("settings.postProcessing.disabledNotice")}
</DisabledNotice>
);
}

return (
<>
<SettingContainer
Expand Down Expand Up @@ -166,7 +151,6 @@ const PostProcessingSettingsPromptsComponent: React.FC = () => {
const [draftName, setDraftName] = useState("");
const [draftText, setDraftText] = useState("");

const enabled = getSetting("post_process_enabled") || false;
const prompts = getSetting("post_process_prompts") || [];
const selectedPromptId = getSetting("post_process_selected_prompt_id") || "";
const selectedPrompt =
Expand Down Expand Up @@ -257,14 +241,6 @@ const PostProcessingSettingsPromptsComponent: React.FC = () => {
setDraftText("");
};

if (!enabled) {
return (
<DisabledNotice>
{t("settings.postProcessing.disabledNotice")}
</DisabledNotice>
);
}

const hasPrompts = prompts.length > 0;
const isDirty =
!!selectedPrompt &&
Expand Down Expand Up @@ -452,6 +428,14 @@ export const PostProcessingSettings: React.FC = () => {

return (
<div className="max-w-3xl w-full mx-auto space-y-6">
<SettingsGroup title={t("settings.postProcessing.hotkey.title")}>
<ShortcutInput
shortcutId="transcribe_with_post_process"
descriptionMode="tooltip"
grouped={true}
/>
</SettingsGroup>

<SettingsGroup title={t("settings.postProcessing.api.title")}>
<PostProcessingSettingsApi />
</SettingsGroup>
Expand Down
1 change: 0 additions & 1 deletion src/i18n/locales/ar/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@
},
"postProcessing": {
"title": "معالجة لاحقة",
"disabledNotice": "المعالجة اللاحقة معطلة حالياً. قم بتمكينها في إعدادات تصحيح الأخطاء للتكوين.",
"api": {
"title": "API (متوافق مع OpenAI)",
"provider": {
Expand Down
1 change: 0 additions & 1 deletion src/i18n/locales/cs/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@
},
"postProcessing": {
"title": "Následné zpracování",
"disabledNotice": "Následné zpracování je nyní vypnuto. Pro konfiguraci jej zapněte v nastavení Ladění.",
"api": {
"title": "API (kompatibilní s OpenAI)",
"provider": {
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,9 @@
},
"postProcessing": {
"title": "Nachbearbeitung",
"disabledNotice": "Die Nachbearbeitung ist derzeit deaktiviert. Aktiviere sie in den Debug-Einstellungen, um sie zu konfigurieren.",
"hotkey": {
"title": "Tastenkürzel"
},
"api": {
"title": "API (OpenAI-kompatibel)",
"provider": {
Expand Down
9 changes: 8 additions & 1 deletion src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"loading": "Loading shortcuts...",
"none": "No shortcuts configured",
"notFound": "Shortcut not found",
"notSet": "Not set",
"pressKeys": "Press keys...",
"bindings": {
"transcribe": {
Expand All @@ -119,6 +120,10 @@
"cancel": {
"name": "Cancel Shortcut",
"description": "The keyboard shortcut to cancel the current recording."
},
"transcribe_with_post_process": {
"name": "Post-Processing Hotkey",
"description": "Optional: A dedicated hotkey that always applies AI post-processing to your transcription."
}
},
"errors": {
Expand Down Expand Up @@ -241,7 +246,9 @@
},
"postProcessing": {
"title": "Post Process",
"disabledNotice": "Post processing is currently disabled. Enable it in Debug settings to configure.",
"hotkey": {
"title": "Hotkey"
},
"api": {
"title": "API (OpenAI Compatible)",
"provider": {
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,9 @@
},
"postProcessing": {
"title": "Post Proceso",
"disabledNotice": "El post procesamiento está actualmente deshabilitado. Habilítalo en la configuración de Depuración para configurarlo.",
"hotkey": {
"title": "Tecla de acceso rápido"
},
"api": {
"title": "API (Compatible con OpenAI)",
"provider": {
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,9 @@
},
"postProcessing": {
"title": "Post-traitement",
"disabledNotice": "Le post-traitement est actuellement désactivé. Activez-le dans les paramètres de débogage pour le configurer.",
"hotkey": {
"title": "Raccourci clavier"
},
"api": {
"title": "API (Compatible OpenAI)",
"provider": {
Expand Down
Loading