From 154368e616a7fabe2536dae0715a0080b10e03c2 Mon Sep 17 00:00:00 2001 From: Alexey Haidamaka Date: Fri, 12 Sep 2025 20:03:19 +0200 Subject: [PATCH 1/2] Added capability to paste last enhancement --- VoiceInk/HotkeyManager.swift | 8 +++++ .../Services/LastTranscriptionService.swift | 35 ++++++++++++++----- VoiceInk/Views/Settings/SettingsView.swift | 17 +++++++++ 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/VoiceInk/HotkeyManager.swift b/VoiceInk/HotkeyManager.swift index 407986e..5872f81 100644 --- a/VoiceInk/HotkeyManager.swift +++ b/VoiceInk/HotkeyManager.swift @@ -7,6 +7,7 @@ extension KeyboardShortcuts.Name { static let toggleMiniRecorder = Self("toggleMiniRecorder") static let toggleMiniRecorder2 = Self("toggleMiniRecorder2") static let pasteLastTranscription = Self("pasteLastTranscription") + static let pasteLastEnhancement = Self("pasteLastEnhancement") } @MainActor @@ -153,6 +154,13 @@ class HotkeyManager: ObservableObject { } } + KeyboardShortcuts.onKeyUp(for: .pasteLastEnhancement) { [weak self] in + guard let self = self else { return } + Task { @MainActor in + LastTranscriptionService.pasteLastEnhancement(from: self.whisperState.modelContext) + } + } + Task { @MainActor in try? await Task.sleep(nanoseconds: 100_000_000) self.setupHotkeyMonitoring() diff --git a/VoiceInk/Services/LastTranscriptionService.swift b/VoiceInk/Services/LastTranscriptionService.swift index 4ef46fa..28899e4 100644 --- a/VoiceInk/Services/LastTranscriptionService.swift +++ b/VoiceInk/Services/LastTranscriptionService.swift @@ -29,7 +29,7 @@ class LastTranscriptionService: ObservableObject { return } - let success = ClipboardManager.copyToClipboard(lastTranscription.enhancedText?.isEmpty == false ? lastTranscription.enhancedText! : lastTranscription.text) + let success = ClipboardManager.copyToClipboard(lastTranscription.text) Task { @MainActor in if success { @@ -57,13 +57,7 @@ class LastTranscriptionService: ObservableObject { return } - // Use enhanced text if available and not empty, otherwise use original text - let textToPaste: String - if let enhancedText = lastTranscription.enhancedText, !enhancedText.isEmpty { - textToPaste = enhancedText - } else { - textToPaste = lastTranscription.text - } + let textToPaste = lastTranscription.text // Delay to give the user time to release modifier keys (especially Control) DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { @@ -71,6 +65,29 @@ class LastTranscriptionService: ObservableObject { } } + static func pasteLastEnhancement(from modelContext: ModelContext) { + guard let lastTranscription = getLastTranscription(from: modelContext) else { + Task { @MainActor in + NotificationManager.shared.showNotification( + title: "No transcription available", + type: .error + ) + } + return + } + + // Only paste if enhancement exists; this includes actual enhancement text or an error message saved in enhancedText. + guard let enhancedText = lastTranscription.enhancedText, !enhancedText.isEmpty else { + // Per requirements, do nothing when there is no enhancement. + return + } + + // Delay to allow modifier keys to be released + DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { + CursorPaster.pasteAtCursor(enhancedText + " ") + } + } + static func retryLastTranscription(from modelContext: ModelContext, whisperState: WhisperState) { Task { @MainActor in guard let lastTranscription = getLastTranscription(from: modelContext), @@ -111,4 +128,4 @@ class LastTranscriptionService: ObservableObject { } } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/VoiceInk/Views/Settings/SettingsView.swift b/VoiceInk/Views/Settings/SettingsView.swift index e54ae0a..2db6b98 100644 --- a/VoiceInk/Views/Settings/SettingsView.swift +++ b/VoiceInk/Views/Settings/SettingsView.swift @@ -128,6 +128,23 @@ struct SettingsView: View { Spacer() } + // Paste Last Enhancement + HStack(spacing: 12) { + Text("Paste Last Enhancement") + .font(.system(size: 13, weight: .medium)) + .foregroundColor(.secondary) + + KeyboardShortcuts.Recorder(for: .pasteLastEnhancement) + .controlSize(.small) + + InfoTip( + title: "Paste Last Enhancement", + message: "Shortcut for pasting the most recent AI-enhanced text. If no enhancement exists, nothing is pasted. If the enhancement failed, the error message is pasted." + ) + + Spacer() + } + Divider() // Middle-Click Toggle From 3e9bd4580d095ce44f5694606798fbc529513f4d Mon Sep 17 00:00:00 2001 From: Alexey Haidamaka Date: Fri, 12 Sep 2025 21:38:44 +0200 Subject: [PATCH 2/2] fixed merge conflicts --- VoiceInk/HotkeyManager.swift | 16 ++-- VoiceInk/Views/Settings/SettingsView.swift | 92 +++++++++++++--------- 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/VoiceInk/HotkeyManager.swift b/VoiceInk/HotkeyManager.swift index 5872f81..2eba0de 100644 --- a/VoiceInk/HotkeyManager.swift +++ b/VoiceInk/HotkeyManager.swift @@ -8,6 +8,7 @@ extension KeyboardShortcuts.Name { static let toggleMiniRecorder2 = Self("toggleMiniRecorder2") static let pasteLastTranscription = Self("pasteLastTranscription") static let pasteLastEnhancement = Self("pasteLastEnhancement") + static let retryLastTranscription = Self("retryLastTranscription") } @MainActor @@ -58,7 +59,7 @@ class HotkeyManager: ObservableObject { // Key state tracking private var currentKeyState = false private var keyPressStartTime: Date? - private let briefPressThreshold = 1.7 + private let briefPressThreshold = 0.8 private var isHandsFreeMode = false // Debounce for Fn key @@ -141,25 +142,31 @@ class HotkeyManager: ObservableObject { self.whisperState = whisperState self.miniRecorderShortcutManager = MiniRecorderShortcutManager(whisperState: whisperState) - if KeyboardShortcuts.getShortcut(for: .pasteLastTranscription) == nil { let defaultPasteShortcut = KeyboardShortcuts.Shortcut(.v, modifiers: [.command, .option]) KeyboardShortcuts.setShortcut(defaultPasteShortcut, for: .pasteLastTranscription) } - + KeyboardShortcuts.onKeyUp(for: .pasteLastTranscription) { [weak self] in guard let self = self else { return } Task { @MainActor in LastTranscriptionService.pasteLastTranscription(from: self.whisperState.modelContext) } } - + KeyboardShortcuts.onKeyUp(for: .pasteLastEnhancement) { [weak self] in guard let self = self else { return } Task { @MainActor in LastTranscriptionService.pasteLastEnhancement(from: self.whisperState.modelContext) } } + + KeyboardShortcuts.onKeyUp(for: .retryLastTranscription) { [weak self] in + guard let self = self else { return } + Task { @MainActor in + LastTranscriptionService.retryLastTranscription(from: self.whisperState.modelContext, whisperState: self.whisperState) + } + } Task { @MainActor in try? await Task.sleep(nanoseconds: 100_000_000) @@ -436,4 +443,3 @@ class HotkeyManager: ObservableObject { } } - diff --git a/VoiceInk/Views/Settings/SettingsView.swift b/VoiceInk/Views/Settings/SettingsView.swift index 2db6b98..211218c 100644 --- a/VoiceInk/Views/Settings/SettingsView.swift +++ b/VoiceInk/Views/Settings/SettingsView.swift @@ -74,6 +74,62 @@ struct SettingsView: View { subtitle: "Additional shortcuts for VoiceInk" ) { VStack(alignment: .leading, spacing: 18) { + // Paste Last Transcription + HStack(spacing: 12) { + Text("Paste Last Transcription") + .font(.system(size: 13, weight: .medium)) + .foregroundColor(.secondary) + + KeyboardShortcuts.Recorder(for: .pasteLastTranscription) + .controlSize(.small) + + InfoTip( + title: "Paste Last Transcription", + message: "Shortcut for pasting the most recent transcription at current cursor position." + ) + + Spacer() + } + + // Paste Last Enhancement + HStack(spacing: 12) { + Text("Paste Last Enhancement") + .font(.system(size: 13, weight: .medium)) + .foregroundColor(.secondary) + + KeyboardShortcuts.Recorder(for: .pasteLastEnhancement) + .controlSize(.small) + + InfoTip( + title: "Paste Last Enhancement", + message: "Shortcut for pasting the most recent AI-enhanced text. If no enhancement exists, nothing is pasted. If the enhancement failed, the error message is pasted." + ) + + Spacer() + } + + // Add separator after Paste Last Enhancement + Divider() + + // Retry Last Transcription + HStack(spacing: 12) { + Text("Retry Last Transcription") + .font(.system(size: 13, weight: .medium)) + .foregroundColor(.secondary) + + KeyboardShortcuts.Recorder(for: .retryLastTranscription) + .controlSize(.small) + + InfoTip( + title: "Retry Last Transcription", + message: "Re-transcribe the last recorded audio using the current model and copy the result." + ) + + Spacer() + } + + Divider() + // Custom Cancel Shortcut VStack(alignment: .leading, spacing: 12) { HStack(spacing: 8) { @@ -109,42 +165,6 @@ struct SettingsView: View { } } - Divider() - - // Paste Last Transcription - HStack(spacing: 12) { - Text("Paste Last Transcription") - .font(.system(size: 13, weight: .medium)) - .foregroundColor(.secondary) - - KeyboardShortcuts.Recorder(for: .pasteLastTranscription) - .controlSize(.small) - - InfoTip( - title: "Paste Last Transcription", - message: "Shortcut for pasting the most recent transcription at current cursor position." - ) - - Spacer() - } - - // Paste Last Enhancement - HStack(spacing: 12) { - Text("Paste Last Enhancement") - .font(.system(size: 13, weight: .medium)) - .foregroundColor(.secondary) - - KeyboardShortcuts.Recorder(for: .pasteLastEnhancement) - .controlSize(.small) - - InfoTip( - title: "Paste Last Enhancement", - message: "Shortcut for pasting the most recent AI-enhanced text. If no enhancement exists, nothing is pasted. If the enhancement failed, the error message is pasted." - ) - - Spacer() - } - Divider() // Middle-Click Toggle