import Cocoa /// 应用主控制器,协调按键监听、语音识别、LLM 纠错和文本注入 final class AppDelegate: NSObject, NSApplicationDelegate { private let keyMonitor = KeyMonitor() private let speechRecognizer = SpeechRecognizer() private let audioMonitor = AudioLevelMonitor() private let recordingPanel = RecordingPanel() private let menuBarController = MenuBarController() private let llmRefiner = LLMRefiner() private var isRecording = false private var finalTranscription: String = "" func applicationDidFinishLaunching(_ notification: Notification) { NSApp.setActivationPolicy(.accessory) // 启动按键监听 let started = keyMonitor.start() if !started { showPermissionAlert() } keyMonitor.onFnDown = { [weak self] in self?.startRecording() } keyMonitor.onFnUp = { [weak self] in self?.stopRecording() } print("[App] Ready") } // MARK: - Recording private func startRecording() { guard !isRecording else { return } isRecording = true finalTranscription = "" recordingPanel.showAnimated() recordingPanel.updateText("Listening...") let language = Config.shared.language.rawValue Task { @MainActor in // 启动音频电平监测 let audioOK = audioMonitor.start() if audioOK { audioMonitor.onLevelUpdate = { [weak self] level in self?.recordingPanel.waveformView.updateLevel(level) } } // 启动语音识别 let speechOK = await speechRecognizer.start(language: language) if !speechOK { recordingPanel.updateText("Recognition error") DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in self?.cancelRecording() } return } speechRecognizer.onResult = { [weak self] text in guard let self = self, self.isRecording else { return } self.finalTranscription = text self.recordingPanel.updateText(text) } speechRecognizer.onError = { [weak self] error in print("[App] Speech error: \(error)") self?.recordingPanel.updateText("Error: \(error.localizedDescription)") } speechRecognizer.onStateChange = { [weak self] isActive in if !isActive, self?.isRecording == true { // 识别意外停止 } } } } private func stopRecording() { guard isRecording else { return } isRecording = false // 停止音频和识别 audioMonitor.stop() _ = speechRecognizer.stop() let text = finalTranscription.trimmingCharacters(in: .whitespacesAndNewlines) // 如果 LLM 启用且已配置 if Config.shared.llmEnabled && Config.shared.isLLMConfigured && !text.isEmpty { recordingPanel.showRefining() Task { @MainActor in do { let result = try await llmRefiner.refine(text: text, config: Config.shared) recordingPanel.hideAnimated { TextInjector.inject(result.refinedText) } } catch { print("[App] LLM error: \(error)") // LLM 失败,回退到原始文本 recordingPanel.hideAnimated { TextInjector.inject(text) } } } } else if !text.isEmpty { recordingPanel.hideAnimated { TextInjector.inject(text) } } else { recordingPanel.hideAnimated() } finalTranscription = "" } private func cancelRecording() { audioMonitor.stop() speechRecognizer.cancel() recordingPanel.hideAnimated() isRecording = false finalTranscription = "" } // MARK: - Permissions private func showPermissionAlert() { let alert = NSAlert() alert.messageText = "Accessibility Permission Required" alert.informativeText = """ Voice Input needs Accessibility permission to detect the Fn key globally. Please go to System Settings > Privacy & Security > Accessibility and enable Voice Input. """ alert.alertStyle = .warning alert.addButton(withTitle: "Open System Settings") alert.addButton(withTitle: "Later") if alert.runModal() == .alertFirstButtonReturn { NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")!) } } func applicationWillTerminate(_ notification: Notification) { keyMonitor.stop() audioMonitor.stop() speechRecognizer.cancel() recordingPanel.close() } }