# FluidVoice Architecture: A Deep Dive into the macOS Dictation App's Modular Design

> Explore the FluidVoice architecture, a modular macOS dictation app. Discover its clean separation of concerns, interchangeable components, and real-time AI-enhanced speech processing.

- Repository: [ALTIC/FluidVoice](https://github.com/altic-dev/FluidVoice)
- Tags: architecture
- Published: 2026-07-08

---

**FluidVoice is a native macOS dictation application built with SwiftUI that uses a centralized service container, pluggable transcription backends, and an AI-enhancement layer to process speech in real-time.** The architecture follows a clean separation of concerns, separating audio capture, transcription providers, post-processing, and UI rendering into distinct, interchangeable components.

## Application Entry Point and Service Initialization

The application launches from [`Sources/Fluid/fluidApp.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/fluidApp.swift), where the `FluidApp` struct serves as the entry point. It instantiates a singleton `AppServices` object that manages all heavy-weight services throughout the app lifecycle.

```swift
// https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/fluidApp.swift
@main
struct FluidApp: App {
    @StateObject private var menuBarManager = MenuBarManager()
    @StateObject private var appServices: AppServices   // ← singleton
    ...
}

```

The top-level `WindowGroup` wraps the UI inside `AdaptiveAppTheme`, injecting both `MenuBarManager` and `AppServices` as environment objects. This setup ensures that services are accessible throughout the view hierarchy while maintaining a single source of truth.

## Central Service Container

At the heart of the FluidVoice architecture lies `AppServices` ([`Sources/Fluid/Services/AppServices.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/AppServices.swift)), a singleton that implements lazy initialization to prevent Swift runtime crashes during launch. Services are only created after the UI signals readiness via `signalUIReady()`, deferring expensive initialization until the interface is fully loaded.

```swift
// https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/AppServices.swift
final class AppServices: ObservableObject {
    static let shared = AppServices()
    ...
    var audioObserver: AudioHardwareObserver { ... }
    var asr: ASRService { ... }
}

```

The container exposes two critical services: **AudioHardwareObserver**, which monitors microphone and audio route changes, and **ASRService**, which orchestrates the active transcription provider. This pattern allows the app to delay initialization of compute-intensive components until they are actually needed.

## Modular Transcription Provider System

FluidVoice abstracts transcription engines through the `TranscriptionProvider` protocol defined in [`Sources/Fluid/Services/TranscriptionProvider.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/TranscriptionProvider.swift). Each provider implements `prepare(progressHandler:)` for model downloading and compilation, plus `transcribeStreaming(_:)` and `transcribe(_:)` methods for speech recognition.

The codebase includes five distinct transcription backends:

- **FluidAudioProvider** ([`Sources/Fluid/Services/FluidAudioProvider.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/FluidAudioProvider.swift)): Apple-Silicon-optimized using the on-device `FluidAudio` framework with optional vocabulary boosting
- **WhisperProvider** ([`Sources/Fluid/Services/WhisperProvider.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/WhisperProvider.swift)): Open-source Whisper models for Intel Macs
- **NemotronProvider** ([`Sources/Fluid/Services/NemotronProvider.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/NemotronProvider.swift)): NVIDIA Nemotron Speech 3.5 models
- **ParakeetRealtimeProvider** ([`Sources/Fluid/Services/ParakeetRealtimeProvider.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ParakeetRealtimeProvider.swift)): Real-time Parakeet "Flash" model for low-latency scenarios
- **ExternalCoreMLTranscriptionProvider** ([`Sources/Fluid/Services/ExternalCoreMLTranscriptionProvider.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ExternalCoreMLTranscriptionProvider.swift)): Generic CoreML wrapper for third-party downloaded models

```swift
// https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/FluidAudioProvider.swift
func prepare(progressHandler: ((ModelPreparationProgress) -> Void)? = nil) async throws {
    // Download model, initialise AsrManager, configure boosting …
}

```

This provider pattern enables runtime switching between transcription engines without modifying the core application logic.

## AI Enhancement and LLM Integration

When **Fluid Intelligence** or cloud AI providers are enabled, raw transcripts flow through the `LLMClient` ([`Sources/Fluid/Services/LLMClient.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/LLMClient.swift)). This client supports both local on-device processing and remote endpoints including OpenAI, Groq, and custom API endpoints.

The enhancement pipeline follows this sequence:

1. `ASRService` emits raw transcript text
2. `DictationPostProcessingService` forwards the text to `LLMClient`
3. The client retrieves API keys from the macOS Keychain via [`KeychainService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/KeychainService.swift)
4. Enhanced text returns to the overlay or gets inserted into the target application

```swift
import Fluid

let transcript = "i ate an apple"
let enhanced = try await LLMClient.shared.enhance(
    text: transcript,
    context: nil,
    model: .fluidIntelligence   // local model
)

```

## UI Overlay and User Interaction

The user interface layer consists of SwiftUI views including [`CommandModeView.swift`](https://github.com/altic-dev/FluidVoice/blob/main/CommandModeView.swift), [`RewriteModeView.swift`](https://github.com/altic-dev/FluidVoice/blob/main/RewriteModeView.swift), and [`BottomOverlayView.swift`](https://github.com/altic-dev/FluidVoice/blob/main/BottomOverlayView.swift). A **Notch-aware overlay** managed by [`NotchOverlayManager.swift`](https://github.com/altic-dev/FluidVoice/blob/main/NotchOverlayManager.swift) creates a live transcription window that adapts to the MacBook notch or renders as a pill-style overlay.

Global hotkey handling occurs in [`Sources/Fluid/Services/GlobalHotkeyManager.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/GlobalHotkeyManager.swift), which binds keyboard shortcuts to transcription controls. The `MenuBarManager` adds status bar integration and quick settings access.

```swift
import Fluid

let hotkey = Hotkey(keyCode: kVK_ANSI_V, modifiers: [.command, .option])
GlobalHotkeyManager.shared.register(hotkey) {
    AppServices.shared.asr.toggleCapture()
}

```

## Persistence and Settings Management

User preferences and history persist through thin wrappers around `UserDefaults` and file-based storage:

- **SettingsStore** ([`Sources/Fluid/Persistence/SettingsStore.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Persistence/SettingsStore.swift)): App-wide preferences including selected speech model and hotkey bindings
- **TranscriptionHistoryStore** ([`Sources/Fluid/Persistence/TranscriptionHistoryStore.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Persistence/TranscriptionHistoryStore.swift)): Local recordings and transcript logs
- **ChatHistoryStore** ([`Sources/Fluid/Persistence/ChatHistoryStore.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Persistence/ChatHistoryStore.swift)): AI conversation history

These stores expose `@Published` properties, enabling automatic SwiftUI updates when underlying data changes. Model files download via [`ModelDownloader.swift`](https://github.com/altic-dev/FluidVoice/blob/main/ModelDownloader.swift) ([`Sources/Fluid/Networking/ModelDownloader.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Networking/ModelDownloader.swift)), which reports progress back to the UI layer.

## Data Flow and Component Interaction

The complete FluidVoice architecture follows a unidirectional data flow:

1. **Startup**: `FluidApp` initializes `AppServices`, which lazily instantiates services after `signalUIReady()`
2. **Trigger**: `GlobalHotkeyManager` captures the activation shortcut and signals `ASRService`
3. **Capture**: The selected provider (e.g., `FluidAudioProvider`) streams audio chunks
4. **Recognition**: `ASRService` produces interim and final text transcripts
5. **Enhancement**: Optional `LLMClient` processing refines the text
6. **Output**: The UI overlay displays live text while `TypingService` injects final results into the target application via Accessibility APIs

This modular design allows developers to swap transcription backends, add new AI providers, or modify UI overlays by implementing the corresponding protocols without touching the core service layer.

## Summary

- **FluidVoice** uses a **SwiftUI** frontend with a singleton `AppServices` container managing lazy-initialized dependencies.
- The **transcription provider system** supports multiple backends including FluidAudio, Whisper, Nemotron, and Parakeet through a unified protocol.
- **AI enhancement** runs through `LLMClient`, supporting both local Fluid Intelligence and remote APIs with Keychain-secured credentials.
- **Persistence** uses `UserDefaults`-based stores with `@Published` properties for reactive UI updates.
- The architecture supports **hot-swappable components**, allowing new speech models or AI endpoints without structural changes.

## Frequently Asked Questions

### What programming language is FluidVoice built with?

FluidVoice is written entirely in **Swift** using the **SwiftUI** framework for the user interface. The codebase leverages modern Swift concurrency patterns with `async/await` for network operations and transcription tasks.

### How does FluidVoice handle different transcription models?

The app uses a **provider protocol** pattern where all transcription engines implement the `TranscriptionProvider` interface. `ASRService` maintains a reference to the active provider, allowing users to switch between Apple-Silicon-optimized FluidAudio, OpenAI Whisper, NVIDIA Nemotron, or custom CoreML models without restarting the application.

### Where does FluidVoice store user settings and API keys?

Settings persist through `SettingsStore` using `UserDefaults`, while sensitive API keys for external AI services are stored in the **macOS Keychain** via [`KeychainService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/KeychainService.swift). Transcription history and chat logs are stored in separate file-based repositories managed by `TranscriptionHistoryStore` and `ChatHistoryStore`.

### Can FluidVoice work with custom AI endpoints?

Yes. The `LLMClient` supports configuration for custom OpenAI-compatible endpoints. Users can specify alternative base URLs and API keys through the settings interface, allowing integration with private LLM deployments or services like Groq and self-hosted models.