import Foundation import Observation /// Which speech engine transcribes an utterance. public enum SpeechEngineChoice: String, CaseIterable, Sendable { case apple case parakeet public var displayName: String { switch self { case .apple: "Apple (streaming)" case .parakeet: "Parakeet (batch)" } } /// Apple shows text while you talk; Parakeet only resolves on release. public var showsLiveText: Bool { self == .apple } } @MainActor @Observable public final class Settings { public static let shared = Settings() public var pushToTalkKey: PushToTalkKey { didSet { defaults.set(pushToTalkKey.rawValue, forKey: Keys.pushToTalkKey) } } public var engine: SpeechEngineChoice { didSet { defaults.set(engine.rawValue, forKey: Keys.engine) } } /// Run the cleanup pass before injecting. Off = raw engine output. public var cleanupEnabled: Bool { didSet { defaults.set(cleanupEnabled, forKey: Keys.cleanupEnabled) } } /// Use the on-device LLM for cleanup instead of the deterministic rule pass. public var smartCleanup: Bool { didSet { defaults.set(smartCleanup, forKey: Keys.smartCleanup) } } /// Play a short tick when capture starts and stops. public var soundEnabled: Bool { didSet { defaults.set(soundEnabled, forKey: Keys.soundEnabled) } } private let defaults = UserDefaults.standard private enum Keys { static let pushToTalkKey = "pushToTalkKey" static let cleanupEnabled = "cleanupEnabled" static let soundEnabled = "soundEnabled" static let engine = "engine" static let smartCleanup = "smartCleanup" } private init() { let raw = defaults.string(forKey: Keys.pushToTalkKey) ?? PushToTalkKey.rightOption.rawValue pushToTalkKey = PushToTalkKey(rawValue: raw) ?? .rightOption // Apple by default: no download, no dependency, live text while speaking. engine = SpeechEngineChoice(rawValue: defaults.string(forKey: Keys.engine) ?? "") ?? .apple cleanupEnabled = defaults.object(forKey: Keys.cleanupEnabled) as? Bool ?? true smartCleanup = defaults.object(forKey: Keys.smartCleanup) as? Bool ?? false soundEnabled = defaults.object(forKey: Keys.soundEnabled) as? Bool ?? true } }