FastAPI service that generates SVG icons from text prompts using Stable Diffusion 1.5 and converts raster output to clean, optimized SVG.
Model loads lazily — startup is instant. The SD pipeline loads on the first generation request (or POST /wake) and unloads automatically after ICONGEN_IDLE_UNLOAD_SECONDS (default 300 s) of inactivity.
- Linux (Ubuntu 22.04+ recommended)
- NVIDIA GPU — RTX 3050 8 GB VRAM or better
- CUDA 11.8+
- Python 3.10+
- Node.js 18+
# 1. System packages (Ubuntu/Debian)
sudo apt-get install -y libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libffi-dev
# 2. Python deps
pip install -r requirements.txt
# 3. Node deps (SVGO)
npm install
# 4. Prefetch model — one time, ~4 GB, no Hugging Face login required
python scripts/download_model.py
# 5. Create output directories
mkdir -p outputs/svg outputs/png outputs/metadata
# 6. Start the service (returns immediately — model loads on first request)
uvicorn app.main:app --host 127.0.0.1 --port 8000Base SD 1.5 is weak at clean icons (it tends to add photo/wood backgrounds). You can load an SD 1.5 style LoRA to get cleaner, more icon-like art. Transparency is still handled in post-processing — the LoRA only improves the source art.
# 1. Prefetch the LoRA (~27 MB, public, no login) into the HF cache
python scripts/download_lora.pyThe script prints the env block to enable it. A ready-made svg-generator/.env
(gitignored) already contains:
ICONGEN_LORA_REPO=artificialguybr/icons-redmond-1-5v-app-icons-lora-for-sd-liberteredmond-sd-1-5
ICONGEN_LORA_WEIGHT_NAME=IconsRedmond15V-Icons.safetensors
ICONGEN_LORA_SCALE=0.8
ICONGEN_LORA_TRIGGER=iconsRestart the service; the LoRA loads once at startup and icons is prepended to every
prompt automatically. To disable, set ICONGEN_LORA_REPO= (empty) or delete .env.
Loading is non-fatal: if the LoRA fails to load, the service logs a warning and runs
without it. Any SD 1.5 LoRA works — just change the repo/filename/trigger.
| Variable | Description |
|---|---|
ICONGEN_LORA_REPO |
HF repo id of the LoRA. Empty = disabled. |
ICONGEN_LORA_WEIGHT_NAME |
.safetensors filename inside the repo. |
ICONGEN_LORA_SCALE |
LoRA strength (0.0–1.0, default 0.8). |
ICONGEN_LORA_TRIGGER |
Activation keyword prepended to every prompt. |
Check service status, GPU availability, and model state.
curl http://127.0.0.1:8000/health{
"ok": true,
"model_loaded": false,
"idle_seconds": null,
"active_requests": 0,
"gpu_available": true,
"gpu_name": "NVIDIA GeForce RTX 3050"
}model_loaded is false on a fresh start — it becomes true after the first generation or POST /wake. idle_seconds is null until the model has been loaded at least once.
List all available style profiles.
curl http://127.0.0.1:8000/profiles{
"profiles": {
"flat-icon": { "name": "flat-icon", ... },
"filled-icon": { "name": "filled-icon", ... },
"outline-icon": { "name": "outline-icon", ... }
}
}Explicitly load the model now. Use this to pre-warm the service so the first generation call doesn't incur cold-start latency. Returns immediately if the model is already loaded.
curl -X POST http://127.0.0.1:8000/wake{"ok": true, "model_loaded": true, "load_seconds": 47.3}load_seconds is 0.0 if the model was already loaded.
Immediately unload the model and free VRAM. The next generation request will trigger a fresh cold load.
curl -X POST http://127.0.0.1:8000/sleep{"ok": true, "was_loaded": true}Generate a single SVG icon. Automatically wakes the model if it is asleep — expect cold-start latency on the first call after idle hibernation.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
prompt |
string | yes | Text description of the icon |
name |
string | yes | Filename base for saved assets (no path separators) |
profile |
string | yes | flat-icon | filled-icon | outline-icon |
color_palette |
string[] | yes | 1–4 hex colors (#rrggbb). Source of truth for SVG fill colors |
export_sizes |
int[] | no | PNG sizes to export (all ≥ 16). Default: [512, 128, 64, 32, 16] |
export_path |
string | no | Relative path under the sandbox root. Default: "" (outputs root) |
seed |
int | no | Seed for reproducibility. Omit for random |
Example — flat blue home icon:
curl -X POST http://127.0.0.1:8000/generate-svg \
-H "Content-Type: application/json" \
-d '{
"prompt": "home",
"name": "home-icon",
"profile": "flat-icon",
"color_palette": ["#2563eb"],
"export_sizes": [512, 128, 64, 32, 16],
"export_path": "my-project/icons"
}'Example — two-color outline settings gear:
curl -X POST http://127.0.0.1:8000/generate-svg \
-H "Content-Type: application/json" \
-d '{
"prompt": "settings gear",
"name": "gear",
"profile": "outline-icon",
"color_palette": ["#1d4ed8", "#93c5fd"],
"export_sizes": [512, 64],
"seed": 42
}'Response:
{
"ok": true,
"name": "home-icon",
"svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" ...>...</svg>",
"svg_path": "/absolute/path/to/outputs/my-project/icons/home-icon.svg",
"png_paths": [
"/absolute/path/to/outputs/my-project/icons/home-icon-512.png",
"..."
],
"metadata": {
"asset_id": "...",
"name": "home-icon",
"seed": 3748291,
"model": "stable-diffusion-v1-5/stable-diffusion-v1-5",
"profile": "flat-icon",
"color_palette": ["#2563eb"],
...
}
}Generate multiple icons in one request. All items share profile, color_palette, export_sizes, and export_path. Auto-wakes model if asleep.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
profile |
string | yes | Style profile for all items |
color_palette |
string[] | yes | 1–4 hex colors applied to every icon |
items |
object[] | yes | Array of {name, prompt, seed?} |
export_sizes |
int[] | no | Sizes for all icons. Default: [512, 128, 64, 32, 16] |
export_path |
string | no | Shared output path |
Example:
curl -X POST http://127.0.0.1:8000/batch-generate-svg \
-H "Content-Type: application/json" \
-d '{
"profile": "flat-icon",
"color_palette": ["#2563eb"],
"export_sizes": [512, 64, 32],
"export_path": "my-project/icons",
"items": [
{"name": "home", "prompt": "home"},
{"name": "star", "prompt": "star"},
{"name": "gear", "prompt": "settings gear"},
{"name": "envelope", "prompt": "envelope"}
]
}'Response:
{
"ok": true,
"results": [ { "ok": true, "name": "home", "svg": "...", ... }, ... ],
"errors": []
}python -m pytest tests/ -vThe service must be running first.
python scripts/test_generate.pyCreate /etc/systemd/system/icongen.service:
[Unit]
Description=IconGen SVG Generator Service
After=network.target
[Service]
Type=simple
User=youruser
WorkingDirectory=/absolute/path/to/svg-generator
ExecStart=/absolute/path/to/.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000
Restart=on-failure
RestartSec=5
# Reuse the prefetched model cache — prevents re-download on every boot.
# Set this to wherever `python scripts/download_model.py` stored the weights
# (default: ~/.cache/huggingface/hub).
Environment=HF_HOME=/home/youruser/.cache/huggingface
# Optional overrides:
# Environment=ICONGEN_IDLE_UNLOAD_SECONDS=600
# Environment=ICONGEN_IDLE_UNLOAD_SECONDS=0 ← disable auto-unload
# Environment=ICONGEN_ALLOWED_OUTPUT_ROOT=/var/lib/icongen/outputs
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl daemon-reload
sudo systemctl enable icongen
sudo systemctl start icongen
sudo journalctl -u icongen -f # tail logsNote: Startup is near-instant — the model loads on first generation request (or POST /wake), not at boot. The first generation after a cold start or idle hibernation will take 30–120 s for the SD pipeline to load into VRAM.
All variables use the ICONGEN_ prefix.
| Variable | Default | Description |
|---|---|---|
ICONGEN_SD_MODEL_ID |
stable-diffusion-v1-5/stable-diffusion-v1-5 |
Hugging Face model ID |
ICONGEN_SD_LOCAL_PATH |
(empty) | Path to a local .safetensors file; skips HF download when set |
ICONGEN_ALLOWED_OUTPUT_ROOT |
./outputs |
Sandbox root — all file writes are restricted to this directory |
ICONGEN_PROFILES_DIR |
./profiles |
Directory containing style profile JSON files |
ICONGEN_MIN_EXPORT_SIZE |
16 |
Minimum allowed PNG export size in pixels |
ICONGEN_IDLE_UNLOAD_SECONDS |
300 |
Seconds of inactivity before auto-unloading from VRAM. Set to 0 to keep model resident once loaded. |
color_paletteis the source of truth for color. Stable Diffusion generates monochrome shapes; colors are applied to SVG paths post-vectorization via palette assignment. Mentioning colors in the prompt may guide composition but does not control the actual SVG fill colors.export_pathmust be relative. Absolute paths and..traversal are rejected by both the MCP layer and the API service.seedis returned in metadata so any icon can be reproduced exactly by passing the same seed, prompt, and palette.- Cold start latency: The first generation after startup or after idle hibernation takes 30–120 s (SD pipeline loading into VRAM).
POST /wakelets you absorb this latency before users arrive.