feat: add optional hotkey for post-processing request (#355)
* feat: add post-processing hotkey * rewrite * bring back toggle * Make sure the toggle on and off propagates for the key binding. * rename for clarity * cleanup --------- Co-authored-by: CJ Pais <cj@cjpais.com>
This commit is contained in:
parent
81b90aa675
commit
462b03b024
25 changed files with 148 additions and 72 deletions
|
|
@ -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 => {
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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 || [];
|
||||
|
||||
|
|
@ -191,7 +188,6 @@ export const usePostProcessProviderState = (): PostProcessProviderState => {
|
|||
// No automatic fetching - user must click refresh button
|
||||
|
||||
return {
|
||||
enabled,
|
||||
providerOptions,
|
||||
selectedProviderId,
|
||||
selectedProvider,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 =
|
||||
|
|
@ -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 &&
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -241,7 +241,6 @@
|
|||
},
|
||||
"postProcessing": {
|
||||
"title": "معالجة لاحقة",
|
||||
"disabledNotice": "المعالجة اللاحقة معطلة حالياً. قم بتمكينها في إعدادات تصحيح الأخطاء للتكوين.",
|
||||
"api": {
|
||||
"title": "API (متوافق مع OpenAI)",
|
||||
"provider": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@
|
|||
"loading": "Loading shortcuts...",
|
||||
"none": "No shortcuts configured",
|
||||
"notFound": "Shortcut not found",
|
||||
"notSet": "Not set",
|
||||
"pressKeys": "Press keys...",
|
||||
"bindings": {
|
||||
"transcribe": {
|
||||
|
|
@ -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": {
|
||||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -237,7 +237,9 @@
|
|||
},
|
||||
"postProcessing": {
|
||||
"title": "Post-Elaborazione",
|
||||
"disabledNotice": "La post-elaborazione è attualmente disattivata. Abilitala nelle opzioni di debug per configurarla.",
|
||||
"hotkey": {
|
||||
"title": "Tasto di scelta rapida"
|
||||
},
|
||||
"api": {
|
||||
"title": "API (Compatibile con OpenAI)",
|
||||
"provider": {
|
||||
|
|
|
|||
|
|
@ -237,7 +237,9 @@
|
|||
},
|
||||
"postProcessing": {
|
||||
"title": "後処理",
|
||||
"disabledNotice": "後処理は現在無効です。設定するにはデバッグ設定で有効にしてください。",
|
||||
"hotkey": {
|
||||
"title": "ホットキー"
|
||||
},
|
||||
"api": {
|
||||
"title": "API(OpenAI互換)",
|
||||
"provider": {
|
||||
|
|
|
|||
|
|
@ -241,7 +241,6 @@
|
|||
},
|
||||
"postProcessing": {
|
||||
"title": "후처리",
|
||||
"disabledNotice": "후처리가 현재 비활성화되어 있습니다. 설정하려면 디버그 설정에서 활성화하세요.",
|
||||
"api": {
|
||||
"title": "API (OpenAI 호환)",
|
||||
"provider": {
|
||||
|
|
|
|||
|
|
@ -237,7 +237,9 @@
|
|||
},
|
||||
"postProcessing": {
|
||||
"title": "Postprocess",
|
||||
"disabledNotice": "Postprocess jest obecnie wyłączony. Włącz go w ustawieniach Debug, aby skonfigurować.",
|
||||
"hotkey": {
|
||||
"title": "Skrót klawiszowy"
|
||||
},
|
||||
"api": {
|
||||
"title": "API (zgodne z OpenAI)",
|
||||
"provider": {
|
||||
|
|
|
|||
|
|
@ -241,7 +241,9 @@
|
|||
},
|
||||
"postProcessing": {
|
||||
"title": "Pós-Processamento",
|
||||
"disabledNotice": "O pós-processamento está atualmente desabilitado. Habilite-o nas configurações de Depuração para configurar.",
|
||||
"hotkey": {
|
||||
"title": "Tecla de atalho"
|
||||
},
|
||||
"api": {
|
||||
"title": "API (Compatível com OpenAI)",
|
||||
"provider": {
|
||||
|
|
|
|||
|
|
@ -237,7 +237,9 @@
|
|||
},
|
||||
"postProcessing": {
|
||||
"title": "Постобработка",
|
||||
"disabledNotice": "Постобработка в настоящее время отключена. Включите его в настройках отладки для настройки.",
|
||||
"hotkey": {
|
||||
"title": "Горячая клавиша"
|
||||
},
|
||||
"api": {
|
||||
"title": "API (совместимый с OpenAI)",
|
||||
"provider": {
|
||||
|
|
|
|||
|
|
@ -241,7 +241,6 @@
|
|||
},
|
||||
"postProcessing": {
|
||||
"title": "Son İşlem",
|
||||
"disabledNotice": "Son işlem şu anda devre dışı. Yapılandırmak için Hata Ayıklama ayarlarında etkinleştirin.",
|
||||
"api": {
|
||||
"title": "API (OpenAI Uyumlu)",
|
||||
"provider": {
|
||||
|
|
|
|||
|
|
@ -237,7 +237,9 @@
|
|||
},
|
||||
"postProcessing": {
|
||||
"title": "Постобробка",
|
||||
"disabledNotice": "Постобробка наразі вимкнена. Увімкніть її в розділі Дебаг.",
|
||||
"hotkey": {
|
||||
"title": "Гаряча клавіша"
|
||||
},
|
||||
"api": {
|
||||
"title": "API (сумісний з OpenAI)",
|
||||
"provider": {
|
||||
|
|
|
|||
|
|
@ -238,7 +238,9 @@
|
|||
},
|
||||
"postProcessing": {
|
||||
"title": "Xử lý sau",
|
||||
"disabledNotice": "Xử lý sau hiện đang bị tắt. Bật nó trong cài đặt Gỡ lỗi để cấu hình.",
|
||||
"hotkey": {
|
||||
"title": "Phím tắt"
|
||||
},
|
||||
"api": {
|
||||
"title": "API (Tương thích OpenAI)",
|
||||
"provider": {
|
||||
|
|
|
|||
|
|
@ -237,7 +237,9 @@
|
|||
},
|
||||
"postProcessing": {
|
||||
"title": "后处理",
|
||||
"disabledNotice": "后处理当前已禁用。请在调试设置中启用以进行配置。",
|
||||
"hotkey": {
|
||||
"title": "快捷键"
|
||||
},
|
||||
"api": {
|
||||
"title": "API(兼容 OpenAI)",
|
||||
"provider": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue