Overview
GPT Image Panel is a lightweight web UI for image generation, image editing, gallery management, and local persistence. It connects to an external GPT-compatible image API and stores images plus metadata on your own server.
This project is only a self-hosted control panel. It does not provide, proxy, resell, or modify any upstream model/API service. Generation capability, billing, account permissions, content policy, and model behavior all come from the upstream provider you configure.
Features
- Image generation through
/v1/images/generations,/v1/responses, or OpenAI-compatible/v1/chat/completions. - Image editing through
/v1/images/edits, including uploaded references and gallery-source edits. - API presets with base URL/path/key, default model, response format, health checks, SOCKS5 proxy, webhook, and env-ref secret support.
- Web-managed Overall Config for selected runtime settings, with env/default/override sources and restart/build-only badges.
- Prompt helper tags, reusable prompt snippets, optional server-side prompt optimizer, and an AI Assistant subsystem for prompt rewrites/checks/variants, parameter recommendations, job diagnosis, edit planning, and gallery image analysis.
- SQLite-backed job queue with SSE progress, cancellation, retry/reuse, persisted history, stage timing metadata, and shared generation/edit concurrency limits.
- Local gallery with cursor pagination, search/filtering, favorites, lightbox navigation, selection-token batch actions, ZIP import/export, thumbnails, byte-size metadata, and async export/import jobs.
- Optional Cloudflare R2 gallery backup sync; local SQLite/images remain the source of truth.
- Access-key gate, IP/Host allowlists, trusted proxy-header support, CSRF origin checks, CSP nonce injection, version checks, and optional JSON/Prometheus metrics.
Architecture
- Backend: FastAPI under
backend/app/; ASGI entrypoint isbackend.app.main:app. - Frontend: SvelteKit static app under
frontend/; production backend servesfrontend/build/. - Runtime storage: generated images under
images/, thumbnails underimages/thumbs/, SQLite data underdata/app.sqlite3, and logs underdata/logs/by default. - Public API routing:
backend/app/api/contract_app.py. - DTOs:
backend/app/schemas/. - Persistence:
backend/app/repositories/. - Upstream API integration:
backend/app/integrations/. - Runtime config:
backend/app/core/settings.py,backend/app/core/overall_config.py,.env.example, anddocker-compose.yml.
Tech Stack
- Python 3.11+
- FastAPI
- Granian
- aiohttp
- aiohttp-socks
- boto3
- SQLite
- Pydantic v2
- Pillow
- zipstream-ng
- SvelteKit
- TypeScript
- Tailwind CSS
- Playwright
- pytest
Project Structure
backend/
app/
api/
core/
integrations/
repositories/
schemas/
services/
tests/
frontend/
src/
lib/
routes/
tests/
deploy/
nginx.conf
images/
data/
Dockerfile
docker-compose.yml
.env.example
requirements.txt
backend/requirements-dev.txt
package.json
Quick Start
Docker Compose
cp .env.example .env
# edit .env: set ACCESS_KEY and any default upstream API values you want
# this example uses plain HTTP on loopback, so disable Secure cookies
ACCESS_COOKIE_SECURE=false docker-compose up -d --force-recreate
Open http://127.0.0.1:9090.
This local HTTP example requires ACCESS_COOKIE_SECURE=false; keep it true when serving the panel over HTTPS. By default, ACCESS_KEY is required. For local-only testing, unset ACCESS_KEY and set ALLOW_UNAUTHENTICATED=true; this makes every non-health API route accessible.
Docker
docker build -t gpt-image-panel .
docker run -d --name gpt-image-panel \
-p 127.0.0.1:9090:9090 \
-e ACCESS_KEY=change-me \
-e ACCESS_COOKIE_SECURE=false \
-v $(pwd)/images:/app/images \
-v $(pwd)/data:/app/data \
gpt-image-panel
If Docker Hub is slow or blocked:
docker build \
--build-arg PYTHON_BASE_IMAGE=docker.m.daocloud.io/library/python:3.11-slim \
--build-arg NODE_BASE_IMAGE=docker.m.daocloud.io/library/node:24-alpine \
-t gpt-image-panel .
Local Development
Create a project-local Python 3.11+ virtual environment first. The .venv directory is local developer state and is not provided by the repository.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r backend/requirements-dev.txt
npm --prefix frontend install
npm run backend:dev
In another terminal:
npm run frontend:dev
Open http://localhost:5173. Vite proxies /api and /health to FastAPI at 127.0.0.1:9090.
Production-style local smoke test:
npm run frontend:build
ALLOW_UNAUTHENTICATED=true .venv/bin/granian --interface asgi backend.app.main:app --host 127.0.0.1 --port 9090
Configuration
Most runtime options live in .env.example. API presets, prompt optimizer, R2 backup, and selected app/runtime options can also be managed through Web Settings / Overall Config. Important variables:
| Variable | Purpose |
|---|---|
ACCESS_KEY |
Access gate key. Required unless it is unset and ALLOW_UNAUTHENTICATED=true. |
DEFAULT_API_URL |
Default upstream API base URL; may omit or include /v1. |
DEFAULT_API_KEY |
Default upstream API key. Prefer env refs such as ${OPENAI_API_KEY} in Web Settings. |
DEFAULT_API_PATH |
/v1/images/generations, /v1/responses, or /v1/chat/completions. |
DEFAULT_RESPONSES_MODEL |
Fallback model for /v1/responses when no request/preset model is provided. |
AIOHTTP_CONNECTION_LIMIT / AIOHTTP_CONNECTION_LIMIT_PER_HOST |
Shared aiohttp connector limits for upstream/probe/download calls. |
APP_VERSION / GITHUB_REPO / ENABLE_VERSION_CHECK |
UI/API version reporting and latest-release checks. |
VERSION_CHECK_CACHE_SECONDS |
Per-process cache TTL for successful latest-release checks. |
MAX_UPSTREAM_IMAGE_BYTES_PER_TASK_MB / UPSTREAM_MEMORY_BUDGET_MB |
Per-task decoded-image cap and process-local weighted upstream-memory admission budget. |
DB_EXECUTOR_WORKERS / SQLITE_BUSY_* |
Dedicated SQLite executor size and short-timeout jittered retry controls. |
IMAGE_CPU_CONCURRENCY / FILE_IO_CONCURRENCY |
Bounded full-image decode and blocking file-I/O concurrency per process. |
IMAGE_JOB_PROGRESS_PERSIST_INTERVAL_SECONDS |
Minimum interval for coalesced image-unit progress writes. |
RUNTIME_METRICS_REFRESH_SECONDS / EVENT_LOOP_LAG_SAMPLE_SECONDS |
Background coordination snapshot and event-loop lag sampling intervals. |
MAX_ACTIVE_GENERATE_JOBS |
Global running generation/edit image-unit limit. |
MAX_QUEUED_GENERATE_JOBS |
Queue capacity before new jobs return 429. |
MAX_PENDING_EDIT_SOURCE_MB |
Global pending edit-source byte reservation cap. |
MAX_SSE_SUBSCRIBERS_GLOBAL / MAX_SSE_SUBSCRIBERS_PER_IP / SSE_CONNECTION_TTL_SECONDS |
SSE slot limits and max connection lifetime. |
IMAGES_DIR |
Saved image directory. |
THUMBNAILS_DIR / THUMBNAIL_* |
Gallery thumbnail storage and generation controls. |
DATA_DIR / DATABASE_FILE |
SQLite runtime storage. |
PROMPT_OPTIMIZER_* |
Optional server-side prompt optimizer settings. |
AI_ASSISTANT_* |
AI Assistant is enabled by default; set AI_ASSISTANT_ENABLED=false to disable it. API URL, key, text model, timeout, route, and host allowlist reuse PROMPT_OPTIMIZER_*. AI_ASSISTANT_MAX_CONCURRENCY caps concurrent upstream assistant calls and AI_ASSISTANT_BATCH_MAX_IMAGES caps one gallery AI batch. |
R2_* |
Optional Cloudflare R2 gallery backup sync settings; custom endpoint hosts require R2_ENDPOINT_HOST_ALLOWLIST. |
PUBLIC_ORIGIN / ALLOWED_HOSTS |
Reverse-proxy Host/CSRF hardening. |
ENABLE_NGINX_ACCEL_REDIRECT / PUBLIC_IMAGE_BASE_URL / PUBLIC_THUMBNAIL_BASE_URL |
Optional nginx/CDN image byte serving behavior. |
GRANIAN_* |
Production runtime process/thread/static-asset tuning. |
ENABLE_METRICS |
Enables JSON/Prometheus metrics endpoints. |
LOG_DIR / LOG_LEVEL / LOG_RETENTION_HOURS |
Backend logs on stdout plus rotated files, retained for 24h by default. |
Secret fields prefer ${ENV_VAR_NAME} references. Literal secrets stored in SQLite require ALLOW_PLAINTEXT_SECRETS=true.
Overall Config persists overrides in SQLite. Some settings are hot-reloaded; restart-required and build-only settings are marked in the UI and should still be changed through .env/Compose for reproducible deployments.
Usage
- Open the panel.
- Unlock with
ACCESS_KEYif enabled. - Open Settings.
- Create or select an API preset.
- Set API base URL, API path, model, response format, and API key/env ref.
- Optionally configure SOCKS5 proxy, webhook, prompt optimizer, AI Assistant, R2 backup, or Overall Config overrides.
- Save the preset and run its health check if needed.
- Generate images from a prompt, or upload/select source images and run edits.
- Use Gallery for reuse, filtering, favorites, batch actions, import/export, and R2 sync.
Supported Upstream Paths
| Path | Notes |
|---|---|
/v1/images/generations |
Standard image generation endpoint; reads image data from data[]. |
/v1/responses |
Sends prompt and model; reads base64 image data from image_generation_call output items. |
/v1/chat/completions |
Sends OpenAI-compatible chat completions requests; extracts image URLs/base64 data from messages or SSE chunks. |
/v1/images/edits |
Used by the Edits flow; sends multipart source images and supported edit params. |
For /v1/responses and /v1/chat/completions, size/quality/format/compression/quantity controls are disabled because those paths do not share the same parameter contract.
API Overview
Key backend routes:
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Health check. |
GET |
/api/access/status |
Read access state. |
POST |
/api/access |
Unlock the panel with the access key. |
GET/POST/DELETE |
/api/access/admin, /api/access/admin/status |
Read, unlock, or clear the admin access state used by protected settings. |
GET |
/api/version, /api/version/latest |
Read current version and optional latest release information. |
GET/PUT |
/api/settings/overall-config |
Read/save Overall Config overrides. |
GET/POST |
/api/settings |
Read/save active preset, prompt optimizer, R2 backup, proxy, and webhook settings. |
POST |
/api/settings/presets |
Create an API preset. |
POST |
/api/settings/presets/{preset_id}/activate |
Activate a saved API preset. |
DELETE |
/api/settings/presets/{preset_id} |
Delete an API preset. |
POST |
/api/settings/presets/{preset_id}/health |
Validate a saved upstream preset. |
POST |
/api/settings/r2/health |
Validate draft R2 backup settings. |
GET/POST |
/api/prompt-snippets |
List/create reusable prompt snippets. |
POST |
/api/prompt-snippets/search |
Search reusable prompt snippets. |
PATCH/DELETE |
/api/prompt-snippets/{snippet_id} |
Update/delete a prompt snippet. |
GET/POST |
/api/prompt/optimizer-system-prompt |
Read/save the prompt optimizer system prompt. |
POST |
/api/prompt/optimize, /api/prompt/optimizer-health |
Optimize a prompt or probe optimizer connectivity. |
POST |
/api/assistant/health |
Probe AI Assistant connectivity. |
POST |
/api/assistant/prompt/rewrite, /api/assistant/prompt/check, /api/assistant/prompt/variants |
Prompt Copilot rewrite, review, and variant tools. |
POST |
/api/assistant/generate/recommend-params |
Recommend only generation parameters supported by the selected API path. |
POST |
/api/assistant/jobs/{job_id}/diagnose, /api/assistant/edit/plan |
Diagnose a job or plan an edit without submitting it. |
POST |
/api/assistant/image/prompt |
Reverse-prompt one validated local raster image in memory; returns a generation prompt without creating a Gallery record. |
POST |
/api/assistant/image/prompt/optimize |
Optimize a reverse-prompt result together with its uploaded source image. |
POST/GET |
/api/assistant/gallery/* |
Describe, reverse-prompt, analyze, batch-analyze, and read AI metadata for local gallery images. |
POST |
/api/generate |
Start generation job. |
POST |
/api/edits |
Start edit job with uploaded source images. |
POST |
/api/edits/from-gallery/{image_id} |
Start edit job from an existing gallery image. |
GET |
/api/generate/jobs |
List live jobs and optional persisted history. |
GET |
/api/generate/jobs/events |
SSE stream for job-list updates. |
GET/DELETE |
/api/generate/{job_id} |
Read or cancel one generation/edit job. |
GET |
/api/generate/{job_id}/events |
SSE stream for one job. |
DELETE |
/api/generate/jobs/history |
Clear terminal job history. |
GET |
/api/gallery |
List/search/filter gallery images. |
POST |
/api/gallery/search |
Search/filter gallery images with a JSON request body. |
GET/DELETE |
/api/gallery/{image_id} |
Read or delete a gallery image. |
PATCH |
/api/gallery/{image_id}/favorite |
Favorite/unfavorite one gallery image. |
POST/PATCH |
/api/gallery/batch/* |
Selection-token, favorite, delete, and download batch actions. |
POST |
/api/gallery/export-jobs, /api/gallery/direct-export-jobs |
Create async gallery export jobs. |
GET |
/api/gallery/export-jobs/{job_id}, /api/gallery/direct-export-jobs/{job_id} |
Read async gallery export job status. |
GET |
/api/gallery/export-jobs/{job_id}/events, /api/gallery/direct-export-jobs/{job_id}/events |
SSE streams for gallery export jobs. |
GET |
/api/gallery/export-jobs/{job_id}/download |
Download a completed tracked export archive. |
POST |
/api/gallery/sync-jobs |
Create an R2 backup sync job. |
GET |
/api/gallery/sync-jobs/{job_id}, /api/gallery/sync-jobs/{job_id}/events |
Read or stream R2 backup sync job status. |
GET |
/api/gallery/import-jobs/{job_id} |
Read async import job status. |
GET |
/api/gallery/import-jobs/{job_id}/events |
SSE stream for async import job status. |
GET |
/api/image/{filename} |
Serve authorized image bytes. |
GET |
/api/thumb/{filename} |
Serve generated gallery thumbnail. |
GET |
/api/download/{filename} |
Download one gallery image. |
GET |
/api/download-all |
Stream gallery ZIP export. |
POST |
/api/import |
Import gallery ZIP archive; async_job=true creates an import job. |
GET |
/api/metrics, /api/metrics/prometheus |
Optional metrics when ENABLE_METRICS=true. |
The public API surface is contract-tested; keep paths, methods, status codes, SSE event names, cookies, and response shapes stable unless a breaking change is intentional.
Contributor Boundaries
- Keep browser calls same-origin through
/api/*; do not add direct frontend calls to upstream model APIs, R2, webhook targets, or arbitrary image URLs. - Keep ownership boundaries intact:
- routers/request orchestration in
backend/app/api/routers/ - DTOs in
backend/app/schemas/ - persistence and SQLite coordination in
backend/app/repositories/ - upstream integrations in
backend/app/integrations/ - mirrored frontend API types in
frontend/src/lib/api/types.ts
- routers/request orchestration in
- Keep public contracts stable unless a breaking change is intentional:
- API paths, methods, status codes, cookies, SSE event names, and response shapes
- generation/edit queue lifecycle, cancellation semantics, and multi-worker SQLite coordination
- Keep validation and safety centralized:
- image byte validation, safe paths, thumbnail/archive helpers
- SSRF-sensitive URL handling in validators, safe connector, and integration clients
- secrets exposed to the frontend only as masked values or env-ref metadata
- Preserve current runtime constraints:
- edits accept up to 16 raster source images
- gallery ZIP import/export keeps existing safety limits
- SSE uses SQLite slot leases with global/per-IP caps and TTL
- R2 sync is backup-only; local SQLite rows and local image files remain the source of truth
- When changing environment variables, update
backend/app/core/settings.py,backend/app/core/overall_config.pywhen user-visible,.env.example,docker-compose.ymlwhen configurable in Compose, and this README. - Do not commit runtime/generated artifacts such as
images/,data/,frontend/build/,.svelte-kit/, Playwright reports, test results, dependency folders, local DB files, or logs.
Testing
Activate the project-local .venv before running backend or contract tests. The npm contract/performance scripts use .venv/bin/python.
npm run frontend:check
npm run frontend:build
.venv/bin/python -m pytest backend/tests -q
npm run test:contract
npm run test:e2e
npm run test:perf
npm run test:e2e:perf
Run the focused subset relevant to your change. For release-bound or broad changes, run all of them.
If Playwright browsers are missing:
npm --prefix frontend exec playwright install chromium
Contributing
For implementation boundaries and contributor-facing invariants, follow the Contributor Boundaries section above. Run the smallest relevant validation set for your change, and keep README/config updates in the same patch when behavior or environment variables change.
License
This project is licensed under CC BY-NC 4.0 (Creative Commons Attribution-NonCommercial 4.0 International).
See LICENSE.