A local web interface and HTTP API for HART text-to-image generation. Built with Nuxt 3 / Nitro; runs inside a Docker container that inherits your existing hart-cli GPU image.
- Prerequisites
- Project layout
- Environment variables
- Local development
- Docker deployment
- Rebuild & restart
- API reference
- Tests
| Requirement | Notes |
|---|---|
| Docker + NVIDIA Container Toolkit | GPU passthrough via gpus: all |
hart-cli:thor image |
Base image with hart-txt2img on PATH |
/data/hart/ host directory |
Models, outputs, and caches mounted here |
| Node.js 20+ | Only needed for local dev; not required for Docker |
Verify the CLI is available inside the base image:
docker run --rm hart-cli:thor which hart-txt2imgpages/
index.vue # Browser UI
server/
api/
generate.post.ts # POST /api/generate – spawns hart-txt2img
middleware/
00-req-logger.ts # Per-request ID + timing logs
routes/
_debug.get.ts # GET /_debug – liveness probe
api.get.ts # GET /api – 404 placeholder
files/
[name].ts # GET|HEAD /files/:name – serve output PNGs
utils/
mutex.ts # Single-slot non-queuing lock (queue size = 1)
tests/
_utils.ts # h3 test server helper
files.test.ts # File-serving route tests
generate.test.ts # Generate endpoint tests
nuxt.config.ts
docker-compose.yml
Dockerfile
All variables have sensible defaults; nothing is required to change for a standard /data/hart layout.
| Variable | Default | Description |
|---|---|---|
MODEL_PATH |
/workspace/models/hart-0.7b-1024px/llm |
Path to the HART LLM |
TEXT_MODEL_PATH |
/workspace/models/Qwen2-VL-1.5B-Instruct |
Path to the text encoder |
OUT_DIR |
/workspace/outputs/hart_samples |
Directory where PNGs are written |
MAX_PROMPT_LEN |
600 |
Maximum prompt character length |
QUEUE_SIZE |
1 |
Concurrent generation slots (only 1 supported) |
TIMEOUT_SECS |
600 |
Generation timeout before SIGKILL |
VERBOSE |
true |
Enable request/response logging |
HART_PORT |
3011 |
Host port mapped to the container |
HART_UID / HART_GID |
$(id -u) / $(id -g) |
UID/GID the container process runs as (set in .env) |
NUXT_APP_BASE_URL |
/ |
Set if serving under a sub-path |
cp .env.example .env # adjust paths/ports if your layout differs
npm install
npm run dev # starts on http://localhost:3011 (hot-reload)
npm run build # production build → .output/
npm run start # serve the production build
npm test # run vitest suiteLocal dev runs the Nuxt dev server directly; it still calls
hart-txt2imgfrom your host PATH, so the CLI and models must be accessible on the host.
Images are built on top of hart-cli:thor and include Node.js 20 copied from the official slim image.
First-time setup:
# Create required host directories if they don't exist
mkdir -p /data/hart/{models,outputs/hart_samples,cache,tmp,triton}
docker compose up -dHost volumes:
| Host path | Container path | Mode |
|---|---|---|
/data/hart/models |
/workspace/models |
read-only |
/data/hart/outputs |
/workspace/outputs |
read-write |
/data/hart |
/workspace |
read-write |
/data/hart/cache |
/workspace/.cache |
read-write |
/data/hart/tmp |
/workspace/.tmp |
read-write |
/data/hart/triton |
/workspace/.triton |
read-write |
The container is placed on an internal network (no outbound internet) with only the HTTP port published to the host, and all HuggingFace/W&B offline flags are set.
cd hart-web
docker compose down
docker compose build --no-cache
sudo systemctl restart hart-webGenerate an image from a text prompt.
Request body (JSON):
Success response 200:
{
"ok": true,
"file": "/files/00010-42-edf1a587.png",
"prompt": "...",
"seed": 42,
"embedMetadata": true,
"duration_ms": 18423,
}Error responses:
| Status | error |
Reason |
|---|---|---|
400 |
bad_request |
Missing/invalid prompt or seed |
429 |
busy |
A generation is already in progress |
500 |
generation_failed |
CLI exited non-zero or no output found |
504 |
timeout |
CLI exceeded TIMEOUT_SECS |
Serve a generated PNG by filename. Also supports HEAD for existence checks.
- Name must match
^[0-9]{5,}-[0-9]+-[0-9a-f]{8}\.png$ - Returns
Content-Type: image/pngwith a 24 h immutable cache header - Returns
400for invalid names,404if the file is not found
curl -I "http://localhost:3011/files/00010-42-edf1a587.png"Returns ok — simple liveness check.
npm testUses Vitest with lightweight in-process h3 servers (no real GPU/CLI needed). The node:child_process spawn is mocked in generate.test.ts.
{ "prompt": "sunset drone view of a city in the Swiss Alps, 35mm, golden hour", "seed": 42, // optional, integer 0–2147483647, default 42 "embedMetadata": true, // optional, embeds iTXt chunk in PNG, default true }