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

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, 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.

// 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), 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.

// 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. 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:

// 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). 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
  4. Enhanced text returns to the overlay or gets inserted into the target application
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, RewriteModeView.swift, and BottomOverlayView.swift. A Notch-aware overlay managed by 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, which binds keyboard shortcuts to transcription controls. The MenuBarManager adds status bar integration and quick settings access.

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:

These stores expose @Published properties, enabling automatic SwiftUI updates when underlying data changes. Model files download via ModelDownloader.swift (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. 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →