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// 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