Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .gitattributes +1 -0
- .gitignore +4 -0
- README.md +51 -5
- app.py +496 -0
- hero/HERO_burgundy-gold_nanobanana.jpeg +3 -0
- requirements.txt +10 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
hero/HERO_burgundy-gold_nanobanana.jpeg filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.venv/
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
.DS_Store
|
README.md
CHANGED
|
@@ -1,13 +1,59 @@
|
|
| 1 |
---
|
| 2 |
title: All The Best
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.26.0
|
| 8 |
-
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: All The Best
|
| 3 |
+
emoji: π«
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: gray
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.26.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: apache-2.0
|
| 11 |
+
short_description: Dead Hugging Face Spaces rendered as boxes of chocolates
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# All The Best
|
| 15 |
+
|
| 16 |
+
Dead Hugging Face Spaces rendered as boxes of chocolates. Each box is generated
|
| 17 |
+
from the Space's own data β its colours, commit log, dependencies and final
|
| 18 |
+
commit date β through six sequential image-to-image passes on
|
| 19 |
+
Qwen-Image-Edit-2511.
|
| 20 |
+
|
| 21 |
+
The interest is in the text: how much printed detail survives across a chain of
|
| 22 |
+
edits, and what has to be explicitly named to stop it being overwritten.
|
| 23 |
+
|
| 24 |
+
An experiment by Max Fowler.
|
| 25 |
+
|
| 26 |
+
## How a box is derived
|
| 27 |
+
|
| 28 |
+
| Box element | Space data |
|
| 29 |
+
|---|---|
|
| 30 |
+
| Ink | `colorFrom` / `colorTo` |
|
| 31 |
+
| Best before | last commit date |
|
| 32 |
+
| Flavour guide (insert card) | commit messages, oldest first |
|
| 33 |
+
| Ingredients line | `requirements.txt` |
|
| 34 |
+
| Format | commit count |
|
| 35 |
+
| Batch | final commit hash |
|
| 36 |
+
| Barcode | repo id |
|
| 37 |
+
|
| 38 |
+
The moulded smiling face is fixed. It is the same in every box.
|
| 39 |
+
|
| 40 |
+
## Passes
|
| 41 |
+
|
| 42 |
+
Run in the order established by the prompt ladder, each naming the elements
|
| 43 |
+
that must survive:
|
| 44 |
+
|
| 45 |
+
1. repaint
|
| 46 |
+
2. best before
|
| 47 |
+
3. ingredients line
|
| 48 |
+
4. insert card
|
| 49 |
+
5. product name β gold script
|
| 50 |
+
6. chocolates
|
| 51 |
+
|
| 52 |
+
## Running
|
| 53 |
+
|
| 54 |
+
`STUB_MODEL = True` in `app.py` runs the whole app without touching the GPU β
|
| 55 |
+
live data, no generation. Flip it to `False` and uncomment the torch block in
|
| 56 |
+
`requirements.txt` to generate for real.
|
| 57 |
+
|
| 58 |
+
Boxes are cached to a Dataset repo, one per Space, and served from there
|
| 59 |
+
afterwards. Set `ARCHIVE_REPO` and a write-scoped `HF_TOKEN` in Space secrets.
|
app.py
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
ALL THE BEST β an experiment by Max Fowler
|
| 3 |
+
|
| 4 |
+
Dead Hugging Face Spaces rendered as boxes of chocolates. Each box is generated
|
| 5 |
+
from the Space's own data through six sequential image-to-image passes on
|
| 6 |
+
Qwen-Image-Edit-2511.
|
| 7 |
+
|
| 8 |
+
STUB_MODEL = True runs the whole app without touching the GPU.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import io
|
| 12 |
+
import json
|
| 13 |
+
import os
|
| 14 |
+
import time
|
| 15 |
+
import datetime as dt
|
| 16 |
+
|
| 17 |
+
import gradio as gr
|
| 18 |
+
from PIL import Image
|
| 19 |
+
from huggingface_hub import HfApi, hf_hub_download, upload_file
|
| 20 |
+
|
| 21 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 22 |
+
# Config
|
| 23 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 24 |
+
|
| 25 |
+
STUB_MODEL = True # flip when ready to spend quota
|
| 26 |
+
MODEL_ID = "Qwen/Qwen-Image-Edit-2511"
|
| 27 |
+
LIGHTNING_ID = "lightx2v/Qwen-Image-Edit-2511-Lightning"
|
| 28 |
+
LIGHTNING_4 = "Qwen-Image-Edit-2511-Lightning-4steps-V1.0-bf16.safetensors"
|
| 29 |
+
LIGHTNING_8 = "Qwen-Image-Edit-2511-Lightning-8steps-V1.0-bf16.safetensors"
|
| 30 |
+
|
| 31 |
+
STEPS = 4 # 4 or 8 β the ladder decides
|
| 32 |
+
HERO_PATH = "hero/HERO_burgundy-gold_nanobanana.jpeg"
|
| 33 |
+
ARCHIVE_REPO = os.environ.get("ARCHIVE_REPO", "") # "you/all-the-best-archive"
|
| 34 |
+
HF_TOKEN = os.environ.get("HF_TOKEN", "")
|
| 35 |
+
ON_SPACE = bool(os.environ.get("SPACE_ID")) # sign-in only exists on a Space
|
| 36 |
+
|
| 37 |
+
api = HfApi(token=HF_TOKEN or None)
|
| 38 |
+
|
| 39 |
+
DEAD = {"PAUSED", "STOPPED", "RUNTIME_ERROR", "BUILD_ERROR",
|
| 40 |
+
"CONFIG_ERROR", "NO_APP_FILE", "SLEEPING"}
|
| 41 |
+
|
| 42 |
+
INK = {"red": "deep red", "yellow": "golden yellow", "green": "deep green",
|
| 43 |
+
"blue": "deep blue", "indigo": "indigo", "purple": "aubergine purple",
|
| 44 |
+
"pink": "dusty pink", "gray": "slate grey"}
|
| 45 |
+
|
| 46 |
+
SWATCH = {"red": "#A8342A", "yellow": "#C8952B", "green": "#2F6B3C", "blue": "#2B4A8C",
|
| 47 |
+
"indigo": "#3B357E", "purple": "#5C3566", "pink": "#B06A80", "gray": "#6E6A62"}
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def format_for(n):
|
| 51 |
+
if n <= 3:
|
| 52 |
+
return "slim sleeve"
|
| 53 |
+
if n <= 12:
|
| 54 |
+
return "standard assortment"
|
| 55 |
+
if n <= 30:
|
| 56 |
+
return "deep assortment"
|
| 57 |
+
return "catering slab"
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 61 |
+
# Data β straight from the Hub, server side
|
| 62 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 63 |
+
|
| 64 |
+
def _card(obj):
|
| 65 |
+
"""card_data may be a dict, a CardData object, or None."""
|
| 66 |
+
card = getattr(obj, "card_data", None)
|
| 67 |
+
if card is None:
|
| 68 |
+
return {}
|
| 69 |
+
if isinstance(card, dict):
|
| 70 |
+
return card
|
| 71 |
+
to_dict = getattr(card, "to_dict", None)
|
| 72 |
+
if callable(to_dict):
|
| 73 |
+
return to_dict()
|
| 74 |
+
return dict(getattr(card, "__dict__", {}) or {})
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def _stage(obj):
|
| 78 |
+
rt = getattr(obj, "runtime", None)
|
| 79 |
+
if isinstance(rt, dict):
|
| 80 |
+
return rt.get("stage")
|
| 81 |
+
return getattr(rt, "stage", None)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def list_dead(account):
|
| 85 |
+
rows = []
|
| 86 |
+
for s in api.list_spaces(author=account,
|
| 87 |
+
expand=["runtime", "cardData", "lastModified", "likes"]):
|
| 88 |
+
st = _stage(s)
|
| 89 |
+
if st not in DEAD:
|
| 90 |
+
continue
|
| 91 |
+
card = _card(s)
|
| 92 |
+
rows.append({
|
| 93 |
+
"id": s.id,
|
| 94 |
+
"name": s.id.split("/")[1],
|
| 95 |
+
"stage": st,
|
| 96 |
+
"moved": str(getattr(s, "last_modified", ""))[:10],
|
| 97 |
+
"likes": getattr(s, "likes", 0) or 0,
|
| 98 |
+
"emoji": card.get("emoji") or "",
|
| 99 |
+
})
|
| 100 |
+
rows.sort(key=lambda r: r["moved"], reverse=True)
|
| 101 |
+
return rows
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def get_grave(repo_id):
|
| 105 |
+
info = api.space_info(repo_id)
|
| 106 |
+
try:
|
| 107 |
+
commits = api.list_repo_commits(repo_id, repo_type="space")
|
| 108 |
+
except Exception:
|
| 109 |
+
commits = []
|
| 110 |
+
try:
|
| 111 |
+
reqs = open(hf_hub_download(repo_id, "requirements.txt", repo_type="space",
|
| 112 |
+
token=HF_TOKEN or None)).read()
|
| 113 |
+
except Exception:
|
| 114 |
+
reqs = ""
|
| 115 |
+
return info, commits, reqs
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def to_spec(info, commits, reqs):
|
| 119 |
+
card = _card(info)
|
| 120 |
+
|
| 121 |
+
msgs = [c.title.split("\n")[0].strip() for c in commits if getattr(c, "title", "")]
|
| 122 |
+
chron = list(reversed(msgs)) # oldest first = flavour guide
|
| 123 |
+
last = commits[0] if commits else None
|
| 124 |
+
|
| 125 |
+
ingredients = [l.split("@")[0].split(";")[0].strip()
|
| 126 |
+
for l in (reqs or "").splitlines()
|
| 127 |
+
if l.strip() and not l.strip().startswith("#")]
|
| 128 |
+
|
| 129 |
+
frm = card.get("colorFrom") or "gray"
|
| 130 |
+
to = card.get("colorTo") or "gray"
|
| 131 |
+
|
| 132 |
+
if last is not None and getattr(last, "created_at", None):
|
| 133 |
+
best_before = last.created_at.strftime("%d %b %Y").upper()
|
| 134 |
+
else:
|
| 135 |
+
best_before = str(getattr(info, "last_modified", ""))[:10]
|
| 136 |
+
|
| 137 |
+
return {
|
| 138 |
+
"repo": info.id,
|
| 139 |
+
"title": card.get("title") or info.id.split("/")[1],
|
| 140 |
+
"author": info.id.split("/")[0],
|
| 141 |
+
"emoji": card.get("emoji") or "",
|
| 142 |
+
"ink_from": frm,
|
| 143 |
+
"ink_to": to,
|
| 144 |
+
"monochrome": frm == to,
|
| 145 |
+
"pieces": len(chron),
|
| 146 |
+
"format": format_for(len(chron)),
|
| 147 |
+
"flavour_guide": chron,
|
| 148 |
+
"ingredients": ingredients,
|
| 149 |
+
"best_before": best_before,
|
| 150 |
+
"batch": (getattr(last, "commit_id", "") or "")[:7],
|
| 151 |
+
"stage": _stage(info) or "UNKNOWN",
|
| 152 |
+
"barcode": info.id,
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 157 |
+
# The six passes β ladder order, verbatim where the ladder settled them
|
| 158 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 159 |
+
|
| 160 |
+
def ink_clause(s):
|
| 161 |
+
a = INK.get(s["ink_from"], "slate grey")
|
| 162 |
+
b = INK.get(s["ink_to"], "slate grey")
|
| 163 |
+
if s["monochrome"]:
|
| 164 |
+
return f"Repaint the box in {a}, lid and base the same single colour."
|
| 165 |
+
return f"Repaint the box in {a} and {b}."
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def build_chain(s):
|
| 169 |
+
"""[(short label, prompt)] in the order the ladder proved works."""
|
| 170 |
+
date = s["best_before"]
|
| 171 |
+
ings = ", ".join(s["ingredients"][:4]).upper()
|
| 172 |
+
guide = ", ".join(f'"{m}"' for m in s["flavour_guide"][:7])
|
| 173 |
+
|
| 174 |
+
return [
|
| 175 |
+
(f'repaint β {s["ink_from"]}' + (", monochrome" if s["monochrome"] else ""),
|
| 176 |
+
f"image_1 is the box to edit. image_2 is a style reference only and must not "
|
| 177 |
+
f"appear in the output. {ink_clause(s)} Following the same layout of printed "
|
| 178 |
+
f"areas as image_2. Keep the box construction, gold ornament, camera angle and "
|
| 179 |
+
f"tray unchanged. Matte uncoated finish, soft diffuse light, no glossy "
|
| 180 |
+
f"highlights. Output only the box from image_1."),
|
| 181 |
+
|
| 182 |
+
(f"best before β {date}",
|
| 183 |
+
f'image_1 is the box. Print the text "BEST BEFORE {date}" in small dot-matrix '
|
| 184 |
+
f"characters in the lower right corner of the lid. Keep everything else unchanged."),
|
| 185 |
+
|
| 186 |
+
("ingredients line",
|
| 187 |
+
f'image_1 is the box. Keep the existing "BEST BEFORE {date}" text exactly where '
|
| 188 |
+
f'it is. Add a second line of text below it reading "INGREDIENTS: {ings}" in '
|
| 189 |
+
f"small sans-serif capitals. Keep everything else unchanged."),
|
| 190 |
+
|
| 191 |
+
(f'insert card β {min(7, len(s["flavour_guide"]))} lines',
|
| 192 |
+
f"image_1 is the box. Place a small printed card resting inside the lid, listing "
|
| 193 |
+
f"in a single column of fine type: {guide}. Keep everything else unchanged."),
|
| 194 |
+
|
| 195 |
+
("product name β gold script",
|
| 196 |
+
f'image_1 is the box. Print the product name "{s["title"]}" in the centre of the '
|
| 197 |
+
f"lid in ornate 1970s gold script lettering with a fine gold underline flourish. "
|
| 198 |
+
f'Keep the existing "BEST BEFORE" text, the ingredients line and the printed card '
|
| 199 |
+
f"exactly as they are. Keep everything else unchanged."),
|
| 200 |
+
|
| 201 |
+
("chocolates",
|
| 202 |
+
"image_1 is the box. Fill the tray cups with round milk chocolates, each with a "
|
| 203 |
+
"simple smiling face pressed into the surface as a shallow relief. Uniform, "
|
| 204 |
+
"identical, factory-moulded. Keep the existing text, the printed card and "
|
| 205 |
+
"everything else unchanged."),
|
| 206 |
+
]
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 210 |
+
# Model
|
| 211 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 212 |
+
|
| 213 |
+
PIPE = None
|
| 214 |
+
|
| 215 |
+
if not STUB_MODEL:
|
| 216 |
+
import torch
|
| 217 |
+
import spaces
|
| 218 |
+
from diffusers import QwenImageEditPlusPipeline
|
| 219 |
+
|
| 220 |
+
def _pipe():
|
| 221 |
+
global PIPE
|
| 222 |
+
if PIPE is None:
|
| 223 |
+
PIPE = QwenImageEditPlusPipeline.from_pretrained(
|
| 224 |
+
MODEL_ID, torch_dtype=torch.bfloat16).to("cuda")
|
| 225 |
+
PIPE.load_lora_weights(
|
| 226 |
+
LIGHTNING_ID, weight_name=LIGHTNING_4 if STEPS <= 4 else LIGHTNING_8)
|
| 227 |
+
PIPE.fuse_lora()
|
| 228 |
+
PIPE.set_progress_bar_config(disable=True)
|
| 229 |
+
return PIPE
|
| 230 |
+
|
| 231 |
+
@spaces.GPU(duration=25)
|
| 232 |
+
def run_pass(images, prompt):
|
| 233 |
+
return _pipe()(image=images, prompt=prompt, num_inference_steps=STEPS,
|
| 234 |
+
true_cfg_scale=1.0, negative_prompt=" ").images[0]
|
| 235 |
+
else:
|
| 236 |
+
def run_pass(images, prompt):
|
| 237 |
+
time.sleep(0.5)
|
| 238 |
+
return images[0]
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
# ββββββββββββββββββββββββββββββββοΏ½οΏ½ββββββββββββββββββββββββββββββββββββ
|
| 242 |
+
# Archive β generate once per space, hold indefinitely
|
| 243 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 244 |
+
|
| 245 |
+
def _key(repo_id):
|
| 246 |
+
return f"boxes/{repo_id.replace('/', '__')}.png"
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def from_archive(repo_id):
|
| 250 |
+
if not ARCHIVE_REPO:
|
| 251 |
+
return None
|
| 252 |
+
try:
|
| 253 |
+
return Image.open(hf_hub_download(ARCHIVE_REPO, _key(repo_id),
|
| 254 |
+
repo_type="dataset",
|
| 255 |
+
token=HF_TOKEN or None))
|
| 256 |
+
except Exception:
|
| 257 |
+
return None
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def to_archive(repo_id, image, spec):
|
| 261 |
+
if not (ARCHIVE_REPO and HF_TOKEN):
|
| 262 |
+
return
|
| 263 |
+
buf = io.BytesIO()
|
| 264 |
+
image.save(buf, format="PNG")
|
| 265 |
+
upload_file(path_or_fileobj=buf.getvalue(), path_in_repo=_key(repo_id),
|
| 266 |
+
repo_id=ARCHIVE_REPO, repo_type="dataset", token=HF_TOKEN)
|
| 267 |
+
meta = dict(spec, produced=dt.datetime.utcnow().isoformat(timespec="seconds"))
|
| 268 |
+
upload_file(path_or_fileobj=json.dumps(meta, indent=2).encode(),
|
| 269 |
+
path_in_repo=f"specs/{repo_id.replace('/', '__')}.json",
|
| 270 |
+
repo_id=ARCHIVE_REPO, repo_type="dataset", token=HF_TOKEN)
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 274 |
+
# HTML fragments
|
| 275 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 276 |
+
|
| 277 |
+
def esc(t):
|
| 278 |
+
return str(t).replace("&", "&").replace("<", "<").replace(">", ">")
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def summary(s):
|
| 282 |
+
ink = (f'<span class="sw" style="background:{SWATCH.get(s["ink_from"], "#6E6A62")}">'
|
| 283 |
+
f'</span>{esc(s["ink_from"])}')
|
| 284 |
+
if not s["monochrome"]:
|
| 285 |
+
ink += (f' <span class="sw" style="background:'
|
| 286 |
+
f'{SWATCH.get(s["ink_to"], "#6E6A62")}"></span>{esc(s["ink_to"])}')
|
| 287 |
+
return (f'<p class="sum">{ink} Β· {esc(s["format"])} Β· '
|
| 288 |
+
f'{s["pieces"]} pieces Β· best before {esc(s["best_before"])} '
|
| 289 |
+
f' Β· batch {esc(s["batch"])}</p>')
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def log_html(lines):
|
| 293 |
+
body = "".join(
|
| 294 |
+
f'<div class="l {c}"><span class="t">{esc(t)}</span>'
|
| 295 |
+
f'<span class="s">{esc(m)}</span></div>' for t, m, c in lines)
|
| 296 |
+
return f'<div class="log">{body}</div>'
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 300 |
+
# Flow
|
| 301 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 302 |
+
|
| 303 |
+
S = {}
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
def search(account):
|
| 307 |
+
account = (account or "").strip()
|
| 308 |
+
if not account:
|
| 309 |
+
return gr.update(choices=[], value=None), '<p class="note">Enter an account.</p>'
|
| 310 |
+
try:
|
| 311 |
+
rows = list_dead(account)
|
| 312 |
+
except Exception as e:
|
| 313 |
+
return gr.update(choices=[], value=None), \
|
| 314 |
+
f'<p class="note err">Could not read account. {esc(e)}</p>'
|
| 315 |
+
if not rows:
|
| 316 |
+
return gr.update(choices=[], value=None), \
|
| 317 |
+
'<p class="note">No dead spaces on this account.</p>'
|
| 318 |
+
choices = [f'{r["name"]} Β· {r["stage"]} Β· {r["moved"]}' for r in rows]
|
| 319 |
+
S["map"] = dict(zip(choices, [r["id"] for r in rows]))
|
| 320 |
+
return (gr.update(choices=choices, value=None),
|
| 321 |
+
f'<p class="note">{len(rows)} dead</p>')
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def select(label):
|
| 325 |
+
if not label:
|
| 326 |
+
return "", gr.update(interactive=False)
|
| 327 |
+
spec = to_spec(*get_grave(S["map"][label]))
|
| 328 |
+
S["spec"] = spec
|
| 329 |
+
return summary(spec), gr.update(interactive=True)
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def generate(profile: gr.OAuthProfile | None = None):
|
| 333 |
+
spec = S.get("spec")
|
| 334 |
+
if spec is None:
|
| 335 |
+
yield gr.update(), '<p class="note">Select a space first.</p>'
|
| 336 |
+
return
|
| 337 |
+
if ON_SPACE and profile is None:
|
| 338 |
+
yield gr.update(), ('<p class="note err">Sign in to generate. '
|
| 339 |
+
'Passes run against your own ZeroGPU allowance.</p>')
|
| 340 |
+
return
|
| 341 |
+
|
| 342 |
+
t0 = time.time()
|
| 343 |
+
lines = []
|
| 344 |
+
|
| 345 |
+
def stamp():
|
| 346 |
+
return f"{time.time() - t0:.1f}s"
|
| 347 |
+
|
| 348 |
+
held = from_archive(spec["repo"])
|
| 349 |
+
if held is not None:
|
| 350 |
+
lines.append((stamp(), "cached β issued from archive", "ok"))
|
| 351 |
+
yield gr.update(value=held), log_html(lines)
|
| 352 |
+
return
|
| 353 |
+
|
| 354 |
+
hero = Image.open(HERO_PATH).convert("RGB")
|
| 355 |
+
img = hero
|
| 356 |
+
lines.append((stamp(), f"hero loaded Β· {hero.width}Γ{hero.height}", "ok"))
|
| 357 |
+
yield gr.update(), log_html(lines)
|
| 358 |
+
|
| 359 |
+
for i, (label, prompt) in enumerate(build_chain(spec), 1):
|
| 360 |
+
lines.append(("β¦", f"{i} Β· {label}", "run"))
|
| 361 |
+
yield gr.update(), log_html(lines)
|
| 362 |
+
img = run_pass([img, hero], prompt)
|
| 363 |
+
lines[-1] = (stamp(), f"{i} Β· {label}", "ok")
|
| 364 |
+
yield gr.update(value=img), log_html(lines)
|
| 365 |
+
|
| 366 |
+
if ARCHIVE_REPO and HF_TOKEN:
|
| 367 |
+
try:
|
| 368 |
+
to_archive(spec["repo"], img, spec)
|
| 369 |
+
lines.append((stamp(), "archived", "ok"))
|
| 370 |
+
except Exception:
|
| 371 |
+
lines.append((stamp(), "archive write failed β not retained", "err"))
|
| 372 |
+
else:
|
| 373 |
+
lines.append((stamp(), "no archive configured β not retained", "err"))
|
| 374 |
+
yield gr.update(value=img), log_html(lines)
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 378 |
+
# Interface
|
| 379 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 380 |
+
|
| 381 |
+
HEAD = """
|
| 382 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 383 |
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet">
|
| 384 |
+
"""
|
| 385 |
+
|
| 386 |
+
CSS = """
|
| 387 |
+
:root{
|
| 388 |
+
--bg:#0D0F0E; --panel:#141816; --panel-2:#1B201D;
|
| 389 |
+
--border:#2A312C; --border-hi:#3C453E;
|
| 390 |
+
--fg:#CBD3CC; --dim:#77837A;
|
| 391 |
+
--amber:#E0A33E; --amber-dim:#8A6725; --alert:#C9553D;
|
| 392 |
+
}
|
| 393 |
+
.gradio-container{
|
| 394 |
+
background:var(--bg)!important; color:var(--fg)!important;
|
| 395 |
+
font-family:'IBM Plex Sans',system-ui,sans-serif!important;
|
| 396 |
+
max-width:1280px!important; margin:0 auto!important; font-size:14px;
|
| 397 |
+
}
|
| 398 |
+
.gradio-container .block,.gradio-container .form{
|
| 399 |
+
background:var(--panel)!important; border:1px solid var(--border)!important;
|
| 400 |
+
border-radius:8px!important; box-shadow:none!important;
|
| 401 |
+
}
|
| 402 |
+
footer{display:none!important}
|
| 403 |
+
|
| 404 |
+
#head .intro{color:var(--dim); max-width:78ch; margin:2px 0 4px;
|
| 405 |
+
line-height:1.6; font-size:13.5px}
|
| 406 |
+
|
| 407 |
+
label span,.gradio-container label{color:var(--dim)!important; font-size:11px!important;
|
| 408 |
+
letter-spacing:.06em}
|
| 409 |
+
input[type=text],textarea,select,.gradio-container input{
|
| 410 |
+
background:var(--panel-2)!important; border:1px solid var(--border-hi)!important;
|
| 411 |
+
color:var(--fg)!important; border-radius:6px!important;
|
| 412 |
+
font-family:'IBM Plex Mono',monospace!important; font-size:13px!important;
|
| 413 |
+
}
|
| 414 |
+
input[type=text]:focus{outline:2px solid var(--amber)!important}
|
| 415 |
+
button.primary{background:var(--amber)!important; color:#14100A!important;
|
| 416 |
+
border:0!important; font-weight:600!important; border-radius:6px!important}
|
| 417 |
+
button.primary:hover{background:#F0B44E!important}
|
| 418 |
+
button.secondary{background:var(--panel-2)!important; color:var(--fg)!important;
|
| 419 |
+
border:1px solid var(--border-hi)!important; border-radius:6px!important}
|
| 420 |
+
button:disabled{background:var(--panel-2)!important; color:var(--dim)!important}
|
| 421 |
+
|
| 422 |
+
table{width:100%; border-collapse:collapse;
|
| 423 |
+
font-family:'IBM Plex Mono',monospace; font-size:12.5px}
|
| 424 |
+
th{text-align:left; font-weight:500; color:var(--dim); font-size:10.5px;
|
| 425 |
+
letter-spacing:.08em; text-transform:uppercase; padding:6px 10px 6px 0;
|
| 426 |
+
border-bottom:1px solid var(--border-hi)}
|
| 427 |
+
td{padding:7px 10px 7px 0; border-bottom:1px solid var(--border); vertical-align:top}
|
| 428 |
+
tr:last-child td{border-bottom:0}
|
| 429 |
+
tbody tr:hover td{background:var(--panel-2)}
|
| 430 |
+
.num{text-align:right; padding-right:0!important; color:var(--dim)}
|
| 431 |
+
.stage{color:var(--alert)}
|
| 432 |
+
.dim{color:var(--dim)}
|
| 433 |
+
table.spec th{width:150px; text-transform:none; letter-spacing:0; font-size:12.5px;
|
| 434 |
+
border-bottom:1px solid var(--border); padding:7px 12px 7px 0}
|
| 435 |
+
.sw{display:inline-block; width:9px; height:9px; margin-right:6px;
|
| 436 |
+
border:1px solid var(--border-hi)}
|
| 437 |
+
|
| 438 |
+
.sum{font-family:'IBM Plex Mono',monospace; font-size:12px; color:var(--dim);
|
| 439 |
+
margin:2px 0 0; line-height:1.9}
|
| 440 |
+
.note{color:var(--dim); font-size:12.5px; margin:6px 0 0}
|
| 441 |
+
.note.err{color:var(--alert)}
|
| 442 |
+
|
| 443 |
+
.log{font-family:'IBM Plex Mono',monospace; font-size:12.5px; min-height:150px}
|
| 444 |
+
.log .l{padding:2.5px 0; color:var(--dim); display:flex; gap:12px}
|
| 445 |
+
.log .l .t{color:var(--border-hi); flex:0 0 52px}
|
| 446 |
+
.log .l.ok .s{color:var(--fg)}
|
| 447 |
+
.log .l.run .s{color:var(--amber)}
|
| 448 |
+
.log .l.err .s{color:var(--alert)}
|
| 449 |
+
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
|
| 450 |
+
"""
|
| 451 |
+
|
| 452 |
+
with gr.Blocks(title="All The Best") as demo:
|
| 453 |
+
|
| 454 |
+
gr.HTML("""
|
| 455 |
+
<div id="head">
|
| 456 |
+
<p class="intro">Dead Hugging Face Spaces rendered as boxes of chocolates.
|
| 457 |
+
Each box is generated from the Space's own data β its colours, commit log,
|
| 458 |
+
dependencies and final commit date β through six sequential image-to-image
|
| 459 |
+
passes on Qwen-Image-Edit-2511. The interest is in the text: how much printed
|
| 460 |
+
detail survives across a chain of edits, and what has to be explicitly named
|
| 461 |
+
to stop it being overwritten.</p>
|
| 462 |
+
</div>""")
|
| 463 |
+
|
| 464 |
+
with gr.Row(equal_height=False):
|
| 465 |
+
|
| 466 |
+
# ββ left: pick a space βοΏ½οΏ½ββββββββββββββββββββββββββββββββββ
|
| 467 |
+
with gr.Column(scale=4):
|
| 468 |
+
with gr.Row():
|
| 469 |
+
account = gr.Textbox(label="Account", value="jbilcke-hf", scale=3)
|
| 470 |
+
find = gr.Button("Search", variant="primary", scale=1)
|
| 471 |
+
|
| 472 |
+
space = gr.Dropdown(label="Dead space", choices=[], interactive=True)
|
| 473 |
+
info = gr.HTML()
|
| 474 |
+
|
| 475 |
+
with gr.Row():
|
| 476 |
+
if ON_SPACE:
|
| 477 |
+
login = gr.LoginButton(scale=1)
|
| 478 |
+
go = gr.Button("Generate box Β· 6 passes", variant="primary",
|
| 479 |
+
interactive=False, scale=2)
|
| 480 |
+
|
| 481 |
+
# ββ right: the box ββββββββββββββββββββββββββββββββββββββββ
|
| 482 |
+
with gr.Column(scale=6):
|
| 483 |
+
box = gr.Image(label="Box", height=430, show_label=False)
|
| 484 |
+
log_out = gr.HTML(
|
| 485 |
+
f'<div class="log"><div class="l"><span class="t">β</span>'
|
| 486 |
+
f'<span class="s">qwen-image-edit-2511 Β· {STEPS}-step Β· '
|
| 487 |
+
f'{"stub" if STUB_MODEL else "live"}</span></div></div>')
|
| 488 |
+
|
| 489 |
+
find.click(search, account, [space, info])
|
| 490 |
+
account.submit(search, account, [space, info])
|
| 491 |
+
space.change(select, space, [info, go])
|
| 492 |
+
go.click(generate, None, [box, log_out])
|
| 493 |
+
|
| 494 |
+
if __name__ == "__main__":
|
| 495 |
+
# Gradio 6 takes theme/css/head at launch, not on Blocks.
|
| 496 |
+
demo.launch(theme=gr.themes.Base(), css=CSS, head=HEAD)
|
hero/HERO_burgundy-gold_nanobanana.jpeg
ADDED
|
Git LFS Details
|
requirements.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=6.0
|
| 2 |
+
huggingface_hub>=0.28
|
| 3 |
+
Pillow
|
| 4 |
+
# --- uncomment when STUB_MODEL = False ---
|
| 5 |
+
# torch
|
| 6 |
+
# diffusers @ git+https://github.com/huggingface/diffusers
|
| 7 |
+
# transformers
|
| 8 |
+
# accelerate
|
| 9 |
+
# peft
|
| 10 |
+
# spaces
|