Conversation
Groundwork for offering Apple's on-device SpeechAnalyzer (macOS 26+) as a third transcription engine alongside WhisperKit and Parakeet (kitlangton#255). - AppleSpeechModel: a single reserved identifier ("apple-speechanalyzer") mirroring ParakeetModel's role as the engine routing table. One entry by design: per-locale assets are OS-managed via AssetInventory and follow the existing Output Language setting, so the engine is one "model" from the user's perspective. - SpeechLocaleResolution: pure resolver mapping the outputLanguage setting (bare ISO codes, nil = Auto) onto SpeechTranscriber's supported locales. Exact BCP-47 match first, then same-language with the user's region preferred ("en" -> "en-US" on a US Mac); auto falls back through the current locale, English, then the first supported locale. Returns nil for unsupported languages so callers surface a clear error instead of silently transcribing in the wrong language. Kept free of Speech imports so it compiles and tests on the macOS 14 deployment target. - HexLog: new AppleSpeech category so Console predicates stay consistent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second of three commits adding Apple's on-device SpeechAnalyzer as a third transcription engine (kitlangton#255). AppleSpeechClient mirrors ParakeetClient's shape: an actor with a compile-time fallback stub, here keyed on `#if compiler(>=6.2)` (the toolchain that ships the macOS 26 SDK) so the app still builds on Xcode 16. The actor is deliberately stateless — locale assets live in the OS-managed AssetInventory and SpeechTranscriber construction is cheap — which is what lets the non-@available TranscriptionClientLive store it directly; all Speech-SDK calls live in a private @available(macOS 26.0, *) enum. - Batch transcription: AVAudioFile -> analyzeSequence -> finalizeAndFinish, results drained concurrently, with a timeout race whose cancellation handler explicitly cancels the unstructured results task. Every error path (including TCA effect cancellation from ESC) tears down via cancelAndFinishNow so no analyzer outlives a request. - Asset management: installs locale packs on demand via AssetInventory.assetInstallationRequest, reporting the request's native Progress through the existing download-progress plumbing; releases stale locale reservations before reserving to avoid exhausting the system's per-app reservation quota (reservation failure is non-fatal). - Locale selection: the outputLanguage setting resolves against SpeechTranscriber.supportedLocales via HexCore's SpeechLocaleResolution; unsupported languages throw an actionable error rather than silently transcribing in the wrong language. TranscriptionClientLive routes by model-name identity exactly like Parakeet: a new isAppleSpeech predicate branches transcribe / download / delete (no-op; assets are OS-managed) / isModelDownloaded, and getAvailableModels advertises the identifier only when the engine reports itself usable (macOS 26+, capable hardware, macOS 26 SDK build) — the UI will key the row's visibility off that in the next commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Final commit of three adding Apple's on-device SpeechAnalyzer engine (kitlangton#255). Surfaces the engine as a single "Apple Speech" row in Settings → Transcription Model, gated at two levels: - OS gate: CuratedModelLoader filters the row out below macOS 26 at State init, so it never exists on older systems (NoModelChooser unaffected). - Hardware/build gate: .modelsLoaded drops the row unless the engine advertised its identifier via getAvailableModels (macOS 26 + capable hardware + macOS 26 SDK build). One row by design: locale assets are OS-managed and follow the existing Output Language setting, so per-locale rows would duplicate that setting. "Installed" for this row means "assets for the currently selected language are on-device" — switching to a language without assets flips the row to not-downloaded, and the existing Download button flow installs the new locale pack with native progress. setOutputLanguage now refreshes the model list so that state tracks the language setting. The auto-switch fallback in .modelsLoaded gets a narrow exemption: when Apple Speech is selected and its row is present, a missing locale pack must prompt a download rather than silently switching engines. When the row is absent (OS downgrade, unsupported hardware), the existing healing to an installed local model still applies — covered by new tests. Show in Finder and Remove Download are hidden for this row: assets live in OS-private storage and AssetInventory has no uninstall API, so offering either would mislead. Parakeet remains the recommended default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds Apple’s on-device SpeechAnalyzer as a selectable, locale-aware transcription engine on macOS 26+, with model-library filtering, OS-managed asset controls, cancellation-safe transcription, and coverage for locale resolution and model selection. ChangesApple SpeechAnalyzer support
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant SettingsFeature
participant ModelDownloadFeature
participant TranscriptionClientLive
participant AppleSpeechClient
participant SpeechAnalyzer
SettingsFeature->>ModelDownloadFeature: refresh models after output language change
ModelDownloadFeature->>TranscriptionClientLive: fetch available models and installation state
TranscriptionClientLive->>AppleSpeechClient: check supported locale assets
AppleSpeechClient-->>TranscriptionClientLive: return installed state
TranscriptionClientLive-->>ModelDownloadFeature: return Apple Speech model information
TranscriptionClientLive->>AppleSpeechClient: transcribe audio
AppleSpeechClient->>SpeechAnalyzer: analyze audio sequence
SpeechAnalyzer-->>AppleSpeechClient: return transcription results
AppleSpeechClient-->>TranscriptionClientLive: return transcript
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Hex/Clients/TranscriptionClient.swift`:
- Around line 104-105: Update the Apple Speech comment above
isAppleSpeech(variant) to append the GitHub issue reference (`#255`), preserving
the existing explanation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f4fa7101-0998-45f5-b9e9-dedc00e52538
📒 Files selected for processing (13)
.changeset/766b5f22.mdCLAUDE.mdHex/Clients/AppleSpeechClient.swiftHex/Clients/TranscriptionClient.swiftHex/Features/Settings/ModelDownload/ModelDownloadFeature.swiftHex/Features/Settings/ModelDownload/ModelDownloadView.swiftHex/Features/Settings/SettingsFeature.swiftHex/Resources/Data/models.jsonHexCore/Sources/HexCore/Logging.swiftHexCore/Sources/HexCore/Logic/SpeechLocaleResolution.swiftHexCore/Sources/HexCore/Models/AppleSpeechModel.swiftHexCore/Tests/HexCoreTests/SpeechLocaleResolutionTests.swiftHexTests/ModelDownloadFeatureTests.swift
Addresses CodeRabbit review feedback on PR kitlangton#269: CLAUDE.md asks that code comments link the GitHub issue a change addresses. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
30e4396 to
a2d2cd5
Compare
|
Addressed the review note — |
|
@kitlangton could you take a look at this? |
Closes #255
Adds Apple's on-device
SpeechAnalyzer/SpeechTranscriberengine as a selectable "Apple Speech" entry in the model library, alongside WhisperKit and Parakeet. Batch-only: it transcribes the recorded file after the hotkey is released, exactly like the existing engines.How it fits the existing architecture
Routing follows the Parakeet pattern precisely: a new
AppleSpeechModelenum ("apple-speechanalyzer") acts as the routing identifier, andTranscriptionClientLivegains anisAppleSpeechbranch parallel toisParakeetin each method. The engine lives in a newAppleSpeechClientactor mirroringParakeetClient's structure, including the compile-time fallback stub — here keyed on#if compiler(>=6.2)(the toolchain that ships the macOS 26 SDK) instead ofcanImport(FluidAudio).Design decisions (and why)
Progress, and Cancel wired through.CuratedModelLoaderfilters the row out below macOS 26 atStateinit, and.modelsLoadeddrops it unless the engine advertised its identifier viagetAvailableModels(macOS 26 +SpeechTranscriber.isAvailablehardware check + macOS 26 SDK build). TheAppleSpeechClientactor is deliberately stateless so the non-@availableTranscriptionClientLivecan store it; all SDK calls live in a private@available(macOS 26.0, *)enum..modelsLoadedfallback that switches away from not-downloaded selections gets a narrow exception: when Apple Speech is selected and its row is present, a missing locale pack prompts a download instead of silently switching engines. When the row is absent (OS downgrade, unsupported hardware), the existing healing to an installed local model still applies.AssetInventoryhas no uninstall API — offering either would mislead.deleteModelis a defensive no-op.SpeechLocaleResolution): exact BCP-47 match, else same-language preferring the user's region ("en" → "en-US" on a US Mac); Auto falls back through the current locale → English → first supported. Unsupported languages throw an actionable error rather than silently transcribing in the wrong language. Being OS-independent, it unit-tests on macOS 14.Toolchain note for releases
The engine only exists in binaries built with Xcode 26. A build from Xcode 16 compiles the stub and the row simply never appears — graceful, but worth knowing that the release toolchain gates the feature.
Tests
cd HexCore && swift test, runs on any macOS).ModelDownloadFeatureTestscovering the OS filter, the engine-advertisement gating, installed-state merging, the auto-switch exemption, and OS-downgrade healing. Two skip viaXCTSkipon hosts below macOS 26 (they need the bundled curated row).xcodebuild test), Release build compiles.Manual verification (macOS 26, Apple Silicon)
AppleSpeechcategory logs with transcript text and file names correctly marked private.Possible follow-up (intentionally out of scope): streaming transcription while recording via
SpeechAnalyzer's volatile results, which would pair naturally with the live-preview work in #238.🤖 Generated with Claude Code
Summary by CodeRabbit