Add REST API for preset-based generation and fallback cover images

REST API (routes/api.py): Three endpoints behind API key auth for
programmatic image generation via presets — list presets, queue
generation with optional overrides, and poll job status.

Shared generation logic extracted from routes/presets.py into
services/generation.py so both web UI and API use the same code path.

Fallback covers: library index pages now show a random generated image
at reduced opacity when no cover is assigned, instead of "No Image".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Aodhan Collins
2026-03-15 21:19:12 +00:00
parent d756ea1d0e
commit 7d79e626a5
20 changed files with 719 additions and 166 deletions

View File

@@ -27,6 +27,7 @@ services/
sync.py # All sync_*() functions + preset resolution helpers
job_queue.py # Background job queue (_enqueue_job, _make_finalize, worker thread)
file_io.py # LoRA/checkpoint scanning, file helpers
generation.py # Shared generation logic (generate_from_preset)
routes/
__init__.py # register_routes(app) — imports and calls all route modules
characters.py # Character CRUD + generation + outfit management
@@ -44,6 +45,7 @@ routes/
strengths.py # Strengths gallery system
transfer.py # Resource transfer system
queue_api.py # /api/queue/* endpoints
api.py # REST API v1 (preset generation, auth)
```
### Dependency Graph
@@ -60,7 +62,8 @@ app.py
│ ├── mcp.py ← (stdlib only: subprocess, os)
│ ├── sync.py ← models, utils
│ ├── job_queue.py ← comfyui, models
── file_io.py ← models, utils
── file_io.py ← models, utils
│ └── generation.py ← prompts, workflow, job_queue, sync, models
└── routes/
├── All route modules ← services/*, utils, models
└── (routes never import from other routes)
@@ -77,6 +80,8 @@ SQLite at `instance/database.db`, managed by Flask-SQLAlchemy. The DB is a cache
**Models**: `Character`, `Look`, `Outfit`, `Action`, `Style`, `Scene`, `Detailer`, `Checkpoint`, `Settings`
The `Settings` model stores LLM provider config, LoRA/checkpoint directory paths, default checkpoint, and `api_key` for REST API authentication.
All category models (except Settings and Checkpoint) share this pattern:
- `{entity}_id` — canonical ID (from JSON, often matches filename without extension)
- `slug` — URL-safe version of the ID (alphanumeric + underscores only, via `re.sub(r'[^a-zA-Z0-9_]', '', id)`)
@@ -180,6 +185,10 @@ LoRA nodes chain: `4 → 16 → 17 → 18 → 19`. Unused LoRA nodes are bypasse
- **`get_available_checkpoints()`** — Scans checkpoint directories.
- **`_count_look_assignments()`** / **`_count_outfit_lora_assignments()`** — DB aggregate queries.
### `services/generation.py` — Shared Generation Logic
- **`generate_from_preset(preset, overrides=None)`** — Core preset generation function used by both the web route and the REST API. Resolves entities, builds prompts, wires the workflow, and enqueues the job. The `overrides` dict accepts: `action`, `checkpoint`, `extra_positive`, `extra_negative`, `seed`, `width`, `height`. Has no `request` or `session` dependencies.
### `services/mcp.py` — MCP/Docker Lifecycle
- **`ensure_mcp_server_running()`** — Ensures the danbooru-mcp Docker container is running.
@@ -351,6 +360,15 @@ Each category follows the same URL pattern:
- `POST /checkpoint/<slug>/save_json`
- `POST /checkpoints/rescan`
### REST API (`/api/v1/`)
Authenticated via `X-API-Key` header (or `api_key` query param). Key is stored in `Settings.api_key` and managed from the Settings page.
- `GET /api/v1/presets` — list all presets (id, slug, name, has_cover)
- `POST /api/v1/generate/<preset_slug>` — queue generation from a preset; accepts JSON body with optional `checkpoint`, `extra_positive`, `extra_negative`, `seed`, `width`, `height`, `count` (120); returns `{"jobs": [{"job_id": ..., "status": "queued"}]}`
- `GET /api/v1/job/<job_id>` — poll job status; returns `{"id", "label", "status", "error", "result"}`
- `POST /api/key/regenerate` — generate a new API key (Settings page)
See `API_GUIDE.md` for full usage examples.
### Job Queue API
All generation routes use the background job queue. Frontend polls:
- `GET /api/queue/<job_id>/status` — returns `{"status": "pending"|"running"|"done"|"failed", "result": {...}}`
@@ -378,7 +396,7 @@ Image retrieval is handled server-side by the `_make_finalize()` callback; there
- Global default checkpoint selector (saves to session via AJAX)
- Resource delete modal (soft/hard) shared across gallery pages
- `initJsonEditor(saveUrl)` — shared JSON editor modal (simple form + raw textarea tabs)
- Context processors inject `all_checkpoints`, `default_checkpoint_path`, and `COMFYUI_WS_URL` into every template.
- Context processors inject `all_checkpoints`, `default_checkpoint_path`, and `COMFYUI_WS_URL` into every template. The `random_gen_image(category, slug)` template global returns a random image path from `static/uploads/<category>/<slug>/` for use as a fallback cover when `image_path` is not set.
- **No `{% block head %}` exists** in layout.html — do not try to use it.
- Generation is async: JS submits the form via AJAX (`X-Requested-With: XMLHttpRequest`), receives a `{"job_id": ...}` response, then polls `/api/queue/<job_id>/status` every ~1.5 seconds until `status == "done"`. The server-side worker handles all ComfyUI polling and image saving via the `_make_finalize()` callback. There are no client-facing finalize HTTP routes.
- **Batch generation** (library pages): Uses a two-phase pattern:
@@ -386,6 +404,7 @@ Image retrieval is handled server-side by the `_make_finalize()` callback; there
2. **Poll phase**: All jobs are polled concurrently via `Promise.all()`, updating UI as each completes
3. **Progress tracking**: Displays currently processing items in real-time using a `Set` to track active jobs
4. **Sorting**: All batch operations sort items by display `name` (not `filename`) for better UX
- **Fallback covers** (library pages): When a resource has no assigned `image_path` but has generated images in its upload folder, a random image is shown at 50% opacity (CSS class `fallback-cover`). The image changes on each page load. Resources with no generations show "No Image".
---