spb/zyquo-cloud Public MIT
Native macOS AI chat client for 12 cloud providers — your keys, every cloud model, one beautiful chat.
Swift 97.4%
Shell 1.7%
Makefile 1%
1//2// PersistenceService.swift3// Zyquo Cloud4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// JSON persistence in ~/Library/Application Support/ZyquoCloud/:9// Conversations/<uuid>.json one file per conversation10// personas.json, templates.json, favorites.json, settings.json11//1213import Foundation1415struct PersistenceService {16 static let shared = PersistenceService()1718 let rootDirectory: URL19 var conversationsDirectory: URL { rootDirectory.appendingPathComponent("Conversations") }2021 private let encoder: JSONEncoder22 private let decoder: JSONDecoder2324 init(rootDirectory: URL? = nil) {25 let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]26 self.rootDirectory = rootDirectory ?? base.appendingPathComponent("ZyquoCloud")27 encoder = JSONEncoder()28 encoder.outputFormatting = [.prettyPrinted, .sortedKeys]29 encoder.dateEncodingStrategy = .iso860130 decoder = JSONDecoder()31 decoder.dateDecodingStrategy = .iso860132 try? FileManager.default.createDirectory(at: conversationsDirectory, withIntermediateDirectories: true)33 }3435 // MARK: - Conversations3637 func loadConversations() -> [Conversation] {38 guard let files = try? FileManager.default.contentsOfDirectory(39 at: conversationsDirectory, includingPropertiesForKeys: nil40 ) else { return [] }41 return files42 .filter { $0.pathExtension == "json" }43 .compactMap { url in44 guard let data = try? Data(contentsOf: url) else { return nil }45 return try? decoder.decode(Conversation.self, from: data)46 }47 .sorted { $0.updatedAt > $1.updatedAt }48 }4950 func save(_ conversation: Conversation) {51 let url = conversationsDirectory.appendingPathComponent("\(conversation.id.uuidString).json")52 guard let data = try? encoder.encode(conversation) else { return }53 try? data.write(to: url, options: .atomic)54 }5556 func delete(_ conversation: Conversation) {57 let url = conversationsDirectory.appendingPathComponent("\(conversation.id.uuidString).json")58 try? FileManager.default.removeItem(at: url)59 }6061 // MARK: - Generic documents (personas, templates, settings…)6263 func load<T: Decodable>(_ type: T.Type, from fileName: String) -> T? {64 let url = rootDirectory.appendingPathComponent(fileName)65 guard let data = try? Data(contentsOf: url) else { return nil }66 return try? decoder.decode(type, from: data)67 }6869 func save<T: Encodable>(_ value: T, to fileName: String) {70 let url = rootDirectory.appendingPathComponent(fileName)71 guard let data = try? encoder.encode(value) else { return }72 try? data.write(to: url, options: .atomic)73 }74}75