Add voice notes: on-device transcription, multi-language, watch capture

- Schema v2: Note.AudioInfo, .m4a sidecar next to the note JSON in iCloud
- AudioRecorderService (iOS/macOS) + SpeechAnalyzer transcription pipeline
  with enabled-language set and confidence-based auto-pick, re-transcribe menu
- Settings > Transcription > Languages with asset download/reserve handling
- watchOS app + accessory complication: one-button recorder, WCSession
  transferFile to phone, WatchInbox staging, direct-upsert ingestion
- Player UI, transcription status chips, quick-capture mic in notes list
- Version 0.2, changelog + README updated
This commit is contained in:
2026-07-15 13:30:43 -04:00
parent 6a93cedaa6
commit 86d74806c9
44 changed files with 3329 additions and 123 deletions
+24
View File
@@ -11,6 +11,9 @@ final class AppServices {
let syncEngine: SyncEngine
let contextService: ContextService
let backupController: BackupController
let audioRecorder: AudioRecorderService
let transcriptionSettings: TranscriptionSettings
let transcriptionService: TranscriptionService
init() async {
let container = NotesModelContainer.make()
@@ -21,6 +24,20 @@ final class AppServices {
self.backupController = BackupController(
configuration: NotesBackupConfiguration(syncEngine: engine)
)
self.audioRecorder = AudioRecorderService()
let settings = TranscriptionSettings()
self.transcriptionSettings = settings
let transcription = TranscriptionService(
syncEngine: engine,
settings: settings,
modelContainer: container
)
self.transcriptionService = transcription
// A watch recording, once ingested, is this device's to transcribe.
engine.onAudioNoteIngested = { [weak transcription] id in
transcription?.enqueue(noteID: id)
}
}
/// The file extension this app owns for backup documents. Used to route
@@ -30,5 +47,12 @@ final class AppServices {
/// Slow startup work (iCloud container discovery) runs after the UI is up.
func startDeferredServices() async {
await syncEngine.connect()
// Drain any watch recordings delivered while the app was closed the
// container is connected now, so they can be written and ingested.
await syncEngine.ingestStagedWatchRecordings()
// Load the supported languages / reservation cap, then pick up any of
// this device's notes that were still pending when it last quit.
await transcriptionSettings.prepare()
transcriptionService.resumePendingWork()
}
}