How FluidVoice Downloads and Caches ASR Models: A Complete Technical Breakdown
FluidVoice downloads ASR models through a two-layer architecture where ASRService orchestrates the operation and model-specific providers handle the actual network fetch, streaming the file to a persistent cache directory in ~/Library/Application Support/FluidVoice/Models/ for subsequent offline use.
FluidVoice's automatic speech recognition (ASR) infrastructure relies on a clean separation between model-agnostic orchestration and provider-specific implementation. This article explains exactly how the open-source macOS dictation app downloads, caches, and retrieves speech-to-text models based on the source code in altic-dev/FluidVoice.
The Entry Point: ASRService Orchestration
All model download operations in FluidVoice flow through ASRService, a singleton service that manages the complete ASR lifecycle. When a user selects a speech model—or when the UI triggers an explicit download—the sequence begins with model selection and provider instantiation.
Model Selection and Provider Creation
The currently active model lives in SettingsStore.shared.selectedSpeechModel. The ASRService creates an appropriate transcription provider through its private getProvider(for:) method:
private func getProvider(for model: SettingsStore.SpeechModel) -> TranscriptionProvider
This method returns concrete implementations such as WhisperProvider, FluidAudioProvider, or ParakeetRealtimeProvider depending on the selected model type. Source: [ASRService.swift](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ASRService.swift#L78-L105).
Initiating the Download
The public entry point ASRService.downloadModel(_:source:progressHandler:) enforces single-operation semantics and publishes state for UI binding:
try await provider.prepare(progressHandler: { progress in … })
Key responsibilities at this stage include:
- Recording the model ID in
downloadingModelId - Setting
modelPreparationPhase = .preparingDownload - Ensuring only one model operation runs concurrently
Source: [ASRService.swift](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ASRService.swift#L107-L120).
Provider-Level Download Implementation
Each concrete provider implements the TranscriptionProvider protocol's prepare(progressHandler:) method. The WhisperProvider implementation demonstrates FluidVoice's download and caching pattern.
Streaming Download with Atomic Placement
In WhisperProvider.swift, the download process constructs a Hugging Face asset URL and delegates to ProgressiveFileDownloader:
let (downloadedURL, response) = try await ProgressiveFileDownloader.download(
url, progressHandler: progressHandler)
try FileManager.default.moveItem(at: downloadedURL, to: destination)
Critical implementation details:
- Temporary file handling: Data streams to a temporary location first
- Atomic move: Only upon successful completion does the file relocate to its final destination
- Destination path:
~/Library/Application Support/FluidVoice/Models/<model-id>
Source: [WhisperProvider.swift](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/WhisperProvider.swift#L343-L409).
Caching Strategy and Cache Hits
FluidVoice's ASR model caching eliminates redundant network requests through explicit existence checks and state publishing.
Cache Validation
Before any network operation, providers call modelsExistOnDisk(). If the cached file is present and valid, provider.prepare skips the download entirely and proceeds directly to model loading.
UI-Bound Cache State
ASRService exposes three key properties for interface binding:
modelsExistOnDisk: Boolean indicating cache presenceisDownloadingModel: Active download statusdownloadProgress: Fractional completion (0.0–1.0)
The computed property modelStatusMessage surfaces this state to users:
var modelStatusMessage: String {
if self.isAsrReady { return "Model ready" }
…
if self.modelsExistOnDisk { return "Model cached, needs loading" }
}
Source: [ASRService.swift](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ASRService.swift#L22-L32).
Loading Cached Models into Memory
When a user initiates dictation, ASRService.ensureReadyTask invokes provider.prepare again. This time the provider detects the cached file and loads it—without network activity—into the appropriate runtime:
- Whisper models: Loaded via Core ML or GGML inference
- FluidAudio/Parakeet models: Initialized through the FluidAudio runtime
The cache directory persists across app launches, enabling immediate availability after first download.
Cancellation and Cache Consistency
FluidVoice handles interrupted downloads gracefully. If cancelModelDownload() is invoked, providers may trigger provider.clearCache() to remove partial files, preventing corrupted cache entries.
Practical Code Examples
Force Download with Progress Tracking
Button("Download Model") {
Task {
do {
try await ASRService.shared.downloadModel(
SettingsStore.shared.selectedSpeechModel,
progressHandler: { fraction in
print("Download progress: \(fraction * 100)%")
}
)
} catch {
print("Download failed: \(error)")
}
}
}
Check Cache State Programmatically
if ASRService.shared.modelsExistOnDisk {
print("Model is already cached – loading directly")
} else {
print("Model not present – will download on first use")
}
Cancel Ongoing Download
ASRService.shared.cancelModelDownload()
Key Source Files
| File | Responsibility |
|---|---|
Sources/Fluid/Services/ASRService.swift |
Central orchestration of model selection, download state management, and provider coordination |
Sources/Fluid/Services/WhisperProvider.swift |
Whisper-specific download implementation with Hugging Face integration |
Sources/Fluid/Services/FluidAudioProvider.swift |
Parakeet (FluidAudio) model handling |
Sources/Fluid/Services/ExternalCoreMLTranscriptionProvider.swift |
Cohere Transcribe Core ML model caching |
Sources/Fluid/Persistence/SettingsStore.swift |
User model preference persistence (selectedSpeechModel) |
These files collectively implement the complete ASR model download and caching lifecycle in FluidVoice.
Summary
- Orchestration layer:
ASRServicemanages all model operations, enforces single-download semantics, and publishes observable state - Provider abstraction: Each ASR backend implements its own
prepare(progressHandler:)for model-specific fetch logic - Atomic caching: Downloads stream to temporary files, then move atomically to
~/Library/Application Support/FluidVoice/Models/<model-id> - Cache validation: Subsequent preparations check disk existence before network requests
- Graceful interruption: Cancellation APIs prevent partial file corruption
Frequently Asked Questions
Where does FluidVoice store downloaded ASR models?
FluidVoice caches ASR models in ~/Library/Application Support/FluidVoice/Models/<model-id>, a directory within the macOS application support folder that persists across app launches and system restarts.
Can FluidVoice work offline after downloading models?
Yes. Once a model is cached via modelsExistOnDisk, ASRService and its providers load the local file directly into memory without network activity, enabling complete offline dictation.
How does FluidVoice handle interrupted or failed downloads?
Providers may invoke clearCache() to remove partial files, and ASRService exposes cancelModelDownload() for user-initiated cancellation. The atomic move pattern ensures only complete, verified downloads reach the cache directory.
Which ASR models does FluidVoice support for download?
The codebase includes providers for Whisper (via WhisperProvider), Parakeet/FluidAudio (via FluidAudioProvider), and Cohere Transcribe (via ExternalCoreMLTranscriptionProvider), with extensibility for additional backends through the TranscriptionProvider protocol.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →