diff --git a/VoiceInk/Services/AIEnhancementService.swift b/VoiceInk/Services/AIEnhancementService.swift index 1a2f549..6578e00 100644 --- a/VoiceInk/Services/AIEnhancementService.swift +++ b/VoiceInk/Services/AIEnhancementService.swift @@ -8,6 +8,7 @@ enum EnhancementPrompt { case aiAssistant } +@MainActor class AIEnhancementService: ObservableObject { private let logger = Logger(subsystem: "com.prakashjoshipax.voiceink", category: "AIEnhancementService") diff --git a/VoiceInk/Services/AudioFileTranscriptionService.swift b/VoiceInk/Services/AudioFileTranscriptionService.swift index cdc41ab..2238a48 100644 --- a/VoiceInk/Services/AudioFileTranscriptionService.swift +++ b/VoiceInk/Services/AudioFileTranscriptionService.swift @@ -106,7 +106,7 @@ class AudioTranscriptionService: ObservableObject { var promptDetectionResult: PromptDetectionService.PromptDetectionResult? = nil if let enhancementService = enhancementService, enhancementService.isConfigured { - let detectionResult = promptDetectionService.analyzeText(text, with: enhancementService) + let detectionResult = await promptDetectionService.analyzeText(text, with: enhancementService) promptDetectionResult = detectionResult await promptDetectionService.applyDetectionResult(detectionResult, to: enhancementService) } diff --git a/VoiceInk/Services/PromptDetectionService.swift b/VoiceInk/Services/PromptDetectionService.swift index ac448b7..01860e5 100644 --- a/VoiceInk/Services/PromptDetectionService.swift +++ b/VoiceInk/Services/PromptDetectionService.swift @@ -16,6 +16,7 @@ class PromptDetectionService { let originalPromptId: UUID? } + @MainActor func analyzeText(_ text: String, with enhancementService: AIEnhancementService) -> PromptDetectionResult { let originalEnhancementState = enhancementService.isEnhancementEnabled let originalPromptId = enhancementService.selectedPromptId @@ -175,4 +176,4 @@ class PromptDetectionService { } return nil } -} \ No newline at end of file +} diff --git a/VoiceInk/Services/ScreenCaptureService.swift b/VoiceInk/Services/ScreenCaptureService.swift index a34f8e6..1f2ad6a 100644 --- a/VoiceInk/Services/ScreenCaptureService.swift +++ b/VoiceInk/Services/ScreenCaptureService.swift @@ -4,6 +4,7 @@ import Vision import os import ScreenCaptureKit +@MainActor class ScreenCaptureService: ObservableObject { @Published var isCapturing = false @Published var lastCapturedText: String? @@ -60,41 +61,41 @@ class ScreenCaptureService: ObservableObject { } } - func extractText(from image: NSImage, completion: @escaping (String?) -> Void) { + private func extractText(from image: NSImage) async -> String? { guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { - completion(nil) - return + return nil } - let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:]) - let request = VNRecognizeTextRequest { request, error in - if let error = error { - self.logger.notice("📸 Text recognition failed: \(error.localizedDescription, privacy: .public)") - completion(nil) - return + let result: Result = await Task.detached(priority: .userInitiated) { + let request = VNRecognizeTextRequest() + request.recognitionLevel = .accurate + request.usesLanguageCorrection = true + request.automaticallyDetectsLanguage = true + + let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:]) + + do { + try requestHandler.perform([request]) + guard let observations = request.results as? [VNRecognizedTextObservation] else { + return .success(nil) + } + + let text = observations + .compactMap { $0.topCandidates(1).first?.string } + .joined(separator: "\n") + + return .success(text.isEmpty ? nil : text) + } catch { + return .failure(error) } - - guard let observations = request.results as? [VNRecognizedTextObservation] else { - completion(nil) - return - } - - let text = observations.compactMap { observation in - observation.topCandidates(1).first?.string - }.joined(separator: "\n") - - completion(text.isEmpty ? nil : text) - } - - request.recognitionLevel = VNRequestTextRecognitionLevel.accurate - request.usesLanguageCorrection = true - request.automaticallyDetectsLanguage = true + }.value - do { - try requestHandler.perform([request]) - } catch { + switch result { + case .success(let text): + return text + case .failure(let error): logger.notice("📸 Text recognition failed: \(error.localizedDescription, privacy: .public)") - completion(nil) + return nil } } @@ -124,11 +125,7 @@ class ScreenCaptureService: ObservableObject { """ if let capturedImage = await captureActiveWindow() { - let extractedText = await withCheckedContinuation({ continuation in - extractText(from: capturedImage) { text in - continuation.resume(returning: text) - } - }) + let extractedText = await extractText(from: capturedImage) if let extractedText = extractedText, !extractedText.isEmpty { contextText += "Window Content:\n\(extractedText)" diff --git a/VoiceInk/Whisper/WhisperState.swift b/VoiceInk/Whisper/WhisperState.swift index 906e4de..af7ba3b 100644 --- a/VoiceInk/Whisper/WhisperState.swift +++ b/VoiceInk/Whisper/WhisperState.swift @@ -327,7 +327,7 @@ class WhisperState: NSObject, ObservableObject { finalPastedText = text if let enhancementService = enhancementService, enhancementService.isConfigured { - let detectionResult = promptDetectionService.analyzeText(text, with: enhancementService) + let detectionResult = await promptDetectionService.analyzeText(text, with: enhancementService) promptDetectionResult = detectionResult await promptDetectionService.applyDetectionResult(detectionResult, to: enhancementService) } @@ -429,4 +429,3 @@ class WhisperState: NSObject, ObservableObject { await dismissMiniRecorder() } } -