Real-time AI image and text generation with async job processing, live status updates via SSE, and a full generation history. One command to run everything.
See claude-session.md for a full summary of the Claude Code session used to build this project.
Run from the project root:
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env
docker compose up --build -d- Frontend: http://localhost:3000
- Backend API: http://localhost:4000
- Health check: http://localhost:4000/health
Main view — prompt form, active jobs, gallery

Image lightbox — expanded job detail with metadata

Advanced parameters — width, height, seed, model selector

Browser
│
├─ POST /api/jobs ──────────────────────────────────────────────────────┐
│ Returns 202 in ~50ms. Job created as PENDING. Never blocks. │
│ │
├─ GET /api/jobs/:id/stream (SSE) ◄─────── DB poll every 1s ◄──────────┤
│ Browser opens EventSource immediately after job creation. │
│ Auto-reconnects up to 3× on disconnect. │
│ │
└─ GET /api/jobs (poll every 5s) ◄── fallback for gallery/history │
│
Backend (Express) │
├─ Routes → validate (Zod) → Prisma → PostgreSQL │
└─ BullMQ Queue ◄──────────────────────────────────────────────────────┘
│
▼
Worker (concurrency: 3)
├─ PENDING → GENERATING → calls Pollinations.ai (180s timeout)
├─ On success → COMPLETED, stores resultUrl / resultText
└─ On failure → FAILED, stores errorMessage (BullMQ retries 2×)
PENDING ──► GENERATING ──► COMPLETED
│ │
▼ ▼
CANCELLED FAILED ──► (retry) ──► PENDING
| Decision | Rationale |
|---|---|
| 202 Accepted on job creation | API never blocks on AI generation. Client gets job ID immediately and subscribes to SSE stream for updates. |
| BullMQ + Redis | Production-grade async queue. Survives restarts, supports priority, cancel, retry, and concurrency control. |
| SSE over WebSocket | One-directional status push is all that's needed. SSE is simpler — native EventSource, no handshake, auto-reconnect built in. |
| Pollinations.ai | Free, no auth, supports both image (Flux models) and text generation. |
| DB polling for SSE (1s) | Simple, reliable. The improvement path is Redis pub/sub when scale demands it. |
| Prisma + PostgreSQL | Type-safe ORM, strong schema, good for filtered pagination queries. |
| shadcn/ui + Tailwind | Accessible, composable, no runtime CSS-in-JS overhead. Dark mode works out of the box. |
| Zod on both sides | Same validation shapes on client and server. 400 errors include field-level messages. |
| Layer | Technology | Version |
|---|---|---|
| Frontend | Next.js + React | 16 / 19 |
| Styling | Tailwind CSS v4 + shadcn/ui | 4 |
| Forms | React Hook Form + Zod | 7 / 4 |
| Backend | Express + TypeScript | 4.21 / 5 |
| Queue | BullMQ | 5 |
| Cache / Queue store | Redis | 7 |
| ORM | Prisma | 5 |
| Database | PostgreSQL | 16 |
| AI provider | Pollinations.ai | — |
| Container | Docker + Docker Compose | — |
- Prompt submission UI with generation type selector (Image / Text)
- Async job processing — API returns in <100ms, never blocks
- Job state machine: PENDING → GENERATING → COMPLETED / FAILED
- Real-time status updates via Server-Sent Events (SSE)
- Gallery with completed images (click to open lightbox)
- Full generation history with search and filter by status / type
- Error handling: input validation, timeouts, graceful failure display
- Multiple generation types — Image (Flux models) and Text
- Prompt enhancement — optional AI rewrite via Pollinations text API before generation
- Real-time SSE — auto-reconnects up to 3× on disconnect
- Cancel jobs — cancel any PENDING or GENERATING job
- Retry jobs — retry any FAILED or CANCELLED job (preserves original enhance flag)
- Priority queue — Low / Normal / High priority mapping to BullMQ numeric priority
- User-defined parameters — width, height, seed, model (6 Flux variants + Turbo)
- Docker Compose —
docker compose up --buildstarts everything - Paginated history — filterable table view with 20-per-page pagination
- Stats bar — live count of total / done / active / failed jobs
- Duration tracking — time from creation to completion shown per job
| Method | Path | Description |
|---|---|---|
POST |
/api/jobs |
Submit job (returns 202 immediately) |
GET |
/api/jobs |
List jobs — ?status=&type=&page=&limit= |
GET |
/api/jobs/:id |
Get single job |
GET |
/api/jobs/:id/stream |
SSE stream — pushes updates until terminal state |
DELETE |
/api/jobs/:id |
Cancel PENDING or GENERATING job |
POST |
/api/jobs/:id/retry |
Retry FAILED or CANCELLED job |
{
"prompt": "A cyberpunk city at night, neon reflections on wet pavement",
"type": "IMAGE",
"enhance": true,
"priority": 5,
"parameters": {
"width": 1024,
"height": 1024,
"seed": 42,
"model": "flux-realism"
}
}Response 202:
{
"id": "cm...",
"status": "PENDING",
"prompt": "...",
"createdAt": "..."
}Prerequisites: Node 20+, PostgreSQL, Redis
# Backend
cd backend
cp .env.example .env # edit DATABASE_URL and REDIS_URL if needed
npm install
npx prisma migrate deploy
npm run dev
# Frontend (separate terminal)
cd frontend
echo "NEXT_PUBLIC_API_URL=http://localhost:4000" > .env.local
npm install
npm run devai-toolkit/
├── backend/
│ ├── prisma/
│ │ └── schema.prisma # DB schema + indexes
│ └── src/
│ ├── index.ts # Express app + worker bootstrap
│ ├── db/prisma.ts # Prisma singleton
│ ├── middleware/validate.ts # Zod request validation middleware
│ ├── validation/
│ │ └── createJobSchema.ts # Zod schema for POST /api/jobs
│ ├── queue/
│ │ ├── queue.ts # BullMQ queue + Redis connection
│ │ └── worker.ts # Job processor + graceful shutdown
│ ├── routes/jobs.ts # All /api/jobs endpoints
│ ├── services/
│ │ ├── ai.ts # Pollinations.ai image + text generation
│ │ └── enhancer.ts # Prompt enhancement via text API
│ └── types/index.ts
├── frontend/
│ ├── app/
│ │ ├── page.tsx # Main page: form + gallery
│ │ └── layout.tsx
│ ├── components/
│ │ ├── PromptForm.tsx # Submission form with advanced params
│ │ ├── Gallery.tsx # Job card grid
│ │ ├── JobCard.tsx # Individual card + lightbox + SSE
│ │ ├── StatusBadge.tsx # Status indicator with animation
│ │ └── StatsBar.tsx # Header counts
│ ├── hooks/
│ │ ├── useJobs.ts # Poll all jobs with interval
│ │ └── useJobStream.ts # SSE stream for single job
│ ├── lib/api.ts # API client
│ └── types/index.ts # type aliases (no interfaces)
└── docker-compose.yml # postgres + redis + backend + frontend
Uses Pollinations.ai — free, no API key required.
- Image:
https://image.pollinations.ai/prompt/{prompt}?width=&height=&seed=&model=- Models:
flux(default),flux-realism,flux-cablyai,flux-anime,flux-3d,turbo - Timeout: 180s
- Models:
- Text:
https://text.pollinations.ai/{prompt}- Timeout: 180s
- Prompt enhancement: sends a rewrite instruction to the text API before generation
Images are hosted by Pollinations.ai — the URL returned is stable as long as the same seed/params are used.
-
Redis pub/sub for SSE — replace 1s DB polling with event-driven pub/sub. Worker publishes on status change, SSE handler subscribes. Eliminates N×polling at scale.
-
Proper database migrations — replace
prisma db pushwith versioned migration files (prisma migrate). Safe for production schema evolution. -
Image storage — download generated images to S3/R2 instead of relying on Pollinations.ai URL stability. Enables thumbnails, CDN delivery.
-
Authentication — user accounts with per-user job isolation, API keys, and rate limits per identity.
-
Rate limiting — per-IP rate limiting on job submission (e.g.,
express-rate-limit). -
Bull Board — mount the BullMQ dashboard for queue observability (job counts, failed jobs, retry from UI).
-
Structured logging — replace
console.*with Winston or Pino for JSON-structured logs with correlation IDs. -
WebSocket — upgrade from SSE to WS to push queue position and estimated wait time to all clients simultaneously.
-
Image variations — generate N variants from one prompt in parallel, return best or let user pick.
-
Test coverage — unit tests for worker logic, integration tests for API routes against a real test DB.