Spaces:
Running on Zero
Running on Zero
Use free local Diffusers image model
Browse files
app.py
CHANGED
|
@@ -103,9 +103,15 @@ class StylePlan:
|
|
| 103 |
|
| 104 |
|
| 105 |
HF_TOKEN = os.environ.get("HF_TOKEN", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
HF_IMAGE_MODEL = os.environ.get("HF_IMAGE_MODEL", "black-forest-labs/FLUX.1-schnell")
|
| 107 |
HF_PROMPT_MODEL = os.environ.get("HF_PROMPT_MODEL", "Qwen/Qwen2.5-Coder-7B-Instruct:fastest")
|
| 108 |
HF_PROMPT_ENDPOINT = os.environ.get("HF_PROMPT_ENDPOINT", "https://router.huggingface.co/v1/chat/completions")
|
|
|
|
|
|
|
| 109 |
|
| 110 |
|
| 111 |
def slugify(value: str) -> str:
|
|
@@ -263,6 +269,8 @@ def short_error(exc: Exception) -> str:
|
|
| 263 |
|
| 264 |
|
| 265 |
def hf_prompt_json(html_code: str, role_lines: list[tuple[str, str]], style_hint: str) -> tuple[dict[str, str] | None, str | None]:
|
|
|
|
|
|
|
| 266 |
if not HF_TOKEN:
|
| 267 |
return None, "HF_TOKEN is not visible to the Space runtime"
|
| 268 |
|
|
@@ -978,7 +986,78 @@ def placeholder_png_bytes(role: str, width: int, height: int) -> bytes:
|
|
| 978 |
return out.getvalue()
|
| 979 |
|
| 980 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 981 |
def hf_image_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
|
|
|
|
|
|
|
| 982 |
if not HF_TOKEN:
|
| 983 |
return None, "HF_TOKEN is not visible to the Space runtime"
|
| 984 |
if InferenceClient is None:
|
|
@@ -1003,15 +1082,20 @@ def hf_image_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None
|
|
| 1003 |
|
| 1004 |
|
| 1005 |
def generate_asset(spec: AssetSpec, index: int, run_id: int) -> tuple[str, str, str | None, str]:
|
| 1006 |
-
png_content,
|
| 1007 |
-
source =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1008 |
if png_content is None:
|
| 1009 |
png_content = local_asset_png(spec, index, run_id)
|
| 1010 |
-
source = "local
|
| 1011 |
return (
|
| 1012 |
png_bytes_to_data_uri(png_content),
|
| 1013 |
write_gallery_image(png_content, spec.role),
|
| 1014 |
-
|
| 1015 |
source,
|
| 1016 |
)
|
| 1017 |
|
|
@@ -1191,7 +1275,7 @@ def generate_images_and_game(html_code: str, roles: str, style_hint: str):
|
|
| 1191 |
gallery.append((gallery_path, f"{spec.role} -> {spec.filename}"))
|
| 1192 |
model_rows.append((spec.role, prompt_model, image_model))
|
| 1193 |
if error:
|
| 1194 |
-
errors.append(f"{spec.role}: image model failed ({error}); used local
|
| 1195 |
|
| 1196 |
rewritten = embed_assets(html_code, assets, specs)
|
| 1197 |
status = (
|
|
|
|
| 103 |
|
| 104 |
|
| 105 |
HF_TOKEN = os.environ.get("HF_TOKEN", "")
|
| 106 |
+
FREE_IMAGE_MODEL = os.environ.get("FREE_IMAGE_MODEL", "segmind/tiny-sd")
|
| 107 |
+
FREE_IMAGE_STEPS = int(os.environ.get("FREE_IMAGE_STEPS", "5"))
|
| 108 |
+
USE_HF_PROMPT_PROVIDER = os.environ.get("USE_HF_PROMPT_PROVIDER", "0") == "1"
|
| 109 |
+
USE_HF_IMAGE_PROVIDER = os.environ.get("USE_HF_IMAGE_PROVIDER", "0") == "1"
|
| 110 |
HF_IMAGE_MODEL = os.environ.get("HF_IMAGE_MODEL", "black-forest-labs/FLUX.1-schnell")
|
| 111 |
HF_PROMPT_MODEL = os.environ.get("HF_PROMPT_MODEL", "Qwen/Qwen2.5-Coder-7B-Instruct:fastest")
|
| 112 |
HF_PROMPT_ENDPOINT = os.environ.get("HF_PROMPT_ENDPOINT", "https://router.huggingface.co/v1/chat/completions")
|
| 113 |
+
FREE_DIFFUSION_PIPE = None
|
| 114 |
+
FREE_DIFFUSION_ERROR = None
|
| 115 |
|
| 116 |
|
| 117 |
def slugify(value: str) -> str:
|
|
|
|
| 269 |
|
| 270 |
|
| 271 |
def hf_prompt_json(html_code: str, role_lines: list[tuple[str, str]], style_hint: str) -> tuple[dict[str, str] | None, str | None]:
|
| 272 |
+
if not USE_HF_PROMPT_PROVIDER:
|
| 273 |
+
return None, None
|
| 274 |
if not HF_TOKEN:
|
| 275 |
return None, "HF_TOKEN is not visible to the Space runtime"
|
| 276 |
|
|
|
|
| 986 |
return out.getvalue()
|
| 987 |
|
| 988 |
|
| 989 |
+
def diffusion_dimensions(spec: AssetSpec) -> tuple[int, int]:
|
| 990 |
+
if is_background_spec(spec):
|
| 991 |
+
return 384, 216
|
| 992 |
+
return 256, 256
|
| 993 |
+
|
| 994 |
+
|
| 995 |
+
def polish_diffusion_asset(image: Image.Image, spec: AssetSpec) -> bytes:
|
| 996 |
+
image = image.convert("RGBA")
|
| 997 |
+
if not is_background_spec(spec):
|
| 998 |
+
# Text-to-image models do not produce real transparency. This makes sprites
|
| 999 |
+
# usable by fading out colors similar to the generated corner background.
|
| 1000 |
+
small = image.resize((128, 128), Image.LANCZOS)
|
| 1001 |
+
corners = [
|
| 1002 |
+
small.getpixel((0, 0)),
|
| 1003 |
+
small.getpixel((127, 0)),
|
| 1004 |
+
small.getpixel((0, 127)),
|
| 1005 |
+
small.getpixel((127, 127)),
|
| 1006 |
+
]
|
| 1007 |
+
bg = tuple(sum(pixel[i] for pixel in corners) // len(corners) for i in range(3))
|
| 1008 |
+
pixels = small.load()
|
| 1009 |
+
for y in range(small.height):
|
| 1010 |
+
for x in range(small.width):
|
| 1011 |
+
r, g, b, a = pixels[x, y]
|
| 1012 |
+
dist = abs(r - bg[0]) + abs(g - bg[1]) + abs(b - bg[2])
|
| 1013 |
+
edge = min(x, y, small.width - 1 - x, small.height - 1 - y)
|
| 1014 |
+
if dist < 64 or edge < 3:
|
| 1015 |
+
a = 0
|
| 1016 |
+
elif dist < 125:
|
| 1017 |
+
a = max(0, min(a, (dist - 64) * 4))
|
| 1018 |
+
pixels[x, y] = (r, g, b, a)
|
| 1019 |
+
image = small.resize((spec.width, spec.height), Image.LANCZOS)
|
| 1020 |
+
else:
|
| 1021 |
+
image = image.resize((spec.width, spec.height), Image.LANCZOS)
|
| 1022 |
+
out = io.BytesIO()
|
| 1023 |
+
image.save(out, format="PNG")
|
| 1024 |
+
return out.getvalue()
|
| 1025 |
+
|
| 1026 |
+
|
| 1027 |
+
def free_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
|
| 1028 |
+
global FREE_DIFFUSION_PIPE, FREE_DIFFUSION_ERROR
|
| 1029 |
+
if FREE_DIFFUSION_ERROR:
|
| 1030 |
+
return None, FREE_DIFFUSION_ERROR
|
| 1031 |
+
try:
|
| 1032 |
+
import torch
|
| 1033 |
+
from diffusers import DiffusionPipeline
|
| 1034 |
+
|
| 1035 |
+
if FREE_DIFFUSION_PIPE is None:
|
| 1036 |
+
FREE_DIFFUSION_PIPE = DiffusionPipeline.from_pretrained(FREE_IMAGE_MODEL)
|
| 1037 |
+
FREE_DIFFUSION_PIPE = FREE_DIFFUSION_PIPE.to("cpu")
|
| 1038 |
+
if hasattr(FREE_DIFFUSION_PIPE, "enable_attention_slicing"):
|
| 1039 |
+
FREE_DIFFUSION_PIPE.enable_attention_slicing()
|
| 1040 |
+
|
| 1041 |
+
width, height = diffusion_dimensions(spec)
|
| 1042 |
+
seed = abs(hash(f"{spec.role}|{spec.prompt}|{index}|{run_id}")) % 2147483647
|
| 1043 |
+
generator = torch.Generator(device="cpu").manual_seed(seed)
|
| 1044 |
+
image = FREE_DIFFUSION_PIPE(
|
| 1045 |
+
spec.prompt,
|
| 1046 |
+
width=width,
|
| 1047 |
+
height=height,
|
| 1048 |
+
num_inference_steps=FREE_IMAGE_STEPS,
|
| 1049 |
+
guidance_scale=6.0,
|
| 1050 |
+
generator=generator,
|
| 1051 |
+
).images[0]
|
| 1052 |
+
return polish_diffusion_asset(image, spec), None
|
| 1053 |
+
except Exception as exc:
|
| 1054 |
+
FREE_DIFFUSION_ERROR = short_error(exc)
|
| 1055 |
+
return None, FREE_DIFFUSION_ERROR
|
| 1056 |
+
|
| 1057 |
+
|
| 1058 |
def hf_image_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
|
| 1059 |
+
if not USE_HF_IMAGE_PROVIDER:
|
| 1060 |
+
return None, None
|
| 1061 |
if not HF_TOKEN:
|
| 1062 |
return None, "HF_TOKEN is not visible to the Space runtime"
|
| 1063 |
if InferenceClient is None:
|
|
|
|
| 1082 |
|
| 1083 |
|
| 1084 |
def generate_asset(spec: AssetSpec, index: int, run_id: int) -> tuple[str, str, str | None, str]:
|
| 1085 |
+
png_content, free_error = free_diffusion_png(spec, index, run_id)
|
| 1086 |
+
source = FREE_IMAGE_MODEL
|
| 1087 |
+
error = free_error
|
| 1088 |
+
if png_content is None:
|
| 1089 |
+
png_content, hf_error = hf_image_png(spec, index, run_id)
|
| 1090 |
+
source = HF_IMAGE_MODEL
|
| 1091 |
+
error = hf_error or free_error
|
| 1092 |
if png_content is None:
|
| 1093 |
png_content = local_asset_png(spec, index, run_id)
|
| 1094 |
+
source = "local procedural fallback"
|
| 1095 |
return (
|
| 1096 |
png_bytes_to_data_uri(png_content),
|
| 1097 |
write_gallery_image(png_content, spec.role),
|
| 1098 |
+
error if source == "local procedural fallback" else None,
|
| 1099 |
source,
|
| 1100 |
)
|
| 1101 |
|
|
|
|
| 1275 |
gallery.append((gallery_path, f"{spec.role} -> {spec.filename}"))
|
| 1276 |
model_rows.append((spec.role, prompt_model, image_model))
|
| 1277 |
if error:
|
| 1278 |
+
errors.append(f"{spec.role}: image model failed ({error}); used local procedural fallback")
|
| 1279 |
|
| 1280 |
rewritten = embed_assets(html_code, assets, specs)
|
| 1281 |
status = (
|