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%
4.9 KB · 150 lines swift
Raw Blame History
1//2//  PromptLibraryStore.swift3//  Zyquo Cloud4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  Observable store for the prompt template library and personas.9//  Built-in items come from PromptLibraryData / PersonaLibraryData and are10//  immutable; user-created items are persisted as JSON via PersistenceService.11//1213import Foundation1415@MainActor16final class PromptLibraryStore: ObservableObject {1718    // MARK: - Published state1920    /// User-created templates (never contains built-ins).21    @Published var userTemplates: [PromptTemplate]22    /// User-created personas (never contains built-ins).23    @Published var userPersonas: [Persona]2425    // MARK: - Constants2627    private static let templatesFileName = "user-templates.json"28    private static let personasFileName = "user-personas.json"2930    private let persistence: PersistenceService3132    // MARK: - Init3334    init(persistence: PersistenceService = .shared) {35        self.persistence = persistence36        self.userTemplates = persistence.load([PromptTemplate].self, from: Self.templatesFileName) ?? []37        self.userPersonas = persistence.load([Persona].self, from: Self.personasFileName) ?? []38    }3940    // MARK: - Combined catalogs (built-in + user)4142    /// All templates: the built-in library followed by user templates.43    var allTemplates: [PromptTemplate] {44        PromptLibraryData.templates + userTemplates45    }4647    /// All personas: the built-in set followed by user personas.48    var allPersonas: [Persona] {49        PersonaLibraryData.personas + userPersonas50    }5152    /// Template categories in display order (built-in order first, then any53    /// user-only categories alphabetically).54    var templateCategories: [String] {55        var seen = Set<String>()56        var ordered: [String] = []57        for template in PromptLibraryData.templates where seen.insert(template.category).inserted {58            ordered.append(template.category)59        }60        let userOnly = Set(userTemplates.map(\.category)).subtracting(seen).sorted()61        return ordered + userOnly62    }6364    /// Templates belonging to a category, built-ins first.65    func templates(in category: String) -> [PromptTemplate] {66        allTemplates.filter { $0.category == category }67    }6869    // MARK: - Template CRUD (user items only)7071    /// Adds a user template. Built-in flags are stripped defensively.72    func add(_ template: PromptTemplate) {73        var template = template74        template.isBuiltIn = false75        userTemplates.append(template)76        saveTemplates()77    }7879    /// Updates a user template in place. Built-in templates are immutable80    /// and silently ignored.81    func update(_ template: PromptTemplate) {82        guard let index = userTemplates.firstIndex(where: { $0.id == template.id }) else { return }83        var template = template84        template.isBuiltIn = false85        userTemplates[index] = template86        saveTemplates()87    }8889    /// Deletes a user template. Built-in templates cannot be deleted.90    func delete(_ template: PromptTemplate) {91        guard userTemplates.contains(where: { $0.id == template.id }) else { return }92        userTemplates.removeAll { $0.id == template.id }93        saveTemplates()94    }9596    // MARK: - Persona CRUD (user items only)9798    /// Adds a user persona.99    func add(_ persona: Persona) {100        userPersonas.append(persona)101        savePersonas()102    }103104    /// Updates a user persona in place. Built-in personas are immutable105    /// and silently ignored.106    func update(_ persona: Persona) {107        guard let index = userPersonas.firstIndex(where: { $0.id == persona.id }) else { return }108        userPersonas[index] = persona109        savePersonas()110    }111112    /// Deletes a user persona. Built-in personas cannot be deleted.113    func delete(_ persona: Persona) {114        guard userPersonas.contains(where: { $0.id == persona.id }) else { return }115        userPersonas.removeAll { $0.id == persona.id }116        savePersonas()117    }118119    // MARK: - Lookup120121    func persona(withID id: UUID) -> Persona? {122        allPersonas.first { $0.id == id }123    }124125    func template(withID id: UUID) -> PromptTemplate? {126        allTemplates.first { $0.id == id }127    }128129    // MARK: - Template application130131    /// Fills a template with the user's input, replacing every `{{input}}`132    /// placeholder. Templates without a placeholder get the input appended.133    static func apply(_ template: PromptTemplate, input: String) -> String {134        guard template.body.contains("{{input}}") else {135            return input.isEmpty ? template.body : template.body + "\n\n" + input136        }137        return template.body.replacingOccurrences(of: "{{input}}", with: input)138    }139140    // MARK: - Persistence141142    private func saveTemplates() {143        persistence.save(userTemplates, to: Self.templatesFileName)144    }145146    private func savePersonas() {147        persistence.save(userPersonas, to: Self.personasFileName)148    }149}150