- Phase 4: Hugging Face Download Engine — streaming GGUF downloads with progress, cancellation, and managed-model storage - Phase 5: Model Source Validation — source-mode compatibility, path/file validation, inline error UI with recovery guidance - Hot-swap: stop runtime → update model_path → restart atomic command - Model library: folder open, delete model file, local model management - SettingsPage unified hub: local models list + HF search with 3-state action buttons - Bug fixes: send button text restored (4 failing tests), reasoning-toggle aria-label, Rust warnings - All 89 tests passing, cargo check clean
107 lines
5.5 KiB
Markdown
107 lines
5.5 KiB
Markdown
# Phase 4: Hugging Face Download Engine
|
|
|
|
## Context
|
|
|
|
After completing the unified runtime workspace (Phase 3), the next priority was implementing the Hugging Face download engine. The HF search UI was already functional, but download buttons used a simulated `setInterval` progress animation with no actual file transfer.
|
|
|
|
**Architecture decision:** Downloads run in Rust (not JavaScript) to avoid IPC bottlenecks for multi-GB GGUF files. Progress is emitted via Tauri v2 events and consumed by a React hook.
|
|
|
|
**Key constraints:**
|
|
- Tauri v2 + React 18 + TypeScript + Vite
|
|
- No `@tauri-apps/plugin-fs` configured; only `shell` plugin active
|
|
- Existing HF search already uses `reqwest` with optional bearer token
|
|
- Managed models directory already resolved via `model_library::paths::managed_models_dir`
|
|
|
|
## Implementation Summary
|
|
|
|
### Rust Backend (`src-tauri`)
|
|
|
|
**`model_library/huggingface.rs`** — Full download engine:
|
|
- `HuggingFaceDownloadState` manages active downloads with `AtomicU64` IDs and `Mutex<HashMap>` cancellation flags
|
|
- `start_hugging_face_download()` streams bytes via `reqwest` + `tokio::fs::File` to a `.part` temp file
|
|
- Emits Tauri events: `started`, throttled `progress` (~500ms), `completed`, `cancelled`, `error`
|
|
- `cancel_hugging_face_download()` sets cancellation flag, removes temp file
|
|
- Path validation rejects empty, `.`, `..`, and backslash-containing segments
|
|
- Direct resolve URL: `https://huggingface.co/{repo}/resolve/main/{fileName}`
|
|
- Bearer token applied when available
|
|
- Temp file renamed to final destination on success
|
|
|
|
**`lib.rs`** — Command registration:
|
|
- `start_hugging_face_download_command` and `cancel_hugging_face_download_command` registered in `invoke_handler`
|
|
- Shared `Arc<HuggingFaceDownloadState>` managed via `.manage()`
|
|
|
|
**`model_library/types.rs`** — Serializable payloads:
|
|
- `HuggingFaceDownloadPhase` enum (Started, Progress, Completed, Cancelled, Error)
|
|
- `HuggingFaceDownloadProgress` struct with camelCase JSON fields
|
|
|
|
**`Cargo.toml`** — Added `tokio` with `fs`, `io-util`, `sync`, `time` features
|
|
|
|
### Frontend (`tauri-app/src`)
|
|
|
|
**`features/runtime/types.ts`** — TypeScript types:
|
|
- `HuggingFaceDownloadPhase` union type
|
|
- `HuggingFaceDownloadProgress` interface matching Rust payload
|
|
|
|
**`features/runtime/api.ts`** — API bridge:
|
|
- `startHuggingFaceDownload(repo, fileName, token)` → `invoke("start_hugging_face_download_command")`
|
|
- `cancelHuggingFaceDownload(downloadId)` → `invoke("cancel_hugging_face_download_command")`
|
|
- `HUGGING_FACE_DOWNLOAD_EVENT` constant for event listening
|
|
|
|
**`features/runtime/useModelDownload.ts`** — React hook:
|
|
- `useModelDownload(token: string)` returns `isDownloading`, `progress`, `speed`, `downloaded`, `total`, `activeFile`, `error`, `startDownload()`, `cancelDownload()`
|
|
- Listens to Tauri events, tracks active download ID, ignores stale events
|
|
- Explicit `ModelDownloadSnapshot` interface for correct TypeScript typing
|
|
|
|
**`features/settings/SettingsPage.tsx`** — UI integration:
|
|
- Removed simulated `setInterval` download state
|
|
- Imported `useModelDownload` hook
|
|
- `.sp-hf-file` class added to each HF file row
|
|
- Active file row switches to real progress-bar state when downloading
|
|
- Shows real `speed`, `downloaded`, `total`, `progress` values
|
|
- Cancel button in active progress state
|
|
- Download buttons have visible focus rings and disabled state during active downloads
|
|
|
|
### Tests
|
|
|
|
**`features/runtime/useModelDownload.test.tsx`** — Hook unit tests:
|
|
- Verifies `startDownload()` invokes correct Tauri command with `{ repo, fileName, token }`
|
|
- Verifies progress/speed/downloaded/total update from events
|
|
- Verifies stale events are ignored
|
|
- Verifies `cancelDownload()` invokes cancel command with active `downloadId`
|
|
|
|
**`features/settings/SettingsPage.download.test.tsx`** — Integration test:
|
|
- Mocks HF search results and `useModelDownload`
|
|
- Verifies `.sp-hf-file` row renders and clicking download calls `startDownload("microsoft/phi-4", "quantized/phi-4-q4.gguf")`
|
|
|
|
**`model_library/huggingface.rs` tests** — Rust unit tests:
|
|
- Direct resolve URL construction
|
|
- MiB conversion
|
|
- Progress percentage calculation
|
|
- Path validation (valid and invalid segments)
|
|
|
|
## Verification
|
|
|
|
- ✅ `npm run build` — passes
|
|
- ✅ `npx tsc --noEmit` — passes (0 errors)
|
|
- ✅ `npm test -- useModelDownload.test.tsx SettingsPage.download.test.tsx` — 3/3 tests pass
|
|
- ✅ `cargo test hugging_face` — passes
|
|
- ⚠️ 6 pre-existing unrelated test failures in `App.test.tsx`, `ChatView.test.tsx`, `MessageBubble.test.tsx` (not caused by this phase)
|
|
- ⚠️ 1 pre-existing Rust warning: `save_index` unused in `local_index.rs` (not caused by this phase)
|
|
|
|
## Files Modified
|
|
|
|
| File | Change |
|
|
|------|--------|
|
|
| `tauri-app/src-tauri/Cargo.toml` | Added `tokio` dependency |
|
|
| `tauri-app/src-tauri/src/model_library/types.rs` | Added download phase enum + progress struct |
|
|
| `tauri-app/src-tauri/src/model_library/huggingface.rs` | Full download engine + tests |
|
|
| `tauri-app/src-tauri/src/lib.rs` | Added download commands + managed state |
|
|
| `tauri-app/src/features/runtime/types.ts` | Added download types |
|
|
| `tauri-app/src/features/runtime/api.ts` | Added download API helpers |
|
|
| `tauri-app/src/features/runtime/useModelDownload.ts` | New React hook |
|
|
| `tauri-app/src/features/runtime/useModelDownload.test.tsx` | Hook unit tests |
|
|
| `tauri-app/src/features/settings/SettingsPage.tsx` | Replaced simulation with real download |
|
|
| `tauri-app/src/features/settings/SettingsPage.download.test.tsx` | Integration test |
|
|
| `.planning/ROADMAP.md` | Realigned to HF download milestone |
|
|
| `.planning/STATE.md` | Updated to Phase 4 complete |
|