feat: Notitie editor in the app, dictation routed to system or editor
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+24
-10
@@ -6,20 +6,25 @@ import SstData
|
||||
import SstWhisper
|
||||
import SwiftUI
|
||||
|
||||
/// Decides at insertion time whether dictated text may be pasted.
|
||||
/// Not when Sst itself is in front (it would land in Sst's own search field) and not for recordings started in the window.
|
||||
/// Decides at insertion time where dictated text goes.
|
||||
/// Sst in front: into the Notitie editor if (and only if) it has the cursor, otherwise nowhere (it would land in Sst's own
|
||||
/// search field). Another app in front: pasted there, except for recordings started in the window.
|
||||
final class InsertionPolicy {
|
||||
var windowSession = false
|
||||
|
||||
func allowed() -> Bool {
|
||||
!windowSession && NSWorkspace.shared.frontmostApplication?.bundleIdentifier != Bundle.main.bundleIdentifier
|
||||
func route() -> InsertionRoute {
|
||||
MainActor.assumeIsolated {
|
||||
let sstInFront = NSWorkspace.shared.frontmostApplication?.bundleIdentifier == Bundle.main.bundleIdentifier
|
||||
if sstInFront { return NoteEditorRegistry.shared.active != nil ? .editor : .none }
|
||||
return windowSession ? .none : .system
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class AppModel: ObservableObject {
|
||||
enum Section: String, CaseIterable, Identifiable {
|
||||
case transcripts, dictionary
|
||||
case transcripts, dictionary, note
|
||||
var id: String { rawValue }
|
||||
}
|
||||
|
||||
@@ -37,6 +42,7 @@ final class AppModel: ObservableObject {
|
||||
let history: HistoryStore
|
||||
let dictionary: DictionaryStore
|
||||
let storage = ModelStorage()
|
||||
let note: NoteStore
|
||||
let historyList: HistoryViewModel
|
||||
let dictionaryList: DictionaryViewModel
|
||||
|
||||
@@ -77,6 +83,7 @@ final class AppModel: ObservableObject {
|
||||
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 note = NoteStore(fileURL: preview ? folder.appending(path: "note.txt") : AppPaths.note)
|
||||
let levelSource = self.levelSource
|
||||
let recorder = AudioRecorder()
|
||||
recorder.onLevel = { levelSource.update($0) }
|
||||
@@ -86,15 +93,17 @@ final class AppModel: ObservableObject {
|
||||
self.corrector = corrector
|
||||
self.dictionary = dictionary
|
||||
self.history = history
|
||||
self.note = note
|
||||
self.historyList = HistoryViewModel(store: history)
|
||||
self.dictionaryList = DictionaryViewModel(store: dictionary, knowledge: knowledge)
|
||||
self.controller = DictationController(
|
||||
recorder: recorder,
|
||||
transcriber: transcriber,
|
||||
cleaner: PassthroughCleaner(),
|
||||
inserter: GatedInserter(
|
||||
inner: TextInserter(pasteboard: SystemPasteboard(), paster: CGEventPaster()),
|
||||
shouldInsert: { policy.allowed() }),
|
||||
inserter: RoutingInserter(
|
||||
system: TextInserter(pasteboard: SystemPasteboard(), paster: CGEventPaster()),
|
||||
editor: EditorInserter(),
|
||||
route: { policy.route() }),
|
||||
corrector: corrector,
|
||||
vocabularyProvider: { settings.usePriming ? VocabularyBuilder.build(from: dictionary.entries) : [] })
|
||||
|
||||
@@ -221,14 +230,18 @@ final class AppModel: ObservableObject {
|
||||
NotificationCenter.default.addObserver(forName: NSApplication.didBecomeActiveNotification, object: nil, queue: .main) { [weak self] _ in
|
||||
MainActor.assumeIsolated { self?.dictionary.reloadIfChanged() }
|
||||
}
|
||||
NotificationCenter.default.addObserver(forName: NSApplication.willTerminateNotification, object: nil, queue: .main) { [history] _ in
|
||||
MainActor.assumeIsolated { history.flush() }
|
||||
NotificationCenter.default.addObserver(forName: NSApplication.willTerminateNotification, object: nil, queue: .main) { [history, note] _ in
|
||||
MainActor.assumeIsolated {
|
||||
history.flush()
|
||||
note.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func start() async {
|
||||
do { try dictionary.load() } catch { lastError = error.localizedDescription }
|
||||
dictionary.startWatching()
|
||||
note.load()
|
||||
switch history.load() {
|
||||
case .recoveredCorrupt:
|
||||
lastError = "Het geschiedenisbestand was beschadigd en is opzij gezet."
|
||||
@@ -364,6 +377,7 @@ final class AppModel: ObservableObject {
|
||||
// MARK: preview
|
||||
|
||||
private func loadPreviewData() {
|
||||
note.text = "Notities voor vrijdag: Supabase-migratie plannen en Claude Code uitproberen."
|
||||
try? dictionary.load()
|
||||
try? dictionary.apply { file in
|
||||
file.append(.term("Anthropic"))
|
||||
|
||||
Reference in New Issue
Block a user