Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
"""
|
| 2 |
Gemma Diffusion — live website builder (gradio.Server backend + custom frontend).
|
| 3 |
|
|
@@ -77,7 +78,7 @@ MAX_ITERS_CAP = 120 # hard cap on denoising steps per block
|
|
| 77 |
# ZeroGPU: the 26B checkpoint (~49 GB bf16) needs the full backing card.
|
| 78 |
GPU_SIZE = os.environ.get("GDIFF_GPU_SIZE", "xlarge")
|
| 79 |
|
| 80 |
-
|
| 81 |
"You are an expert front-end web developer with great visual taste. When asked to "
|
| 82 |
"build or change a web page, respond with a SINGLE, complete, self-contained HTML5 "
|
| 83 |
"document. Put all CSS in a <style> tag and any JavaScript in a <script> tag inside "
|
|
@@ -87,10 +88,22 @@ SYSTEM_PROMPT = (
|
|
| 87 |
"<!DOCTYPE html>."
|
| 88 |
)
|
| 89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
_MARKER_RE = re.compile(
|
| 91 |
r"<\|?(?:channel|turn|think|image|audio|video|tool(?:_call|_response)?)\|?>"
|
| 92 |
)
|
| 93 |
-
_FENCE_RE = re.compile(r"```(?:html)?\s*(.*?)\s*```", re.DOTALL)
|
| 94 |
|
| 95 |
|
| 96 |
# --------------------------------------------------------------------------- #
|
|
@@ -110,24 +123,26 @@ CANVAS_LEN = model.config.canvas_length
|
|
| 110 |
PAD_ID = tokenizer.pad_token_id or 0
|
| 111 |
print(f"[gdiff] Model ready | canvas_length={CANVAS_LEN}", flush=True)
|
| 112 |
|
| 113 |
-
# Cache of the last *cleaned* page so a follow-up tweak can warm-start in place.
|
| 114 |
model._last_clean_html = None
|
|
|
|
| 115 |
|
| 116 |
|
| 117 |
# --------------------------------------------------------------------------- #
|
| 118 |
# Helpers (CPU-only; safe to run in the gradio.Server main process)
|
| 119 |
# --------------------------------------------------------------------------- #
|
| 120 |
-
def warm_canvas_from_cache():
|
| 121 |
-
"""Starting canvas (first block) built from the previous *cleaned* page.
|
| 122 |
|
| 123 |
Returns a CPU tensor (it is pickled across the ZeroGPU process boundary and moved
|
| 124 |
-
to CUDA inside the GPU worker). We re-tokenize the cleaned HTML rather than
|
| 125 |
-
raw output tokens so a mangled header can't compound across tweaks.
|
| 126 |
"""
|
| 127 |
-
|
| 128 |
-
|
|
|
|
| 129 |
return None
|
| 130 |
-
ids = tokenizer(
|
| 131 |
if not ids:
|
| 132 |
return None
|
| 133 |
if len(ids) < CANVAS_LEN:
|
|
@@ -151,12 +166,7 @@ def clean_text(text: str) -> str:
|
|
| 151 |
|
| 152 |
|
| 153 |
def extract_html(text: str) -> str:
|
| 154 |
-
"""Pull a usable HTML document out of the (possibly mangled) model output.
|
| 155 |
-
|
| 156 |
-
Anchor on the first intact structural tag and rebuild whatever the diffused tweak ate
|
| 157 |
-
off the front, so the result is always a valid document (never quirks mode and never a
|
| 158 |
-
broken ``DOCTYPE>`` / ``html lang=`` header).
|
| 159 |
-
"""
|
| 160 |
text = clean_text(text)
|
| 161 |
fenced = _FENCE_RE.search(text)
|
| 162 |
if fenced:
|
|
@@ -181,6 +191,15 @@ def extract_html(text: str) -> str:
|
|
| 181 |
return text.strip()
|
| 182 |
|
| 183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
class QueueDiffusionStreamer(BaseStreamer):
|
| 185 |
def __init__(self, tok, q: "queue_lib.Queue"):
|
| 186 |
self.tok = tok
|
|
@@ -212,12 +231,12 @@ class QueueDiffusionStreamer(BaseStreamer):
|
|
| 212 |
self.q.put(("end", self._decode(self.confirmed_ids), self.block, self.step))
|
| 213 |
|
| 214 |
|
| 215 |
-
def build_messages(history_json: str, prompt: str):
|
| 216 |
try:
|
| 217 |
history = json.loads(history_json) if history_json else []
|
| 218 |
except json.JSONDecodeError:
|
| 219 |
history = []
|
| 220 |
-
messages = [{"role": "system", "content":
|
| 221 |
for turn in history:
|
| 222 |
role = turn.get("role")
|
| 223 |
content = turn.get("content", "")
|
|
@@ -280,7 +299,7 @@ def _gpu_stream(input_ids, max_new_tokens, max_iters, full_denoise, canvas_ids):
|
|
| 280 |
# --------------------------------------------------------------------------- #
|
| 281 |
# Server
|
| 282 |
# --------------------------------------------------------------------------- #
|
| 283 |
-
app = Server(title="Gemma Diffusion
|
| 284 |
|
| 285 |
|
| 286 |
@app.api(name="generate", concurrency_limit=1, time_limit=600, stream_every=0.05)
|
|
@@ -292,23 +311,24 @@ def generate(
|
|
| 292 |
full_denoise: bool = False,
|
| 293 |
anim_delay: float = 0.0,
|
| 294 |
warm_start: bool = True,
|
|
|
|
| 295 |
) -> str:
|
| 296 |
-
"""Stream the diffusion generation as JSON frames (one per denoising step).
|
| 297 |
-
|
| 298 |
-
The model writes a self-contained HTML document; the frontend renders it live.
|
| 299 |
-
"""
|
| 300 |
prompt = (prompt or "").strip()
|
| 301 |
if not prompt:
|
| 302 |
yield json.dumps({"kind": "error", "message": "Empty prompt."})
|
| 303 |
return
|
| 304 |
|
| 305 |
-
|
|
|
|
|
|
|
|
|
|
| 306 |
max_iters = max(1, min(int(max_iters), MAX_ITERS_CAP))
|
| 307 |
|
| 308 |
# Tweak warm-start: seed the diffusion's first canvas with the previous page's own
|
| 309 |
# tokens (native `canvas_ids` API) so the model edits the existing page in place.
|
| 310 |
is_tweak = bool(last_assistant_html(history_json))
|
| 311 |
-
canvas_ids = warm_canvas_from_cache() if (warm_start and is_tweak) else None
|
| 312 |
warming = canvas_ids is not None
|
| 313 |
|
| 314 |
input_ids = tokenizer.apply_chat_template(
|
|
@@ -336,16 +356,18 @@ def generate(
|
|
| 336 |
"canvas": CANVAS_LEN,
|
| 337 |
"max_iters": max_iters,
|
| 338 |
"warming": warming,
|
|
|
|
| 339 |
}
|
| 340 |
)
|
| 341 |
if anim_delay and kind == "draft":
|
| 342 |
_time.sleep(float(anim_delay))
|
| 343 |
|
| 344 |
-
final_source = extract_html(last_text)
|
| 345 |
-
# Cache the *cleaned* output so the next tweak warm-starts from a valid
|
|
|
|
| 346 |
if final_source.strip():
|
| 347 |
-
model
|
| 348 |
-
yield json.dumps({"kind": "done", "source": final_source})
|
| 349 |
|
| 350 |
|
| 351 |
@app.get("/", response_class=HTMLResponse)
|
|
@@ -362,4 +384,4 @@ if __name__ == "__main__":
|
|
| 362 |
server_name=os.environ.get("GDIFF_HOST", "0.0.0.0"),
|
| 363 |
server_port=int(os.environ.get("GDIFF_PORT", "7860")),
|
| 364 |
show_error=True,
|
| 365 |
-
)
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
"""
|
| 3 |
Gemma Diffusion — live website builder (gradio.Server backend + custom frontend).
|
| 4 |
|
|
|
|
| 78 |
# ZeroGPU: the 26B checkpoint (~49 GB bf16) needs the full backing card.
|
| 79 |
GPU_SIZE = os.environ.get("GDIFF_GPU_SIZE", "xlarge")
|
| 80 |
|
| 81 |
+
SYSTEM_PROMPT_HTML = (
|
| 82 |
"You are an expert front-end web developer with great visual taste. When asked to "
|
| 83 |
"build or change a web page, respond with a SINGLE, complete, self-contained HTML5 "
|
| 84 |
"document. Put all CSS in a <style> tag and any JavaScript in a <script> tag inside "
|
|
|
|
| 88 |
"<!DOCTYPE html>."
|
| 89 |
)
|
| 90 |
|
| 91 |
+
SYSTEM_PROMPT_PYTHON = (
|
| 92 |
+
"You are an expert Python developer. When asked to write or change a Python program, "
|
| 93 |
+
"respond with a SINGLE, complete, self-contained Python script. Do not use external "
|
| 94 |
+
"packages that require installation unless explicitly requested; prefer the standard "
|
| 95 |
+
"library. When asked to modify an existing script, return the FULL updated script "
|
| 96 |
+
"with the change applied. Do not include explanations or markdown code fences — "
|
| 97 |
+
"output only raw Python code."
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
def system_prompt_for(mode: str) -> str:
|
| 101 |
+
return SYSTEM_PROMPT_PYTHON if (mode or "").lower() == "python" else SYSTEM_PROMPT_HTML
|
| 102 |
+
|
| 103 |
_MARKER_RE = re.compile(
|
| 104 |
r"<\|?(?:channel|turn|think|image|audio|video|tool(?:_call|_response)?)\|?>"
|
| 105 |
)
|
| 106 |
+
_FENCE_RE = re.compile(r"```(?:html|python)?\s*(.*?)\s*```", re.DOTALL)
|
| 107 |
|
| 108 |
|
| 109 |
# --------------------------------------------------------------------------- #
|
|
|
|
| 123 |
PAD_ID = tokenizer.pad_token_id or 0
|
| 124 |
print(f"[gdiff] Model ready | canvas_length={CANVAS_LEN}", flush=True)
|
| 125 |
|
| 126 |
+
# Cache of the last *cleaned* page/code so a follow-up tweak can warm-start in place.
|
| 127 |
model._last_clean_html = None
|
| 128 |
+
model._last_clean_python = None
|
| 129 |
|
| 130 |
|
| 131 |
# --------------------------------------------------------------------------- #
|
| 132 |
# Helpers (CPU-only; safe to run in the gradio.Server main process)
|
| 133 |
# --------------------------------------------------------------------------- #
|
| 134 |
+
def warm_canvas_from_cache(mode: str = "webpage"):
|
| 135 |
+
"""Starting canvas (first block) built from the previous *cleaned* page/code.
|
| 136 |
|
| 137 |
Returns a CPU tensor (it is pickled across the ZeroGPU process boundary and moved
|
| 138 |
+
to CUDA inside the GPU worker). We re-tokenize the cleaned HTML/python rather than
|
| 139 |
+
reuse raw output tokens so a mangled header can't compound across tweaks.
|
| 140 |
"""
|
| 141 |
+
attr = "_last_clean_python" if (mode or "").lower() == "python" else "_last_clean_html"
|
| 142 |
+
src = getattr(model, attr, None)
|
| 143 |
+
if not src:
|
| 144 |
return None
|
| 145 |
+
ids = tokenizer(src, add_special_tokens=False).input_ids[:CANVAS_LEN]
|
| 146 |
if not ids:
|
| 147 |
return None
|
| 148 |
if len(ids) < CANVAS_LEN:
|
|
|
|
| 166 |
|
| 167 |
|
| 168 |
def extract_html(text: str) -> str:
|
| 169 |
+
"""Pull a usable HTML document out of the (possibly mangled) model output."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
text = clean_text(text)
|
| 171 |
fenced = _FENCE_RE.search(text)
|
| 172 |
if fenced:
|
|
|
|
| 191 |
return text.strip()
|
| 192 |
|
| 193 |
|
| 194 |
+
def extract_python(text: str) -> str:
|
| 195 |
+
"""Pull usable Python code out of the (possibly mangled) model output."""
|
| 196 |
+
text = clean_text(text)
|
| 197 |
+
fenced = _FENCE_RE.search(text)
|
| 198 |
+
if fenced:
|
| 199 |
+
text = fenced.group(1)
|
| 200 |
+
return text.strip()
|
| 201 |
+
|
| 202 |
+
|
| 203 |
class QueueDiffusionStreamer(BaseStreamer):
|
| 204 |
def __init__(self, tok, q: "queue_lib.Queue"):
|
| 205 |
self.tok = tok
|
|
|
|
| 231 |
self.q.put(("end", self._decode(self.confirmed_ids), self.block, self.step))
|
| 232 |
|
| 233 |
|
| 234 |
+
def build_messages(history_json: str, prompt: str, mode: str = "webpage"):
|
| 235 |
try:
|
| 236 |
history = json.loads(history_json) if history_json else []
|
| 237 |
except json.JSONDecodeError:
|
| 238 |
history = []
|
| 239 |
+
messages = [{"role": "system", "content": system_prompt_for(mode)}]
|
| 240 |
for turn in history:
|
| 241 |
role = turn.get("role")
|
| 242 |
content = turn.get("content", "")
|
|
|
|
| 299 |
# --------------------------------------------------------------------------- #
|
| 300 |
# Server
|
| 301 |
# --------------------------------------------------------------------------- #
|
| 302 |
+
app = Server(title="Gemma Diffusion Builder")
|
| 303 |
|
| 304 |
|
| 305 |
@app.api(name="generate", concurrency_limit=1, time_limit=600, stream_every=0.05)
|
|
|
|
| 311 |
full_denoise: bool = False,
|
| 312 |
anim_delay: float = 0.0,
|
| 313 |
warm_start: bool = True,
|
| 314 |
+
mode: str = "webpage",
|
| 315 |
) -> str:
|
| 316 |
+
"""Stream the diffusion generation as JSON frames (one per denoising step)."""
|
|
|
|
|
|
|
|
|
|
| 317 |
prompt = (prompt or "").strip()
|
| 318 |
if not prompt:
|
| 319 |
yield json.dumps({"kind": "error", "message": "Empty prompt."})
|
| 320 |
return
|
| 321 |
|
| 322 |
+
mode = (mode or "webpage").lower()
|
| 323 |
+
is_python = mode == "python"
|
| 324 |
+
|
| 325 |
+
messages = build_messages(history_json, prompt, mode)
|
| 326 |
max_iters = max(1, min(int(max_iters), MAX_ITERS_CAP))
|
| 327 |
|
| 328 |
# Tweak warm-start: seed the diffusion's first canvas with the previous page's own
|
| 329 |
# tokens (native `canvas_ids` API) so the model edits the existing page in place.
|
| 330 |
is_tweak = bool(last_assistant_html(history_json))
|
| 331 |
+
canvas_ids = warm_canvas_from_cache(mode) if (warm_start and is_tweak) else None
|
| 332 |
warming = canvas_ids is not None
|
| 333 |
|
| 334 |
input_ids = tokenizer.apply_chat_template(
|
|
|
|
| 356 |
"canvas": CANVAS_LEN,
|
| 357 |
"max_iters": max_iters,
|
| 358 |
"warming": warming,
|
| 359 |
+
"mode": mode,
|
| 360 |
}
|
| 361 |
)
|
| 362 |
if anim_delay and kind == "draft":
|
| 363 |
_time.sleep(float(anim_delay))
|
| 364 |
|
| 365 |
+
final_source = extract_python(last_text) if is_python else extract_html(last_text)
|
| 366 |
+
# Cache the *cleaned* output so the next tweak warm-starts from a valid artifact.
|
| 367 |
+
cache_attr = "_last_clean_python" if is_python else "_last_clean_html"
|
| 368 |
if final_source.strip():
|
| 369 |
+
setattr(model, cache_attr, final_source)
|
| 370 |
+
yield json.dumps({"kind": "done", "source": final_source, "mode": mode})
|
| 371 |
|
| 372 |
|
| 373 |
@app.get("/", response_class=HTMLResponse)
|
|
|
|
| 384 |
server_name=os.environ.get("GDIFF_HOST", "0.0.0.0"),
|
| 385 |
server_port=int(os.environ.get("GDIFF_PORT", "7860")),
|
| 386 |
show_error=True,
|
| 387 |
+
)
|