Update app.py
Browse filesMake SSD-1B ZeroGPU the required image generator
app.py
CHANGED
|
@@ -22,6 +22,11 @@ try:
|
|
| 22 |
except Exception:
|
| 23 |
InferenceClient = None
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
STARTER_HTML = """<!DOCTYPE html>
|
| 27 |
<html>
|
|
@@ -116,6 +121,16 @@ class IntegrationReport:
|
|
| 116 |
|
| 117 |
|
| 118 |
HF_TOKEN = os.environ.get("HF_TOKEN", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
FREE_IMAGE_MODEL = os.environ.get("FREE_IMAGE_MODEL", "segmind/tiny-sd")
|
| 120 |
FREE_IMAGE_STEPS = int(os.environ.get("FREE_IMAGE_STEPS", "5"))
|
| 121 |
USE_HF_PROMPT_PROVIDER = os.environ.get("USE_HF_PROMPT_PROVIDER", "0") == "1"
|
|
@@ -136,6 +151,20 @@ FREE_DIFFUSION_PIPE = None
|
|
| 136 |
FREE_DIFFUSION_ERROR = None
|
| 137 |
CONTROLNET_PIPE = None
|
| 138 |
CONTROLNET_ERROR = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
|
| 141 |
def slugify(value: str) -> str:
|
|
@@ -530,7 +559,7 @@ def palette_for(text: str) -> list[tuple[int, int, int]]:
|
|
| 530 |
(("sci-fi", "scifi", "space", "arena", "metal", "shooter"), [(30, 216, 236), (235, 64, 96), (18, 22, 31), (245, 192, 51)]),
|
| 531 |
]
|
| 532 |
for words, colors in themed:
|
| 533 |
-
if
|
| 534 |
return colors
|
| 535 |
|
| 536 |
digest = hashlib.sha256(text.encode("utf-8")).digest()
|
|
@@ -672,7 +701,7 @@ def draw_top_down_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 672 |
colors = palette_for(spec.prompt)
|
| 673 |
width, height = spec.width, spec.height
|
| 674 |
|
| 675 |
-
if
|
| 676 |
image = Image.new("RGBA", (width, height), (18, 22, 31, 255))
|
| 677 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 678 |
panel = 64
|
|
@@ -690,7 +719,7 @@ def draw_top_down_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 690 |
draw.line((0, y, width, y), fill=(34, 215, 236, 95), width=2)
|
| 691 |
return finish_png(image)
|
| 692 |
|
| 693 |
-
if
|
| 694 |
image = Image.new("RGBA", (width, height), (202, 166, 99, 255))
|
| 695 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 696 |
for _ in range(26):
|
|
@@ -716,7 +745,7 @@ def draw_top_down_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 716 |
draw.line(shifted, fill=(105, 77, 48, 155), width=5, joint="curve")
|
| 717 |
return finish_png(image)
|
| 718 |
|
| 719 |
-
if
|
| 720 |
image = Image.new("RGBA", (width, height), (64, 132, 70, 255))
|
| 721 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 722 |
for _ in range(85):
|
|
@@ -753,7 +782,7 @@ def draw_top_down_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 753 |
draw.ellipse((x - inner, y - inner, x + inner, y + inner), fill=(58, rng.randint(126, 166), 69, 245))
|
| 754 |
return finish_png(image)
|
| 755 |
|
| 756 |
-
if
|
| 757 |
image = Image.new("RGBA", (width, height), (48, 50, 62, 255))
|
| 758 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 759 |
tile = 54
|
|
@@ -767,7 +796,7 @@ def draw_top_down_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 767 |
)
|
| 768 |
return finish_png(image)
|
| 769 |
|
| 770 |
-
if
|
| 771 |
image = Image.new("RGBA", (width, height), (52, 23, 18, 255))
|
| 772 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 773 |
for _ in range(24):
|
|
@@ -781,7 +810,7 @@ def draw_top_down_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 781 |
draw.line(points, fill=(255, 202, 64, 210), width=rng.randint(2, 5), joint="curve")
|
| 782 |
return finish_png(image)
|
| 783 |
|
| 784 |
-
if
|
| 785 |
image = Image.new("RGBA", (width, height), (18, 114, 158, 255))
|
| 786 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 787 |
for _ in range(42):
|
|
@@ -836,7 +865,7 @@ def draw_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 836 |
return draw_isometric_background(spec, rng)
|
| 837 |
|
| 838 |
if (
|
| 839 |
-
|
| 840 |
and not is_space_background_prompt(prompt)
|
| 841 |
):
|
| 842 |
image = Image.new("RGBA", (spec.width, spec.height), (18, 22, 31, 255))
|
|
@@ -863,11 +892,6 @@ def draw_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 863 |
[(x + stripe, y), (x + stripe + 12, y), (x + stripe - 12, y + 34), (x + stripe - 24, y + 34)],
|
| 864 |
fill=(245, 192, 51, 210),
|
| 865 |
)
|
| 866 |
-
for _ in range(18):
|
| 867 |
-
x = rng.randint(0, spec.width)
|
| 868 |
-
y = rng.randint(0, spec.height)
|
| 869 |
-
r = rng.randint(10, 32)
|
| 870 |
-
draw.ellipse((x - r, y - r, x + r, y + r), outline=(224, 54, 85, 100), width=2)
|
| 871 |
out = io.BytesIO()
|
| 872 |
image.save(out, format="PNG")
|
| 873 |
return out.getvalue()
|
|
@@ -901,7 +925,7 @@ def draw_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 901 |
image.save(out, format="PNG")
|
| 902 |
return out.getvalue()
|
| 903 |
|
| 904 |
-
if
|
| 905 |
sky = (184, 224, 239)
|
| 906 |
grass = (80, 151, 82)
|
| 907 |
image = Image.new("RGBA", (spec.width, spec.height), sky + (255,))
|
|
@@ -927,13 +951,16 @@ def draw_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 927 |
],
|
| 928 |
fill=path_color,
|
| 929 |
)
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
|
|
|
|
|
|
|
|
|
|
| 937 |
for _ in range(10):
|
| 938 |
x = rng.randint(-40, spec.width)
|
| 939 |
y = rng.randint(horizon - 35, horizon + 20)
|
|
@@ -942,7 +969,7 @@ def draw_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 942 |
image.save(out, format="PNG")
|
| 943 |
return out.getvalue()
|
| 944 |
|
| 945 |
-
if
|
| 946 |
image = Image.new("RGBA", (spec.width, spec.height), (52, 23, 18, 255))
|
| 947 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 948 |
for y in range(0, spec.height, 58):
|
|
@@ -951,16 +978,20 @@ def draw_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 951 |
x = rng.randint(-80, spec.width)
|
| 952 |
y = rng.randint(0, spec.height)
|
| 953 |
draw.line((x, y, x + rng.randint(120, 320), y + rng.randint(-20, 20)), fill=(255, 111, 43, 220), width=rng.randint(5, 12))
|
| 954 |
-
|
| 955 |
-
|
| 956 |
-
|
| 957 |
-
|
| 958 |
-
draw.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 959 |
out = io.BytesIO()
|
| 960 |
image.save(out, format="PNG")
|
| 961 |
return out.getvalue()
|
| 962 |
|
| 963 |
-
if
|
| 964 |
image = Image.new("RGBA", (spec.width, spec.height), (48, 50, 62, 255))
|
| 965 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 966 |
tile = 54
|
|
@@ -968,15 +999,11 @@ def draw_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 968 |
for x in range(0, spec.width, tile):
|
| 969 |
shade = rng.randint(48, 78)
|
| 970 |
draw.rectangle((x, y, x + tile - 2, y + tile - 2), fill=(shade, shade + 2, shade + 8, 255), outline=(27, 29, 38, 190))
|
| 971 |
-
for _ in range(22):
|
| 972 |
-
x = rng.randint(0, spec.width)
|
| 973 |
-
y = rng.randint(0, spec.height)
|
| 974 |
-
draw.ellipse((x - 8, y - 8, x + 8, y + 8), fill=(185, 146, 80, 110))
|
| 975 |
out = io.BytesIO()
|
| 976 |
image.save(out, format="PNG")
|
| 977 |
return out.getvalue()
|
| 978 |
|
| 979 |
-
if
|
| 980 |
image = Image.new("RGBA", (spec.width, spec.height), (8, 54, 92, 255))
|
| 981 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 982 |
for y in range(spec.height):
|
|
@@ -1000,20 +1027,16 @@ def draw_background(spec: AssetSpec, rng: random.Random) -> bytes:
|
|
| 1000 |
color = tuple(int(colors[2][i] * (1 - mix) + colors[0][i] * mix) for i in range(3))
|
| 1001 |
draw.line((0, y, spec.width, y), fill=color + (255,))
|
| 1002 |
|
| 1003 |
-
|
| 1004 |
-
|
| 1005 |
-
|
| 1006 |
-
|
| 1007 |
-
|
| 1008 |
-
|
| 1009 |
-
|
| 1010 |
-
|
| 1011 |
-
|
| 1012 |
-
|
| 1013 |
-
y = rng.randint(0, spec.height)
|
| 1014 |
-
size = rng.randint(2, 7)
|
| 1015 |
-
fill = rng.choice(colors) + (rng.randint(90, 190),)
|
| 1016 |
-
draw.rectangle((x, y, x + size, y + size), fill=fill)
|
| 1017 |
|
| 1018 |
out = io.BytesIO()
|
| 1019 |
image.save(out, format="PNG")
|
|
@@ -1449,7 +1472,8 @@ def diffusion_negative_prompt(spec: AssetSpec) -> str:
|
|
| 1449 |
}.get(spec.camera, "")
|
| 1450 |
return (
|
| 1451 |
common
|
| 1452 |
-
+ ", person, character, player, hero, creature, monster, vehicle, mascot, foreground subject"
|
|
|
|
| 1453 |
+ camera_negative
|
| 1454 |
)
|
| 1455 |
return common + ", landscape, environment scene, room, horizon, opaque background, drop shadow"
|
|
@@ -1507,6 +1531,104 @@ def build_camera_control_image(spec: AssetSpec, width: int = 1024, height: int =
|
|
| 1507 |
return image.convert("RGB")
|
| 1508 |
|
| 1509 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1510 |
def controlnet_background_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
|
| 1511 |
"""Run SDXL Depth ControlNet when a CUDA Space explicitly enables it."""
|
| 1512 |
global CONTROLNET_PIPE, CONTROLNET_ERROR
|
|
@@ -1677,7 +1799,7 @@ def hf_structure_background_png(spec: AssetSpec, index: int, run_id: int) -> tup
|
|
| 1677 |
|
| 1678 |
def generate_asset(spec: AssetSpec, index: int, run_id: int) -> tuple[str, str, str | None, str]:
|
| 1679 |
is_background = is_background_spec(spec)
|
| 1680 |
-
if not is_background and not USE_DIFFUSION_FOR_SPRITES and not USE_HF_IMAGE_PROVIDER:
|
| 1681 |
png_content = local_asset_png(spec, index, run_id)
|
| 1682 |
return (
|
| 1683 |
png_bytes_to_data_uri(png_content),
|
|
@@ -1687,6 +1809,7 @@ def generate_asset(spec: AssetSpec, index: int, run_id: int) -> tuple[str, str,
|
|
| 1687 |
)
|
| 1688 |
background_models_enabled = any(
|
| 1689 |
(
|
|
|
|
| 1690 |
USE_CONTROLNET_FOR_BACKGROUNDS,
|
| 1691 |
USE_HF_STRUCTURE_PROVIDER,
|
| 1692 |
USE_DIFFUSION_FOR_BACKGROUNDS,
|
|
@@ -1706,7 +1829,18 @@ def generate_asset(spec: AssetSpec, index: int, run_id: int) -> tuple[str, str,
|
|
| 1706 |
source = ""
|
| 1707 |
errors: list[str] = []
|
| 1708 |
|
| 1709 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1710 |
png_content, error = controlnet_background_png(spec, index, run_id)
|
| 1711 |
source = f"{CONTROLNET_BASE_MODEL} + {CONTROLNET_MODEL}"
|
| 1712 |
if error:
|
|
@@ -1737,7 +1871,7 @@ def generate_asset(spec: AssetSpec, index: int, run_id: int) -> tuple[str, str,
|
|
| 1737 |
return (
|
| 1738 |
png_bytes_to_data_uri(png_content),
|
| 1739 |
write_gallery_image(png_content, spec.role),
|
| 1740 |
-
"; ".join(errors) if
|
| 1741 |
source,
|
| 1742 |
)
|
| 1743 |
|
|
@@ -1898,12 +2032,17 @@ def model_source_note(image_model: str) -> str:
|
|
| 1898 |
return "depth-controlled diffusion—stronger camera structure; still requires visual perspective review"
|
| 1899 |
if "via fal-ai" in lowered or "via replicate" in lowered:
|
| 1900 |
return "routed image-to-image refinement—preserves a layout guide but consumes account inference credit"
|
|
|
|
|
|
|
| 1901 |
return "diffusion output—higher visual variety; verify subject, perspective, alpha edges, and prompt compliance"
|
| 1902 |
|
| 1903 |
|
| 1904 |
def model_configuration_summary() -> str:
|
| 1905 |
prompt_source = HF_PROMPT_MODEL if USE_HF_PROMPT_PROVIDER else "local prompt interpreter"
|
| 1906 |
sprite_chain = []
|
|
|
|
|
|
|
|
|
|
| 1907 |
if USE_DIFFUSION_FOR_SPRITES:
|
| 1908 |
sprite_chain.append(FREE_IMAGE_MODEL)
|
| 1909 |
if USE_HF_IMAGE_PROVIDER:
|
|
@@ -1911,6 +2050,8 @@ def model_configuration_summary() -> str:
|
|
| 1911 |
sprite_chain.append("shape-aware procedural renderer (no neural model)")
|
| 1912 |
sprite_source = " → ".join(sprite_chain)
|
| 1913 |
background_chain = []
|
|
|
|
|
|
|
| 1914 |
if USE_CONTROLNET_FOR_BACKGROUNDS:
|
| 1915 |
background_chain.append(f"{CONTROLNET_BASE_MODEL} + {CONTROLNET_MODEL}")
|
| 1916 |
if USE_HF_STRUCTURE_PROVIDER:
|
|
@@ -1928,18 +2069,29 @@ def model_configuration_summary() -> str:
|
|
| 1928 |
remote_sources.append(HF_IMAGE_MODEL)
|
| 1929 |
remote_fallback = " → ".join(remote_sources) if remote_sources else "disabled"
|
| 1930 |
neural_models = []
|
|
|
|
|
|
|
| 1931 |
if USE_DIFFUSION_FOR_SPRITES or USE_DIFFUSION_FOR_BACKGROUNDS:
|
| 1932 |
neural_models.append(FREE_IMAGE_MODEL)
|
| 1933 |
if USE_CONTROLNET_FOR_BACKGROUNDS:
|
| 1934 |
neural_models.append(f"{CONTROLNET_BASE_MODEL} + {CONTROLNET_MODEL}")
|
| 1935 |
neural_models.extend(remote_sources)
|
| 1936 |
neural_status = ", ".join(dict.fromkeys(neural_models)) if neural_models else "none active—procedural code only"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1937 |
return (
|
| 1938 |
"**Configured model pipeline:** "
|
| 1939 |
f"prompts: `{prompt_source}` · sprites: `{sprite_source}` · backgrounds: `{background_source}` · "
|
| 1940 |
-
f"remote image fallback: `{remote_fallback}` · neural image models: `{neural_status}`
|
| 1941 |
-
"
|
| 1942 |
-
"
|
|
|
|
|
|
|
| 1943 |
)
|
| 1944 |
|
| 1945 |
|
|
@@ -2015,6 +2167,7 @@ def empty_generation_result(message: str, html_code: str = ""):
|
|
| 2015 |
)
|
| 2016 |
|
| 2017 |
|
|
|
|
| 2018 |
def generate_images_and_game(
|
| 2019 |
html_code: str,
|
| 2020 |
roles: str,
|
|
@@ -2050,14 +2203,17 @@ def generate_images_and_game(
|
|
| 2050 |
errors.append(f"prompt model: {prompt_error}")
|
| 2051 |
|
| 2052 |
for index, spec in enumerate(specs):
|
| 2053 |
-
|
|
|
|
|
|
|
|
|
|
| 2054 |
assets[spec.role] = data_uri
|
| 2055 |
gallery_paths[spec.role] = gallery_path
|
| 2056 |
image_models[spec.role] = image_model
|
| 2057 |
png_content = base64.b64decode(data_uri.split(",", 1)[1])
|
| 2058 |
quality[spec.role] = validate_asset_png(png_content, spec)
|
| 2059 |
if error:
|
| 2060 |
-
errors.append(f"{spec.role}:
|
| 2061 |
|
| 2062 |
state = {
|
| 2063 |
"html_code": html_code,
|
|
@@ -2079,6 +2235,7 @@ def generate_images_and_game(
|
|
| 2079 |
)
|
| 2080 |
|
| 2081 |
|
|
|
|
| 2082 |
def regenerate_selected_asset(state: dict, selected_role: str, approved_roles: list[str]):
|
| 2083 |
if not state or not selected_role:
|
| 2084 |
return (
|
|
@@ -2101,7 +2258,16 @@ def regenerate_selected_asset(state: dict, selected_role: str, approved_roles: l
|
|
| 2101 |
|
| 2102 |
spec = specs[index]
|
| 2103 |
run_id = time.time_ns()
|
| 2104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2105 |
state["assets"][selected_role] = data_uri
|
| 2106 |
state["gallery_paths"][selected_role] = gallery_path
|
| 2107 |
state["image_models"][selected_role] = image_model
|
|
|
|
| 22 |
except Exception:
|
| 23 |
InferenceClient = None
|
| 24 |
|
| 25 |
+
try:
|
| 26 |
+
import spaces as hf_spaces
|
| 27 |
+
except Exception:
|
| 28 |
+
hf_spaces = None
|
| 29 |
+
|
| 30 |
|
| 31 |
STARTER_HTML = """<!DOCTYPE html>
|
| 32 |
<html>
|
|
|
|
| 121 |
|
| 122 |
|
| 123 |
HF_TOKEN = os.environ.get("HF_TOKEN", "")
|
| 124 |
+
PRIMARY_IMAGE_MODEL = os.environ.get("PRIMARY_IMAGE_MODEL", "segmind/SSD-1B")
|
| 125 |
+
PRIMARY_LORA_MODEL = os.environ.get("PRIMARY_LORA_MODEL", "latent-consistency/lcm-lora-ssd-1b")
|
| 126 |
+
PRIMARY_IMAGE_STEPS = int(os.environ.get("PRIMARY_IMAGE_STEPS", "4"))
|
| 127 |
+
PRIMARY_GUIDANCE_SCALE = float(os.environ.get("PRIMARY_GUIDANCE_SCALE", "1.5"))
|
| 128 |
+
PRIMARY_BACKGROUND_STRENGTH = float(os.environ.get("PRIMARY_BACKGROUND_STRENGTH", "0.72"))
|
| 129 |
+
USE_PRIMARY_IMAGE_MODEL = os.environ.get("USE_PRIMARY_IMAGE_MODEL", "1") == "1"
|
| 130 |
+
REQUIRE_PRIMARY_IMAGE_MODEL = os.environ.get(
|
| 131 |
+
"REQUIRE_PRIMARY_IMAGE_MODEL",
|
| 132 |
+
"1" if os.environ.get("SPACE_ID") else "0",
|
| 133 |
+
) == "1"
|
| 134 |
FREE_IMAGE_MODEL = os.environ.get("FREE_IMAGE_MODEL", "segmind/tiny-sd")
|
| 135 |
FREE_IMAGE_STEPS = int(os.environ.get("FREE_IMAGE_STEPS", "5"))
|
| 136 |
USE_HF_PROMPT_PROVIDER = os.environ.get("USE_HF_PROMPT_PROVIDER", "0") == "1"
|
|
|
|
| 151 |
FREE_DIFFUSION_ERROR = None
|
| 152 |
CONTROLNET_PIPE = None
|
| 153 |
CONTROLNET_ERROR = None
|
| 154 |
+
PRIMARY_TEXT_PIPE = None
|
| 155 |
+
PRIMARY_IMAGE_TO_IMAGE_PIPE = None
|
| 156 |
+
PRIMARY_MODEL_ERROR = None
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def gpu_task(duration: int):
|
| 160 |
+
"""Use a real ZeroGPU allocation on Hugging Face and remain importable in local tests."""
|
| 161 |
+
if hf_spaces is not None:
|
| 162 |
+
return hf_spaces.GPU(duration=duration)
|
| 163 |
+
|
| 164 |
+
def identity(function):
|
| 165 |
+
return function
|
| 166 |
+
|
| 167 |
+
return identity
|
| 168 |
|
| 169 |
|
| 170 |
def slugify(value: str) -> str:
|
|
|
|
| 559 |
(("sci-fi", "scifi", "space", "arena", "metal", "shooter"), [(30, 216, 236), (235, 64, 96), (18, 22, 31), (245, 192, 51)]),
|
| 560 |
]
|
| 561 |
for words, colors in themed:
|
| 562 |
+
if contains_any_term(lowered, words):
|
| 563 |
return colors
|
| 564 |
|
| 565 |
digest = hashlib.sha256(text.encode("utf-8")).digest()
|
|
|
|
| 701 |
colors = palette_for(spec.prompt)
|
| 702 |
width, height = spec.width, spec.height
|
| 703 |
|
| 704 |
+
if contains_any_term(prompt, ("sci-fi", "scifi", "cyberpunk", "space station", "spaceship hangar", "metal arena")):
|
| 705 |
image = Image.new("RGBA", (width, height), (18, 22, 31, 255))
|
| 706 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 707 |
panel = 64
|
|
|
|
| 719 |
draw.line((0, y, width, y), fill=(34, 215, 236, 95), width=2)
|
| 720 |
return finish_png(image)
|
| 721 |
|
| 722 |
+
if contains_any_term(prompt, ("desert", "sand", "dune", "badlands", "wasteland", "canyon")):
|
| 723 |
image = Image.new("RGBA", (width, height), (202, 166, 99, 255))
|
| 724 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 725 |
for _ in range(26):
|
|
|
|
| 745 |
draw.line(shifted, fill=(105, 77, 48, 155), width=5, joint="curve")
|
| 746 |
return finish_png(image)
|
| 747 |
|
| 748 |
+
if contains_any_term(prompt, ("garden", "flower", "grass", "meadow", "forest", "jungle", "path")):
|
| 749 |
image = Image.new("RGBA", (width, height), (64, 132, 70, 255))
|
| 750 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 751 |
for _ in range(85):
|
|
|
|
| 782 |
draw.ellipse((x - inner, y - inner, x + inner, y + inner), fill=(58, rng.randint(126, 166), 69, 245))
|
| 783 |
return finish_png(image)
|
| 784 |
|
| 785 |
+
if contains_any_term(prompt, ("dungeon", "castle", "stone", "crypt")):
|
| 786 |
image = Image.new("RGBA", (width, height), (48, 50, 62, 255))
|
| 787 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 788 |
tile = 54
|
|
|
|
| 796 |
)
|
| 797 |
return finish_png(image)
|
| 798 |
|
| 799 |
+
if contains_any_term(prompt, ("lava", "fire", "volcano")):
|
| 800 |
image = Image.new("RGBA", (width, height), (52, 23, 18, 255))
|
| 801 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 802 |
for _ in range(24):
|
|
|
|
| 810 |
draw.line(points, fill=(255, 202, 64, 210), width=rng.randint(2, 5), joint="curve")
|
| 811 |
return finish_png(image)
|
| 812 |
|
| 813 |
+
if contains_any_term(prompt, ("ocean", "sea", "water", "underwater")):
|
| 814 |
image = Image.new("RGBA", (width, height), (18, 114, 158, 255))
|
| 815 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 816 |
for _ in range(42):
|
|
|
|
| 865 |
return draw_isometric_background(spec, rng)
|
| 866 |
|
| 867 |
if (
|
| 868 |
+
contains_any_term(prompt, ("sci-fi", "scifi", "cyberpunk", "space station", "spaceship hangar", "metal arena", "hazard grid"))
|
| 869 |
and not is_space_background_prompt(prompt)
|
| 870 |
):
|
| 871 |
image = Image.new("RGBA", (spec.width, spec.height), (18, 22, 31, 255))
|
|
|
|
| 892 |
[(x + stripe, y), (x + stripe + 12, y), (x + stripe - 12, y + 34), (x + stripe - 24, y + 34)],
|
| 893 |
fill=(245, 192, 51, 210),
|
| 894 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 895 |
out = io.BytesIO()
|
| 896 |
image.save(out, format="PNG")
|
| 897 |
return out.getvalue()
|
|
|
|
| 925 |
image.save(out, format="PNG")
|
| 926 |
return out.getvalue()
|
| 927 |
|
| 928 |
+
if contains_any_term(prompt, ("garden", "flower", "grass", "meadow", "forest", "path")):
|
| 929 |
sky = (184, 224, 239)
|
| 930 |
grass = (80, 151, 82)
|
| 931 |
image = Image.new("RGBA", (spec.width, spec.height), sky + (255,))
|
|
|
|
| 951 |
],
|
| 952 |
fill=path_color,
|
| 953 |
)
|
| 954 |
+
# Small colored flowers easily read as a generic particle overlay. Only add
|
| 955 |
+
# them when the request explicitly calls for a flower-rich environment.
|
| 956 |
+
if contains_any_term(prompt, ("garden", "flower", "flowers", "meadow")):
|
| 957 |
+
for _ in range(18):
|
| 958 |
+
x = rng.randint(0, spec.width)
|
| 959 |
+
y = rng.randint(horizon + 48, spec.height - 18)
|
| 960 |
+
stem = rng.randint(5, 12)
|
| 961 |
+
draw.line((x, y + stem, x, y), fill=(43, 109, 53, 210), width=2)
|
| 962 |
+
petal = rng.choice(colors) + (230,)
|
| 963 |
+
draw.rectangle((x - 3, y - 2, x + 3, y + 3), fill=petal)
|
| 964 |
for _ in range(10):
|
| 965 |
x = rng.randint(-40, spec.width)
|
| 966 |
y = rng.randint(horizon - 35, horizon + 20)
|
|
|
|
| 969 |
image.save(out, format="PNG")
|
| 970 |
return out.getvalue()
|
| 971 |
|
| 972 |
+
if contains_any_term(prompt, ("lava", "fire", "volcano")):
|
| 973 |
image = Image.new("RGBA", (spec.width, spec.height), (52, 23, 18, 255))
|
| 974 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 975 |
for y in range(0, spec.height, 58):
|
|
|
|
| 978 |
x = rng.randint(-80, spec.width)
|
| 979 |
y = rng.randint(0, spec.height)
|
| 980 |
draw.line((x, y, x + rng.randint(120, 320), y + rng.randint(-20, 20)), fill=(255, 111, 43, 220), width=rng.randint(5, 12))
|
| 981 |
+
# Keep lava as connected terrain cracks. Random glowing circles caused a
|
| 982 |
+
# persistent confetti/particle layer in every lava-background trial.
|
| 983 |
+
for x in range(-40, spec.width + 80, 150):
|
| 984 |
+
y = int(spec.height * 0.68) + rng.randint(-24, 24)
|
| 985 |
+
draw.polygon(
|
| 986 |
+
((x, y), (x + 86, y - 18), (x + 132, y + 4), (x + 52, y + 27)),
|
| 987 |
+
fill=(82, 42, 30, 245),
|
| 988 |
+
outline=(119, 55, 34, 255),
|
| 989 |
+
)
|
| 990 |
out = io.BytesIO()
|
| 991 |
image.save(out, format="PNG")
|
| 992 |
return out.getvalue()
|
| 993 |
|
| 994 |
+
if contains_any_term(prompt, ("dungeon", "castle", "stone", "crypt")):
|
| 995 |
image = Image.new("RGBA", (spec.width, spec.height), (48, 50, 62, 255))
|
| 996 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 997 |
tile = 54
|
|
|
|
| 999 |
for x in range(0, spec.width, tile):
|
| 1000 |
shade = rng.randint(48, 78)
|
| 1001 |
draw.rectangle((x, y, x + tile - 2, y + tile - 2), fill=(shade, shade + 2, shade + 8, 255), outline=(27, 29, 38, 190))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1002 |
out = io.BytesIO()
|
| 1003 |
image.save(out, format="PNG")
|
| 1004 |
return out.getvalue()
|
| 1005 |
|
| 1006 |
+
if contains_any_term(prompt, ("ocean", "sea", "water", "underwater")):
|
| 1007 |
image = Image.new("RGBA", (spec.width, spec.height), (8, 54, 92, 255))
|
| 1008 |
draw = ImageDraw.Draw(image, "RGBA")
|
| 1009 |
for y in range(spec.height):
|
|
|
|
| 1027 |
color = tuple(int(colors[2][i] * (1 - mix) + colors[0][i] * mix) for i in range(3))
|
| 1028 |
draw.line((0, y, spec.width, y), fill=color + (255,))
|
| 1029 |
|
| 1030 |
+
# Unknown themes still get large connected terrain bands rather than a field
|
| 1031 |
+
# of unrelated dots, which was repeatedly mistaken for a particle effect.
|
| 1032 |
+
for band in range(7):
|
| 1033 |
+
y = int(spec.height * (band + 1) / 8)
|
| 1034 |
+
offset = rng.randint(-28, 28)
|
| 1035 |
+
draw.line(
|
| 1036 |
+
((0, y + offset), (spec.width, y - offset)),
|
| 1037 |
+
fill=colors[(band + 1) % len(colors)] + (105,),
|
| 1038 |
+
width=rng.randint(18, 46),
|
| 1039 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1040 |
|
| 1041 |
out = io.BytesIO()
|
| 1042 |
image.save(out, format="PNG")
|
|
|
|
| 1472 |
}.get(spec.camera, "")
|
| 1473 |
return (
|
| 1474 |
common
|
| 1475 |
+
+ ", person, character, player, hero, creature, monster, vehicle, mascot, foreground subject, "
|
| 1476 |
+
"particle effect, particles, confetti, sparkles, floating dots, glowing orbs, embers, lens flare"
|
| 1477 |
+ camera_negative
|
| 1478 |
)
|
| 1479 |
return common + ", landscape, environment scene, room, horizon, opaque background, drop shadow"
|
|
|
|
| 1531 |
return image.convert("RGB")
|
| 1532 |
|
| 1533 |
|
| 1534 |
+
def initialize_primary_image_model() -> None:
|
| 1535 |
+
"""Load the production diffusion pipelines once under ZeroGPU CUDA emulation."""
|
| 1536 |
+
global PRIMARY_TEXT_PIPE, PRIMARY_IMAGE_TO_IMAGE_PIPE, PRIMARY_MODEL_ERROR
|
| 1537 |
+
if not USE_PRIMARY_IMAGE_MODEL or PRIMARY_TEXT_PIPE is not None or PRIMARY_MODEL_ERROR:
|
| 1538 |
+
return
|
| 1539 |
+
if not os.environ.get("SPACE_ID"):
|
| 1540 |
+
return
|
| 1541 |
+
|
| 1542 |
+
try:
|
| 1543 |
+
import torch
|
| 1544 |
+
from diffusers import AutoPipelineForImage2Image, AutoPipelineForText2Image, LCMScheduler
|
| 1545 |
+
|
| 1546 |
+
model_kwargs = {
|
| 1547 |
+
"torch_dtype": torch.float16,
|
| 1548 |
+
"variant": "fp16",
|
| 1549 |
+
"use_safetensors": True,
|
| 1550 |
+
}
|
| 1551 |
+
if HF_TOKEN:
|
| 1552 |
+
model_kwargs["token"] = HF_TOKEN
|
| 1553 |
+
|
| 1554 |
+
text_pipe = AutoPipelineForText2Image.from_pretrained(PRIMARY_IMAGE_MODEL, **model_kwargs)
|
| 1555 |
+
text_pipe.scheduler = LCMScheduler.from_config(text_pipe.scheduler.config)
|
| 1556 |
+
text_pipe.load_lora_weights(PRIMARY_LORA_MODEL)
|
| 1557 |
+
text_pipe.fuse_lora()
|
| 1558 |
+
text_pipe = text_pipe.to("cuda")
|
| 1559 |
+
if hasattr(text_pipe, "set_progress_bar_config"):
|
| 1560 |
+
text_pipe.set_progress_bar_config(disable=True)
|
| 1561 |
+
if hasattr(text_pipe, "enable_vae_slicing"):
|
| 1562 |
+
text_pipe.enable_vae_slicing()
|
| 1563 |
+
|
| 1564 |
+
image_pipe = AutoPipelineForImage2Image.from_pipe(text_pipe)
|
| 1565 |
+
image_pipe.scheduler = LCMScheduler.from_config(image_pipe.scheduler.config)
|
| 1566 |
+
if hasattr(image_pipe, "set_progress_bar_config"):
|
| 1567 |
+
image_pipe.set_progress_bar_config(disable=True)
|
| 1568 |
+
|
| 1569 |
+
PRIMARY_TEXT_PIPE = text_pipe
|
| 1570 |
+
PRIMARY_IMAGE_TO_IMAGE_PIPE = image_pipe
|
| 1571 |
+
except Exception as exc:
|
| 1572 |
+
PRIMARY_MODEL_ERROR = short_error(exc)
|
| 1573 |
+
|
| 1574 |
+
|
| 1575 |
+
def primary_diffusion_prompt(spec: AssetSpec) -> str:
|
| 1576 |
+
if is_background_spec(spec):
|
| 1577 |
+
return (
|
| 1578 |
+
f"{spec.prompt} Production-quality 2D game background only. Preserve the supplied camera layout "
|
| 1579 |
+
"and readable gameplay space. Use connected environmental forms and clean terrain structure. "
|
| 1580 |
+
"No characters and no decorative overlay."
|
| 1581 |
+
)
|
| 1582 |
+
return (
|
| 1583 |
+
f"{spec.prompt} One complete isolated game sprite only, centered, fully visible, readable silhouette, "
|
| 1584 |
+
"camera angle exactly as specified, on a plain uniform white studio background."
|
| 1585 |
+
)
|
| 1586 |
+
|
| 1587 |
+
|
| 1588 |
+
def primary_diffusion_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
|
| 1589 |
+
"""Generate every production asset with SSD-1B; backgrounds use camera-guided img2img."""
|
| 1590 |
+
if PRIMARY_MODEL_ERROR:
|
| 1591 |
+
return None, PRIMARY_MODEL_ERROR
|
| 1592 |
+
if PRIMARY_TEXT_PIPE is None:
|
| 1593 |
+
initialize_primary_image_model()
|
| 1594 |
+
if PRIMARY_TEXT_PIPE is None:
|
| 1595 |
+
return None, "primary ZeroGPU diffusion model is not initialized on this runtime"
|
| 1596 |
+
|
| 1597 |
+
try:
|
| 1598 |
+
import torch
|
| 1599 |
+
|
| 1600 |
+
seed = abs(hash(f"primary|{spec.role}|{spec.prompt}|{index}|{run_id}")) % 2147483647
|
| 1601 |
+
generator = torch.Generator(device="cuda").manual_seed(seed)
|
| 1602 |
+
common = {
|
| 1603 |
+
"prompt": primary_diffusion_prompt(spec),
|
| 1604 |
+
"negative_prompt": diffusion_negative_prompt(spec),
|
| 1605 |
+
"num_inference_steps": PRIMARY_IMAGE_STEPS,
|
| 1606 |
+
"guidance_scale": PRIMARY_GUIDANCE_SCALE,
|
| 1607 |
+
"generator": generator,
|
| 1608 |
+
}
|
| 1609 |
+
if is_background_spec(spec):
|
| 1610 |
+
if PRIMARY_IMAGE_TO_IMAGE_PIPE is None:
|
| 1611 |
+
return None, "primary camera-guided image-to-image pipeline is unavailable"
|
| 1612 |
+
guide_png = local_asset_png(spec, index, run_id)
|
| 1613 |
+
guide = Image.open(io.BytesIO(guide_png)).convert("RGB").resize((1344, 768), Image.LANCZOS)
|
| 1614 |
+
image = PRIMARY_IMAGE_TO_IMAGE_PIPE(
|
| 1615 |
+
image=guide,
|
| 1616 |
+
strength=PRIMARY_BACKGROUND_STRENGTH,
|
| 1617 |
+
**common,
|
| 1618 |
+
).images[0]
|
| 1619 |
+
else:
|
| 1620 |
+
image = PRIMARY_TEXT_PIPE(width=1024, height=1024, **common).images[0]
|
| 1621 |
+
return polish_diffusion_asset(image, spec), None
|
| 1622 |
+
except Exception as exc:
|
| 1623 |
+
return None, short_error(exc)
|
| 1624 |
+
|
| 1625 |
+
|
| 1626 |
+
# Hugging Face ZeroGPU virtualizes CUDA placement during module import. Loading
|
| 1627 |
+
# here keeps the weights resident between decorated requests instead of paying the
|
| 1628 |
+
# model-load cost on every generation.
|
| 1629 |
+
initialize_primary_image_model()
|
| 1630 |
+
|
| 1631 |
+
|
| 1632 |
def controlnet_background_png(spec: AssetSpec, index: int, run_id: int) -> tuple[bytes | None, str | None]:
|
| 1633 |
"""Run SDXL Depth ControlNet when a CUDA Space explicitly enables it."""
|
| 1634 |
global CONTROLNET_PIPE, CONTROLNET_ERROR
|
|
|
|
| 1799 |
|
| 1800 |
def generate_asset(spec: AssetSpec, index: int, run_id: int) -> tuple[str, str, str | None, str]:
|
| 1801 |
is_background = is_background_spec(spec)
|
| 1802 |
+
if not is_background and not USE_PRIMARY_IMAGE_MODEL and not USE_DIFFUSION_FOR_SPRITES and not USE_HF_IMAGE_PROVIDER:
|
| 1803 |
png_content = local_asset_png(spec, index, run_id)
|
| 1804 |
return (
|
| 1805 |
png_bytes_to_data_uri(png_content),
|
|
|
|
| 1809 |
)
|
| 1810 |
background_models_enabled = any(
|
| 1811 |
(
|
| 1812 |
+
USE_PRIMARY_IMAGE_MODEL,
|
| 1813 |
USE_CONTROLNET_FOR_BACKGROUNDS,
|
| 1814 |
USE_HF_STRUCTURE_PROVIDER,
|
| 1815 |
USE_DIFFUSION_FOR_BACKGROUNDS,
|
|
|
|
| 1829 |
source = ""
|
| 1830 |
errors: list[str] = []
|
| 1831 |
|
| 1832 |
+
if USE_PRIMARY_IMAGE_MODEL:
|
| 1833 |
+
png_content, error = primary_diffusion_png(spec, index, run_id)
|
| 1834 |
+
source = f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL} on ZeroGPU"
|
| 1835 |
+
if error:
|
| 1836 |
+
errors.append(f"primary ZeroGPU diffusion: {error}")
|
| 1837 |
+
if REQUIRE_PRIMARY_IMAGE_MODEL:
|
| 1838 |
+
raise RuntimeError(
|
| 1839 |
+
"Neural image generation is required, but the primary model failed. "
|
| 1840 |
+
f"No procedural image was returned. Details: {error}"
|
| 1841 |
+
)
|
| 1842 |
+
|
| 1843 |
+
if png_content is None and is_background and USE_CONTROLNET_FOR_BACKGROUNDS:
|
| 1844 |
png_content, error = controlnet_background_png(spec, index, run_id)
|
| 1845 |
source = f"{CONTROLNET_BASE_MODEL} + {CONTROLNET_MODEL}"
|
| 1846 |
if error:
|
|
|
|
| 1871 |
return (
|
| 1872 |
png_bytes_to_data_uri(png_content),
|
| 1873 |
write_gallery_image(png_content, spec.role),
|
| 1874 |
+
"; ".join(errors) if errors and "ZeroGPU" not in source else None,
|
| 1875 |
source,
|
| 1876 |
)
|
| 1877 |
|
|
|
|
| 2032 |
return "depth-controlled diffusion—stronger camera structure; still requires visual perspective review"
|
| 2033 |
if "via fal-ai" in lowered or "via replicate" in lowered:
|
| 2034 |
return "routed image-to-image refinement—preserves a layout guide but consumes account inference credit"
|
| 2035 |
+
if "zerogpu" in lowered:
|
| 2036 |
+
return "locally hosted neural diffusion—no per-image API credit; backgrounds are camera-guided and still require visual review"
|
| 2037 |
return "diffusion output—higher visual variety; verify subject, perspective, alpha edges, and prompt compliance"
|
| 2038 |
|
| 2039 |
|
| 2040 |
def model_configuration_summary() -> str:
|
| 2041 |
prompt_source = HF_PROMPT_MODEL if USE_HF_PROMPT_PROVIDER else "local prompt interpreter"
|
| 2042 |
sprite_chain = []
|
| 2043 |
+
primary_source = f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL} on ZeroGPU"
|
| 2044 |
+
if USE_PRIMARY_IMAGE_MODEL:
|
| 2045 |
+
sprite_chain.append(primary_source)
|
| 2046 |
if USE_DIFFUSION_FOR_SPRITES:
|
| 2047 |
sprite_chain.append(FREE_IMAGE_MODEL)
|
| 2048 |
if USE_HF_IMAGE_PROVIDER:
|
|
|
|
| 2050 |
sprite_chain.append("shape-aware procedural renderer (no neural model)")
|
| 2051 |
sprite_source = " → ".join(sprite_chain)
|
| 2052 |
background_chain = []
|
| 2053 |
+
if USE_PRIMARY_IMAGE_MODEL:
|
| 2054 |
+
background_chain.append(f"camera-guided {primary_source}")
|
| 2055 |
if USE_CONTROLNET_FOR_BACKGROUNDS:
|
| 2056 |
background_chain.append(f"{CONTROLNET_BASE_MODEL} + {CONTROLNET_MODEL}")
|
| 2057 |
if USE_HF_STRUCTURE_PROVIDER:
|
|
|
|
| 2069 |
remote_sources.append(HF_IMAGE_MODEL)
|
| 2070 |
remote_fallback = " → ".join(remote_sources) if remote_sources else "disabled"
|
| 2071 |
neural_models = []
|
| 2072 |
+
if USE_PRIMARY_IMAGE_MODEL:
|
| 2073 |
+
neural_models.append(f"{PRIMARY_IMAGE_MODEL} + {PRIMARY_LORA_MODEL}")
|
| 2074 |
if USE_DIFFUSION_FOR_SPRITES or USE_DIFFUSION_FOR_BACKGROUNDS:
|
| 2075 |
neural_models.append(FREE_IMAGE_MODEL)
|
| 2076 |
if USE_CONTROLNET_FOR_BACKGROUNDS:
|
| 2077 |
neural_models.append(f"{CONTROLNET_BASE_MODEL} + {CONTROLNET_MODEL}")
|
| 2078 |
neural_models.extend(remote_sources)
|
| 2079 |
neural_status = ", ".join(dict.fromkeys(neural_models)) if neural_models else "none active—procedural code only"
|
| 2080 |
+
enforcement = "required; generation stops on model failure" if REQUIRE_PRIMARY_IMAGE_MODEL else "development fallback allowed"
|
| 2081 |
+
if PRIMARY_MODEL_ERROR:
|
| 2082 |
+
readiness = f"startup error: {PRIMARY_MODEL_ERROR}"
|
| 2083 |
+
elif PRIMARY_TEXT_PIPE is not None:
|
| 2084 |
+
readiness = "loaded on ZeroGPU"
|
| 2085 |
+
else:
|
| 2086 |
+
readiness = "loads in the deployed ZeroGPU runtime"
|
| 2087 |
return (
|
| 2088 |
"**Configured model pipeline:** "
|
| 2089 |
f"prompts: `{prompt_source}` · sprites: `{sprite_source}` · backgrounds: `{background_source}` · "
|
| 2090 |
+
f"remote image fallback: `{remote_fallback}` · neural image models: `{neural_status}` · "
|
| 2091 |
+
f"neural enforcement: `{enforcement}` · primary readiness: `{readiness}`. "
|
| 2092 |
+
"Every production image is generated by the primary neural model. Backgrounds start from a deterministic "
|
| 2093 |
+
"camera guide and are refined with image-to-image diffusion; the procedural renderer remains an emergency "
|
| 2094 |
+
"development fallback and is blocked in the deployed Space when the primary model fails."
|
| 2095 |
)
|
| 2096 |
|
| 2097 |
|
|
|
|
| 2167 |
)
|
| 2168 |
|
| 2169 |
|
| 2170 |
+
@gpu_task(duration=120)
|
| 2171 |
def generate_images_and_game(
|
| 2172 |
html_code: str,
|
| 2173 |
roles: str,
|
|
|
|
| 2203 |
errors.append(f"prompt model: {prompt_error}")
|
| 2204 |
|
| 2205 |
for index, spec in enumerate(specs):
|
| 2206 |
+
try:
|
| 2207 |
+
data_uri, gallery_path, error, image_model = generate_asset(spec, index, run_id)
|
| 2208 |
+
except RuntimeError as exc:
|
| 2209 |
+
return empty_generation_result(str(exc), html_code)
|
| 2210 |
assets[spec.role] = data_uri
|
| 2211 |
gallery_paths[spec.role] = gallery_path
|
| 2212 |
image_models[spec.role] = image_model
|
| 2213 |
png_content = base64.b64decode(data_uri.split(",", 1)[1])
|
| 2214 |
quality[spec.role] = validate_asset_png(png_content, spec)
|
| 2215 |
if error:
|
| 2216 |
+
errors.append(f"{spec.role}: generation fallback used ({error}); actual source: {image_model}")
|
| 2217 |
|
| 2218 |
state = {
|
| 2219 |
"html_code": html_code,
|
|
|
|
| 2235 |
)
|
| 2236 |
|
| 2237 |
|
| 2238 |
+
@gpu_task(duration=90)
|
| 2239 |
def regenerate_selected_asset(state: dict, selected_role: str, approved_roles: list[str]):
|
| 2240 |
if not state or not selected_role:
|
| 2241 |
return (
|
|
|
|
| 2258 |
|
| 2259 |
spec = specs[index]
|
| 2260 |
run_id = time.time_ns()
|
| 2261 |
+
try:
|
| 2262 |
+
data_uri, gallery_path, error, image_model = generate_asset(spec, index, run_id)
|
| 2263 |
+
except RuntimeError as exc:
|
| 2264 |
+
rendered = render_generation_state(state, "Kept", [str(exc)])
|
| 2265 |
+
roles = [item.role for item in specs]
|
| 2266 |
+
return (
|
| 2267 |
+
*rendered,
|
| 2268 |
+
state,
|
| 2269 |
+
gr.CheckboxGroup(choices=roles, value=approved_roles or []),
|
| 2270 |
+
)
|
| 2271 |
state["assets"][selected_role] = data_uri
|
| 2272 |
state["gallery_paths"][selected_role] = gallery_path
|
| 2273 |
state["image_models"][selected_role] = image_model
|