mfielding92 commited on
Commit
e768b88
·
verified ·
1 Parent(s): 57eee12

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +52 -30
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
- SYSTEM_PROMPT = (
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 reuse
125
- raw output tokens so a mangled header can't compound across tweaks.
126
  """
127
- html = getattr(model, "_last_clean_html", None)
128
- if not html:
 
129
  return None
130
- ids = tokenizer(html, add_special_tokens=False).input_ids[:CANVAS_LEN]
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": SYSTEM_PROMPT}]
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 Website Builder")
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
- messages = build_messages(history_json, prompt)
 
 
 
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 header.
 
346
  if final_source.strip():
347
- model._last_clean_html = final_source
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
+ )