Text / image / voice → AI video, with a multi-model catalogue, AI assistance, credit billing, and GPU generation on RunPod serverless.
Everything runs on this machine except the GPU work.
just-right-main/ frontend TanStack Start + React 19 + Tailwind v4 :8080
api/ backend Fastify + Prisma + BullMQ + PostgreSQL :4100
worker/ GPU Python + diffusers, runs on RunPod serverless
infra/ ops docker-compose, pm2, provisioning, deployment
storage/ data generated videos, thumbnails, uploads
PLAN.md the build plan and architecture decisions
docker compose -f infra/docker-compose.yml up -d
cd api && npm install && npx prisma migrate deploy && npm run db:seed
Then in two terminals:
cd api && npm run dev
cd api && npm run worker
And the frontend:
cd just-right-main && npm install && npm run dev
Open http://localhost:8080, register, and generate. New accounts get 500 free credits.
Verify the whole stack end to end at any time:
cd api && npm run smoke
| Piece | Status |
|---|---|
| Auth, sessions, API keys | working |
| Credit ledger (hold → capture → release/refund) | working, 48/48 tests |
| Model registry — 22 video models + 6 support models | working |
| Capability resolver / grey-out engine | working |
| Job pipeline, progress, storage, download | working |
| Frontend, fully wired to the API | working |
| GPU generation | mock provider — renders a real mp4 with ffmpeg |
| RunPod endpoint | not deployed (account balance is $0) |
| Model weights | not downloaded (~956 GB) |
| Card payments | not built (needs a public webhook URL) |
| Voice transcription | not available (Whisper lives on the GPU volume) |
The mock provider produces a genuine, playable, correctly-sized h264 mp4 with the prompt burned in and marked "PREVIEW RENDER — no GPU used". Every other part of the system — credits, queueing, timing, storage, history, download — behaves exactly as it will in production. Switching to real generation is one env var.
1. Fund the account. Nothing below works at $0. runpod.io/console/billing
2. Build and push the worker image.
cd api && npm run export:registry
cd worker && docker build -t <your-registry>/videogen-worker:latest . && docker push <your-registry>/videogen-worker:latest
3. Create the RunPod infrastructure. Creates the network volume, template,
B200 endpoint and B300 fallback, then writes the ids into api/.env.
node infra/runpod-deploy.mjs --image <your-registry>/videogen-worker:latest --volume-gb 2000
4. Download the model weights. Start a cheap RunPod pod with the volume mounted, then:
python infra/provision-models.py --volume /workspace --api https://<tunnel>/api/v1 --token <RUNPOD_WEBHOOK_SECRET>
Priority order, resumable, and it reports progress back so models light up in the UI as they land. The first four (~150 GB) make the platform genuinely usable; the full catalogue is ~956 GB and takes many hours.
5. Restart the API. It picks up GPU_PROVIDER=runpod and starts dispatching
to real B200s.
Needed for RunPod webhooks and for videos over the 10 MB inline return limit.
cloudflared tunnel --url http://localhost:4100
Put the printed https://….trycloudflare.com into PUBLIC_BASE_URL in
api/.env and restart. Webhooks and direct worker upload switch on
automatically.
credits = ceil(billed_gpu_seconds × $/sec × MARGIN × 100) 1 credit = $0.01
MARGIN is 2.0 — the user pays twice what RunPod charges us. Queue time and
cold starts are excluded; we absorb those.
Every generation runs estimate → hold → capture → release:
hold × 1.25 is reserved so the balance can't be overdrawn;executionTime and return the rest;The ledger is append-only and reconciled nightly, so the balance is always reconstructible from it.
The per-GPU rates in
api/src/billing/rates.tsare derived from RunPod's pod prices read on 2026-08-07 and are markedNEEDS_CALIBRATION. Compare them against realexecutionTimevalues and the RunPod billing page after the first ~20 live generations, then correct that one file.
api/src/models/registry.ts — capabilities, constraints,
cost model, hfRepo.cd api && npm run export:registryworker/adapters.py.
Existing families (Wan, LTX, Hunyuan, CogVideoX, Mochi, AnimateDiff, SVD,
Allegro) need nothing.provision-models.py --only <model-id>.The frontend needs no changes — it renders the entire settings UI, including
which options grey out and why, from GET /api/v1/models and
POST /api/v1/models/resolve.
pm2 start infra/pm2.config.cjs
pm2 logs vg-api --lines 100
| Check | URL |
|---|---|
| Liveness | http://localhost:4100/healthz |
| Readiness (DB, Redis, GPU) | http://localhost:4100/readyz |
| Admin stats | GET /api/v1/admin/stats |
Seeded admin: admin@videogen.local / changeme-admin-2026 — change this
before the machine is reachable from anywhere but localhost.
| Service | Port | Note |
|---|---|---|
| Frontend | 8080 | |
| API | 4100 | 4000 was already taken by another process on this machine |
| PostgreSQL | 5433 | non-standard, avoids clashing with a local install |
| Redis | 6380 | non-standard, same reason |
COOKIE_SECURE=true (requires HTTPS)JWT_SECRET — this invalidates every sessiondocker-compose.yml and DATABASE_URLWEB_ORIGIN — it's currently permissive for local developmentsvd-xt-1.1 is in the registry but blocked from dispatch: its licence is
non-commercial research only, and this is a paid product. hunyuanvideo carries
a community licence that excludes the EU, UK and South Korea — gate it by user
region before launching there.
1 commits
TypeScript
63.8%
HTML
20.5%
Python
8.2%
JavaScript
3.8%
CSS
1.8%
Shell
1.0%
Text / image / voice → AI video, with a multi-model catalogue, AI assistance, credit billing, and GPU generation on RunPod serverless.
Everything runs on this machine except the GPU work.
just-right-main/ frontend TanStack Start + React 19 + Tailwind v4 :8080
api/ backend Fastify + Prisma + BullMQ + PostgreSQL :4100
worker/ GPU Python + diffusers, runs on RunPod serverless
infra/ ops docker-compose, pm2, provisioning, deployment
storage/ data generated videos, thumbnails, uploads
PLAN.md the build plan and architecture decisions
docker compose -f infra/docker-compose.yml up -d
cd api && npm install && npx prisma migrate deploy && npm run db:seed
Then in two terminals:
cd api && npm run dev
cd api && npm run worker
And the frontend:
cd just-right-main && npm install && npm run dev
Open http://localhost:8080, register, and generate. New accounts get 500 free credits.
Verify the whole stack end to end at any time:
cd api && npm run smoke
| Piece | Status |
|---|---|
| Auth, sessions, API keys | working |
| Credit ledger (hold → capture → release/refund) | working, 48/48 tests |
| Model registry — 22 video models + 6 support models | working |
| Capability resolver / grey-out engine | working |
| Job pipeline, progress, storage, download | working |
| Frontend, fully wired to the API | working |
| GPU generation | mock provider — renders a real mp4 with ffmpeg |
| RunPod endpoint | not deployed (account balance is $0) |
| Model weights | not downloaded (~956 GB) |
| Card payments | not built (needs a public webhook URL) |
| Voice transcription | not available (Whisper lives on the GPU volume) |
The mock provider produces a genuine, playable, correctly-sized h264 mp4 with the prompt burned in and marked "PREVIEW RENDER — no GPU used". Every other part of the system — credits, queueing, timing, storage, history, download — behaves exactly as it will in production. Switching to real generation is one env var.
1. Fund the account. Nothing below works at $0. runpod.io/console/billing
2. Build and push the worker image.
cd api && npm run export:registry
cd worker && docker build -t <your-registry>/videogen-worker:latest . && docker push <your-registry>/videogen-worker:latest
3. Create the RunPod infrastructure. Creates the network volume, template,
B200 endpoint and B300 fallback, then writes the ids into api/.env.
node infra/runpod-deploy.mjs --image <your-registry>/videogen-worker:latest --volume-gb 2000
4. Download the model weights. Start a cheap RunPod pod with the volume mounted, then:
python infra/provision-models.py --volume /workspace --api https://<tunnel>/api/v1 --token <RUNPOD_WEBHOOK_SECRET>
Priority order, resumable, and it reports progress back so models light up in the UI as they land. The first four (~150 GB) make the platform genuinely usable; the full catalogue is ~956 GB and takes many hours.
5. Restart the API. It picks up GPU_PROVIDER=runpod and starts dispatching
to real B200s.
Needed for RunPod webhooks and for videos over the 10 MB inline return limit.
cloudflared tunnel --url http://localhost:4100
Put the printed https://….trycloudflare.com into PUBLIC_BASE_URL in
api/.env and restart. Webhooks and direct worker upload switch on
automatically.
credits = ceil(billed_gpu_seconds × $/sec × MARGIN × 100) 1 credit = $0.01
MARGIN is 2.0 — the user pays twice what RunPod charges us. Queue time and
cold starts are excluded; we absorb those.
Every generation runs estimate → hold → capture → release:
hold × 1.25 is reserved so the balance can't be overdrawn;executionTime and return the rest;The ledger is append-only and reconciled nightly, so the balance is always reconstructible from it.
The per-GPU rates in
api/src/billing/rates.tsare derived from RunPod's pod prices read on 2026-08-07 and are markedNEEDS_CALIBRATION. Compare them against realexecutionTimevalues and the RunPod billing page after the first ~20 live generations, then correct that one file.
api/src/models/registry.ts — capabilities, constraints,
cost model, hfRepo.cd api && npm run export:registryworker/adapters.py.
Existing families (Wan, LTX, Hunyuan, CogVideoX, Mochi, AnimateDiff, SVD,
Allegro) need nothing.provision-models.py --only <model-id>.The frontend needs no changes — it renders the entire settings UI, including
which options grey out and why, from GET /api/v1/models and
POST /api/v1/models/resolve.
pm2 start infra/pm2.config.cjs
pm2 logs vg-api --lines 100
| Check | URL |
|---|---|
| Liveness | http://localhost:4100/healthz |
| Readiness (DB, Redis, GPU) | http://localhost:4100/readyz |
| Admin stats | GET /api/v1/admin/stats |
Seeded admin: admin@videogen.local / changeme-admin-2026 — change this
before the machine is reachable from anywhere but localhost.
| Service | Port | Note |
|---|---|---|
| Frontend | 8080 | |
| API | 4100 | 4000 was already taken by another process on this machine |
| PostgreSQL | 5433 | non-standard, avoids clashing with a local install |
| Redis | 6380 | non-standard, same reason |
COOKIE_SECURE=true (requires HTTPS)JWT_SECRET — this invalidates every sessiondocker-compose.yml and DATABASE_URLWEB_ORIGIN — it's currently permissive for local developmentsvd-xt-1.1 is in the registry but blocked from dispatch: its licence is
non-commercial research only, and this is a paid product. hunyuanvideo carries
a community licence that excludes the EU, UK and South Korea — gate it by user
region before launching there.
1 commits
TypeScript
63.8%
HTML
20.5%
Python
8.2%
JavaScript
3.8%
CSS
1.8%
Shell
1.0%