⚙ How it works ▦ Results 🖼 Screenshots
01 Overview
Not scattered apps but one toolkit: a self-hosted, multi-machine generative-AI studio that turns a prompt into text, images, music, speech, a 3D mesh, or an Office document. Two web front-ends sit over a spine of Dockerized model servers and cloud APIs, with a custom ComfyUI node pack wiring the same fleet into node graphs. It was built to sit on top of the LiteLLM / Hugging Face / GPU plumbing already running on my home server, and to let autonomous pipelines generate their own assets.
AssetForge
Express + Socket.IO app: one prompt across 5 pipelines (text · image · audio · 3D · document) and ~20 output formats, with a persistent SQLite job queue, live logs, and a gallery.
AI Playground
A Fastify multi-task ML playground — 19 task types over ~130 curated model entries spanning Hugging Face, LiteLLM (Claude · Gemini · GPT · Ollama), and the local GPU services.
Model servers
Small Flask / FastAPI wrappers that give heavyweight models a one-endpoint REST API: MusicGen, Shap-E, ACE-Step, TripoSR (image→3D), and Kokoro neural TTS.
ComfyUI nodes
Three first-party ComfyUI nodes (TTS · MusicGen · TripoSR) that let graphs on a GPU PC call the fleet over the LAN and return native audio tensors or GLB files.
02 Why I built it
The home server already had the pieces — an LLM gateway, Hugging Face access, a couple of small GPUs — but no single place to say "make me a thing." AssetForge and AI Playground are that unifying front-end over plumbing that was already configured. The second motive was to feed my browser-game generation platform: a summon_asset CLI and MCP server let build pipelines call the studio programmatically, so an automated game build can synthesize its own sprites, sound effects, music, and 3D models. And some of it is simply family-facing — a shared story generator reads its output aloud through the same TTS stack. Cost and locality run through all of it: local GPU services are labelled "free," free-tier cloud models are preferred where they suffice, and there are deliberate CPU fallbacks for when the small server GPU can't cope.
03 What I built & how it works
Two front-ends, a spine of model servers, and a health poller that prefers the fast path.
Fig. 1 — the front-ends orchestrate; the GPU PC handles image→3D and neural TTS; a background poller routes to whichever backend is awake.
- Describe — a prompt, an asset type, a backend, and per-format options rendered from a server-side capability matrix.
- Queue — one UUID job row in SQLite, processed a single job at a time to avoid resource contention on shared hardware.
- Dispatch — route to the right pipeline; a 5-minute health poll prefers the local GPU when it's awake and cloud APIs when it isn't.
- Generate — the model server or API runs; heavyweight models fall back to CPU mid-request rather than fail on the 2 GB card.
- Stream & store — Socket.IO logs stream live and replay for late-joining subscribers; the output and its full log land in the job row.
04 🛠 Skills & tech used
05 Notable challenges & decisions
Most of the work was fitting real models onto small, shared hardware without the UI ever knowing.
Engineering around a 2 GB server GPU
MusicGen runs in fp16, but Shap-E has to fall back to fp32 because the Maxwell-era 960M can't do half-precision LayerNorm. Every model server has a CUDA-OOM → CPU retry mid-request, and MusicGen exposes an /unload endpoint purely to hand VRAM back to a sibling service. ACE-Step is CPU-only — the server's driver can't reach the CUDA version it needs — with a bfloat16 source patch that roughly halved its RAM, from ~10 GB to ~5.3 GB.
A sync façade over an async model server
ACE-Step's real API is submit / poll / download. The wrapper spawns it as a subprocess, waits for the model to finish downloading, then hides the whole submit→poll→fetch dance inside one blocking /generate call — so AssetForge can treat it exactly like the simpler servers. The same sync-over-async pattern wraps the Tripo3D and Meshy cloud pollers.
Prefer the fast path, degrade quietly
A 5-minute health poller lets pipelines silently prefer the GPU PC — ComfyUI for images, TripoSR for 3D — when it's awake, and fall back to cloud APIs when it isn't. The honest trade-off is that when a container has actually been removed, the request just returns a 503 rather than pretending.
SSE over a home LAN is fragile
The story generator needed six separate fixes to keep token streams alive: close-on-response rather than request, a compression filter that skips text/event-stream, keepalive / no-delay sockets, and 15-second heartbeat comments so a NAT or proxy doesn't drop the connection during the latency before the first token.
Deliberate constraints. The job queue processes one job at a time on purpose — most backends share one small GPU or CPU budget — and every backend and output format lives in a server-side capability matrix that the UI renders forms from, so adding a model is a server-only change.
06 Results
Sources: service source and README files, container health output, and dated task summaries. There is no git history for these services, so figures are reconstructed from those and file timestamps.
07 Screenshots
The AssetForge screenshots below were captured 2026-08-02 from the verified runnable clone (Steps to Build tab). The other studio surfaces are not part of the clone and remain placeholders.


provider-grouped model dropdown
flag-grouped voice browser
the three home-server nodes wired into a graph
08 Honest status
This is one coherent toolkit built Feb–Apr 2026, described here as a write-up rather than a live demo. The two web front-ends, TTS Studio, and the story generator were up at the last survey; the MusicGen, Shap-E, and ACE-Step model containers were later deleted, though their sources remain. Update (2026-08-02): a runnable partial clone now exists (Steps to Build tab) — AssetForge plus rebuilt MusicGen and Shap-E services, verified end-to-end on CPU with no keys (a real music clip and a real 3D GIF), which also confirms the deleted containers rebuild cleanly from source. The Juice GPU-over-IP wiring is present but dormant; the compose files currently run on the local GPU instead. Everything is unauthenticated and LAN-only by design — a home-server convenience, not production security — and there is a known latent bug in one ComfyUI node's 3D path that I'd want to retest before demoing it. None of these services is a git repository, so the timeline and figures are reconstructed from dated task summaries and file timestamps rather than commit history.
genai-studio — Build Recipe
Take a bare machine to a running slice of a self-hosted generative-AI studio:
AssetForge, a unified web app that generates text, images, music, 3D
previews, and office documents (5 pipelines, ~30 backend entries, ~20 output
formats including .docx/.pptx/.xlsx) through one interface — with a
persistent job queue, live Socket.IO log streaming, a history browser, and a
gallery — plus two bundled model microservices: MusicGen (text→music) and
Shap-E (text→3D rotating-GIF preview). Key-free by default: the music
pipeline works out of the box on CPU, no API keys, no GPU.
This is a partial clone of a larger personal studio — 3 of its 7 services ship here; the other 4 are deliberately left out (named and explained under Known limitations).
Status: ✅ Verified 2026-08-02 — built from this clone on a Linux Docker host (
docker compose up -d --build). First boot clean (healthy containers, empty job list); a key-free 5-second music job ran end-to-end on CPU (queued → MusicGen-small download → generation in ~351 s total → real 316 KB PCM WAV downloaded via the API); a text job with no LLM configured failed honestly with the "LITELLM_URL not configured" message while the app stayed healthy; the single-concurrency queue behaved as designed; and with--profile 3da Shap-E text→3D job produced a real rotating GIF on CPU (~11 min warm — the first-ever request also downloads ~3.4 GB of weights and needs the 30-minute budget documented below); and withLITELLM_URLpointed at an OpenAI-compatible endpoint the text pipeline generated a real markdown asset end-to-end (verified against a LiteLLM gateway with a disposable key, revoked after the test). Browser pass: SPA renders, the History and Gallery views show the generated assets, 0 console errors. The prebuilt tarball (docker saveof all three images) size is recorded in../PREBUILT_IMAGES.md. The clean clone lives inclone/.Sensitive data: none. Post-rotation the sources were already key-free (env-only per-service keys); home-server/tailnet/LAN identifiers were scrubbed; no runtime databases or outputs ship — and the studio services that hold family data (StoryForge, TTS Studio) are deliberately not part of this clone.
What it is
Three containers (only the first two run by default):
| Service | Stack | Role | Port |
|---|---|---|---|
assetforge |
Node 20 / Express + Socket.IO + better-sqlite3 | Web UI, one-at-a-time job queue, 5 generation pipelines (text / image / audio / 3D / document), format converters (FFmpeg, sharp, docx/pptxgenjs/exceljs) | 8212 |
music-gen |
Python / Flask + Transformers | facebook/musicgen-small wrapper: /generate returns WAV; lazy model load, CUDA-OOM→CPU retry built in |
8208 |
shap-e (opt-in, --profile 3d) |
Python / Flask + Diffusers | openai/shap-e wrapper: /generate returns a rotating multi-frame GIF (~12 fps) |
8209 |
The frontend is a hand-built vanilla-JS SPA (no build step); backend/format choices are server-declared capability matrices, so the UI renders its forms from the API. Job logs are buffered in SQLite and replayed to late-joining WebSocket subscribers.
Prerequisites
New machine? See ../SETUP.md. Then, per tier:
| Tier | You need |
|---|---|
| 1 — prebuilt containers (recommended) | Docker (see ../SETUP.md) + this bundle's clone/ folder (for the compose file). |
| 2 — build from source | Same as Tier 1. |
| 3 — bare-metal | Node 20+ and ffmpeg for AssetForge; Python 3.10+ with PyTorch for the model services (commands below). |
Configuration — nothing required
docker compose up needs no .env and no keys. What works at each level:
| Setting | Unlocks | Without it |
|---|---|---|
| (nothing) | Music generation on CPU — the whole audio pipeline, WAV/MP3/FLAC/OGG | — |
--profile 3d |
Local Shap-E text→3D GIF previews (key-free, CPU). First 3D request is slow: it downloads ~3.4 GB of weights and loads the model (~4 min on CPU) before the ~7-minute inference — the pipeline's 30-minute budget (SHAP_E_TIMEOUT_MS) covers this; later requests skip straight to inference. Submit with "assetType":"threed". |
3D jobs fail with a clear "start it with --profile 3d" message |
LITELLM_URL (+ key/model) |
Text + Document pipelines (md/html/docx/pptx/xlsx) and the blender-llm 3D backend — any OpenAI-compatible endpoint; local Ollama works key-free | those jobs fail with a clear "not configured" message |
HF_TOKEN or OPENAI_API_KEY |
Image pipeline (FLUX.1-Schnell / SDXL via HF, or DALL-E 3) | image jobs fail with "token not configured" |
TRIPO_API_KEY / MESHY_API_KEY / COMFYUI_URL / TRIPOSR_URL / BLENDER_HUB_URL / ACE_STEP_URL |
Cloud image→3D, an external ComfyUI/TripoSR/BlenderHub/ACE-Step | backends stay listed but report down / a clear error |
All documented one line at a time in clone/.env.example.
Tier 1 — Run the prebuilt containers (recommended)
Availability: the prebuilt images are available on request — they are not published or linked anywhere. Ask Kevin for them, or build from source via Tier 2.
docker load -i genai-studio-clone-image.tar # loads all three images
cd clone
docker compose up -d # → http://localhost:8212 (no --build)
To produce the tarball on a machine that has built the images (Tier 2):
docker save genai-studio-assetforge genai-studio-music-gen genai-studio-shap-e -o genai-studio-clone-image.tar.
The model-service images are honestly large — they carry a full PyTorch base.
Tier 2 — Build from source
cd clone
docker compose up -d --build # → http://localhost:8212
curl -s localhost:8212/health # → {"status":"ok","service":"assetforge",...}
curl -s localhost:8208/health # → {"loaded":false,"model":"facebook/musicgen-small","status":"ok",...}
Open http://localhost:8212 → Audio → MusicGen Small (Local) → WAV → set duration ~10 s → a prompt like "upbeat lo-fi hip-hop with jazz chords" → Generate, and watch the live log stream. The first request downloads and loads the model (a few minutes); after that, expect roughly real-time×8–20 on CPU — ~4–5 minutes for a 30-second clip, proportionally less for short clips. The app boots with an empty job list; History and Gallery fill as you generate.
Tier 3 — Bare-metal (three processes)
AssetForge (needs Node 20+ and ffmpeg on PATH):
cd clone/assetforge
npm install --omit=dev
DATA_DIR=./data OUTPUT_DIR=./outputs TEMP_DIR=./temp \
MUSIC_GEN_URL=http://localhost:8208 SHAP_E_URL=http://localhost:8209 \
node server.js # → http://localhost:8212
music-gen / shap-e (Python 3.10+; CPU-only PyTorch is fine):
cd clone/music-gen # or clone/shap-e
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements.txt
python app.py # :8208 (music-gen) / :8209 (shap-e)
On Windows, set the same variables with $env: before node server.js.
Optional extras (all off by default)
- Shap-E text→3D —
docker compose --profile 3d up -d --build. Key-free and CPU-capable, but slow: ~5–7 minutes per GIF at the low end (16 steps, small frame size; CPU-dependent — the verified host measured ~7); the UI's default 64 steps takes proportionally longer. It auto-uses an NVIDIA GPU if you uncomment thedeployblock indocker-compose.yml(fp16 on modern GPUs, fp32 on older ones — the service picks by compute capability). - LLM text + documents — point
LITELLM_URLat any OpenAI-compatible endpoint. Local key-free path with Ollama:
bash
# in clone/.env
LITELLM_URL=http://host.docker.internal:11434 # no /v1 — the app appends /v1/chat/completions
# (Linux: if host.docker.internal doesn't resolve, use your host's LAN IP)
LITELLM_API_KEY=ollama
LITELLM_MODEL=llama3.2 # forces one model for all text/document backends
With a LiteLLM gateway that defines the app's model aliases
(claude-sonnet, gemini-flash, …) you can leave LITELLM_MODEL unset and
the per-backend dropdown routes to different models.
- Images — HF_TOKEN (HuggingFace, free tier exists) unlocks
FLUX.1-Schnell and SDXL; OPENAI_API_KEY unlocks DALL-E 3.
- External 3D/image services — COMFYUI_URL (image jobs silently prefer a
healthy ComfyUI over HF), TRIPOSR_URL (image→GLB), BLENDER_HUB_URL
(LLM-written Blender scripts — pairs with the blenderhub recipe),
ACE_STEP_URL (long-form music, 10–600 s), TRIPO_API_KEY/MESHY_API_KEY
(cloud image→GLB). Each is health-gated or fails with a clear message.
Verify
curl -s localhost:8212/health # queue state, uptime
curl -s localhost:8212/api/backends/audio # backend matrix (server-declared)
curl -s localhost:8208/health # music-gen up
# end-to-end key-free music job (short clip to keep CPU time reasonable):
curl -s -X POST localhost:8212/api/jobs -H 'Content-Type: application/json' \
-d '{"assetType":"audio","backend":"music-gen","format":"wav",
"prompt":"calm piano melody","options":{"duration":5}}'
# poll GET /api/jobs/<jobId> until "status":"completed", then download:
# GET /api/assets/<jobId>/download
Tests (assetforge ships a small node:test suite with mocked HTTP):
cd clone/assetforge && npm install && node --test test/local-backends.test.js
— 3 tests, all passing as shipped.
What was stripped from the personal version (de-identification + minimalism)
- The remote-GPU backends. The original studio could reach a GPU PC over the LAN for ComfyUI, TripoSR, and Bark (LAN IPs set via compose env). Those backends stay in the capability matrix but now default to unset env vars — the health poller simply reports them down and jobs fail with a clear message instead of dialing someone's home network.
- The Juice GPU-over-IP shim. Both model services shipped a
juice-entrypoint.shthatLD_PRELOADed a remote-GPUlibcuda.so(plus thelibatomic1package it needed). Removed entirely — the services keep their existing built-in CUDA-detect / OOM→CPU fallback and just runpython app.py. - The hard LiteLLM coupling. Text/document generation now activates only
when
LITELLM_URLis set (any OpenAI-compatible endpoint; a newLITELLM_MODELoverride handles endpoints without the personal gateway's model aliases). No LLM gateway container is bundled. - Identifying strings in the AssetForge README: a LAN IP, a Tailscale
hostname, home-server storage paths (
/mnt/...), and "built for" wording → localhost URLs, container paths, neutral wording. GPU-node labels → neutral. Comments referencing "this homeserver" → generic. - No runtime data ships. Fresh named volumes; the app boots with an empty job list, and the model caches download on first use.
- Family data was never at risk here — the studio's story-generator and TTS-history databases (which do contain family names) belong to services that are not part of this clone (see below).
Bugs found and fixed during cloning (faithful, minimal fixes)
- Stale TripoSR test — the shipped test mocked the TripoSR wrapper
returning raw GLB bytes, but the pipeline had since moved to a JSON
{mesh_url}+ separate-download contract, so the test failed against the code next to it. The mock now matches the real contract (verified: 3/3 tests pass). - README port drift — the original README's architecture diagram and
access URLs said
:8211; the service has always listened on:8212. Corrected (the README is user-facing — it renders in-app on the Docs page).
Known limitations (stated honestly)
- Personal-LAN tool: no authentication on any endpoint, and Socket.IO CORS
is
*. Put it behind a reverse proxy with auth before exposing it. - One job at a time, by design. The queue is deliberately
single-concurrency (the original shared one small GPU/CPU budget). Also,
the in-memory queue does not survive restarts: jobs stuck
runningare marked failed at startup, andpendingrows are not re-enqueued — the original's "no jobs are lost" comment overstates it. - CPU generation is slow. MusicGen: ~4–5 min per 30 s clip (clips clamp to 5–30 s; short clips are the sane default). Shap-E: ~5–7 min per GIF at minimum settings (CPU-dependent), more at the UI defaults. These are real model inference times, not bugs.
- The image pipeline needs a key. There is no key-free image backend —
without
HF_TOKEN/OPENAI_API_KEY/COMFYUI_URL, image jobs fail with a clear message. Note thehf-sd15menu entry actually maps to SDXL (HF delisted SD 1.5 upstream; the original absorbed that). - 4 of the studio's 7 services are not cloned, deliberately:
- AI Playground (19-task ML playground, ~130 curated models) — key-free it reduces to dashboards and avatar generation; nearly every interesting task needs HF/LLM credentials, so a clone would demo mostly empty.
- TTS Studio and StoryForge (voice browser; family story generator) — their cores require an LLM/TTS stack, and their histories/databases carry family data. Cleanest de-identification was to not ship them.
- ACE-Step (full-quality music with lyrics) — a ~5 GB model download and a
heavyweight patched build for minutes-long CPU generations; too heavy for
a reasonable demo clone. Its backend entry remains and works if you point
ACE_STEP_URLat your own instance. - A hidden
barkaudio backend exists in the pipeline (not in the UI) for a Gradio tts-generation-webui — kept as-is, off unlessTTS_GEN_URLis set. - The containers run as root (as the original did); hardening is left to the deployer.