Skip to content

Commit 6da0e39

Browse files
authored
Merge pull request #334 from altic-dev/B/hotkey-activation-modes
Add hotkey activation modes
2 parents 43fa7dd + 10289a4 commit 6da0e39

13 files changed

Lines changed: 643 additions & 185 deletions

Sources/Fluid/Analytics/AnalyticsService.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ final class AnalyticsService {
8989
"ai_processing_enabled": anyDictationShortcutUsesAI,
9090
"streaming_preview_enabled": settings.enableStreamingPreview,
9191
"press_and_hold_mode": settings.pressAndHoldMode,
92+
"hotkey_activation_mode": settings.hotkeyMode.rawValue,
9293
"copy_to_clipboard_enabled": settings.copyTranscriptionToClipboard,
9394
]
9495

Sources/Fluid/ContentView.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ struct ContentView: View {
169169
@State private var openAIBaseURL: String = ModelRepository.shared.defaultBaseURL(for: "openai")
170170

171171
@State private var enableDebugLogs: Bool = SettingsStore.shared.enableDebugLogs
172-
@State private var pressAndHoldModeEnabled: Bool = SettingsStore.shared.pressAndHoldMode
172+
@State private var hotkeyMode: HotkeyActivationMode = SettingsStore.shared.hotkeyMode
173173
@State private var enableStreamingPreview: Bool = SettingsStore.shared.enableStreamingPreview
174174
@State private var copyToClipboard: Bool = SettingsStore.shared.copyTranscriptionToClipboard
175175

@@ -1258,7 +1258,7 @@ struct ContentView: View {
12581258
commandModeShortcutEnabled: self.$isCommandModeShortcutEnabled,
12591259
rewriteShortcutEnabled: self.$isRewriteModeShortcutEnabled,
12601260
hotkeyManagerInitialized: self.$hotkeyManagerInitialized,
1261-
pressAndHoldModeEnabled: self.$pressAndHoldModeEnabled,
1261+
hotkeyMode: self.$hotkeyMode,
12621262
enableStreamingPreview: self.$enableStreamingPreview,
12631263
copyToClipboard: self.$copyToClipboard,
12641264
hotkeyManager: self.hotkeyManager,
@@ -1561,9 +1561,9 @@ struct ContentView: View {
15611561
derivedBaseURL = ModelRepository.shared.defaultBaseURL(for: currentSelectedProviderID)
15621562
derivedSelectedModel = storedSelectedModelByProvider[currentSelectedProviderID] ?? ModelRepository.shared.defaultModels(for: currentSelectedProviderID).first ?? ""
15631563
} else {
1564-
// Unknown provider - fallback to OpenAI
1564+
// Unknown provider - fail closed instead of silently sending to OpenAI.
15651565
derivedCurrentProvider = currentSelectedProviderID
1566-
derivedBaseURL = ModelRepository.shared.defaultBaseURL(for: "openai")
1566+
derivedBaseURL = ""
15671567
derivedSelectedModel = storedSelectedModelByProvider[currentSelectedProviderID] ?? ""
15681568
}
15691569

@@ -2683,7 +2683,7 @@ struct ContentView: View {
26832683

26842684
self.hotkeyManagerInitialized = self.hotkeyManager?.validateEventTapHealth() ?? false
26852685

2686-
self.hotkeyManager?.enablePressAndHoldMode(self.pressAndHoldModeEnabled)
2686+
self.hotkeyManager?.setHotkeyMode(self.hotkeyMode)
26872687

26882688
// Set cancel callback for Escape key handling (closes mode views, resets recording state)
26892689
// Returns true if it handled something (so GlobalHotkeyManager knows to consume the event)
@@ -3128,7 +3128,7 @@ private extension ContentView {
31283128
self.selectedInputUID = AudioDevice.getDefaultInputDevice()?.uid ?? ""
31293129
self.selectedOutputUID = SettingsStore.shared.preferredOutputDeviceUID ?? ""
31303130
self.enableDebugLogs = SettingsStore.shared.enableDebugLogs
3131-
self.pressAndHoldModeEnabled = SettingsStore.shared.pressAndHoldMode
3131+
self.hotkeyMode = SettingsStore.shared.hotkeyMode
31323132
self.enableStreamingPreview = SettingsStore.shared.enableStreamingPreview
31333133
self.copyToClipboard = SettingsStore.shared.copyTranscriptionToClipboard
31343134
self.launchAtStartup = SettingsStore.shared.launchAtStartup

Sources/Fluid/Persistence/BackupService.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ struct SettingsBackupPayload: Codable, Equatable {
4242
let enableDebugLogs: Bool
4343
let shareAnonymousAnalytics: Bool
4444
let pressAndHoldMode: Bool
45+
let hotkeyMode: HotkeyActivationMode?
4546
let enableStreamingPreview: Bool
4647
let enableAIStreaming: Bool
4748
let copyTranscriptionToClipboard: Bool
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Foundation
2+
3+
enum HotkeyActivationMode: String, Codable, CaseIterable, Identifiable {
4+
case toggle, hold, automatic
5+
6+
var id: String { self.rawValue }
7+
8+
var displayName: String {
9+
switch self {
10+
case .toggle: return "Toggle"
11+
case .hold: return "Hold"
12+
case .automatic: return "Automatic (Both)"
13+
}
14+
}
15+
16+
var description: String {
17+
switch self {
18+
case .toggle: return "Tap once to start, tap again to stop."
19+
case .hold: return "Record only while the shortcut is held."
20+
case .automatic: return "Tap to toggle, hold for push-to-talk."
21+
}
22+
}
23+
}

Sources/Fluid/Persistence/SettingsStore.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,9 +1275,11 @@ final class SettingsStore: ObservableObject {
12751275
}
12761276
}
12771277

1278-
var pressAndHoldMode: Bool {
1279-
get { self.defaults.bool(forKey: Keys.pressAndHoldMode) }
1280-
set { self.defaults.set(newValue, forKey: Keys.pressAndHoldMode) }
1278+
var pressAndHoldMode: Bool { get { self.defaults.object(forKey: Keys.hotkeyMode) != nil ? self.hotkeyMode == .hold : self.defaults.bool(forKey: Keys.pressAndHoldMode) } set { self.hotkeyMode = newValue ? .hold : .toggle } }
1279+
1280+
var hotkeyMode: HotkeyActivationMode {
1281+
get { self.defaults.string(forKey: Keys.hotkeyMode).flatMap(HotkeyActivationMode.init(rawValue:)) ?? (self.defaults.bool(forKey: Keys.pressAndHoldMode) ? .hold : .toggle) }
1282+
set { objectWillChange.send(); self.defaults.set(newValue.rawValue, forKey: Keys.hotkeyMode); self.defaults.set(newValue == .hold, forKey: Keys.pressAndHoldMode) }
12811283
}
12821284

12831285
var enableStreamingPreview: Bool {
@@ -2262,6 +2264,7 @@ final class SettingsStore: ObservableObject {
22622264
enableDebugLogs: self.enableDebugLogs,
22632265
shareAnonymousAnalytics: self.shareAnonymousAnalytics,
22642266
pressAndHoldMode: self.pressAndHoldMode,
2267+
hotkeyMode: self.hotkeyMode,
22652268
enableStreamingPreview: self.enableStreamingPreview,
22662269
enableAIStreaming: self.enableAIStreaming,
22672270
copyTranscriptionToClipboard: self.copyTranscriptionToClipboard,
@@ -2333,7 +2336,7 @@ final class SettingsStore: ObservableObject {
23332336
self.betaReleasesEnabled = payload.betaReleasesEnabled
23342337
self.enableDebugLogs = payload.enableDebugLogs
23352338
self.shareAnonymousAnalytics = payload.shareAnonymousAnalytics
2336-
self.pressAndHoldMode = payload.pressAndHoldMode
2339+
self.hotkeyMode = payload.hotkeyMode ?? (payload.pressAndHoldMode ? .hold : .toggle)
23372340
self.enableStreamingPreview = payload.enableStreamingPreview
23382341
self.enableAIStreaming = payload.enableAIStreaming
23392342
self.copyTranscriptionToClipboard = payload.copyTranscriptionToClipboard
@@ -3618,6 +3621,7 @@ private extension SettingsStore {
36183621
static let transcriptionSoundVolume = "TranscriptionSoundVolume"
36193622
static let transcriptionSoundIndependentVolume = "TranscriptionSoundIndependentVolume"
36203623
static let pressAndHoldMode = "PressAndHoldMode"
3624+
static let hotkeyMode = "HotkeyMode"
36213625
static let enableStreamingPreview = "EnableStreamingPreview"
36223626
static let enableAIStreaming = "EnableAIStreaming"
36233627
static let copyTranscriptionToClipboard = "CopyTranscriptionToClipboard"

Sources/Fluid/Services/CommandModeService.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@ final class CommandModeService: ObservableObject {
486486

487487
} catch {
488488
let errorMsg = "Error: \(error.localizedDescription)"
489+
DebugLogger.shared.error("Command mode failed: \(error.localizedDescription)", source: "CommandModeService")
489490
self.conversationHistory.append(Message(
490491
role: .assistant,
491492
content: errorMsg,
@@ -717,7 +718,7 @@ final class CommandModeService: ObservableObject {
717718
} else if ModelRepository.shared.isBuiltIn(providerID) {
718719
baseURL = ModelRepository.shared.defaultBaseURL(for: providerID)
719720
} else {
720-
baseURL = ModelRepository.shared.defaultBaseURL(for: "openai")
721+
baseURL = ""
721722
}
722723

723724
// Build conversation with agentic system prompt

Sources/Fluid/Services/DictationAIPostProcessingGate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ enum DictationAIPostProcessingGate {
5050
if ModelRepository.shared.isBuiltIn(providerID) {
5151
return ModelRepository.shared.defaultBaseURL(for: providerID)
5252
}
53-
// Unknown provider - fallback to OpenAI
54-
return ModelRepository.shared.defaultBaseURL(for: "openai")
53+
// Unknown provider: fail closed instead of silently treating it as OpenAI.
54+
return ""
5555
}
5656

5757
static func providerKey(for providerID: String) -> String {

0 commit comments

Comments
 (0)