104 lines
5.2 KiB
Swift
104 lines
5.2 KiB
Swift
import Foundation
|
|
import Observation
|
|
|
|
@MainActor
|
|
@Observable
|
|
final class SybilSettingsStore {
|
|
private enum Keys {
|
|
static let apiBaseURL = "sybil.ios.apiBaseURL"
|
|
static let adminToken = "sybil.ios.adminToken"
|
|
static let preferredProvider = "sybil.ios.preferredProvider"
|
|
static let preferredOpenAIModel = "sybil.ios.preferredOpenAIModel"
|
|
static let preferredAnthropicModel = "sybil.ios.preferredAnthropicModel"
|
|
static let preferredXAIModel = "sybil.ios.preferredXAIModel"
|
|
static let preferredHermesAgentModel = "sybil.ios.preferredHermesAgentModel"
|
|
static let quickQuestionPreferredProvider = "sybil.ios.quickQuestionPreferredProvider"
|
|
static let quickQuestionPreferredOpenAIModel = "sybil.ios.quickQuestionPreferredOpenAIModel"
|
|
static let quickQuestionPreferredAnthropicModel = "sybil.ios.quickQuestionPreferredAnthropicModel"
|
|
static let quickQuestionPreferredXAIModel = "sybil.ios.quickQuestionPreferredXAIModel"
|
|
static let quickQuestionPreferredHermesAgentModel = "sybil.ios.quickQuestionPreferredHermesAgentModel"
|
|
}
|
|
|
|
private let defaults: UserDefaults
|
|
|
|
var apiBaseURL: String
|
|
var adminToken: String
|
|
var preferredProvider: Provider
|
|
var preferredModelByProvider: [Provider: String]
|
|
var quickQuestionPreferredProvider: Provider
|
|
var quickQuestionPreferredModelByProvider: [Provider: String]
|
|
|
|
init(defaults: UserDefaults = .standard) {
|
|
self.defaults = defaults
|
|
|
|
let storedBaseURL = defaults.string(forKey: Keys.apiBaseURL)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let fallbackBaseURL = "http://127.0.0.1:8787"
|
|
self.apiBaseURL = storedBaseURL?.isEmpty == false ? storedBaseURL! : fallbackBaseURL
|
|
|
|
self.adminToken = defaults.string(forKey: Keys.adminToken) ?? ""
|
|
|
|
let provider = defaults.string(forKey: Keys.preferredProvider).flatMap(Provider.init(rawValue:)) ?? .openai
|
|
self.preferredProvider = provider
|
|
|
|
let preferredModels: [Provider: String] = [
|
|
.openai: defaults.string(forKey: Keys.preferredOpenAIModel) ?? "gpt-4.1-mini",
|
|
.anthropic: defaults.string(forKey: Keys.preferredAnthropicModel) ?? "claude-3-5-sonnet-latest",
|
|
.xai: defaults.string(forKey: Keys.preferredXAIModel) ?? "grok-3-mini",
|
|
.hermesAgent: defaults.string(forKey: Keys.preferredHermesAgentModel) ?? "hermes-agent"
|
|
]
|
|
self.preferredModelByProvider = preferredModels
|
|
|
|
self.quickQuestionPreferredProvider =
|
|
defaults.string(forKey: Keys.quickQuestionPreferredProvider).flatMap(Provider.init(rawValue:)) ?? provider
|
|
self.quickQuestionPreferredModelByProvider = [
|
|
.openai: defaults.string(forKey: Keys.quickQuestionPreferredOpenAIModel) ?? preferredModels[.openai] ?? "gpt-4.1-mini",
|
|
.anthropic: defaults.string(forKey: Keys.quickQuestionPreferredAnthropicModel) ?? preferredModels[.anthropic] ?? "claude-3-5-sonnet-latest",
|
|
.xai: defaults.string(forKey: Keys.quickQuestionPreferredXAIModel) ?? preferredModels[.xai] ?? "grok-3-mini",
|
|
.hermesAgent: defaults.string(forKey: Keys.quickQuestionPreferredHermesAgentModel) ?? preferredModels[.hermesAgent] ?? "hermes-agent"
|
|
]
|
|
}
|
|
|
|
func persist() {
|
|
defaults.set(apiBaseURL.trimmingCharacters(in: .whitespacesAndNewlines), forKey: Keys.apiBaseURL)
|
|
|
|
let trimmedToken = adminToken.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if trimmedToken.isEmpty {
|
|
defaults.removeObject(forKey: Keys.adminToken)
|
|
} else {
|
|
defaults.set(trimmedToken, forKey: Keys.adminToken)
|
|
}
|
|
|
|
defaults.set(preferredProvider.rawValue, forKey: Keys.preferredProvider)
|
|
defaults.set(preferredModelByProvider[.openai], forKey: Keys.preferredOpenAIModel)
|
|
defaults.set(preferredModelByProvider[.anthropic], forKey: Keys.preferredAnthropicModel)
|
|
defaults.set(preferredModelByProvider[.xai], forKey: Keys.preferredXAIModel)
|
|
defaults.set(preferredModelByProvider[.hermesAgent], forKey: Keys.preferredHermesAgentModel)
|
|
|
|
defaults.set(quickQuestionPreferredProvider.rawValue, forKey: Keys.quickQuestionPreferredProvider)
|
|
defaults.set(quickQuestionPreferredModelByProvider[.openai], forKey: Keys.quickQuestionPreferredOpenAIModel)
|
|
defaults.set(quickQuestionPreferredModelByProvider[.anthropic], forKey: Keys.quickQuestionPreferredAnthropicModel)
|
|
defaults.set(quickQuestionPreferredModelByProvider[.xai], forKey: Keys.quickQuestionPreferredXAIModel)
|
|
defaults.set(quickQuestionPreferredModelByProvider[.hermesAgent], forKey: Keys.quickQuestionPreferredHermesAgentModel)
|
|
}
|
|
|
|
var trimmedTokenOrNil: String? {
|
|
let value = adminToken.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
return value.isEmpty ? nil : value
|
|
}
|
|
|
|
var normalizedAPIBaseURL: URL? {
|
|
var raw = apiBaseURL.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !raw.isEmpty else { return nil }
|
|
|
|
while raw.hasSuffix("/") {
|
|
raw.removeLast()
|
|
}
|
|
|
|
guard let components = URLComponents(string: raw) else {
|
|
return nil
|
|
}
|
|
|
|
return components.url
|
|
}
|
|
}
|