import Foundation import SwiftData import os import AppKit @MainActor final class ModelPrewarmService: ObservableObject { private let whisperState: WhisperState private let modelContext: ModelContext private let logger = Logger(subsystem: "com.prakashjoshipax.voiceink", category: "ModelPrewarm") private lazy var serviceRegistry = TranscriptionServiceRegistry( whisperState: whisperState, modelsDirectory: whisperState.modelsDirectory ) private let prewarmAudioURL = Bundle.main.url(forResource: "esc", withExtension: "wav") private let prewarmEnabledKey = "PrewarmModelOnWake" init(whisperState: WhisperState, modelContext: ModelContext) { self.whisperState = whisperState self.modelContext = modelContext setupNotifications() schedulePrewarmOnAppLaunch() } // MARK: - Notification Setup private func setupNotifications() { let center = NSWorkspace.shared.notificationCenter // Trigger on wake from sleep center.addObserver( self, selector: #selector(schedulePrewarm), name: NSWorkspace.didWakeNotification, object: nil ) logger.notice("🌅 ModelPrewarmService initialized - listening for wake and app launch") } // MARK: - Trigger Handlers /// Trigger on app launch (cold start) private func schedulePrewarmOnAppLaunch() { logger.notice("🌅 App launched, scheduling prewarm") Task { try? await Task.sleep(for: .seconds(3)) await performPrewarm() } } /// Trigger on wake from sleep or screen unlock @objc private func schedulePrewarm() { logger.notice("🌅 Mac activity detected (wake/unlock), scheduling prewarm") Task { try? await Task.sleep(for: .seconds(3)) await performPrewarm() } } // MARK: - Core Prewarming Logic private func performPrewarm() async { guard shouldPrewarm() else { return } guard let audioURL = prewarmAudioURL else { logger.error("❌ Prewarm audio file (esc.wav) not found") return } guard let currentModel = whisperState.currentTranscriptionModel else { logger.notice("🌅 No model selected, skipping prewarm") return } logger.notice("🌅 Prewarming \(currentModel.displayName)") let startTime = Date() do { let transcribedText = try await serviceRegistry.transcribe(audioURL: audioURL, model: currentModel) let duration = Date().timeIntervalSince(startTime) let transcription = Transcription( text: "[PREWARM] \(transcribedText)", duration: 1.0, audioFileURL: audioURL.absoluteString, transcriptionModelName: currentModel.displayName, transcriptionDuration: duration ) modelContext.insert(transcription) try? modelContext.save() logger.notice("🌅 Prewarm completed in \(String(format: "%.2f", duration))s") } catch { logger.error("❌ Prewarm failed: \(error.localizedDescription)") } } // MARK: - Validation private func shouldPrewarm() -> Bool { // Check if user has enabled prewarming let isEnabled = UserDefaults.standard.object(forKey: prewarmEnabledKey) as? Bool ?? true guard isEnabled else { logger.notice("🌅 Prewarm disabled by user") return false } // Only prewarm local models (Parakeet and Whisper need ANE compilation) guard let model = whisperState.currentTranscriptionModel else { return false } switch model.provider { case .local, .parakeet: return true default: logger.notice("🌅 Skipping prewarm - cloud models don't need it") return false } } deinit { NSWorkspace.shared.notificationCenter.removeObserver(self) logger.notice("🌅 ModelPrewarmService deinitialized") } }