Shalmoni commited on
Commit
0a73965
·
verified ·
1 Parent(s): 809f869

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +197 -89
app.py CHANGED
@@ -13,7 +13,7 @@ HORDE_STATUS = "https://stablehorde.net/api/v2/generate/status/{id}"
13
 
14
  # HF Space secret recommended for priority
15
  HORDE_API_KEY = os.getenv("HORDE_API_KEY", "")
16
- CLIENT_AGENT = "StitchMaster/0.1 (https://huggingface.co/spaces/your-space)"
17
 
18
  DEFAULT_STEPS = 24
19
  DEFAULT_W = 704 # keep defaults under the 715px threshold
@@ -29,19 +29,111 @@ def _headers():
29
  "apikey": HORDE_API_KEY if HORDE_API_KEY else "0000000000"
30
  }
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  # =========================
33
- # Horde client with debugging
34
  # =========================
35
- def horde_txt2img(prompt: str,
36
- steps: int = DEFAULT_STEPS,
37
- width: int = DEFAULT_W,
38
- height: int = DEFAULT_H,
39
- model: Optional[str] = MODEL):
 
 
 
 
 
 
 
 
40
  dbg = []
41
-
42
- if not prompt or not prompt.strip():
43
  raise gr.Error("Please enter a prompt.")
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  payload = {
46
  "prompt": prompt.strip(),
47
  "params": {
@@ -52,49 +144,29 @@ def horde_txt2img(prompt: str,
52
  },
53
  "nsfw": False,
54
  "censor_nsfw": True,
55
- # Ask Horde to return a CDN URL if available (many deployments support this)
56
  "r2": True
57
  }
58
  if model:
59
  payload["models"] = [model]
60
 
61
- # -------- Submit (with KudosUpfront fallback) --------
62
  try:
63
- submit = requests.post(HORDE_URL, json=payload, headers=_headers(), timeout=30)
64
-
65
- # Auto-fallback if KudosUpfront required
66
- if submit.status_code == 403:
67
- try:
68
- body = submit.json()
69
- except Exception:
70
- body = {"message": submit.text}
71
- msg = (body.get("message") or "").lower()
72
- rc = body.get("rc") or ""
73
- if "kudos" in msg or rc == "KudosUpfront":
74
- # Clamp params to stay under upfront limits
75
- payload["params"]["steps"] = min(int(payload["params"]["steps"]), 30)
76
- payload["params"]["width"] = min(int(payload["params"]["width"]), 704)
77
- payload["params"]["height"] = min(int(payload["params"]["height"]), 704)
78
- dbg.append("Fallback applied: steps<=30, width/height<=704. Retrying submit...")
79
- submit = requests.post(HORDE_URL, json=payload, headers=_headers(), timeout=30)
80
-
81
- dbg.append(f"SUBMIT status={submit.status_code}")
82
  if submit.status_code >= 300:
83
  dbg.append(f"SUBMIT body={submit.text[:500]}")
84
  submit.raise_for_status()
85
-
86
  submit_j = submit.json()
87
  job_id = submit_j.get("id")
88
  if not job_id:
89
  dbg.append(f"SUBMIT json={submit_j}")
90
  raise gr.Error("Horde submit succeeded but no job id returned.")
91
  dbg.append(f"JOB id={job_id}")
92
-
93
  except Exception:
94
  dbg.append("SUBMIT exception:\n" + traceback.format_exc())
95
  return None, "\n".join(dbg)
96
 
97
- # -------- Poll --------
98
  start = time.time()
99
  while True:
100
  try:
@@ -105,7 +177,6 @@ def horde_txt2img(prompt: str,
105
  status_r.raise_for_status()
106
  s = status_r.json()
107
 
108
- # progress info
109
  k = s.get("kudos", "?")
110
  queue = s.get("queue_position", "?")
111
  eta = s.get("wait_time", "?")
@@ -125,7 +196,7 @@ def horde_txt2img(prompt: str,
125
  dbg.append(f"GEN keys: {list(g0.keys())}")
126
  dbg.append(f"img_type: {g0.get('img_type')}")
127
 
128
- # Prefer URL fields if present
129
  url = g0.get("r2") or g0.get("url") or g0.get("src") or g0.get("image_url")
130
  if isinstance(url, str) and (url.startswith("http://") or url.startswith("https://")):
131
  dbg.append("Found URL in generation → fetching…")
@@ -133,31 +204,17 @@ def horde_txt2img(prompt: str,
133
  r = requests.get(url, timeout=60)
134
  r.raise_for_status()
135
  img_bytes = r.content
 
136
  except Exception as e:
137
  dbg.append(f"URL fetch failed: {type(e).__name__}: {e}")
138
  return None, "\n".join(dbg)
139
- return _decode_bytes_to_image(img_bytes, dbg)
140
 
141
- # Else fall back to base64 field
142
  b64 = g0.get("img")
143
  if not b64:
144
  dbg.append("No 'img' field present.")
145
  return None, "\n".join(dbg)
146
 
147
- # If 'img' looks like URL text (rare), just fetch it
148
- if b64.startswith("http://") or b64.startswith("https://"):
149
- dbg.append("img field is a URL string → fetching…")
150
- try:
151
- r = requests.get(b64, timeout=60)
152
- r.raise_for_status()
153
- img_bytes = r.content
154
- except Exception as e:
155
- dbg.append(f"URL fetch failed: {type(e).__name__}: {e}")
156
- return None, "\n".join(dbg)
157
- return _decode_bytes_to_image(img_bytes, dbg)
158
-
159
- # Base64 path
160
- # 1) fix base64 padding if needed
161
  pad = (-len(b64)) % 4
162
  if pad:
163
  b64 = b64 + ("=" * pad)
@@ -165,11 +222,9 @@ def horde_txt2img(prompt: str,
165
  img_bytes = base64.b64decode(b64, validate=False)
166
  except binascii.Error as e:
167
  dbg.append(f"Base64 decode error: {e}")
168
- # Try to interpret as text (maybe it's a URL encoded in base64)
169
  try:
170
- txt = base64.b64decode(b64 + "==", validate=False).decode("utf-8", "ignore").strip()
171
  if txt.startswith("http"):
172
- dbg.append("Base64 decoded to text URL → fetching…")
173
  r = requests.get(txt, timeout=60)
174
  r.raise_for_status()
175
  img_bytes = r.content
@@ -192,18 +247,14 @@ def horde_txt2img(prompt: str,
192
  return None, "\n".join(dbg)
193
 
194
  def _decode_bytes_to_image(img_bytes: bytes, dbg: list[str]):
195
- # Log header
196
  head = img_bytes[:12]
197
  dbg.append(f"header bytes: {head.hex(' ')}")
198
-
199
- # Try Pillow first
200
  try:
201
  img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
202
  return img, "\n".join(dbg)
203
  except Exception as e:
204
  dbg.append(f"PIL decode failed: {type(e).__name__}: {e}")
205
 
206
- # Fallback: imageio
207
  try:
208
  arr = imageio.imread(io.BytesIO(img_bytes))
209
  if isinstance(arr, np.ndarray):
@@ -217,7 +268,6 @@ def _decode_bytes_to_image(img_bytes: bytes, dbg: list[str]):
217
  except Exception as e:
218
  dbg.append(f"imageio decode failed: {type(e).__name__}: {e}")
219
 
220
- # Last resort: save bytes for inspection
221
  try:
222
  tmp = f"unknown_img_{int(time.time())}.bin"
223
  with open(tmp, "wb") as f:
@@ -228,17 +278,33 @@ def _decode_bytes_to_image(img_bytes: bytes, dbg: list[str]):
228
 
229
  return None, "\n".join(dbg)
230
 
231
- def generate_image(prompt, steps, size):
232
- # size like "704x704"
233
- try:
234
- w, h = [int(x.strip()) for x in size.lower().split("x")]
235
- except Exception:
236
- w, h = DEFAULT_W, DEFAULT_H
237
- img, debug = horde_txt2img(prompt, steps=steps, width=w, height=h)
238
  if img is None:
239
  gr.Warning("Generation failed. See debug log for details.")
240
  return img, debug
241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  # =========================
243
  # UI
244
  # =========================
@@ -267,8 +333,9 @@ CUSTOM_CSS = """
267
  }
268
  """
269
 
270
- with gr.Blocks(css=CUSTOM_CSS, title="Image Checkpoints – Stable Horde") as demo:
271
- gr.Markdown("### Image Checkpoints (Stable Horde)\nGenerate per-prompt frames. If a run fails, check the debug panel.")
 
272
 
273
  with gr.Row():
274
  steps = gr.Slider(8, 50, value=DEFAULT_STEPS, step=1, label="Steps (quality/time)")
@@ -277,31 +344,72 @@ with gr.Blocks(css=CUSTOM_CSS, title="Image Checkpoints – Stable Horde") as de
277
  value=f"{DEFAULT_W}x{DEFAULT_H}",
278
  label="Resolution"
279
  )
 
 
 
 
280
 
281
  # Shared debug panel
282
  debug_box = gr.Code(label="Debug log", interactive=False)
283
 
284
- prompt_boxes, gen_buttons, img_outputs = [], [], []
285
- for i in range(1, 5):
286
- with gr.Row():
287
- with gr.Column(scale=1, min_width=320):
288
- p = gr.Textbox(
289
- placeholder=f"Prompt input (Image {i})",
290
- lines=4,
291
- label=None,
292
- elem_classes=["prompt-box"]
293
- )
294
- b = gr.Button(f"Generate image {i}", elem_classes=["pill"])
295
- with gr.Column(scale=2, min_width=380):
296
- img = gr.Image(label=f"Image {i} output", type="pil", elem_classes=["image-out"])
297
- prompt_boxes.append(p); gen_buttons.append(b); img_outputs.append(img)
298
-
299
- for i in range(4):
300
- gen_buttons[i].click(
301
- fn=generate_image,
302
- inputs=[prompt_boxes[i], steps, size],
303
- outputs=[img_outputs[i], debug_box]
304
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
  if __name__ == "__main__":
307
  demo.queue().launch()
 
 
13
 
14
  # HF Space secret recommended for priority
15
  HORDE_API_KEY = os.getenv("HORDE_API_KEY", "")
16
+ CLIENT_AGENT = "StitchMaster/0.2 (https://huggingface.co/spaces/your-space)"
17
 
18
  DEFAULT_STEPS = 24
19
  DEFAULT_W = 704 # keep defaults under the 715px threshold
 
29
  "apikey": HORDE_API_KEY if HORDE_API_KEY else "0000000000"
30
  }
31
 
32
+ def pil_to_b64(img_pil: Image.Image) -> str:
33
+ buf = io.BytesIO()
34
+ img_pil.save(buf, format="PNG")
35
+ return base64.b64encode(buf.getvalue()).decode("utf-8")
36
+
37
+ def build_prompt(user_text: str, is_first: bool, lock_longshot: bool = True) -> str:
38
+ """Compose continuity-aware prompt text."""
39
+ user_text = (user_text or "").strip()
40
+ longshot_plus = (
41
+ "single continuous long shot; no cuts or new shot; no angle switch; "
42
+ "smooth camera motion (pan/tilt/zoom only); unbroken continuity"
43
+ )
44
+ if is_first:
45
+ base = f"Opening frame. {user_text}" if user_text else "Opening frame."
46
+ if lock_longshot:
47
+ base += ". " + longshot_plus
48
+ return base
49
+ # Subsequent frames
50
+ base = (
51
+ "Treat the previous frame as a still from the same continuous long shot. "
52
+ "Maintain style, subject identity, lighting, and camera continuity. "
53
+ f"Generate the next moment: {user_text if user_text else 'advance the action naturally.'}"
54
+ )
55
+ if lock_longshot:
56
+ base += ". " + longshot_plus
57
+ return base
58
+
59
  # =========================
60
+ # Horde client with debugging (txt2img OR img2img)
61
  # =========================
62
+ def horde_generate(
63
+ prompt: str,
64
+ steps: int = DEFAULT_STEPS,
65
+ width: int = DEFAULT_W,
66
+ height: int = DEFAULT_H,
67
+ model: Optional[str] = MODEL,
68
+ init_image: Optional[Image.Image] = None,
69
+ denoise: float = 0.45, # 0.0 = identical, 1.0 = big change
70
+ ):
71
+ """
72
+ If init_image is provided, tries img2img first (source_image + source_processing='img2img').
73
+ Falls back to txt2img if Horde rejects it.
74
+ """
75
  dbg = []
76
+ if not (prompt and prompt.strip()):
 
77
  raise gr.Error("Please enter a prompt.")
78
 
79
+ def _submit(payload):
80
+ sub = requests.post(HORDE_URL, json=payload, headers=_headers(), timeout=30)
81
+ # Auto-fallback if KudosUpfront required
82
+ if sub.status_code == 403:
83
+ try:
84
+ body = sub.json()
85
+ except Exception:
86
+ body = {"message": sub.text}
87
+ msg = (body.get("message") or "").lower()
88
+ rc = body.get("rc") or ""
89
+ if "kudos" in msg or rc == "KudosUpfront":
90
+ payload["params"]["steps"] = min(int(payload["params"]["steps"]), 30)
91
+ payload["params"]["width"] = min(int(payload["params"]["width"]), 704)
92
+ payload["params"]["height"] = min(int(payload["params"]["height"]), 704)
93
+ dbg.append("Fallback applied: steps<=30, width/height<=704. Retrying submit...")
94
+ sub = requests.post(HORDE_URL, json=payload, headers=_headers(), timeout=30)
95
+ return sub
96
+
97
+ # ---- try img2img if init_image provided ----
98
+ tried_img2img = False
99
+ if init_image is not None:
100
+ tried_img2img = True
101
+ payload = {
102
+ "prompt": prompt.strip(),
103
+ "params": {
104
+ "steps": int(steps),
105
+ "width": int(width),
106
+ "height": int(height),
107
+ "n": 1,
108
+ "denoise": float(denoise)
109
+ },
110
+ "nsfw": False,
111
+ "censor_nsfw": True,
112
+ "source_processing": "img2img",
113
+ "source_image": pil_to_b64(init_image),
114
+ "r2": True
115
+ }
116
+ if model:
117
+ payload["models"] = [model]
118
+
119
+ try:
120
+ submit = _submit(payload)
121
+ dbg.append(f"SUBMIT (img2img) status={submit.status_code}")
122
+ if submit.status_code >= 300:
123
+ dbg.append(f"SUBMIT body={submit.text[:500]}")
124
+ submit.raise_for_status()
125
+ submit_j = submit.json()
126
+ job_id = submit_j.get("id")
127
+ if not job_id:
128
+ dbg.append(f"SUBMIT json={submit_j}")
129
+ raise gr.Error("Horde submit succeeded but no job id returned.")
130
+ dbg.append(f"JOB id={job_id}")
131
+ # Poll & decode
132
+ return _poll_and_decode(job_id, dbg)
133
+ except Exception:
134
+ dbg.append("IMG2IMG path failed, falling back to text-only:\n" + traceback.format_exc())
135
+
136
+ # ---- txt2img path ----
137
  payload = {
138
  "prompt": prompt.strip(),
139
  "params": {
 
144
  },
145
  "nsfw": False,
146
  "censor_nsfw": True,
 
147
  "r2": True
148
  }
149
  if model:
150
  payload["models"] = [model]
151
 
 
152
  try:
153
+ submit = _submit(payload)
154
+ dbg.append(f"SUBMIT (txt2img{', after img2img fail' if tried_img2img else ''}) status={submit.status_code}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  if submit.status_code >= 300:
156
  dbg.append(f"SUBMIT body={submit.text[:500]}")
157
  submit.raise_for_status()
 
158
  submit_j = submit.json()
159
  job_id = submit_j.get("id")
160
  if not job_id:
161
  dbg.append(f"SUBMIT json={submit_j}")
162
  raise gr.Error("Horde submit succeeded but no job id returned.")
163
  dbg.append(f"JOB id={job_id}")
164
+ return _poll_and_decode(job_id, dbg)
165
  except Exception:
166
  dbg.append("SUBMIT exception:\n" + traceback.format_exc())
167
  return None, "\n".join(dbg)
168
 
169
+ def _poll_and_decode(job_id: str, dbg: list[str]):
170
  start = time.time()
171
  while True:
172
  try:
 
177
  status_r.raise_for_status()
178
  s = status_r.json()
179
 
 
180
  k = s.get("kudos", "?")
181
  queue = s.get("queue_position", "?")
182
  eta = s.get("wait_time", "?")
 
196
  dbg.append(f"GEN keys: {list(g0.keys())}")
197
  dbg.append(f"img_type: {g0.get('img_type')}")
198
 
199
+ # Prefer URL if present
200
  url = g0.get("r2") or g0.get("url") or g0.get("src") or g0.get("image_url")
201
  if isinstance(url, str) and (url.startswith("http://") or url.startswith("https://")):
202
  dbg.append("Found URL in generation → fetching…")
 
204
  r = requests.get(url, timeout=60)
205
  r.raise_for_status()
206
  img_bytes = r.content
207
+ return _decode_bytes_to_image(img_bytes, dbg)
208
  except Exception as e:
209
  dbg.append(f"URL fetch failed: {type(e).__name__}: {e}")
210
  return None, "\n".join(dbg)
 
211
 
212
+ # Base64 branch
213
  b64 = g0.get("img")
214
  if not b64:
215
  dbg.append("No 'img' field present.")
216
  return None, "\n".join(dbg)
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  pad = (-len(b64)) % 4
219
  if pad:
220
  b64 = b64 + ("=" * pad)
 
222
  img_bytes = base64.b64decode(b64, validate=False)
223
  except binascii.Error as e:
224
  dbg.append(f"Base64 decode error: {e}")
 
225
  try:
226
+ txt = base64.b64decode(b64 + '==', validate=False).decode("utf-8", "ignore").strip()
227
  if txt.startswith("http"):
 
228
  r = requests.get(txt, timeout=60)
229
  r.raise_for_status()
230
  img_bytes = r.content
 
247
  return None, "\n".join(dbg)
248
 
249
  def _decode_bytes_to_image(img_bytes: bytes, dbg: list[str]):
 
250
  head = img_bytes[:12]
251
  dbg.append(f"header bytes: {head.hex(' ')}")
 
 
252
  try:
253
  img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
254
  return img, "\n".join(dbg)
255
  except Exception as e:
256
  dbg.append(f"PIL decode failed: {type(e).__name__}: {e}")
257
 
 
258
  try:
259
  arr = imageio.imread(io.BytesIO(img_bytes))
260
  if isinstance(arr, np.ndarray):
 
268
  except Exception as e:
269
  dbg.append(f"imageio decode failed: {type(e).__name__}: {e}")
270
 
 
271
  try:
272
  tmp = f"unknown_img_{int(time.time())}.bin"
273
  with open(tmp, "wb") as f:
 
278
 
279
  return None, "\n".join(dbg)
280
 
281
+ # =========================
282
+ # Gradio glue
283
+ # =========================
284
+ def generate_opening(prompt_text, steps, size, lock):
285
+ w, h = _parse_size(size)
286
+ prompt = build_prompt(prompt_text, is_first=True, lock_longshot=lock)
287
+ img, debug = horde_generate(prompt, steps=steps, width=w, height=h, init_image=None)
288
  if img is None:
289
  gr.Warning("Generation failed. See debug log for details.")
290
  return img, debug
291
 
292
+ def generate_next(prompt_text, steps, size, lock, prev_img, change):
293
+ w, h = _parse_size(size)
294
+ prompt = build_prompt(prompt_text, is_first=False, lock_longshot=lock)
295
+ init_img = prev_img if isinstance(prev_img, Image.Image) else None
296
+ img, debug = horde_generate(prompt, steps=steps, width=w, height=h, init_image=init_img, denoise=float(change))
297
+ if img is None:
298
+ gr.Warning("Generation failed. See debug log for details.")
299
+ return img, debug
300
+
301
+ def _parse_size(s):
302
+ try:
303
+ w, h = [int(x.strip()) for x in str(s).lower().split("x")]
304
+ except Exception:
305
+ w, h = DEFAULT_W, DEFAULT_H
306
+ return w, h
307
+
308
  # =========================
309
  # UI
310
  # =========================
 
333
  }
334
  """
335
 
336
+ with gr.Blocks(css=CUSTOM_CSS, title="Image Checkpoints – Stable Horde (txt2img + img2img)") as demo:
337
+ gr.Markdown("### Image Checkpoints (Stable Horde) Opening shot + next scenes\n"
338
+ "Image 2–4 use the previous output as the init image (img2img) with a continuity slider.")
339
 
340
  with gr.Row():
341
  steps = gr.Slider(8, 50, value=DEFAULT_STEPS, step=1, label="Steps (quality/time)")
 
344
  value=f"{DEFAULT_W}x{DEFAULT_H}",
345
  label="Resolution"
346
  )
347
+ lock = gr.Checkbox(value=True, label="Lock camera (long shot, no cuts)")
348
+
349
+ # Continuity / denoise slider for img2img steps (2–4)
350
+ change = gr.Slider(0.05, 0.95, value=0.45, step=0.05, label="Change from previous frame (denoise)")
351
 
352
  # Shared debug panel
353
  debug_box = gr.Code(label="Debug log", interactive=False)
354
 
355
+ # ---- Row 1: Opening shot ----
356
+ with gr.Row():
357
+ with gr.Column(scale=1, min_width=320):
358
+ p1 = gr.Textbox(
359
+ placeholder="Describe the opening shot",
360
+ lines=4,
361
+ label=None,
362
+ elem_classes=["prompt-box"]
363
+ )
364
+ b1 = gr.Button("Generate image 1", elem_classes=["pill"])
365
+ with gr.Column(scale=2, min_width=380):
366
+ img1 = gr.Image(label="Image 1 output", type="pil", elem_classes=["image-out"])
367
+
368
+ # ---- Row 2: Next scene ----
369
+ with gr.Row():
370
+ with gr.Column(scale=1, min_width=320):
371
+ p2 = gr.Textbox(
372
+ placeholder="Describe the next scene",
373
+ lines=4,
374
+ label=None,
375
+ elem_classes=["prompt-box"]
376
+ )
377
+ b2 = gr.Button("Generate image 2", elem_classes=["pill"])
378
+ with gr.Column(scale=2, min_width=380):
379
+ img2 = gr.Image(label="Image 2 output", type="pil", elem_classes=["image-out"])
380
+
381
+ # ---- Row 3: Next scene ----
382
+ with gr.Row():
383
+ with gr.Column(scale=1, min_width=320):
384
+ p3 = gr.Textbox(
385
+ placeholder="Describe the next scene",
386
+ lines=4,
387
+ label=None,
388
+ elem_classes=["prompt-box"]
389
+ )
390
+ b3 = gr.Button("Generate image 3", elem_classes=["pill"])
391
+ with gr.Column(scale=2, min_width=380):
392
+ img3 = gr.Image(label="Image 3 output", type="pil", elem_classes=["image-out"])
393
+
394
+ # ---- Row 4: Next scene ----
395
+ with gr.Row():
396
+ with gr.Column(scale=1, min_width=320):
397
+ p4 = gr.Textbox(
398
+ placeholder="Describe the next scene",
399
+ lines=4,
400
+ label=None,
401
+ elem_classes=["prompt-box"]
402
+ )
403
+ b4 = gr.Button("Generate image 4", elem_classes=["pill"])
404
+ with gr.Column(scale=2, min_width=380):
405
+ img4 = gr.Image(label="Image 4 output", type="pil", elem_classes=["image-out"])
406
+
407
+ # Wire callbacks
408
+ b1.click(fn=generate_opening, inputs=[p1, steps, size, lock], outputs=[img1, debug_box])
409
+ b2.click(fn=generate_next, inputs=[p2, steps, size, lock, img1, change], outputs=[img2, debug_box])
410
+ b3.click(fn=generate_next, inputs=[p3, steps, size, lock, img2, change], outputs=[img3, debug_box])
411
+ b4.click(fn=generate_next, inputs=[p4, steps, size, lock, img3, change], outputs=[img4, debug_box])
412
 
413
  if __name__ == "__main__":
414
  demo.queue().launch()
415
+