Archived
54 lines
2.1 KiB
Swift
54 lines
2.1 KiB
Swift
import Foundation
|
|
|
|
struct HistoryEntry: Codable, Equatable {
|
|
let role: String
|
|
let text: String
|
|
let timestamp: Date
|
|
}
|
|
|
|
/// Last 50 final transcript entries persisted as JSON in UserDefaults.
|
|
///
|
|
/// Persistence-only store. The visible transcript is the single canonical
|
|
/// collection owned by `TranscriptHandler.events`; this class provides the
|
|
/// durable snapshot it initializes from and syncs to. It performs no dedupe —
|
|
/// all suppression is source-aware and time-bounded in the handler.
|
|
@MainActor
|
|
final class TranscriptHistory: ObservableObject {
|
|
@Published private(set) var entries: [HistoryEntry] = []
|
|
|
|
static let key = "voice_history_isolated_tests"
|
|
private let storageKey: String
|
|
static let maxEntries = 50
|
|
|
|
init(origin: String? = nil, userID: String? = nil) {
|
|
// Length-prefixed parts prevent ambiguous origin/account combinations.
|
|
storageKey = origin.flatMap { origin in userID.map { "voice_history_v2:\(origin.utf8.count):\(origin):\($0)" } } ?? Self.key
|
|
if let raw = AppPreferences.store.string(forKey: storageKey),
|
|
let data = raw.data(using: .utf8),
|
|
let decoded = try? JSONDecoder().decode([HistoryEntry].self, from: data) {
|
|
entries = Array(decoded.suffix(Self.maxEntries))
|
|
}
|
|
}
|
|
|
|
/// Replaces the persisted snapshot with exactly the finals currently in the
|
|
/// canonical visible collection. Called by `TranscriptHandler` after every
|
|
/// change so persistence and the UI never diverge. No dedupe is applied here:
|
|
/// same-source repeats are legitimate entries.
|
|
func sync(finals: [(role: String, text: String, timestamp: Date)]) {
|
|
entries = Array(finals.suffix(Self.maxEntries).map {
|
|
HistoryEntry(role: $0.role, text: $0.text, timestamp: $0.timestamp)
|
|
})
|
|
persist()
|
|
}
|
|
|
|
func clear() {
|
|
entries.removeAll()
|
|
persist()
|
|
}
|
|
|
|
private func persist() {
|
|
guard let data = try? JSONEncoder().encode(entries) else { return }
|
|
AppPreferences.store.set(String(decoding: data, as: UTF8.self), forKey: storageKey)
|
|
}
|
|
}
|