SPB Git

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%
2.8 KB · 75 lines swift
Raw Blame History
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