diff --git a/src-tauri/src/actions.rs b/src-tauri/src/actions.rs index 8232dedcd..98973faea 100644 --- a/src-tauri/src/actions.rs +++ b/src-tauri/src/actions.rs @@ -436,6 +436,56 @@ impl ShortcutAction for CancelAction { } } +struct RetypeLastAction; + +impl ShortcutAction for RetypeLastAction { + fn start(&self, app: &AppHandle, _binding_id: &str, _shortcut_str: &str) { + debug!("RetypeLastAction::start called"); + + let app_handle = app.clone(); + tauri::async_runtime::spawn(async move { + let hm = app_handle.state::>(); + + match hm.get_latest_entry().await { + Ok(Some(entry)) => { + let text = entry + .post_processed_text + .filter(|t| !t.is_empty()) + .unwrap_or(entry.transcription_text); + + if text.is_empty() { + debug!("RetypeLastAction: Latest entry has empty text"); + return; + } + + debug!("RetypeLastAction: Retyping text: '{}'", text); + + let app_for_paste = app_handle.clone(); + app_handle + .run_on_main_thread(move || { + if let Err(e) = utils::paste(text, app_for_paste) { + error!("RetypeLastAction: Failed to paste text: {}", e); + } + }) + .unwrap_or_else(|e| { + error!("RetypeLastAction: Failed to run on main thread: {:?}", e); + }); + } + Ok(None) => { + debug!("RetypeLastAction: No history entries found"); + } + Err(e) => { + error!("RetypeLastAction: Failed to get latest entry: {}", e); + } + } + }); + } + + fn stop(&self, _app: &AppHandle, _binding_id: &str, _shortcut_str: &str) { + // Nothing to do on stop for retype + } +} + // Test Action struct TestAction; @@ -470,6 +520,10 @@ pub static ACTION_MAP: Lazy>> = Lazy::ne "cancel".to_string(), Arc::new(CancelAction) as Arc, ); + map.insert( + "retype_last".to_string(), + Arc::new(RetypeLastAction) as Arc, + ); map.insert( "test".to_string(), Arc::new(TestAction) as Arc, diff --git a/src-tauri/src/managers/history.rs b/src-tauri/src/managers/history.rs index 0a8c7f07b..eff459cb5 100644 --- a/src-tauri/src/managers/history.rs +++ b/src-tauri/src/managers/history.rs @@ -379,6 +379,32 @@ impl HistoryManager { Ok(entries) } + pub async fn get_latest_entry(&self) -> Result> { + let conn = self.get_connection()?; + let mut stmt = conn.prepare( + "SELECT id, file_name, timestamp, saved, title, transcription_text, post_processed_text, post_process_prompt FROM transcription_history ORDER BY timestamp DESC LIMIT 1" + )?; + + let result = stmt.query_row([], |row| { + Ok(HistoryEntry { + id: row.get("id")?, + file_name: row.get("file_name")?, + timestamp: row.get("timestamp")?, + saved: row.get("saved")?, + title: row.get("title")?, + transcription_text: row.get("transcription_text")?, + post_processed_text: row.get("post_processed_text")?, + post_process_prompt: row.get("post_process_prompt")?, + }) + }); + + match result { + Ok(entry) => Ok(Some(entry)), + Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), + Err(e) => Err(e.into()), + } + } + pub async fn toggle_saved_status(&self, id: i64) -> Result<()> { let conn = self.get_connection()?; diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index 1bd0d718e..2772eaad9 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -544,6 +544,16 @@ pub fn get_default_settings() -> AppSettings { current_binding: "escape".to_string(), }, ); + bindings.insert( + "retype_last".to_string(), + ShortcutBinding { + id: "retype_last".to_string(), + name: "Retype Last".to_string(), + description: "Retypes the last transcription.".to_string(), + default_binding: "".to_string(), + current_binding: "".to_string(), + }, + ); AppSettings { bindings, diff --git a/src-tauri/src/shortcut.rs b/src-tauri/src/shortcut.rs index a827ad838..1169f0d52 100644 --- a/src-tauri/src/shortcut.rs +++ b/src-tauri/src/shortcut.rs @@ -81,6 +81,25 @@ pub fn change_binding( } } + // If the new binding is empty, unregister the existing one and save + if binding.trim().is_empty() { + if !binding_to_modify.current_binding.trim().is_empty() { + if let Err(e) = unregister_shortcut(&app, binding_to_modify.clone()) { + warn!("Failed to unregister shortcut when clearing: {}", e); + } + } + if let Some(mut b) = settings.bindings.get(&id).cloned() { + b.current_binding = binding; + settings.bindings.insert(id.clone(), b.clone()); + settings::write_settings(&app, settings); + return Ok(BindingResponse { + success: true, + binding: Some(b.clone()), + error: None, + }); + } + } + // Unregister the existing binding if let Err(e) = unregister_shortcut(&app, binding_to_modify.clone()) { let error_msg = format!("Failed to unregister shortcut: {}", e); diff --git a/src/components/settings/HandyShortcut.tsx b/src/components/settings/HandyShortcut.tsx index bc595f004..7fda4c76c 100644 --- a/src/components/settings/HandyShortcut.tsx +++ b/src/components/settings/HandyShortcut.tsx @@ -331,10 +331,14 @@ export const HandyShortcut: React.FC = ({ ) : (
startRecording(shortcutId)} > - {formatKeyCombination(binding.current_binding, osType)} + {binding.current_binding + ? formatKeyCombination(binding.current_binding, osType) + : t("settings.general.shortcut.notSet", "Not set")}
)} {
+ diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 3e1fd3267..bb70a68af 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -89,6 +89,7 @@ "loading": "Loading shortcuts...", "none": "No shortcuts configured", "notFound": "Shortcut not found", + "notSet": "Not set", "pressKeys": "Press keys...", "bindings": { "transcribe": { @@ -98,6 +99,10 @@ "cancel": { "name": "Cancel", "description": "Cancels the current recording." + }, + "retype_last": { + "name": "Retype Last", + "description": "Retypes the last transcription." } }, "errors": {