# How the Tauri Desktop Crate Calls llmfit‑core and Manages Ollama Pull State

> Discover how the Tauri desktop crate integrates with llmfit-core to manage Ollama pull state. Learn about its command facade, PullHandle, and non-blocking poll commands for seamless UI updates.

- Repository: [Alex Jones/llmfit](https://github.com/AlexsJones/llmfit)
- Tags: internals
- Published: 2026-09-11

---

**The Tauri desktop crate acts as a thin command façade that instantiates an `OllamaProvider` from `llmfit‑core`, stores the resulting `PullHandle` in a mutex-protected state, and exposes non-blocking poll commands to stream progress events to the UI.**

The `llmfit‑desktop` crate in the AlexsJones/llmfit repository serves as a lightweight frontend wrapper that delegates all model-fitting and Ollama management logic to the shared `llmfit‑core` library. By maintaining thread-safe state through a managed `AppState` struct, the desktop application orchestrates long-running pull operations without blocking the UI thread. This architecture allows the Tauri frontend to invoke core capabilities while receiving real-time progress updates through a command-based polling mechanism.

## Initializing Shared State with the Core Provider

When the Tauri application launches, it constructs an `AppState` struct that holds a single instance of the core provider and a placeholder for the active pull operation. According to the source in **`llmfit‑desktop/src/main.rs`** (lines 57‑60), the state initialization looks like this:

```rust
.manage(AppState {
    ollama: OllamaProvider::new(),
    pull_handle: Mutex::new(None),
})

```

The `OllamaProvider` is imported from **`llmfit‑core/src/providers.rs`** and created via its default constructor `OllamaProvider::new()`. This provider encapsulates all HTTP communication with the local Ollama daemon. The `pull_handle` field uses `std::sync::Mutex<Option<PullHandle>>` to ensure safe access across the asynchronous Tauri command boundary, allowing the UI to start a pull in one invocation and poll it in another.

## Exposing Tauri Commands to the Frontend

The desktop crate exposes functionality through functions annotated with `#[tauri::command]` and registered via `tauri::generate_handler!`. These commands act as a thin translation layer between the JavaScript frontend and the Rust core:

- **`get_system_specs`** – Invokes `SystemSpecs::detect()` from **`llmfit‑core/src/hardware.rs`** to query RAM, CPU, and GPU metadata.
- **`get_model_fits`** – Constructs a `ModelDatabase` (from **`llmfit‑core/src/models.rs`**), runs the analysis pipeline in **`llmfit‑core/src/analysis.rs`**, and maps the resulting `ModelFit` structs into serializable DTOs.
- **`is_ollama_available`** – Forwards the health check to `OllamaProvider::is_available()` in the core provider.

None of these commands implement business logic; they merely forward data from `llmfit‑core` to the frontend.

## Managing Ollama Pull State

The most complex interaction involves spawning a background model pull and reporting incremental progress. The desktop crate implements a start-and-poll pattern using channels and mutexes.

### Starting a Background Pull

The `start_pull` command (defined in **`llmfit‑desktop/src/main.rs`** lines 36‑45) receives a model tag from the frontend, delegates the network operation to the core, and captures the handle:

```rust
#[tauri::command]
fn start_pull(model_tag: String, state: State<'_, AppState>) -> Result<String, String> {
    let handle = state.ollama.start_pull(&model_tag)?;
    let mut pull = state.pull_handle.lock().map_err(|e| e.to_string())?;
    *pull = Some(handle);
    Ok("started".to_string())
}

```

The `OllamaProvider::start_pull` implementation in **`llmfit‑core/src/providers.rs`** (lines 89‑102) spawns a dedicated thread that streams the Ollama API response lines. It parses the JSON progress into `PullEvent` variants and sends them through an `std::sync::mpsc::channel`. The `PullHandle` struct returned to the desktop contains the `Receiver<PullEvent>` and is stored inside the mutex guarded `AppState`.

### Polling for Progress Updates

Because the pull runs in a background thread, the frontend polls for updates using the `poll_pull` command (lines 46‑80 in **`llmfit‑desktop/src/main.rs`**):

```rust
#[tauri::command]
fn poll_pull(state: State<'_, AppState>) -> Result<PullStatus, String> {
    let pull = state.pull_handle.lock().map_err(|e| e.to_string())?;
    if let Some(ref handle) = *pull {
        match handle.receiver.try_recv() {
            Ok(PullEvent::Progress { status, percent }) => {
                Ok(PullStatus { status, percent, complete: false, error: None })
            }
            Ok(PullEvent::Done) => {
                Ok(PullStatus { status: "complete".into(), percent: 100.0, complete: true, error: None })
            }
            Ok(PullEvent::Error(e)) => {
                Ok(PullStatus { status: "error".into(), percent: 0.0, complete: false, error: Some(e) })
            }
            Err(std::sync::mpsc::TryRecvError::Empty) => {
                Ok(PullStatus { status: "in_progress".into(), percent: 0.0, complete: false, error: None })
            }
            Err(std::sync::mpsc::TryRecvError::Disconnected) => {
                Ok(PullStatus { status: "disconnected".into(), percent: 0.0, complete: false, error: None })
            }
        }
    } else {
        Err("No pull in progress".to_string())
    }
}

```

The `PullEvent` enum defined in **`llmfit‑core/src/providers.rs`** (lines 38‑46) distinguishes between `Progress`, `Done`, and `Error` states. By using `try_recv`, the command remains non-blocking; it returns immediately with the latest status or an "in_progress" marker if the channel is empty.

## Front‑End Integration Example

From the TypeScript side, the frontend starts a pull and schedules periodic polls:

```typescript
import { invoke } from '@tauri-apps/api/core';

// Initiate the pull
await invoke('start_pull', { modelTag: 'llama3.1:8b' });

// Poll every 500ms until completion
const poll = async () => {
  const status: PullStatus = await invoke('poll_pull');
  console.log(`${status.status}: ${status.percent}%`);
  
  if (!status.complete && !status.error) {
    setTimeout(poll, 500);
  }
};

poll();

```

Fetching static data follows the same pattern:

```typescript
const specs = await invoke<SystemInfo>('get_system_specs');
const fits = await invoke<ModelFitInfo[]>('get_model_fits');

```

## Summary

- **Separation of concerns**: The Tauri crate (`llmfit‑desktop`) contains zero model-fitting logic; it strictly delegates to `llmfit‑core` via the `OllamaProvider` struct.
- **State management**: A mutex-protected `Option<PullHandle>` in `AppState` persists the channel receiver across separate command invocations, enabling asynchronous pull tracking.
- **Event streaming**: The core provider spawns a background thread that translates Ollama’s streaming JSON into typed `PullEvent` messages, which the desktop crate exposes through a non-blocking `poll_pull` command.
- **File locations**: Command handlers live in **`llmfit‑desktop/src/main.rs`**, while the pull implementation and event definitions reside in **`llmfit‑core/src/providers.rs`**.

## Frequently Asked Questions

### How does the Tauri desktop crate maintain pull state between commands?

The crate uses a `Mutex<Option<PullHandle>>` inside the managed `AppState` struct. When `start_pull` is invoked, the mutex is locked, the `PullHandle` (containing the `Receiver<PullEvent>`) is stored, and the lock is released. Subsequent calls to `poll_pull` acquire the same mutex to access the receiver and check for new progress messages without blocking the main thread.

### What happens if the frontend polls before a pull is started?

The `poll_pull` command checks if the mutex contains `Some(handle)`. If the `Option` is `None`, it returns an error string `"No pull in progress"`, which the frontend can handle to disable polling UI elements until a valid handle exists.

### Where is the actual HTTP request to Ollama implemented?

The HTTP streaming logic is encapsulated in `OllamaProvider::start_pull` within **`llmfit‑core/src/providers.rs`** (lines 89‑102). This method spawns a thread that reads the Ollama API response line-by-line, parses the JSON, and sends structured events through an `mpsc` channel back to the desktop crate.

### Can the desktop crate run multiple simultaneous pulls?

The current implementation stores a single `Option<PullHandle>` in the global state. Starting a new pull overwrites the previous handle, effectively abandoning the old receiver. To support concurrent pulls, the state would need to be changed to a `HashMap<String, PullHandle>` keyed by model tag.