@@ -17,6 +17,10 @@ class AppState: ObservableObject {
1717 @Published var previousApplication : NSRunningApplication ?
1818 @Published var selectedImages : [ Data ] = [ ] // Store selected image data
1919
20+ // Command management
21+ @Published var commandManager = CommandManager ( )
22+ @Published var customCommandsManager = CustomCommandsManager ( )
23+
2024 // Current provider with UI binding support
2125 @Published private( set) var currentProvider : String
2226
@@ -77,6 +81,12 @@ class AppState: ObservableObject {
7781 if asettings. openAIApiKey. isEmpty && asettings. geminiApiKey. isEmpty && asettings. mistralApiKey. isEmpty {
7882 print ( " Warning: No API keys configured. " )
7983 }
84+
85+ // Perform migration from old system to new CommandManager if needed
86+ MigrationHelper . shared. migrateIfNeeded (
87+ commandManager: commandManager,
88+ customCommandsManager: customCommandsManager
89+ )
8090 }
8191
8292 // For Gemini changes
@@ -134,4 +144,89 @@ class AppState: ObservableObject {
134144 let config = OllamaConfig ( baseURL: baseURL, model: model, keepAlive: keepAlive)
135145 ollamaProvider = OllamaProvider ( config: config)
136146 }
147+
148+ // Process a command (unified method for all command types)
149+ func processCommand( _ command: CommandModel ) {
150+ guard !selectedText. isEmpty else { return }
151+
152+ isProcessing = true
153+
154+ Task {
155+ do {
156+ let prompt = command. prompt
157+ let result = try await activeProvider. processText (
158+ systemPrompt: prompt,
159+ userPrompt: selectedText,
160+ images: [ ]
161+ )
162+
163+ // Determine what to do with the result based on command settings
164+ if command. useResponseWindow {
165+ // Display in response window
166+ let window = ResponseWindow (
167+ title: " \( command. name) Result " ,
168+ content: result,
169+ selectedText: selectedText,
170+ option: nil // Using nil since this is using the generic CommandModel
171+ )
172+
173+ WindowManager . shared. addResponseWindow ( window)
174+ window. makeKeyAndOrderFront ( nil )
175+ window. orderFrontRegardless ( )
176+ } else {
177+ // Replace selected text by setting clipboard and pasting
178+ NSPasteboard . general. clearContents ( )
179+ NSPasteboard . general. setString ( result, forType: . string)
180+
181+ // Reactivate previous application and paste
182+ if let previousApp = previousApplication {
183+ previousApp. activate ( )
184+
185+ // Wait briefly for activation then paste once
186+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.2 ) {
187+ self . simulatePaste ( )
188+ }
189+ }
190+ }
191+ } catch {
192+ // Handle error
193+ print ( " Error processing command: \( error) " )
194+ }
195+
196+ isProcessing = false
197+ }
198+ }
199+
200+ // Helper method to replace selected text
201+ func replaceSelectedText( with newText: String ) {
202+ NSPasteboard . general. clearContents ( )
203+ NSPasteboard . general. setString ( newText, forType: . string)
204+
205+ // Reactivate previous application and paste
206+ if let previousApp = previousApplication {
207+ previousApp. activate ( )
208+
209+ // Wait briefly for activation then paste once
210+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.2 ) {
211+ self . simulatePaste ( )
212+ }
213+ }
214+ }
215+
216+ // Simulate paste command
217+ private func simulatePaste( ) {
218+ guard let source = CGEventSource ( stateID: . hidSystemState) else { return }
219+
220+ // Create a Command + V key down event
221+ let keyDown = CGEvent ( keyboardEventSource: source, virtualKey: 0x09 , keyDown: true )
222+ keyDown? . flags = . maskCommand
223+
224+ // Create a Command + V key up event
225+ let keyUp = CGEvent ( keyboardEventSource: source, virtualKey: 0x09 , keyDown: false )
226+ keyUp? . flags = . maskCommand
227+
228+ // Post the events to the HID event system
229+ keyDown? . post ( tap: . cghidEventTap)
230+ keyUp? . post ( tap: . cghidEventTap)
231+ }
137232}
0 commit comments