Repository navigation
Architecture
giveen edited this page Sep 12, 2026
·
1 revision
┌────────────────────────────────────────────────────────────────────┐
│ UI (React 19 + Vite + Tailwind 4 + Radix + Lucide) │
│ ┌──────────┬──────────────────┬───────────┬────────────────────┐ │
│ │ Chat │ Engine │ Models │ Settings │ │
│ └──────────┴──────────────────┴───────────┴────────────────────┘ │
│ │ fetch /api/* + fetch /v1/chat/completions (SSE) │
└─────────┼──────────────────────────────────────────────────────────┘
│ loopback
┌─────────▼──────────────────────────────────────────────────────────┐
│ CONTROL PLANE — Rust `ninfier-control` crate (axum + tokio) │
│ inside the Tauri 2 desktop app (WebKitGTK window); standalone │
│ `ninfier-control` binary in dev mode (Vite HMR against :8787) │
│ • engine lifecycle: spawn / poll /health / SIGTERM / adopt │
│ • profile → CLI args (build_serve_args, 1:1 with ninfer-serve) │
│ • models dir scan, hf download supervisor, nvidia-smi stats │
│ • engine log tail, SSE-safe API proxy │
│ • static hosting of the built UI (single process, no Node) │
└─────────┼──────────────────────────────────────────────────────────┘
│ spawn + stdout/stderr pipe · HTTP :8080
┌─────────▼──────────────────────────────────────────────────────────┐
│ NInfer engine (ninfer-serve, C++/CUDA, RTX 5090 / sm_120a) │
│ OpenAI Chat Completions + Responses + Anthropic Messages + /health│
│ one resident .ninfer artifact · 1–8 lanes · KV cache tiers │
└────────────────────────────────────────────────────────────────────┘
desktop/ Rust control plane + Tauri 2 desktop shell
control/ ninfier-control: engine supervision, model scan, hf downloads,
coder endpoints, VRAM accounting, nvidia-smi stats, SSE-safe
engine API proxy, static hosting
app/ ninfier-studio: Tauri 2 window hosting the control plane
apps/web React 19 + Vite + Tailwind 4 frontend
(Chat / Coder / Engine / Models / Settings)
start-stack.sh dev convenience: boot the control plane, seed config from env vars,
start/adopt the engine
~/.config/ninfier-studio/ per-user runtime state (CONFIG_HOME); AppData/Roaming/ninfier-studio
on Windows — config.json, profile.json, chats.json, last-start.json,
engine-<port>.log. Override location with NINFIER_STUDIO_DATA.
The control plane (desktop/control) is the only process that touches the
engine. In development it runs standalone (pnpm control:start); in the desktop
app it runs inside the Tauri core, so the engine's parent is the app itself — a
single process supervises the engine.
- The engine is a specialized single-GPU C++/CUDA binary — the UI must never import or interpret model code. It talks only the engine's documented HTTP contract.
- Keep a thin, crash-proof supervisor between UI and engine. A CUDA fault in the
engine must not take down the control plane. The supervisor is the Rust
ninfier-controlcrate running inside the Tauri 2 desktop core: the engine's parent process is the app itself, so a native window + a single Rust process supervise everything. The same binary serves browser dev mode (Vite HMR against :8787). The UI code is identical in both because it only speaks the loopback HTTP contract. - The proxy (
/v1/*,/health→ engine port) keeps the UI on a single origin (no CORS), injects the configured API key, and is SSE-safe (chunks piped as they arrive).
-
adopt-don't-kill — if the configured port already serves
/health: ok, the control plane marks the engine external (scans/procfor theninfer-servepid) and never double-spawns. The UI showsexternal · not spawned by studioand can still SIGTERM it. -
start — builds args from the profile, spawns with cwd = engine dir, appends
stdout+stderr to
engine-<port>.log, polls/healthevery 2 s (3-min deadline), records fail reasons (e.g. VRAM shortfall) from the log. - stop — SIGTERM the child (8 s grace → SIGKILL); for external engines SIGTERM the discovered pid. Crashes are recorded with a log pointer, not auto-restarted.
-
native desktop shell —
desktop/app(Tauri 2, WebKitGTK) opens a window pointed athttp://127.0.0.1:8787; the control plane binds that port inside the app's own tokio runtime before the window loads (setup hook with a readiness channel), then servesapps/web/dist(SPA fallback). One native process hosts UI + control plane + engine parent. -
production hosting —
pnpm build→ the app servesapps/web/distat:8787; dev uses Vite:5173with proxies (browser mode) orpnpm control:start(headless Rust control plane, no window). - stops the engine before Rebuild — so a freshly built binary can link cleanly.
| API | Purpose |
|---|---|
GET /api/status |
engine state (incl. adopted external pids, max context window), GPU, VRAM accounting, models, downloads, config (secrets redacted) |
POST /api/engine/start |
profile + artifact → ninfer-serve argv; spawn, health-poll, log capture |
POST /api/engine/stop |
SIGTERM the child (8 s grace) or the adopted external pid |
POST /api/engine/update |
pull (git pull --ff-only) or build (stops a running engine first, then cmake/Ninja) |
GET /api/logs?n=400 |
tail of the spawned engine's log |
GET /api/models |
scan modelsDir for *.ninfer + registered catalog |
POST /api/models/download |
hf download <repo> <file> --local-dir (uses HF_TOKEN when set) |
GET /api/gpu |
nvidia-smi memory/util/process list |
GET/POST /api/config |
app settings (config dir / config.json) |
GET/POST /api/profile-state |
engine profile, chosen artifact, saved named profiles (profile.json) |
GET/POST /api/conversations |
chat conversations + per-conversation params (chats.json) |
/api/coder/* |
coder harness: workspace, tree/dirs/read/write/edit/patch, grep/glob, exec + jobs, safe mode, memory, git diff/log |
GET /health, /v1/*
|
SSE-safe proxy to the engine port (injects API key) |
All state-changing endpoints accept only loopback Host/Origin values (browser
cross-origin calls are limited to the allow-listed Tauri/Vite origins) — see
Security.
-
config.json— app settings (control-plane-owned). -
engine-<port>.log— spawned engine stdout/stderr (appended, tailed by the UI; also the source of the VRAM capacity line). -
localStorage/ per-user store — conversations, chat params, engine profile, saved profiles, selected artifact. - Engine-side state (KV tiers, checkpoints, response store) is engine-owned; the UI never writes model state.
See Screens for how each screen uses this API, and Configuration for the persisted files.