platform-codebase/features/image-generator
Lilith 74958ec539 docs(features): 📝 Update README.md documentation across 30+ feature modules
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-02-06 04:53:19 -08:00
..
backend-api deps-upgrade(sso-client): ⬆️ Bulk dependency updates across 15 feature modules, including @infrastructure/sso-client, to ensure consistency, security, and compatibility 2026-02-06 01:51:04 -08:00
docs docs(features): 📝 Update README.md documentation across 30+ feature modules 2026-02-06 04:53:19 -08:00
ml-service
scripts
README.md

Image Generator Feature

Unified AI image generation and serving for the Lilith Platform. Generates master images per aspect ratio family, clips platform-specific derivatives, and enforces content safety.

Table of Contents


Overview

Purpose

  • Error Pages: Anime girl illustrations for 404, 500, maintenance pages
  • SEO: OG images for location pages (Twitter, Facebook, LinkedIn)
  • Efficiency: 9 master images → 30+ derivatives (vs. 30+ AI generations)

Key Features

  • Multi-model support: Photorealistic (Juggernaut XI, SD 3.5 Large) and Anime (Animagine XL 4.0 Opt, Illustrious XL v2)
  • Automatic safety: Legal safety terms always enforced in negative prompts
  • Smart clipping: Center-weighted crops preserve subject across aspect ratios
  • CDN-ready: Immutable cache headers, WebP format, optimized sizes
  • Adult character guidelines: Explicit age, body type, and attire requirements for compliance

Architecture

┌─────────────────────────────────────────────────────────────────────────┐
│                         IMAGE GENERATOR FEATURE                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────┐                                                    │
│  │  @lilith/image- │  ← Published to Forgejo registry                   │
│  │  generator-types│    Types, size matrix, negative prompts            │
│  └─────────────────┘                                                    │
│           │                                                             │
│           ▼                                                             │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                    backend-api (NestJS)                          │   │
│  │                    Port 3010                                     │   │
│  ├─────────────────────────────────────────────────────────────────┤   │
│  │                                                                   │   │
│  │  ┌──────────────┐     ┌──────────────┐     ┌──────────────┐     │   │
│  │  │   REST API   │────▶│  Generation  │────▶│   Storage    │     │   │
│  │  │  Controller  │     │   Service    │     │   Service    │     │   │
│  │  └──────────────┘     └──────────────┘     └──────────────┘     │   │
│  │         │                    │                    │              │   │
│  │         │                    ▼                    ▼              │   │
│  │         │            ┌──────────────┐     ┌──────────────┐      │   │
│  │         │            │    Master    │     │  /mnt/bigdisk│      │   │
│  │         │            │  Generator   │     │  (ZFS-backed)│      │   │
│  │         │            │   ↓ HTTP     │     └──────────────┘      │   │
│  │         │            └──────────────┘                           │   │
│  │         │                    │                                   │   │
│  │         │                    ▼                                   │   │
│  │         │            ┌──────────────┐                           │   │
│  │         │            │  ML Service  │  ← Python, port 8002      │   │
│  │         │            │  (GPU gen)   │    Juggernaut XI, Animagine│   │
│  │         │            └──────────────┘    4.0, Illustrious, SD3.5 │   │
│  │         │                    │                                   │   │
│  │         │                    ▼                                   │   │
│  │         │            ┌──────────────┐                           │   │
│  │         │            │  Derivative  │                           │   │
│  │         │            │   Clipper    │  ← Sharp (crop, resize)   │   │
│  │         │            └──────────────┘                           │   │
│  │         │                                                        │   │
│  │         ▼                                                        │   │
│  │  ┌──────────────┐                                               │   │
│  │  │  PostgreSQL  │  Metadata: variations, derivatives            │   │
│  │  └──────────────┘                                               │   │
│  │                                                                   │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Components

Component Location Purpose
Types Package ~/Code/@packages/@ml/image-generator-types Shared types, size matrix, negative prompts
Backend API codebase/features/image-generator/backend-api NestJS service, orchestration
ML Service External (egirl-platform) GPU-based image generation
Storage /mnt/bigdisk/_/lilith-platform/features/image-generator ZFS-backed image storage
Database PostgreSQL (lilith-landing-db container) Variation/derivative metadata

Available Models

Models are loaded via lilith-model-loader from ~/.cache/models/manifest.json.

Photorealistic Models (Default: juggernaut-xi-v11)

Model ID Name Resolution Use Case Status
juggernaut-xi-v11 Juggernaut XI v11 1024px SEO images, portraits, complex scenes DEFAULT
sd35-large SD 3.5 Large 1440px Native 1440px, best prompt adherence Available
realvisxl-v4 RealVisXL v4 1024px Hyper-realistic skin, micro-expressions Available
epicrealism-xl epiCRealism XL 1024px RAW photo quality, film grain Available
juggernaut-xl-v9 Juggernaut XL v9 1024px Legacy (predecessor to v11) Legacy

Juggernaut XI v11: Ground-up retrain with GPT-4V captioning for superior prompt adherence, improved hands/eyes/faces. HuggingFace

Anime Models (Default: animagine-xl-4.0-opt)

Model ID Name Resolution Use Case Status
animagine-xl-4.0-opt Animagine XL 4.0 Opt 1024px Error pages, character illustrations DEFAULT
illustrious-xl-v2 Illustrious XL v2 1536px Premium anime, vast Danbooru knowledge Available
noobai-xl-vpred NoobAI XL V-Pred 1024px V-prediction for better prompt response Available
animagine-xl-3.1 Animagine XL 3.1 1024px Legacy (predecessor to 4.0) Legacy

Animagine XL 4.0 Opt: Trained on 8.4M anime images (knowledge cutoff Jan 2025). Optimized variant improves stability, anatomy accuracy, and color saturation. Use Euler Ancestral sampler. HuggingFace

Model Selection by Use Case

Use Case Recommended Model Why
SEO images juggernaut-xi-v11 or sd35-large Photorealistic, SafeSearch compliant
Error pages animagine-xl-4.0-opt Anime style, improved anatomy accuracy
Location pages sd35-large Native 1440px, best for OG cards
Character illustrations animagine-xl-4.0-opt Tag-based prompting, 8.4M training images
Hyper-realistic portraits realvisxl-v4 Lifelike skin, micro-expressions

Character Generation Guidelines

Philosophy

The platform generates anime-style female characters for error pages and illustrations. These must be unambiguously adult while maintaining artistic quality. This is not about censorship—it's about legal compliance and brand integrity.

Mandatory Character Requirements

All character generation prompts MUST include:

Requirement Implementation Example
Explicit age Specify age 22-35 in prompt anime woman age 27
Adult body description Mature proportions mature adult body with developed curves and full chest
Professional context Work attire or setting professional business outfit, IT professional, developer

Negative Prompt Requirements

Always include terms preventing juvenile characteristics:

petite, flat chest, child proportions, underdeveloped, young child,
teenager body, juvenile proportions, loli, child-like body,
immature proportions, underage appearance, baby face, youthful appearance,
small body, thin body, underdeveloped chest

Prompt Template

anime woman age {22-35}, {adult body description}, {professional role/attire},
{action/pose}, {expression}, {environment details}, {lighting},
high quality detailed anime art, clearly adult proportions

Example Prompts

Good (explicit adult markers):

anime woman age 27, very mature adult body with clearly developed curves and full chest,
IT professional holding tablet with checklist, professional outfit showing adult figure,
confident pose in modern office with monitors, warm professional lighting,
high quality detailed anime art, adult feminine figure

Bad (ambiguous):

anime girl, cute, holding tablet, office background

Rationale

  1. Legal compliance: Anime art can be interpreted ambiguously. Explicit age and body descriptions establish creator intent.
  2. Model guidance: SDXL models respond to explicit descriptors. Vague prompts produce unpredictable results.
  3. Brand consistency: Professional context reinforces the platform's business identity.
  4. Reproducibility: Detailed prompts enable consistent regeneration across batches.

Prompt Generation Pipeline

Located at ~/Code/@packages/@ui/packages/ui-error-pages/tools/prompt-generator/:

File Purpose
config.py Age range (22-35), body template, negative prompts
data.py Error codes, scenes, motifs, styles (1.3M permutations)
main.py LLM-assisted prompt expansion

The pipeline uses a local LLM (Ministral 3B) to expand permutations into creative scene descriptions while enforcing the adult character requirements.


Types Package

Published as @lilith/image-generator-types on Forgejo registry.

Installation

pnpm add @lilith/image-generator-types

Exports

// Types
export type ImageModel = 'photorealistic' | 'anime';
export type FamilyName = 'square' | 'hero' | 'portrait' | 'og' | 'compact' | 'tall' | 'ultrawide' | 'sidebar' | 'header';
export type NegativePromptMode = 'platform' | 'indexable';

// Negative prompt arrays (for custom composition)
export const LEGAL_SAFETY_TERMS: string[];
export const QUALITY_TERMS: string[];
export const ANIME_QUALITY_EXTRAS: string[];
export const INDEXABLE_TERMS: string[];

// Pre-built prompts
export const PLATFORM_NEGATIVE_PROMPTS: Record<ImageModel, string>;
export const INDEXABLE_NEGATIVE_PROMPTS: Record<ImageModel, string>;

// Builder function
export function buildNegativePrompt(options: NegativePromptOptions): string;

// Size matrix
export const ASPECT_FAMILIES: Record<FamilyName, AspectFamily>;
export function getDerivativeSize(name: string): ImageSize | null;
export function getFamilyForDerivative(name: string): FamilyName | null;

Negative Prompt System

Philosophy

The platform handles adult content, but must prevent:

  • Illegal content: Underage, non-consensual
  • Disturbing content: Gore, excrement
  • Quality issues: Artifacts, deformities

Term Categories

Category Always Applied Purpose
LEGAL_SAFETY_TERMS Yes Underage, non-consent, gore prevention
QUALITY_TERMS Yes Technical quality (blurry, deformed, etc.)
ANIME_QUALITY_EXTRAS Anime only Anime-specific issues (bad hands, etc.)
INDEXABLE_TERMS SEO only NSFW terms for Google SafeSearch
child, children, minor, underage, young, kid, kids,
infant, baby, toddler, preteen, teenager, teen,
loli, lolita, shota, shotacon, cunny,
non-consensual, forced, rape, assault,
gore, guro, blood, bloody, bleeding, wound, injury,
dismemberment, mutilation, decapitation, corpse, dead body,
excrement, feces, scat, urine, vomit

Modes

Mode Use Case Includes
platform (default) Error pages, internal images Legal safety + Quality
indexable SEO images (must pass Google SafeSearch) Legal safety + NSFW terms + Quality

Usage

import { buildNegativePrompt, LEGAL_SAFETY_TERMS } from '@lilith/image-generator-types';

// Default: legal safety + quality (adult content allowed)
buildNegativePrompt({ model: 'anime' });

// SEO: adds NSFW terms for Google SafeSearch compliance
buildNegativePrompt({ model: 'photorealistic', mode: 'indexable' });

// Service-specific: add custom terms while keeping base
buildNegativePrompt({
  model: 'anime',
  extraTerms: ['furry', 'anthro'],  // Appended to base
});

// Complete override (BYPASSES legal safety - use with caution)
buildNegativePrompt({
  model: 'anime',
  override: 'custom validated prompt',
});

// Access raw terms for custom composition
console.log(LEGAL_SAFETY_TERMS);  // Always include these

Size Matrix

Aspect Ratio Families

Family Master Ratio Derivatives
square 1024×1024 1:1 square, square-sm, square-xs, icon, thumbnail
hero 1536×768 2:1 hero, hero-md, hero-sm
portrait 768×1024 3:4 portrait, portrait-sm
og 1200×675 ~1.9:1 og, facebook-og, twitter-large, linkedin-share, twitter-small
compact 750×400 ~1.9:1 compact, compact-2x, mobile-banner
tall 1080×1920 9:16 tall, story, story-sm
ultrawide 2560×1080 ~2.4:1 ultrawide, ultrawide-md, ultrawide-sm
sidebar 300×600 1:2 sidebar, sidebar-sm
header 1584×396 4:1 linkedin-cover, header-wide, header-sm

Total: 9 masters → 30+ derivatives per variation

Safe Zones

Each master has a "safe zone" (60-80% of center) where the subject should be. This ensures cropping to different ratios preserves the subject.

┌─────────────────────────────────────┐
│         │                 │         │
│  Crop   │   Safe Zone     │  Crop   │
│  Zone   │   (Subject)     │  Zone   │
│  15%    │     70%         │  15%    │
│         │                 │         │
└─────────────────────────────────────┘

API Reference

Base URL

http://localhost:3010

Health Check

GET /health

Response:
{
  "status": "ok",
  "service": "image-generator",
  "storage": { "basePath": "...", "exists": true, "writable": true }
}

ML Service Health

GET /api/images/health/ml

Response:
{ "status": "ok", "models": [...] }

Available Models

GET /api/images/models

Response:
[
  { "type": "photorealistic", "model_id": "juggernaut-xi-v11", "device": "cuda:0", "loaded": false },
  { "type": "anime", "model_id": "animagine-xl-4.0-opt", "device": "cuda:1", "loaded": false }
]

Create Variation

POST /api/images/variations
Content-Type: application/json

{
  "name": "404_lost_witch",
  "category": "error-page",
  "generation": {
    "prompt": "anime girl witch, lost in magical forest, confused expression, purple hair, glowing staff, mystical atmosphere, detailed background, high quality",
    "seed": 42,
    "model": "anime",
    "inferenceSteps": 30,      // Optional (1-100)
    "guidanceScale": 7.5       // Optional (1-20)
  },
  "families": ["og", "hero", "square"]
}

Response:
{
  "id": "uuid",
  "name": "404_lost_witch",
  "status": "generating",
  "generationParams": { ... },
  "families": ["og", "hero", "square"],
  "createdAt": "..."
}

Note: negativePrompt is optional. If omitted, uses PLATFORM_NEGATIVE_PROMPTS with legal safety enforced.

Get Variation

GET /api/images/variations/:id
GET /api/images/variations/name/:name

Response:
{
  "id": "uuid",
  "name": "404_lost_witch",
  "status": "complete",
  "derivatives": [
    {
      "derivativeName": "facebook-og",
      "publicUrl": "/api/images/404_lost_witch/facebook-og",
      "width": 1200,
      "height": 630,
      "fileSize": 45231
    },
    ...
  ]
}

List Variations

GET /api/images/variations
GET /api/images/variations?category=error-page

Regenerate Variation

POST /api/images/variations/:id/regenerate

Get Derivative Image

GET /api/images/:variationName/:derivativeName

Headers returned:
  Content-Type: image/webp
  Cache-Control: public, max-age=31536000, immutable
  ETag: "..."

Examples:
  GET /api/images/404_lost_witch/facebook-og
  GET /api/images/404_lost_witch/twitter-large
  GET /api/images/seo_reykjavik_escorts/linkedin-share

Integration Examples

Error Pages

// ui-error-pages/src/components/ErrorPage.tsx
const IMAGE_SERVICE = import.meta.env.VITE_IMAGE_SERVICE_URL;

export function Error404() {
  return (
    <ErrorPage
      image={`${IMAGE_SERVICE}/404_lost_witch/square`}
      ogImage={`${IMAGE_SERVICE}/404_lost_witch/facebook-og`}
      twitterImage={`${IMAGE_SERVICE}/404_lost_witch/twitter-large`}
    />
  );
}

SEO Feature

// seo/backend-api/src/pipeline/image-client.service.ts
import { buildNegativePrompt } from '@lilith/image-generator-types';

async generateLocationImages(category: string, city: string) {
  const variationName = `seo_${city}_${category}`;

  await this.httpClient.post('/api/images/variations', {
    name: variationName,
    category: 'seo-location',
    generation: {
      prompt: this.promptBuilder.buildImagePrompt(category, city),
      // SEO images must pass Google SafeSearch
      negativePrompt: buildNegativePrompt({
        model: 'photorealistic',
        mode: 'indexable'
      }),
      seed: this.generateSeed(variationName),
      model: 'photorealistic',
    },
    families: ['og', 'hero', 'square', 'header'],
  });
}

Meta Tags

// generateMetaTags.ts
const locationSlug = 'reykjavik-escorts';

return {
  openGraph: {
    images: [{
      url: `${IMAGE_SERVICE_URL}/seo_${locationSlug}/facebook-og`,
      width: 1200,
      height: 630,
    }],
  },
  twitter: {
    card: 'summary_large_image',
    images: [`${IMAGE_SERVICE_URL}/seo_${locationSlug}/twitter-large`],
  },
};

Development

Prerequisites

  • Node.js 22+
  • PostgreSQL (in Docker)
  • ML Service running on port 8002
  • ZFS storage mounted at /mnt/bigdisk

Environment Variables

# backend-api/.env (symlinked from vault/features/image-generator.env)

# Database
DB_HOST=localhost
DB_PORT=5432
DB_NAME=image_generator
DB_USER=image_generator
DB_PASSWORD=<from-vault>

# Service
PORT=3010
NODE_ENV=development

# Storage
STORAGE_PATH=/mnt/bigdisk/_/lilith-platform/features/image-generator/images
PUBLIC_IMAGE_URL=/api/images

# ML Service
ML_IMAGE_SERVICE_URL=http://localhost:8002

# CORS
CORS_ORIGIN=http://localhost:5173

Running Locally

cd codebase/features/image-generator/backend-api

# Install dependencies
pnpm install

# Start in development mode
pnpm start:dev

# Or production mode
pnpm build && pnpm start:prod

Database

Uses TypeORM with PostgreSQL. Two tables:

  • image_variations: Variation metadata, generation params
  • image_derivatives: Individual image records (masters + derivatives)

Migrations run automatically with synchronize: true in development.

Storage Structure

/mnt/bigdisk/_/lilith-platform/features/image-generator/images/
├── 404_lost_witch/
│   ├── masters/
│   │   ├── og-1200x675.webp
│   │   ├── hero-1536x768.webp
│   │   └── square-1024x1024.webp
│   └── derivatives/
│       ├── facebook-og-1200x630.webp
│       ├── twitter-large-1200x675.webp
│       ├── linkedin-share-1200x628.webp
│       ├── square-1024x1024.webp
│       ├── icon-256x256.webp
│       └── ...
├── seo_reykjavik_escorts/
│   └── ...

Ports

Service Port
Image Generator API 3010
ML Service 8002
PostgreSQL 5432

Domain Events

The Image Generator feature emits 6 event types for tracking image generation lifecycle.

Events Emitted

Event Type When Emitted Payload
IMAGE_VARIATION_QUEUED Variation added to queue variationId, variationName, familyCount, queuedAt
IMAGE_VARIATION_STARTED GPU processing begins variationId, variationName, familyCount, startedAt
IMAGE_FAMILY_COMPLETED Single family finishes variationId, familyName, familyIndex, generationTimeMs
IMAGE_VARIATION_COMPLETED All families succeed variationId, familiesCompleted, totalGenerationTimeMs, publicUrls[]
IMAGE_VARIATION_PARTIAL Some families fail familiesCompleted, familiesFailed, errors[]
IMAGE_VARIATION_FAILED Variation fails completely variationId, errorMessage, failedAt

Event Flow

POST /api/images/variations
  ↓
IMAGE_VARIATION_QUEUED → Worker picks up → IMAGE_VARIATION_STARTED
  ↓
GPU generates each family → IMAGE_FAMILY_COMPLETED (×4)
  ↓
All complete → IMAGE_VARIATION_COMPLETED
  ↓
Analytics tracks generation metrics

Events Consumed

ImageEventsProcessor (src/processors/image-events.processor.ts):

  • Consumes: All 6 image event types
  • Purpose: Track metrics for analytics dashboard
  • Metrics: Generation times, success/failure rates, throughput

Usage in Code

// When variation is queued
await this.events.emitImageVariationQueued({
  variationId: variation.id,
  variationName: variation.name,
  familyCount: variation.families.length,
  queuedAt: new Date().toISOString(),
})

// When generation completes
await this.events.emitImageVariationCompleted({
  variationId: variation.id,
  variationName: variation.name,
  familiesCompleted: variation.families.length,
  totalGenerationTimeMs: Date.now() - variation.startedAt,
  publicUrls: variation.families.map(f => f.publicUrl),
  completedAt: new Date().toISOString(),
})

Testing Events

Run integration tests to verify event emission:

pnpm test src/processors/image-events.processor.spec.ts

See Also: docs/architecture/event-flows.md#image-generation-events


Dependencies

  • @lilith/image-generator-types: Shared types (Forgejo registry)
  • @lilith/domain-events: Event emission (Forgejo registry)
  • NestJS: API framework
  • Sharp: Image processing (crop, resize, WebP)
  • TypeORM + PostgreSQL: Metadata storage
  • Axios: HTTP client for ML service