- HTML 61%
- Python 37.9%
- JavaScript 0.7%
- Batchfile 0.4%
- Formulaire admin presets = miroir exact : prompt, négatif, refImage, largeurs (y compris 16k), hauteur, étapes, CFG, clip skip, seed, batch, denoise, HR (scale/steps/upscaler), sampler, scheduler, LoRAs (coche, poids, intervalle) + boutons Copier depuis la page / Appliquer à la page - Bug racine « presets inutilisables » : les LoRAs d'un preset n'étaient pas re-cochées au chargement car le nom n'était pas sérialisé (readCurrentParams et readAdminPresetParams transportent désormais l.name) - Les selects apsSampler/apsScheduler/apsHrUpscaler sont clones de la page - Selftest : section 22 (clonage des selects, lecture/écriture du formulaire, round-trip LoRAs par nom) — SELFTEST PASS (22 sections) |
||
|---|---|---|
| vendor | ||
| .gitignore | ||
| apple-touch-icon.png | ||
| auth.py | ||
| cacert.pem | ||
| config.example.json | ||
| favicon.png | ||
| icon-192.png | ||
| icon-512.png | ||
| index.html | ||
| manifest.json | ||
| README.md | ||
| run_selftest.py | ||
| server.py | ||
| start-ui.bat | ||
| sw.js | ||
| test_auth.py | ||
| test_lora_access.py | ||
Image Studio UI
Interface web de génération d'images pour llama-swap (sd-server). Serveur Python léger (stdlib uniquement, aucune dépendance à installer), sert l'UI et fait proxy vers le serveur SD en amont.
Démarrage rapide
start-ui.bat
Le batch lance python server.py en fenêtre minimisée, vérifie le port
8788 et affiche l'URL. Double-cliquer suffit.
Manuellement :
cd /chemin/vers/image-studio
python server.py
Puis ouvrir https://localhost:8788 (certificat auto-généré auto-signé, le navigateur affichera un avertissement normal).
L'UI écoute sur 0.0.0.0:8788 (HTTPS) + 8789 (redirection HTTP → HTTPS).
Configuration
Fichier config.json à côté de server.py :
{
"port": 8788,
"host": "0.0.0.0",
"upstream": "http://127.0.0.1:9251",
"trusted_reverse_proxy": false,
"https": true,
"auth": { ... }
}
| Clé | Rôle |
|---|---|
port |
Port HTTPS de l'UI (8788). Le port+1 (8789) fait la redirection HTTP→HTTPS. |
host |
Interface d'écoute. 0.0.0.0 = accessible sur le réseau. |
upstream |
URL du serveur SD en amont (llama-swap / sd-server), ex. http://127.0.0.1:9251. L'UI ne l'expose pas : le frontend route uniquement par /api/proxy/<chemin> (routage vers l'upstream forcé, aucune option directe). |
https |
true = HTTPS avec le certificat auto-généré du dossier .pwa/ (ou tls_cert/tls_key si fournis). |
trusted_reverse_proxy |
false, ou une liste d'adresses (IP exactes ou CIDR) des reverse proxies autorisés. Quand la liste est non vide : seule l'IP source de la liste est acceptée (l'IP réelle du client est lue dans X-Forwarded-For), toute autre connexion reçoit un 403. Permet d'exposer l'UI via un proxy extérieur (Caddy/Nginx/tunnel) sans l'ouvrir à tous. |
Priorité des options : flag CLI > variable d'env > config.json.
--upstream <url>ouSD_UI_UPSTREAM--trusted-proxy="IP1,IP2,CIDR"ouSD_UI_TRUSTED_PROXYpython server.py <port>pour changer le port.
Authentification
Section auth de config.json. Désactivée par défaut (enabled: false) :
l'UI est libre d'accès, aucun compte requis.
Comptes locaux (SQLite)
Avec auth.enabled: true, les mots de passe sont stockés dans users.db
(hachage PBKDF2-HMAC-SHA256). Gestion en ligne de commande — fonctionne
même si enabled est false dans la config :
python server.py users add <nom> <mot_de_passe>
python server.py users passwd <nom> <nouveau_mdp>
python server.py users remove <nom>
python server.py users list
session_ttl: durée de validité d'une session en secondes (défaut 3600).cookie_name: nom du cookie de session (défautui_session).
SSO OpenID Connect
Actif seulement si auth.enabled: true et issuer et client_id
sont renseignés. À ce moment, un bouton « Se connecter via SSO » apparaît
sur la page de login.
"oidc": {
"issuer": "https://auth.example.com/realms/MyRealm",
"client_id": "my-image-studio-client",
"client_secret": "<secret-fourni-par-votre-fournisseur>",
"scope": "openid profile email",
"base_url": "https://studio.example.com"
}
| Champ | Rôle |
|---|---|
issuer |
URL du fournisseur OIDC. Le serveur récupère tout automatiquement via issuer/.well-known/openid-configuration (endpoints autorisation/tokens/JWKS/userinfo). |
client_id |
Identifiant de l'app. Doit correspondre à l'aud du id_token (sinon rejet). Obligatoire. |
client_secret |
Clé secrète. Optionnel ici : envoyé en Basic auth à l'échange du code si présent. |
scope |
Claims demandés. openid obligatoire ; profile/email donnent nom et e-mail. |
base_url |
URL publique du serveur (vue depuis l'internet). Détermine l'URL de callback. |
URL de callback (à inscrire comme Redirect URI autorisée dans le fournisseur) :
https://studio.example.com/auth/oidc/callback
Elle est calculée comme base_url + "/auth/oidc/callback".
- Via un reverse proxy : renseigner
base_urlavec l'URL publique, sinon le callback serait calculé avec l'IP interne du proxy et rejeté. - Accès direct sans proxy : laisser
base_urlvide (reconstruit depuis l'en-têteHost, schémahttps).
Le flux : PKCE (S256) + state anti-CSRF + validation nonce,
vérification du JWS du id_token (RS256/PS256/ES256 via le jwks_uri),
contrôles iss/aud/exp. Un compte local est créé ou lié par son
sub au premier login.
Note TLS (certificats expirés) : sur certains builds Windows de
Python, le store de confiance système contient une version obsolète de la
racine Let's Encrypt, ce qui provoque certificate has expired sur les
appels sortants vers le fournisseur OIDC, alors que le certificat du
fournisseur est valide. auth.py contourne en chargeant explicitement
les racines ISRG X1/X2 à jour depuis le fichier cacert.pem (à côté
de server.py). Si ce fichier manque, le comportement par défaut est
utilisé. Pour le régénérer :
curl -fsS -o _bundle.pem https://curl.se/ca/cacert.pem
REM extraire les blocs « ISRG Root X1 » et « ISRG Root X2 » du bundle
REM dans cacert.pem (2 certificats au total)
Sessions de génération (jobs)
La génération survit à la fermeture de la page :
POST /api/generatecrée un job exécuté en thread, persisté dansjobs/<id>.json(rétention 48 h).- Au retour, l'UI relit
GET /api/jobs/<id>et reprend là où elle en était.
Batch 1 par 1 + progression réelle
- Chaque image du batch est générée par une requête (
batch_size: 1) et affichée immédiatement dans la grille, au fil de l'eau (pas de téléchargement de tout le lot à la fin). - Le serveur persiste
images_done,seeds_doneetprogressaprès chaque image. - La barre de progression reflète la vraie progression (images faites / images demandées) via le job côté serveur.
- Les seeds sont extraits de
info(llama-swap renvoieinfocomme chaîne JSON, le serveur la décode).
Endpoints principaux
| Méthode | Chemin | Rôle |
|---|---|---|
GET |
/ |
L'UI (redirigée vers /login si auth activée et non connecté). |
GET |
/api/proxy-mode |
Indique le mode du backend (le frontend route systématiquement par /api/proxy — aucune option frontend). |
GET |
/api/proxy/<chemin> |
Proxy vers l'upstream (query string relayée). |
POST |
/api/generate |
Démarre un job de génération. |
GET |
/api/jobs/<id> |
État d'un job + images/seeds faits. |
GET |
/api/auth/status |
{"auth": bool, "oidc": bool, "user": …}. |
POST |
/api/auth/login |
Connexion locale (username/password). |
POST |
/api/auth/logout |
Déconnexion. |
GET |
/auth/oidc |
Redirection vers le fournisseur (si OIDC actif). |
GET |
/auth/oidc/callback |
Retour du fournisseur (échange du code). |
Arborescence
image-studio/
├── server.py # Serveur (stdlib Python, proxy, jobs, auth)
├── auth.py # Module d'authentification (SQLite + OIDC)
├── config.json # Configuration (non committée — voir config.example.json)
├── index.html # L'UI
├── vendor/ # Bibliothèques front (Bootstrap, Font Awesome…)
├── .pwa/ # Certificats auto-générés (cert.pem, key.pem)
├── cacert.pem # Racines ISRG X1/X2 (TLS des appels OIDC)
├── users.db # Base SQLite des comptes (créée à la 1re use)
├── jobs/ # Jobs de génération persistés
├── start-ui.bat # Lancement facile
└── README.md
Prérequis
- Python 3 (stdlib seule — aucun
pip install). - llama-swap / sd-server démarré sur le port défini dans
upstream(défauthttp://127.0.0.1:9251).