Skip to content

oxmah/espresso

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Espresso!

Espresso!

A small Windows desktop app for generating images, with a unified UX over both cloud APIs and local inference. Cloud side: full control over OpenAI's gpt-image-2 (moderation, batch retries, live cost). Local side: a bundled stable-diffusion.cpp runtime with SDXL / Flux 1 / Flux 2 / SD3 support and a curated catalog of pre-vetted models - no Python dependencies, no GPU drivers to fight, just a single .exe.

Built because ChatGPT's image moderation is inconsistent and ComfyUI is too technical for casual creative work. Espresso aims for the middle ground: simpler than ComfyUI, more capable than the ChatGPT image dialog.

Espresso main window

More screenshots - model library, settings

The catalog ships 25 pre-vetted models across SDXL, Flux 1, Flux 2, SD3, Z-Image and Qwen-Image, with free-text search on CivitAI and HuggingFace next to it:

Model library

API keys go to the Windows Credential Manager, never to disk in plaintext:

Settings

What works today

Cloud (OpenAI API)

  • All gpt-image-2 parameters exposed: model, size (up to 3840×2160), quality, moderation (low / auto), format + compression, background, n
  • API key in Windows Credential Manager (DPAPI-backed via keyring) - no plain JSON
  • Mode INSISTER - retries through moderation rejections until N images collected, with cancel + max-retries safety net
  • Mode THINKING - routes through OpenAI's Responses API with a thin-proxy orchestrator that forwards your prompt verbatim to gpt-image-2 (no paraphrase) for ChatGPT-quality results
  • Live cost estimate per generation (text + image input + output tokens at current OpenAI rates)

Local (sd.cpp)

  • Bundled stable-diffusion.cpp runtime - installer dialog auto-detects NVIDIA, fetches the right CUDA / AVX2 / Vulkan binary archive plus the separate CUDA runtime DLLs, atomic install
  • Multi-file model support - Flux 1 / Flux 2 (Klein 4B / Klein 9B / dev) / SD3 with the right --diffusion-model + --t5xxl / --clip_l / --vae / --llm plumbing per family
  • Per-variant LLM auto-resolution for Flux 2 - Klein 4B → Qwen3 4B, Klein 9B → Qwen3 8B, dev → Mistral 24B, picked from the DiT filename
  • VRAM-aware --offload-to-cpu auto-detect - queries nvidia-smi at model selection, auto-ticks the offload toggle when the model + auxiliaries don't fit free VRAM (with explicit override)
  • Sampler / scheduler presets ("Pipeline" combo) - Auto, Portraits / réalisme (DPM++ 2M Karras), Illustration / art (DPM++ 2S Ancestral Karras), Vitesse (Euler Ancestral Karras), Reproductible
  • Live step counter - sd.cpp's step N/M parsed from stdout and surfaced in the progress bar

Library / model browser

  • "Sélection Espresso" - curated catalog of 7 pre-vetted models: SDXL Base 1.0, Juggernaut XL v9, Flux 2 Klein 4B GGUF, Flux 1 schnell GGUF, Flux 1 dev GGUF, SD3 Medium, Flux 2 Klein 9B GGUF (⚡ very-big-card)
  • Free-text search on CivitAI + HuggingFace with NSFW opt-in toggle
  • HuggingFace token in Settings (separate keyring slot) for gated repos like Black Forest Labs FLUX.2-dev - auth header attached only to real huggingface.co hosts
  • Auxiliary files dialog - sequential download of T5XXL FP8 / CLIP-L / VAE / Qwen3 / Mistral GGUFs with KiB-scaled progress (no Qt int32 overflow on 5+ GiB downloads)

Shared

  • Unified ☁/⚙ dropdown - every model from every backend in one combo, params panel adapts (cloud → Quality / N / INSISTER / THINKING / Modération ; local → Pipeline / Steps / CFG / Seed / Décharger sur RAM)
  • Auto-save with timestamped filenames (millisecond precision, no collisions) + a .txt sidecar per image with the prompt and parameters used
  • Live preview with mouse-wheel zoom, click+drag pan, double-click to reset
  • Multi-image thumbnail strip for batches - click a thumb to swap the main view
  • Task queue with snapshot of params at enqueue time, drag-reorder, auto-drain when idle
  • Visual feedback: progress bar (determinate or indeterminate), elapsed-time counter, pulsing status dot
  • Keyboard shortcuts - Ctrl+Enter (generate), Esc (cancel if running), Ctrl+, (settings)
  • Accessibility - accessible names + descriptions on every actionable widget for NVDA / JAWS / Narrator
  • Dark, flat, arty UI - single accent color (#ff4d80), sharp corners, animated pixel-art logo on the header, editorial typography
  • Network defenses: streaming response cap, HTTPS-only image fallback, UNC path rejection, redacted on-disk logs, filename sanitisation on every external trust boundary
  • Windows taskbar identity so the app appears under its own icon (not the Python interpreter)

Roadmap

Phase Scope Status
A Identity (rename to Espresso, logo, brand font) ✅ done
B0 PySide6 port + split main.py into espresso/ package, audit B0 ✅ done
B1 Backend abstraction + sd.cpp scaffold ✅ done
B2 Model & LoRA registry + Bibliothèque dialog ✅ done
B3 Unified ☁/⚙ dropdown ✅ done
B3.1 sd.cpp installer with NVIDIA detection + 2-archive auto-fetch ✅ done
B4 Multi-file model support (Flux 1, Flux 2 Klein, SD3) - live-validated on RTX 3080 8 GiB + RTX 4090 24 GiB ✅ done
B4.x VRAM-saver --offload-to-cpu toggle ✅ done
B4.y Per-variant LLM auto-resolution for Flux 2 ✅ done
B4.z VRAM-aware auto-tick of --offload-to-cpu ✅ done
B4.aa Sampler / scheduler presets (Pipeline combo) ✅ done
B4.bb Featured curated catalog ("Sélection Espresso") ✅ done
B5 Distribution polish - code signing, GitHub Releases auto-update, signed installer next

Run from source

Requires Python 3.11+ and uv.

uv sync
uv run python -m espresso

On first launch click Réglages and paste:

  • your OpenAI sk-… key (required for cloud generations)
  • your HuggingFace hf_… token (optional - required for gated repos like FLUX.2-dev; create one at https://huggingface.co/settings/tokens with Read permission)

For local generation, click Bibliothèque then TÉLÉCHARGER on a Featured entry. The first launch on a multi-file model (Flux / SD3) opens a second dialog to fetch the auxiliary files (T5XXL / CLIP-L / VAE / LLM encoder), which are reused across runs.

If sd.cpp's binary isn't installed yet, picking a ⚙ local model and clicking GÉNÉRER opens the install dialog - auto-detects NVIDIA, picks the CUDA variant, fetches both archives. AMD / Intel users can pick Vulkan manually.

Diagnostic

For verbose logs (recommended when reporting a bug):

set ESPRESSO_DEBUG_VERBOSE=1
uv run python -m espresso

Logs land in %APPDATA%\Espresso\app.log (rolling, redacted) - share that file when reporting an issue.

Tests + lint

uv sync --group dev
uv run pytest
uv run ruff check

The repo carries 1200+ tests covering the registry layer, sd.cpp wrapper, format detection, multi-file resolution, VRAM auto-detect, sampler presets, the Featured catalog, the task queue, and the 3D print-prep pipeline. Ruff is clean.

Build it yourself

Prerequisites: Windows x86-64 and uv (it fetches the right Python by itself). Then:

build.bat

The script syncs the dependencies including the build group (PyInstaller), wipes any previous build/ and dist/ output, runs PyInstaller in one-dir windowed mode with the app resources bundled (Pillow data, the app icon, the animated logo), archives the result, compiles the installer, and writes a SHA-256 for each artifact. The Qt runtime is collected by PyInstaller's own PySide6 hooks, which pull in only what the three imported modules need. The installer step needs Inno Setup (winget install --id JRSoftware.InnoSetup --exact) - the build fails without it rather than shipping an incomplete artifact set.

You get:

Path What it is
dist\Espresso\Espresso.exe the app, runnable right there
dist\Espresso-Setup-<version>-win-x64.exe the installer (single file)
dist\Espresso-<version>-win-x64.zip the portable archive
dist\*.sha256 one checksum per artifact

No Python, no CUDA toolkit and no drivers are needed on the target machine.

Install notes

  • Easiest path: grab Espresso-Setup-<version>-win-x64.exe from the Releases page and run it. Per-user install (no admin rights), Start Menu shortcut, optional Desktop shortcut, clean uninstaller. Being a single file, the Setup exe is also fully scannable on VirusTotal - the multi-file zip is not (VirusTotal does not inspect archives over 3 MB). Uninstalling never touches %APPDATA%\Espresso: your downloaded models and settings survive.
  • Portable path: grab the zip, extract it fully, then run Espresso\Espresso.exe. Verify the download first with Get-FileHash .\Espresso-<version>-win-x64.zip -Algorithm SHA256 against the checksum published with the release.
  • With the zip, do not launch the exe from inside the zip viewer, and do not copy Espresso.exe out on its own: Windows then extracts or copies the exe without its _internal folder, and it dies with "Failed to load Python DLL ... python311.dll". The exe and _internal must stay side by side - to launch from the Desktop, make a shortcut (right-click → Send to → Desktop), not a copy. (The installer has none of these traps.)
  • The build is unsigned, so Windows may show a SmartScreen prompt on first run ("More info" → "Run anyway"). Code signing is on the roadmap (B5).
  • The sd.cpp runtime and model weights are never bundled - they are downloaded on demand into %APPDATA%\Espresso\ (see the paths table below), which is why the archive stays around 69 MB instead of tens of gigabytes.

Files & paths

Path Purpose
%APPDATA%\Espresso\config.json last-used parameters (plain JSON, no API key)
%APPDATA%\Espresso\models\ downloaded model files (.safetensors, .gguf)
%APPDATA%\Espresso\models\aux\ shared auxiliary files (T5XXL, CLIP-L)
%APPDATA%\Espresso\models\aux\flux1\ Flux 1 VAE
%APPDATA%\Espresso\models\aux\flux2\ Flux 2 VAE + per-variant LLM (Qwen3 / Mistral)
%APPDATA%\Espresso\models\aux\sd3\ SD3 VAE bundle
%APPDATA%\Espresso\sd.exe + DLLs bundled sd.cpp runtime
%APPDATA%\Espresso\registry.json cached CivitAI / HF browse results (24h TTL)
Windows Credential Manager: Espresso / openai_api_key OpenAI API key (DPAPI-encrypted, per-user)
Windows Credential Manager: Espresso / huggingface_token HF read token (DPAPI-encrypted, per-user)
%APPDATA%\Espresso\app.log rolling diagnostic log (sk-… redacted)
%APPDATA%\Espresso\last_error.log stack trace of the last API failure (redacted)
%APPDATA%\Espresso\crash.log stack trace of the last app-level crash (redacted)
~\Pictures\Espresso\ default output folder

The previous location %APPDATA%\OpenAI Image GUI\ is migrated automatically on first launch - your existing config and API key carry over without re-paste.

Notes on the OpenAI API

  • The API rejects response_format as an unknown parameter for gpt-image-2, despite some docs showing it. Confirmed by live test. The code does not send it. gpt-image-2 always returns base64 in data[].b64_json.
  • The API also rejects quality_mode and thinking body parameters on /v1/images/generations (claimed by third-party blogs but not real). Thinking mode is reachable only via /v1/responses with the image_generation tool.
  • gpt-image-2 does not support background: "transparent" (only auto / opaque); the UI exposes only the supported values.
  • gpt-image-2 requires organization verification in the OpenAI console. gpt-image-1 works without verification and accepts moderation: "low" too - useful as a fallback while waiting for verification.
  • ChatGPT web GPT-5 silently rephrases prompts before invoking image_generation - that's why some borderline prompts pass on web but get blocked by Espresso (which forwards verbatim by design, see THINKING_PROXY_SYSTEM). When you hit a moderation block here that ChatGPT accepted, try rewording in neutral terms or fall back to the web UI.

Notes on sd.cpp (local backend)

  • Multi-file architectures (Flux 1 / Flux 2 / SD3) need the --diffusion-model flag (NOT -m) for the DiT file, plus separate --t5xxl / --clip_l / --vae / --llm flags for the auxiliaries. The wrapper handles this automatically based on the model family detection.
  • Each Flux 2 variant uses a different LLM encoder with incompatible tensor structures. Klein 4B → Qwen3 4B (~2.4 GB), Klein 9B → Qwen3 8B (~4.7 GB), dev → Mistral Small 3.2 24B (~14 GB). The wrapper picks the right one from the DiT filename.
  • Community FP8 quants of Flux 1 dev that strip the model.diffusion_model.* tensor name prefix (Forge / A1111 convention) are NOT compatible with sd.cpp. Stick to the leejet GGUF mirrors (leejet/FLUX.1-dev-gguf, leejet/FLUX.1-schnell-gguf) - pre-pinned in the Featured catalog.
  • The CUDA binary archive does NOT include the CUDA runtime DLLs. The installer fetches them as a second archive (cudart-sd-...zip) automatically.

Tech stack

Layer Choice
Language Python 3.11+
Package manager uv
UI PySide6 (Qt 6) - LGPL, dynamically linked
HTTP httpx
Image processing Pillow
Secret storage keyring (Windows Credential Manager via DPAPI)
Subprocess builtin (sd.cpp wrapping, nvidia-smi probing)
Local inference stable-diffusion.cpp (leejet) - bundled binary, GGUF + safetensors
Model registry CivitAI public API + HuggingFace Hub API
Tests / lint pytest + ruff
Packaging PyInstaller one-dir, windowed + Inno Setup installer

Logo & brand assets

Espresso's visual identity lives in assets/: the animated pixel-art logo (header of the app and of this README) and the red coffee cup (window icon) are original creations by the project author, and are covered by the same MIT license as the code. See assets/README.md for details.

License

MIT. Use it, fork it, ship it commercially - just keep the copyright notice.

Third-party components keep their own terms. Everything the app depends on is permissively licensed (BSD-3, MIT, Apache-2.0) with one exception worth naming: Qt, via PySide6, is LGPL-3.0. It is dynamically linked and its DLLs sit as separate replaceable files in _internal/, which is what the LGPL asks for. If you redistribute a modified build, that arrangement is your obligation to preserve. stable-diffusion.cpp (MIT) is downloaded at runtime rather than bundled, and the models the catalog points to carry their own licenses - several are noncommercial, so check before selling anything you generate.