AIHistoryManager.swift 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import Foundation
  2. @MainActor
  3. @Observable
  4. final class AIHistoryManager {
  5. static let shared = AIHistoryManager()
  6. private(set) var entries: [AIHistoryEntry] = []
  7. private let defaults: UserDefaults
  8. private let storageKey = "reddora.ai.history.entries"
  9. private let maxEntries = 100
  10. private let persistQueue = DispatchQueue(label: "com.reddora.ai-history.persist", qos: .utility)
  11. init(defaults: UserDefaults = .standard) {
  12. self.defaults = defaults
  13. load()
  14. }
  15. var isEmpty: Bool {
  16. entries.isEmpty
  17. }
  18. func entries(matching filter: AIHistoryFilter) -> [AIHistoryEntry] {
  19. guard let toolKind = filter.toolKind else { return entries }
  20. return entries.filter { $0.toolKind == toolKind }
  21. }
  22. func save(_ entry: AIHistoryEntry) {
  23. entries.removeAll { $0.id == entry.id }
  24. entries.insert(entry, at: 0)
  25. if entries.count > maxEntries {
  26. entries = Array(entries.prefix(maxEntries))
  27. }
  28. persist()
  29. }
  30. func delete(id: UUID) {
  31. guard entries.contains(where: { $0.id == id }) else { return }
  32. entries.removeAll { $0.id == id }
  33. persist()
  34. }
  35. func deleteAll() {
  36. guard !entries.isEmpty else { return }
  37. entries.removeAll()
  38. persist()
  39. }
  40. private func load() {
  41. guard let data = defaults.data(forKey: storageKey) else {
  42. entries = []
  43. return
  44. }
  45. do {
  46. let decoder = JSONDecoder()
  47. decoder.dateDecodingStrategy = .iso8601
  48. entries = try decoder.decode([AIHistoryEntry].self, from: data)
  49. } catch {
  50. entries = []
  51. }
  52. }
  53. private func persist() {
  54. let snapshot = entries
  55. let storageKey = storageKey
  56. let defaults = defaults
  57. persistQueue.async {
  58. do {
  59. let encoder = JSONEncoder()
  60. encoder.dateEncodingStrategy = .iso8601
  61. let data = try encoder.encode(snapshot)
  62. defaults.set(data, forKey: storageKey)
  63. } catch {
  64. // Keep in-memory entries if persistence fails.
  65. }
  66. }
  67. }
  68. }