feat: wire list view models into AppModel, add strings, list helpers and debug snapshot mode
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,8 @@ final class AppModel: ObservableObject {
|
||||
let history: HistoryStore
|
||||
let dictionary: DictionaryStore
|
||||
let storage = ModelStorage()
|
||||
let historyList: HistoryViewModel
|
||||
let dictionaryList: DictionaryViewModel
|
||||
|
||||
// Observable state
|
||||
@Published private(set) var state: DictationState = .idle
|
||||
@@ -62,13 +64,19 @@ final class AppModel: ObservableObject {
|
||||
private var loadingVariant: String?
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init() {
|
||||
let settings = AppSettings()
|
||||
convenience init() {
|
||||
self.init(preview: false)
|
||||
}
|
||||
|
||||
/// `preview: true` is for snapshots: sample data in a temporary folder, no permission prompts, no model, no hotkey.
|
||||
init(preview: Bool) {
|
||||
let settings = preview ? AppSettings(defaults: UserDefaults(suiteName: "sst-preview")!) : AppSettings()
|
||||
let policy = InsertionPolicy()
|
||||
let knowledge = SystemWordKnowledge()
|
||||
let corrector = DictionaryCorrector(knowledge: knowledge)
|
||||
let dictionary = DictionaryStore(fileURL: AppPaths.dictionary)
|
||||
let history = HistoryStore(fileURL: AppPaths.history)
|
||||
let folder = FileManager.default.temporaryDirectory.appending(path: "sst-preview-\(UUID().uuidString)", directoryHint: .isDirectory)
|
||||
let dictionary = DictionaryStore(fileURL: preview ? folder.appending(path: "dictionary.txt") : AppPaths.dictionary)
|
||||
let history = HistoryStore(fileURL: preview ? folder.appending(path: "history.json") : AppPaths.history)
|
||||
let levelSource = self.levelSource
|
||||
let recorder = AudioRecorder()
|
||||
recorder.onLevel = { levelSource.update($0) }
|
||||
@@ -78,6 +86,8 @@ final class AppModel: ObservableObject {
|
||||
self.corrector = corrector
|
||||
self.dictionary = dictionary
|
||||
self.history = history
|
||||
self.historyList = HistoryViewModel(store: history)
|
||||
self.dictionaryList = DictionaryViewModel(store: dictionary, knowledge: knowledge)
|
||||
self.controller = DictationController(
|
||||
recorder: recorder,
|
||||
transcriber: transcriber,
|
||||
@@ -89,7 +99,11 @@ final class AppModel: ObservableObject {
|
||||
vocabularyProvider: { settings.usePriming ? VocabularyBuilder.build(from: dictionary.entries) : [] })
|
||||
|
||||
wire()
|
||||
Task { await start() }
|
||||
if preview {
|
||||
loadPreviewData()
|
||||
} else {
|
||||
Task { await start() }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: derived
|
||||
@@ -100,7 +114,7 @@ final class AppModel: ObservableObject {
|
||||
|
||||
/// Why recording is not possible right now, or nil when it is.
|
||||
var recordingBlocker: String? {
|
||||
if !permissions.allGranted { return Strings.openSystemSettings }
|
||||
if !permissions.allGranted { return Strings.permissionsMissing }
|
||||
switch modelStatus {
|
||||
case .ready: return nil
|
||||
case .loading(let variant): return Strings.modelLoading(title(for: variant))
|
||||
@@ -181,9 +195,13 @@ final class AppModel: ObservableObject {
|
||||
dictionary.onChange = { [weak self] in
|
||||
guard let self else { return }
|
||||
self.corrector.update(entries: self.dictionary.entries)
|
||||
self.dictionaryList.reload()
|
||||
self.dictionaryRevision += 1
|
||||
}
|
||||
history.onChange = { [weak self] in self?.historyRevision += 1 }
|
||||
history.onChange = { [weak self] in
|
||||
self?.historyRevision += 1
|
||||
self?.historyList.refresh()
|
||||
}
|
||||
NotificationCenter.default.addObserver(forName: NSApplication.willTerminateNotification, object: nil, queue: .main) { [history] _ in
|
||||
MainActor.assumeIsolated { history.flush() }
|
||||
}
|
||||
@@ -240,7 +258,7 @@ final class AppModel: ObservableObject {
|
||||
}
|
||||
|
||||
func stopWindowRecording() {
|
||||
guard policy.windowSession else { return }
|
||||
guard isRecording else { return }
|
||||
Task {
|
||||
await controller.handle(.released)
|
||||
policy.windowSession = false
|
||||
@@ -310,4 +328,37 @@ final class AppModel: ObservableObject {
|
||||
history.removeAll()
|
||||
history.flush()
|
||||
}
|
||||
|
||||
func openDictionaryFile() {
|
||||
NSWorkspace.shared.open(dictionary.fileURL)
|
||||
}
|
||||
|
||||
// MARK: preview
|
||||
|
||||
private func loadPreviewData() {
|
||||
try? dictionary.load()
|
||||
try? dictionary.apply { file in
|
||||
file.append(.term("Anthropic"))
|
||||
file.append(.term("Supabase"))
|
||||
file.append(.correction(heard: "cloud code", wrote: "Claude Code"))
|
||||
file.append(.correction(heard: "cloud", wrote: "Claude"))
|
||||
}
|
||||
let now = Date()
|
||||
history.add(HistoryEntry(date: now.addingTimeInterval(-86_400 * 2), duration: 5.1,
|
||||
rawText: "een oudere notitie over de planning", finalText: "Een oudere notitie over de planning."))
|
||||
history.add(HistoryEntry(date: now.addingTimeInterval(-3_600), duration: 7.2,
|
||||
rawText: "ik wil de meeting verplaatsen naar tomorrow morning",
|
||||
finalText: "Ik wil de meeting verplaatsen naar tomorrow morning."))
|
||||
history.add(HistoryEntry(
|
||||
date: now.addingTimeInterval(-300), duration: 4.4,
|
||||
rawText: "ik gebruik cloud code met supabase", finalText: "Ik gebruik Claude Code met Supabase.",
|
||||
corrections: [
|
||||
AppliedCorrection(heard: "cloud code", wrote: "Claude Code", rule: "cloud code", start: 11, length: 11),
|
||||
AppliedCorrection(heard: "supabase", wrote: "Supabase", rule: "Supabase", start: 27, length: 8),
|
||||
]))
|
||||
activeVariant = ModelCatalog.defaultVariant
|
||||
modelStatus = .ready
|
||||
lastDuration = 4.4
|
||||
historyList.selection = history.entries.first?.id
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user