spb/zyquo-local Public MIT
Native macOS AI chat that runs LLMs 100% locally on Apple Silicon with MLX — no cloud, no API keys.
Swift 97.2%
Shell 1.8%
Makefile 1%
1//2// PersistenceService.swift3// Zyquo Local4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//89import Foundation1011/// Conversations, personas and settings persisted as JSON under12/// ~/Library/Application Support/ZyquoLocal/.13enum PersistenceService {14 static var appSupportDirectory: URL {15 let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]16 let dir = base.appendingPathComponent("ZyquoLocal")17 try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)18 return dir19 }2021 private static var conversationsDirectory: URL {22 let dir = appSupportDirectory.appendingPathComponent("Conversations")23 try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)24 return dir25 }2627 private static let encoder: JSONEncoder = {28 let e = JSONEncoder()29 e.dateEncodingStrategy = .iso860130 e.outputFormatting = [.sortedKeys]31 return e32 }()3334 private static let decoder: JSONDecoder = {35 let d = JSONDecoder()36 d.dateDecodingStrategy = .iso860137 return d38 }()3940 // MARK: - Conversations (one JSON file per conversation)4142 static func loadConversations() -> [Conversation] {43 let files = (try? FileManager.default.contentsOfDirectory(44 at: conversationsDirectory, includingPropertiesForKeys: nil)) ?? []45 return files46 .filter { $0.pathExtension == "json" }47 .compactMap { url in48 guard let data = try? Data(contentsOf: url) else { return nil }49 return try? decoder.decode(Conversation.self, from: data)50 }51 .sorted { $0.updatedAt > $1.updatedAt }52 }5354 static func save(_ conversation: Conversation) {55 let url = conversationsDirectory.appendingPathComponent("\(conversation.id.uuidString).json")56 try? encoder.encode(conversation).write(to: url, options: .atomic)57 }5859 static func delete(conversationID: UUID) {60 let url = conversationsDirectory.appendingPathComponent("\(conversationID.uuidString).json")61 try? FileManager.default.removeItem(at: url)62 }6364 // MARK: - Generic single-document storage (settings, personas, prompts)6566 static func loadDocument<T: Decodable>(_ type: T.Type, named name: String) -> T? {67 let url = appSupportDirectory.appendingPathComponent("\(name).json")68 guard let data = try? Data(contentsOf: url) else { return nil }69 return try? decoder.decode(T.self, from: data)70 }7172 static func saveDocument<T: Encodable>(_ value: T, named name: String) {73 let url = appSupportDirectory.appendingPathComponent("\(name).json")74 try? encoder.encode(value).write(to: url, options: .atomic)75 }76}77