aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x[-rw-r--r--]imggen144
1 files changed, 121 insertions, 23 deletions
diff --git a/imggen b/imggen
index 39f0bb6..749f64a 100644..100755
--- a/imggen
+++ b/imggen
@@ -1,38 +1,136 @@
#!/bin/bash
-# Copyright (C) 2026 Danilo
+# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
# Licensed under the GNU General Public License version 2.
#
# Host-side wrapper for local SDXL image generation on Intel Arc.
-# Handles GPU passthrough, the /out bind-mount (so output lands on the host,
-# never trapped inside the container), and a persistent model cache (so the
-# ~7.5GB of weights download once, not every run).
+# Subcommands manage a persistent daemon (model resident in VRAM); a bare
+# prompt talks to the daemon if up, else falls back to a one-shot run.
#
# Usage:
-# imggen "a red bicycle against a stone wall"
-# imggen "a foggy harbour at dawn" --steps 30 --seed 42
-# imggen "portrait of a fox" -o fox.png -n "blurry, low quality"
-#
-# All arguments are passed straight through to generate.py.
+# imggen start [-m realvis] # spin up daemon, load model, hold VRAM
+# imggen stop # kill daemon, free VRAM
+# imggen status # is it up? which model?
+# imggen list # show model keys
+# imggen "a red bicycle" [-m realvis] [--steps 30] [--seed 42] [-o out.png] [-n "blurry"]
set -euo pipefail
-IMAGE="imggen:local"
+IMGGEN_DIR="${IMGGEN_DIR:-$HOME/Programming/GIT/imggen}"
+cd "$IMGGEN_DIR"
+
+# Load .env for OUT dir + port on the host side.
+if [ -f .env ]; then set -a; . ./.env; set +a; fi
-# Where generated images land on the host. Override with IMGGEN_OUT.
OUT_DIR="${IMGGEN_OUT:-$HOME/Pictures/AI-gen}"
+PORT="${IMGGEN_PORT:-8765}"
+BASE="http://127.0.0.1:${PORT}"
+mkdir -p "$OUT_DIR"
-# Persistent model cache on the host. Override with IMGGEN_CACHE.
-CACHE_DIR="${IMGGEN_CACHE:-/data/LLM-models/imggen-models}"
+usage() {
+ echo "usage:" >&2
+ echo " imggen start [-m KEY] | stop | status | list" >&2
+ echo " imggen \"<prompt>\" [-m KEY] [--steps N] [--guidance G] [--seed S] [-n NEG] [-o name.png]" >&2
+ exit 1
+}
-mkdir -p "$OUT_DIR" "$CACHE_DIR"
+daemon_up() { curl -sf "${BASE}/status" >/dev/null 2>&1; }
-if [ "$#" -eq 0 ]; then
- echo "usage: imggen \"<prompt>\" [--steps N] [--guidance G] [--seed S] [-n \"<negative>\"] [-o name.png]" >&2
- exit 1
-fi
+case "${1:-}" in
+ start)
+ shift
+ model="${IMGGEN_MODEL:-sdxl}"
+ if [ "${1:-}" = "-m" ] || [ "${1:-}" = "--model" ]; then model="$2"; fi
+ IMGGEN_MODEL="$model" docker compose up -d
+ echo "daemon starting with model '$model' (VRAM ~9GB held until 'imggen stop')"
+ exit 0
+ ;;
+ stop)
+ docker compose down
+ echo "daemon stopped, VRAM freed"
+ exit 0
+ ;;
+ status)
+ if daemon_up; then curl -s "${BASE}/status"; echo; else echo "daemon down"; fi
+ exit 0
+ ;;
+ list)
+ echo "sdxl general (default)"
+ echo "realvis photoreal, permissive"
+ echo "pony unrestricted, versatile"
+ exit 0
+ ;;
+ "" ) usage ;;
+ -h|--help) usage ;;
+esac
-exec docker run --rm -it \
- --device /dev/dri \
- -v "$OUT_DIR:/out" \
- -v "$CACHE_DIR:/models" \
- "$IMAGE" "$@"
+# --- prompt path ---
+PROMPT="$1"; shift
+MODEL=""; NEG=""; STEPS=""; GUID=""; SEED=""; OUT=""; W=""; H=""
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ -m|--model) MODEL="$2"; shift 2 ;;
+ -n|--negative) NEG="$2"; shift 2 ;;
+ -s|--steps) STEPS="$2"; shift 2 ;;
+ -g|--guidance) GUID="$2"; shift 2 ;;
+ --seed) SEED="$2"; shift 2 ;;
+ -W|--width) W="$2"; shift 2 ;;
+ -H|--height) H="$2"; shift 2 ;;
+ -o|--out) OUT="$2"; shift 2 ;;
+ *) echo "unknown arg: $1" >&2; usage ;;
+ esac
+done
+
+if daemon_up; then
+ # Interactive menu only when no -m and a TTY is present.
+ if [ -z "$MODEL" ] && [ -t 0 ] && [ -t 1 ]; then
+ echo "which model? 1) sdxl 2) realvis 3) pony" >&2
+ read -rp "choice [1]: " choice || choice=1
+ case "$choice" in 2) MODEL=realvis ;; 3) MODEL=pony ;; *) MODEL=sdxl ;; esac
+ fi
+ MODEL="${MODEL:-sdxl}"
+ OUT_NAME="${OUT:-${MODEL}-$(date +%Y%m%d-%H%M%S).png}"
+ OUT_PATH="${OUT_DIR}/${OUT_NAME}"
+
+ # Swap resident model if needed.
+ current="$(curl -s "${BASE}/status" | sed -n 's/.*"model": *"\([^"]*\)".*/\1/p')"
+ if [ "$current" != "$MODEL" ]; then
+ echo "swapping daemon model $current -> $MODEL ..." >&2
+ curl -sf -X POST "${BASE}/model" -H 'Content-Type: application/json' \
+ -d "{\"name\":\"$MODEL\"}" >/dev/null
+ fi
+
+ # Build JSON request. jq not assumed; hand-build (values are ours/simple).
+ req="{\"prompt\":$(printf '%s' "$PROMPT" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')"
+ [ -n "$NEG" ] && req="$req,\"negative\":$(printf '%s' "$NEG" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')"
+ [ -n "$STEPS" ] && req="$req,\"steps\":$STEPS"
+ [ -n "$GUID" ] && req="$req,\"guidance\":$GUID"
+ [ -n "$SEED" ] && req="$req,\"seed\":$SEED"
+ [ -n "$W" ] && req="$req,\"width\":$W"
+ [ -n "$H" ] && req="$req,\"height\":$H"
+ req="$req}"
+
+ if curl -sf -X POST "${BASE}/generate" -H 'Content-Type: application/json' \
+ -d "$req" -o "$OUT_PATH"; then
+ echo "Saved ${OUT_PATH}"
+ else
+ echo "generate failed (see daemon logs: docker compose logs)" >&2
+ rm -f "$OUT_PATH"
+ exit 1
+ fi
+else
+ # One-shot fallback: non-interactive, model defaults to sdxl or -m KEY.
+ echo "daemon down, one-shot run (slower: model loads then exits)" >&2
+ fb_model="${MODEL:-sdxl}"
+ OUT_NAME="${OUT:-${fb_model}-$(date +%Y%m%d-%H%M%S).png}"
+ OUT_PATH="${OUT_DIR}/${OUT_NAME}"
+ set -- "$PROMPT"
+ [ -n "$MODEL" ] && set -- "$@" -m "$MODEL"
+ [ -n "$NEG" ] && set -- "$@" -n "$NEG"
+ [ -n "$STEPS" ] && set -- "$@" -s "$STEPS"
+ [ -n "$GUID" ] && set -- "$@" -g "$GUID"
+ [ -n "$SEED" ] && set -- "$@" --seed "$SEED"
+ [ -n "$W" ] && set -- "$@" -W "$W"
+ [ -n "$H" ] && set -- "$@" -H "$H"
+ set -- "$@" -o "$OUT_NAME"
+ docker compose run --rm imggen generate.py "$@"
+fi