SPB Git

spb/zyquo-router Public MIT

One local endpoint, every AI provider — a private OpenAI-compatible LLM gateway for your Mac (170 models, 12 providers).

Swift 95.7% Python 2.3% Shell 1.2% Makefile 0.9%
2.8 KB · 95 lines swift
Raw Blame History
1//2//  RouterConfigStore.swift3//  Zyquo Router4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  User routing configuration: aliases (fast → cerebras/…), fallback chains,9//  disabled models, favorites. Persisted to router-config.json; the server10//  takes a snapshot at Start (restart applies changes to a running server).11//1213import Foundation1415struct RouterConfig: Codable {16    static let fileName = "router-config.json"1718    var aliases: [String: String] = [:]19    var fallbackChains: [String: [String]] = [:]20    var disabledIDs: Set<String> = []21    var favoriteIDs: Set<String> = []22}2324@MainActor25final class RouterConfigStore: ObservableObject {26    static var fileName: String { RouterConfig.fileName }2728    @Published var config: RouterConfig {29        didSet { persistence.save(config, to: Self.fileName) }30    }3132    private let persistence: PersistenceService3334    init(persistence: PersistenceService = .shared) {35        self.persistence = persistence36        config = persistence.load(RouterConfig.self, from: Self.fileName) ?? RouterConfig()37    }3839    /// Snapshot for the server thread (also used headless via `load`).40    static func snapshot() -> RouterConfig {41        PersistenceService.shared.load(RouterConfig.self, from: fileName) ?? RouterConfig()42    }4344    func isDisabled(_ namespacedID: String) -> Bool {45        config.disabledIDs.contains(namespacedID)46    }4748    func setDisabled(_ disabled: Bool, for namespacedID: String) {49        if disabled {50            config.disabledIDs.insert(namespacedID)51        } else {52            config.disabledIDs.remove(namespacedID)53        }54    }5556    func isFavorite(_ namespacedID: String) -> Bool {57        config.favoriteIDs.contains(namespacedID)58    }5960    func toggleFavorite(_ namespacedID: String) {61        if !config.favoriteIDs.insert(namespacedID).inserted {62            config.favoriteIDs.remove(namespacedID)63        }64    }6566    func setAlias(_ alias: String, target: String) {67        let name = alias.trimmingCharacters(in: .whitespaces)68        guard !name.isEmpty else { return }69        config.aliases[name] = target70    }7172    func removeAlias(_ alias: String) {73        config.aliases.removeValue(forKey: alias)74    }7576    func setChain(for namespacedID: String, chain: [String]) {77        if chain.isEmpty {78            config.fallbackChains.removeValue(forKey: namespacedID)79        } else {80            config.fallbackChains[namespacedID] = chain81        }82    }8384    /// Export for Settings → Advanced (never includes keys).85    func exportData() throws -> Data {86        let encoder = JSONEncoder()87        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]88        return try encoder.encode(config)89    }9091    func importData(_ data: Data) throws {92        config = try JSONDecoder().decode(RouterConfig.self, from: data)93    }94}95