Skip to content

Latest commit

 

History

History
130 lines (100 loc) · 4.71 KB

File metadata and controls

130 lines (100 loc) · 4.71 KB

AGENTS.md — hart-web

Agent-oriented guide for AI coding assistants working in this repository.


Project overview

hart-web is a local HTTP API and browser UI for HART text-to-image generation. Stack: Nuxt 3 / Nitro (TypeScript), running inside a Docker container that inherits a hart-cli:thor GPU image.


Repository layout

pages/
  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
tests/
  _utils.ts               # In-process h3 test-server helper
  files.test.ts
  generate.test.ts
nuxt.config.ts
vitest.config.ts
docker-compose.yml
Dockerfile

Development commands

npm install          # install dependencies
npm run dev          # dev server with hot-reload (http://localhost:3011)
npm test             # run the full Vitest suite
npm run build        # production build → .output/
npm run start        # serve the production build
  • Uses Vitest with a node environment.
  • Tests spin up lightweight in-process h3 servers — no Docker, GPU, or real CLI binary needed.
  • node:child_process spawn is mocked in generate.test.ts; do not remove or alter those mocks.
  • All test files live in tests/ and match **/*.test.ts.
  • passWithNoTests: false — a test file with no test cases is a failure.

Critical architecture constraints

Single-slot mutex

server/utils/mutex.ts exposes a module-level boolean lock (tryAcquire). Only one /api/generate request may be in flight at any time; a second concurrent request receives 429 busy immediately with no queuing.

  • Do not introduce async queuing or waitlist logic without understanding that the lock is intentionally non-queuing.
  • Do not move the mutex state into a request-scoped variable.

Subprocess invocation

generate.post.ts spawns hart-txt2img using node:child_process spawn with an array of arguments (no shell expansion). Prompt text is passed as a CLI argument; never build a shell string with user input interpolated into it.

File-serving route

/files/:name enforces the pattern ^[0-9]{5,}-[0-9]+-[0-9a-f]{8}\.png$ (case-insensitive flag i). Do not relax this regex without considering path-traversal implications.


Environment variables (dev)

Copy .env.example to .env. Note that some defaults differ by context:

  • .env.example sets OUT_DIR=/data/hart/outputs/hart_samples.
  • docker-compose.yml passes OUT_DIR=/workspace/outputs/hart_samples.
  • nuxt.config.ts falls back to TIMEOUT_SECS=300 if nothing is set, while .env.example and Docker set 600.

Key variables:

Variable Default Notes
MODEL_PATH /workspace/models/hart-0.7b-1024px/llm HART LLM path
TEXT_MODEL_PATH /workspace/models/Qwen2-VL-1.5B-Instruct Text encoder path
OUT_DIR context-dependent Output dir; see note above
MAX_PROMPT_LEN 600 Prompt character limit
TIMEOUT_SECS context-dependent 600 in env/Docker, 300 code fallback

For local dev without a real model, the tests mock the spawn call so the model paths are irrelevant.


Implementation notes

  • All server code is TypeScript.
  • Input validation happens before I/O or subprocess execution; preserve that order when adding request fields.
  • queueSize exists in runtime config, but the current implementation is a single-slot mutex and effectively supports only one in-flight generation.
  • Do not add runtime dependencies without a clear justification; this is an intentionally thin stack.

Docker notes

The production container is built on top of hart-cli:thor and is attached to two bridge networks: hart_pub (regular bridge, carries the published port) and hart_internal (internal: true, no external routing). Outbound internet is gated at the application layer via HuggingFace and W&B offline environment variables (e.g. HF_HUB_OFFLINE=1), not purely by the network topology. Do not write code that assumes outbound network access from within the container.