spb/zyquo-atlas Public License
The AI-native macOS web browser — every surface, intelligent.
Swift 75.2%
JavaScript 22%
Shell 2%
Makefile 0.9%
1//2// ModelCatalog.swift3// Zyquo Atlas4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// Single source of truth for model data. Built-in entries are generated from9// docs/PROVIDERS.md (see ModelCatalogData.swift); dynamic /models refreshes and10// user-defined custom models layer on top. Views and clients never hardcode11// model IDs.12//1314import Foundation1516@MainActor17final class ModelCatalog: ObservableObject {18 /// Built-in catalog (generated from docs/PROVIDERS.md — keep in sync).19 @Published private(set) var builtIn: [AIModel] = ModelCatalogData.all20 /// User-defined custom models (custom ID + base URL).21 @Published var customModels: [AIModel] = []22 /// Model IDs confirmed live by the last dynamic refresh, per provider.23 @Published private(set) var liveModelIDs: [ProviderID: Set<String>] = [:]24 /// Favorite model IDs, pinned at the top of pickers.25 @Published var favoriteIDs: Set<String> = []2627 var all: [AIModel] { builtIn + customModels }2829 func models(for provider: ProviderID) -> [AIModel] {30 all.filter { $0.provider == provider }31 .sorted { rank($0) < rank($1) }32 }3334 func model(id: String, provider: ProviderID) -> AIModel? {35 all.first { $0.id == id && $0.provider == provider }36 }3738 /// Cheapest non-legacy chat model for a provider (used for key tests and39 /// auto-title generation). Non-reasoning models are preferred — reasoning40 /// models burn their token budget thinking, useless for tiny utility calls.41 func cheapestModel(for provider: ProviderID) -> AIModel? {42 let candidates = models(for: provider).filter { !$0.isLegacy }43 let plain = candidates.filter { !$0.capabilities.reasoning }44 return (plain.isEmpty ? candidates : plain)45 .min { ($0.pricing?.outputPerMTok ?? .infinity) < ($1.pricing?.outputPerMTok ?? .infinity) }46 }4748 /// Default model offered for new conversations.49 var defaultModel: AIModel? {50 all.first { $0.isRecommended } ?? all.first51 }5253 /// Merges a dynamic /models listing: known models are marked live; unknown54 /// IDs are surfaced so the user can add them.55 func applyLiveListing(_ ids: [String], for provider: ProviderID) {56 liveModelIDs[provider] = Set(ids)57 }5859 /// IDs returned by the provider but absent from the built-in catalog.60 func unknownLiveIDs(for provider: ProviderID) -> [String] {61 guard let live = liveModelIDs[provider] else { return [] }62 let known = Set(models(for: provider).map(\.id))63 return live.subtracting(known).sorted()64 }6566 private func rank(_ model: AIModel) -> Int {67 if favoriteIDs.contains(model.id) { return 0 }68 if model.isRecommended { return 1 }69 if model.isLegacy { return 3 }70 return 271 }72}73