# FluidVoice ModelRepository: How Voice Models Are Managed and Downloaded

> Explore the FluidVoice ModelRepository to learn how AI provider configurations and on-device voice recognition models are managed and downloaded securely from Hugging Face.

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

---

**The ModelRepository is a singleton registry that centralizes AI provider configurations, default model lists, and API endpoints, while the HuggingFaceModelDownloader handles secure, cached downloads of on-device voice recognition models from Hugging Face repositories.**

In the FluidVoice codebase, the `ModelRepository` serves as the single source of truth for managing AI provider metadata and available models. This Swift singleton consolidates provider IDs, default model names, and API endpoints in one location, while a separate download pipeline handles fetching Core ML voice recognition models on demand. Understanding how these components interact is essential for developers extending FluidVoice's speech-to-text capabilities or integrating new AI providers.

## What Is the ModelRepository?

The `ModelRepository` is implemented as a singleton in [`Sources/Fluid/Services/ModelRepository.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ModelRepository.swift). It acts as a centralized registry that abstracts away provider-specific details from the UI layer, ensuring consistent data access across the application.

### Core Responsibilities

The repository manages several critical functions:

- **Built-in provider IDs**: Hardcoded identifiers like `openai`, `anthropic`, and `ollama` exposed via `ModelRepository.builtInProviderIDs`
- **Default model lists**: The `defaultModels(for:)` method returns preset models when users haven't configured custom ones
- **API endpoint resolution**: `defaultBaseURL(for:)` provides the REST API base URL for each provider
- **Display metadata**: Human-readable names via `displayName(for:)` and documentation links via `providerWebsiteURL(for:)`
- **Storage keys**: `providerKey(for:)` and `providerKeys(for:)` generate UserDefaults-compatible keys for persisting custom provider settings
- **Built-in detection**: `isBuiltIn(_:)` checks whether a provider is internal or user-added

### Integration with the UI Layer

Views like `CommandModeView` and `RewriteModeView` call `ModelRepository.shared` to populate model pickers. When a user selects a provider, the view queries `defaultModels(for:)` to update the available options, ensuring consistent data across the app without hardcoding provider logic in the UI layer.

## How Voice Models Are Downloaded

For on-device automatic speech recognition (ASR), FluidVoice does not bundle model files. Instead, it downloads them from Hugging Face repositories at runtime using the `HuggingFaceModelDownloader` class in [`Sources/Fluid/Networking/ModelDownloader.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Networking/ModelDownloader.swift).

### The HuggingFaceModelDownloader

The downloader initializes with default parameters pointing to the FluidInference organization:

```swift
let downloader = HuggingFaceModelDownloader()
// Defaults: owner = "FluidInference", repo = "parakeet-tdt-0.6b-v3-coreml"

```

Custom repositories can be specified via the alternate initializer, allowing flexibility for different model versions or private forks.

### The Download Pipeline

The download process follows a robust validation chain:

1. **Presence check**: The UI calls `ensureModelsPresent(at:progressHandler:)` which scans the local cache for required Core ML bundles and vocabulary files
2. **Integrity validation**: The downloader inspects files for HTML markup (indicating proxy blocks or errors) by examining the `Content-Type` header and the first 512 bytes of the file
3. **Progress tracking**: A closure receives per-file and overall progress updates for UI progress bars
4. **Error handling**: Corrupted or partial downloads are automatically deleted and queued for re-download when the byte count mismatches the server's `Content-Length` header

### Loading Local ASR Models

Once downloaded, models are loaded into memory using `loadLocalAsrModels(from:)`, available only on arm64 devices. This method handles both modern structures (Preprocessor + Encoder) and legacy formats (MelEncoder), returning ready-to-use Core ML objects for speech recognition.

## Practical Code Examples

### Listing Default Models for a Provider

```swift
import Fluid

let provider = SettingsStore.shared.currentProviderID
let defaultModels = ModelRepository.shared.defaultModels(for: provider)
print("Available models: \(defaultModels)")

```

This queries [`Sources/Fluid/Services/ModelRepository.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ModelRepository.swift) to retrieve the hardcoded default model list for the specified provider ID.

### Fetching Remote Model Lists

```swift
import Fluid

let provider = "openai"
let baseURL = ModelRepository.shared.defaultBaseURL(for: provider)
let apiKey = SettingsStore.shared.apiKey(for: provider)

Task {
    do {
        let models = try await ModelRepository.shared.fetchModels(
            for: provider,
            baseURL: baseURL,
            apiKey: apiKey
        )
        print("Remote models: \(models)")
    } catch {
        print("Fetch failed: \(error)")
    }
}

```

The `fetchModels` method in [`ModelRepository.swift`](https://github.com/altic-dev/FluidVoice/blob/main/ModelRepository.swift) handles the authenticated API call to retrieve available models from providers like OpenAI.

### Downloading and Loading ASR Models

```swift
import Fluid

let downloader = HuggingFaceModelDownloader()
let modelRoot = FileManager.default
    .urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
    .appendingPathComponent("ParakeetASR")

Task {
    do {
        // Download if missing or corrupted
        try await downloader.ensureModelsPresent(at: modelRoot) { progress, filename in
            print(String(format: "Downloading %.1f%%: %@", progress * 100, filename))
        }
        
        // Load into Core ML objects
        let asrModels = try await downloader.loadLocalAsrModels(from: modelRoot)
        print("ASR ready: \(asrModels.version)")
    } catch {
        print("Error: \(error)")
    }
}

```

This example from [`Sources/Fluid/Networking/ModelDownloader.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Networking/ModelDownloader.swift) demonstrates the complete pipeline from remote download to local inference setup.

### Integrating with SwiftUI Views

```swift
struct CommandModeView: View {
    @State private var availableModels: [String] = []
    @ObservedObject var settings = SettingsStore.shared
    
    var body: some View {
        SearchableModelPicker(
            models: availableModels,
            selectedModel: $settings.commandModeSelectedModel
        )
        .onAppear(perform: updateAvailableModels)
    }
    
    private func updateAvailableModels() {
        let provider = settings.currentProviderID
        availableModels = ModelRepository.shared.defaultModels(for: provider)
    }
}

```

As implemented in [`Sources/Fluid/Views/CommandModeView.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Views/CommandModeView.swift), views bind directly to the repository for real-time model list updates.

## Summary

- The **ModelRepository** in [`Sources/Fluid/Services/ModelRepository.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ModelRepository.swift) is a singleton that centralizes provider metadata, default models, and API configuration
- **UI components** like `CommandModeView` consume `ModelRepository.shared` to maintain a single source of truth for model selection
- **Voice model downloads** are handled by `HuggingFaceModelDownloader` in [`Sources/Fluid/Networking/ModelDownloader.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Networking/ModelDownloader.swift), which fetches Core ML models from Hugging Face repositories
- The download pipeline includes **integrity checks** (HTML sniffing, Content-Length validation) and **progress callbacks** for robust UX
- **ASR models** are loaded via `loadLocalAsrModels(from:)` only on arm64 devices, supporting both modern and legacy model structures

## Frequently Asked Questions

### Where is the ModelRepository defined in the FluidVoice codebase?

The `ModelRepository` is defined as a singleton class in [`Sources/Fluid/Services/ModelRepository.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ModelRepository.swift). It consolidates all provider-related metadata including built-in provider IDs, default model lists, base URLs, and display names into a single accessible location that the rest of the application references.

### How does FluidVoice handle voice model downloads without bundling them in the app?

FluidVoice uses the `HuggingFaceModelDownloader` class to download Core ML voice recognition models on first use from Hugging Face repositories (specifically the FluidInference organization). This keeps the app bundle size small while ensuring users receive the latest model versions, with files cached in the Application Support directory for subsequent launches.

### What validation does FluidVoice perform on downloaded voice models?

The downloader validates downloads by checking the `Content-Type` header and inspecting the first 512 bytes for HTML markup (which would indicate a proxy block or error page). It also verifies that the received byte count matches the server's `Content-Length` header, automatically deleting and re-queueing files that fail these integrity checks.

### Can developers use custom voice models or providers with FluidVoice?

Yes. The `HuggingFaceModelDownloader` accepts custom owner and repository names via its initializer, allowing developers to point to different Hugging Face repositories. For AI providers, the `ModelRepository` supports custom provider IDs alongside built-in ones, with storage keys generated via `providerKey(for:)` to persist custom configurations in UserDefaults.