ASTRALK commited on
Commit
61e9d81
Β·
verified Β·
1 Parent(s): 8595175

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +442 -0
app.py ADDED
@@ -0,0 +1,442 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Comic Book Generator β€” Gradio app (live-demo build).
2
+
3
+ Flow for the video:
4
+ 1. Type an idea, press Enter / Generate.
5
+ 2. A big STOPWATCH starts (gr.Timer) and ticks live while the comic is made β€” Gemma
6
+ gatekeeps + writes a 25-page / 50-panel story, FLUX paints every panel, and the
7
+ latest finished panel shows as a live thumbnail.
8
+ 3. At the end the stopwatch FREEZES on the exact generation time (no cold start, because
9
+ the GPUs are pre-warmed) and the full comic reader is revealed: header (with the time),
10
+ two image+caption panels per page, and β—€ β–Ά arrows to page through all 25 pages.
11
+
12
+ Run:
13
+ COMIC_BACKEND=mock python app.py # offline, no GPU
14
+ COMIC_BACKEND=modal python app.py # live Gemma + FLUX on Modal (pre-warm first!)
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import io
20
+ import os
21
+ import threading
22
+ import time
23
+
24
+ import gradio as gr
25
+ from PIL import Image
26
+
27
+ from comic import generate_comic, make_backends
28
+ from comic.schema import PAGES, TOTAL_PANELS
29
+
30
+ BACKEND = os.environ.get("COMIC_BACKEND", "mock")
31
+ WRITER, ARTIST = make_backends(BACKEND) # bound once; pre-warm + generation share them
32
+
33
+ # Module-level timing state, polled by the gr.Timer to tick the stopwatch smoothly while
34
+ # the (long-running) generator streams. Single-user demo -> a global is fine.
35
+ TIMER = {"t0": None, "running": False, "final": None}
36
+
37
+
38
+ # ── small renderers ──────────────────────────────────────────────────────────
39
+
40
+ def _pil(b):
41
+ return Image.open(io.BytesIO(b)).convert("RGB") if b else None
42
+
43
+
44
+ def _fmt(elapsed: float) -> str:
45
+ m = int(elapsed // 60)
46
+ s = elapsed - 60 * m
47
+ return f"{m}:{s:04.1f}" if m else f"{s:.1f}s"
48
+
49
+
50
+ _INK = "#16120d"
51
+ _PAPER2 = "#fbf6e9"
52
+
53
+
54
+ def _clock_html(elapsed: float, running: bool) -> str:
55
+ col = "#e23b2e" if running else "#1f8a4c"
56
+ sub = "INKING YOUR COMIC…" if running else "DONE Β· TOTAL TIME (GPUS PRE-WARMED)"
57
+ return (
58
+ f"<div style='text-align:center;background:{_PAPER2};border:3px solid {_INK};"
59
+ f"border-radius:5px;box-shadow:7px 7px 0 {_INK};padding:18px 10px;margin:4px 0 8px'>"
60
+ f"<div style='font-family:Anton,sans-serif;font-size:62px;color:{col};line-height:1;"
61
+ f"letter-spacing:2px'>{_fmt(elapsed)}</div>"
62
+ f"<div style='font-size:12px;font-weight:700;letter-spacing:2px;color:{_INK};"
63
+ f"margin-top:6px'>{sub}</div></div>"
64
+ )
65
+
66
+
67
+ def _progress_html(message: str, progress: float = 0.0, tone: str = "info") -> str:
68
+ colors = {"info": "#214bd6", "error": "#e23b2e", "refuse": "#f4ad15", "ok": "#1f8a4c"}
69
+ bar = max(0.0, min(1.0, progress))
70
+ col = colors.get(tone, "#214bd6")
71
+ return (
72
+ f"<div style='font-family:\"Spline Sans\",system-ui;color:{_INK};font-size:14px'>"
73
+ f"<div style='margin-bottom:7px;font-weight:600'>{message}</div>"
74
+ f"<div style='background:#e3d8bd;border:2px solid {_INK};border-radius:4px;height:12px;"
75
+ f"overflow:hidden'>"
76
+ f"<div style='width:{bar*100:.0f}%;height:12px;background:{col};"
77
+ f"transition:width .3s'></div></div></div>"
78
+ )
79
+
80
+
81
+ def _title_html(title: str, logline: str = "", time_note: str = "") -> str:
82
+ sub = (f"<div style='font-size:14px;color:#6d6453;margin-top:6px;font-style:italic'>"
83
+ f"{logline}</div>" if logline else "")
84
+ tn = (f"<div style='display:inline-block;font-size:12px;font-weight:700;letter-spacing:1px;"
85
+ f"text-transform:uppercase;color:#fff;background:#1f8a4c;border:2px solid {_INK};"
86
+ f"box-shadow:2px 2px 0 {_INK};padding:4px 10px;margin-top:10px'>{time_note}</div>"
87
+ if time_note else "")
88
+ return (
89
+ f"<div style='text-align:center;padding:18px 16px;background:{_PAPER2};"
90
+ f"border:3px solid {_INK};border-radius:5px;box-shadow:7px 7px 0 {_INK}'>"
91
+ f"<div style='font-family:Anton,sans-serif;font-size:34px;letter-spacing:1px;"
92
+ f"text-transform:uppercase;color:{_INK};line-height:1'>{title}</div>{sub}{tn}</div>"
93
+ )
94
+
95
+
96
+ def _caption_html(text: str) -> str:
97
+ text = text or ""
98
+ return (
99
+ f"<div style='font-family:\"Spline Sans\",system-ui;font-size:15px;line-height:1.55;"
100
+ f"color:{_INK};background:{_PAPER2};border:3px solid {_INK};border-radius:4px;"
101
+ f"box-shadow:4px 4px 0 {_INK};padding:12px 15px;margin:6px 0 14px;font-weight:500'>{text}</div>"
102
+ )
103
+
104
+
105
+ def _label_html(idx: int) -> str:
106
+ return (f"<div style='font-family:Anton,sans-serif;font-size:20px;letter-spacing:1px;"
107
+ f"color:{_INK};text-align:right;padding-top:8px'>PAGE {idx + 1} / {PAGES}</div>")
108
+
109
+
110
+ def _page_view(comic, idx: int):
111
+ """(img1, cap1, img2, cap2, label) for a 0-based page index."""
112
+ panels = comic.page_panels(idx + 1) if comic else []
113
+ p1 = panels[0] if len(panels) > 0 else None
114
+ p2 = panels[1] if len(panels) > 1 else None
115
+ return (
116
+ _pil(p1.image) if p1 else None,
117
+ _caption_html(p1.caption if p1 else ""),
118
+ _pil(p2.image) if p2 else None,
119
+ _caption_html(p2.caption if p2 else ""),
120
+ _label_html(idx),
121
+ )
122
+
123
+
124
+ # ── output bundle plumbing ───────────────────────────────────────────────────
125
+ _KEYS = ["input_view", "gen_view", "reader_view", "gen_btn", "clock", "status",
126
+ "status0", "live_img", "gen_timer", "title", "img1", "cap1", "img2", "cap2",
127
+ "label", "comic", "idx"]
128
+ _POS = {k: i for i, k in enumerate(_KEYS)}
129
+
130
+
131
+ def _bundle(**kw):
132
+ out = [gr.skip()] * len(_KEYS)
133
+ for k, v in kw.items():
134
+ out[_POS[k]] = v
135
+ return tuple(out)
136
+
137
+
138
+ # ── callbacks ────────────────────────────────────────────────────────────────
139
+
140
+ def on_generate(idea):
141
+ """Generator: run the pipeline, tick the stopwatch, reveal the full comic at done."""
142
+ idea = (idea or "").strip()
143
+ if not idea:
144
+ yield _bundle(status0=_progress_html("Please describe the comic you want.", 0, "error"))
145
+ return
146
+
147
+ # start the stopwatch + switch to the generation view
148
+ TIMER["t0"] = time.monotonic()
149
+ TIMER["running"] = True
150
+ TIMER["final"] = None
151
+ yield _bundle(
152
+ input_view=gr.update(visible=False),
153
+ gen_view=gr.update(visible=True),
154
+ gen_btn=gr.update(interactive=False),
155
+ gen_timer=gr.update(active=True),
156
+ clock=_clock_html(0.0, True),
157
+ status=_progress_html("Reading your idea and planning the story…", 0.02),
158
+ live_img=None,
159
+ )
160
+
161
+ def elapsed():
162
+ return time.monotonic() - TIMER["t0"]
163
+
164
+ comic = None
165
+ for ev in generate_comic(idea, writer=WRITER, artist=ARTIST):
166
+ if ev.comic is not None:
167
+ comic = ev.comic
168
+
169
+ if ev.kind in ("refused", "error"):
170
+ TIMER["running"] = False
171
+ tone = "refuse" if ev.kind == "refused" else "error"
172
+ icon = "🚫 " if ev.kind == "refused" else "⚠ "
173
+ yield _bundle(
174
+ input_view=gr.update(visible=True),
175
+ gen_view=gr.update(visible=False),
176
+ gen_btn=gr.update(interactive=True),
177
+ gen_timer=gr.update(active=False),
178
+ status0=_progress_html(icon + ev.message, 0.0, tone))
179
+ return
180
+
181
+ if ev.kind == "image" and ev.panel is not None and ev.panel.image:
182
+ yield _bundle(
183
+ comic=comic,
184
+ clock=_clock_html(elapsed(), True),
185
+ status=_progress_html(ev.message, ev.progress),
186
+ live_img=_pil(ev.panel.image))
187
+ continue
188
+
189
+ if ev.kind == "done":
190
+ TIMER["running"] = False
191
+ TIMER["final"] = elapsed()
192
+ img1, cap1, img2, cap2, label = _page_view(comic, 0)
193
+ note = f"✨ {TOTAL_PANELS} panels generated in {_fmt(TIMER['final'])}"
194
+ yield _bundle(
195
+ gen_view=gr.update(visible=False),
196
+ reader_view=gr.update(visible=True),
197
+ gen_timer=gr.update(active=False),
198
+ clock=_clock_html(TIMER["final"], False),
199
+ title=_title_html(comic.bible.title, comic.bible.logline, note),
200
+ img1=img1, cap1=cap1, img2=img2, cap2=cap2, label=label,
201
+ comic=comic, idx=0)
202
+ return
203
+
204
+ # status / bible / panels
205
+ yield _bundle(comic=comic, clock=_clock_html(elapsed(), True),
206
+ status=_progress_html(ev.message, ev.progress))
207
+
208
+
209
+ def on_clock_tick():
210
+ """Smoothly tick the stopwatch between generator yields; freeze when not running."""
211
+ if TIMER["running"] and TIMER["t0"] is not None:
212
+ return gr.update(value=_clock_html(time.monotonic() - TIMER["t0"], True))
213
+ return gr.skip()
214
+
215
+
216
+ def on_prev(comic, idx):
217
+ if comic is None:
218
+ return (gr.skip(),) * 6
219
+ idx = max(0, int(idx) - 1)
220
+ img1, cap1, img2, cap2, label = _page_view(comic, idx)
221
+ return img1, cap1, img2, cap2, label, idx
222
+
223
+
224
+ def on_next(comic, idx):
225
+ if comic is None:
226
+ return (gr.skip(),) * 6
227
+ idx = min(PAGES - 1, int(idx) + 1)
228
+ img1, cap1, img2, cap2, label = _page_view(comic, idx)
229
+ return img1, cap1, img2, cap2, label, idx
230
+
231
+
232
+ def on_new():
233
+ TIMER["running"] = False
234
+ return (
235
+ gr.update(visible=True), # input_view
236
+ gr.update(visible=False), # gen_view
237
+ gr.update(visible=False), # reader_view
238
+ gr.update(value="", interactive=True), # idea
239
+ gr.update(interactive=True), # gen_btn
240
+ gr.update(active=False), # gen_timer
241
+ None, # live_img
242
+ None, # comic
243
+ 0, # idx
244
+ )
245
+
246
+
247
+ def on_warm():
248
+ threading.Thread(target=WRITER.warm, daemon=True).start()
249
+ threading.Thread(target=ARTIST.warm, daemon=True).start()
250
+ note = ("Models warming… give them a moment, then generate."
251
+ if BACKEND == "modal" else "Ready (mock backend).")
252
+ return gr.update(value=_progress_html(note, 0.0))
253
+
254
+
255
+ # ── UI ───────────────────────────────────────────────────────────────────────
256
+
257
+ CSS = """
258
+ @import url('https://fonts.googleapis.com/css2?family=Anton&family=Spline+Sans:wght@400;500;600;700&display=swap');
259
+
260
+ :root {
261
+ --paper:#f0e6cf; --paper2:#fbf6e9; --ink:#16120d; --ink-soft:#6d6453;
262
+ --red:#e23b2e; --blue:#214bd6; --yellow:#f4ad15;
263
+ }
264
+
265
+ /* warm paper + halftone-dot page */
266
+ gradio-app, .gradio-container, body {
267
+ background-color: var(--paper) !important;
268
+ background-image: radial-gradient(rgba(22,18,13,0.07) 0.7px, transparent 0.8px) !important;
269
+ background-size: 15px 15px !important;
270
+ color: var(--ink) !important;
271
+ font-family:'Spline Sans', ui-sans-serif, sans-serif !important;
272
+ }
273
+ .gradio-container {max-width: 940px !important; margin: 0 auto !important;}
274
+ #landing {max-width: 720px; margin: 0 auto;}
275
+
276
+ /* hero comic panel */
277
+ .comic-hero {position:relative; text-align:center; padding:38px 26px 24px; margin:10px 0 22px;
278
+ background:var(--paper2); border:3px solid var(--ink); border-radius:5px;
279
+ box-shadow:9px 9px 0 var(--ink); overflow:hidden;}
280
+ .comic-hero::before {content:''; position:absolute; inset:0;
281
+ background-image:radial-gradient(var(--ink) 1px, transparent 1.3px);
282
+ background-size:11px 11px; opacity:.05; pointer-events:none;}
283
+ .comic-hero .kicker {position:relative; display:inline-block; font-weight:700; font-size:12px;
284
+ letter-spacing:3px; text-transform:uppercase; color:var(--paper2); background:var(--ink);
285
+ padding:5px 13px; transform:rotate(-1.4deg);}
286
+ .comic-hero h1 {position:relative; font-family:'Anton',sans-serif; font-weight:400; font-size:62px;
287
+ line-height:0.9; letter-spacing:1px; margin:20px 0 0; text-transform:uppercase; color:var(--ink);
288
+ text-shadow:3px 3px 0 rgba(226,59,46,0.9);}
289
+ .comic-hero h1 .accent {color:var(--red); text-shadow:3px 3px 0 var(--ink);}
290
+ .comic-hero p {position:relative; color:var(--ink-soft); font-size:16px; max-width:520px;
291
+ margin:18px auto 0; line-height:1.55;}
292
+ .comic-hero p b {color:var(--ink); font-weight:700;}
293
+ .comic-hero .pills {position:relative; margin-top:18px; display:flex; gap:9px; justify-content:center;
294
+ flex-wrap:wrap;}
295
+ .comic-hero .pill {font-size:11px; font-weight:700; letter-spacing:1px; text-transform:uppercase;
296
+ color:var(--ink); background:var(--paper); border:2px solid var(--ink); border-radius:3px;
297
+ padding:5px 11px; box-shadow:2px 2px 0 var(--ink);}
298
+
299
+ /* the idea textbox as an inked panel */
300
+ #idea_box {background:transparent !important; border:none !important; box-shadow:none !important;}
301
+ #idea_box textarea {background:var(--paper2) !important; border:3px solid var(--ink) !important;
302
+ box-shadow:6px 6px 0 var(--ink) !important; border-radius:4px !important; color:var(--ink) !important;
303
+ font-family:'Spline Sans' !important; font-size:16px !important; line-height:1.5; min-height:104px;
304
+ padding:14px 16px !important;}
305
+ #idea_box textarea::placeholder {color:#a79c86 !important;}
306
+
307
+ /* example chips + nav buttons as comic stickers */
308
+ #examples {gap:9px; justify-content:center; flex-wrap:wrap; margin-top:4px;}
309
+ #examples button, #reader_col button {flex:0 0 auto !important; min-width:0 !important;
310
+ background:var(--paper2) !important; color:var(--ink) !important; border:2.5px solid var(--ink) !important;
311
+ box-shadow:3px 3px 0 var(--ink) !important; border-radius:3px !important; font-weight:600 !important;
312
+ transition:transform .08s ease, box-shadow .08s ease !important;}
313
+ #examples button:hover, #reader_col button:hover {transform:translate(-1px,-1px) !important;
314
+ box-shadow:4px 4px 0 var(--ink) !important;}
315
+ #examples button:active, #reader_col button:active {transform:translate(2px,2px) !important;
316
+ box-shadow:1px 1px 0 var(--ink) !important;}
317
+
318
+ /* the big generate CTA */
319
+ #gen_btn {margin-top:10px; background:var(--red) !important; color:#fff !important;
320
+ border:3px solid var(--ink) !important; box-shadow:7px 7px 0 var(--ink) !important;
321
+ border-radius:4px !important; font-family:'Anton',sans-serif !important; font-weight:400 !important;
322
+ font-size:22px !important; letter-spacing:1.5px; text-transform:uppercase;
323
+ transition:transform .1s ease, box-shadow .1s ease !important;}
324
+ #gen_btn:hover {transform:translate(-2px,-2px) !important; box-shadow:10px 10px 0 var(--ink) !important;}
325
+ #gen_btn:active {transform:translate(3px,3px) !important; box-shadow:3px 3px 0 var(--ink) !important;}
326
+
327
+ /* staggered entrance */
328
+ @keyframes riseIn {from{opacity:0; transform:translateY(16px);} to{opacity:1; transform:none;}}
329
+ .comic-hero {animation:riseIn .5s both;}
330
+ #idea_box {animation:riseIn .5s .08s both;}
331
+ #examples {animation:riseIn .5s .16s both;}
332
+ #gen_btn {animation:riseIn .5s .24s both;}
333
+
334
+ #reader_col {max-width: 760px; margin: 0 auto;}
335
+ #gen_col {max-width: 680px; margin: 0 auto;}
336
+ #panel_img img, #live_img img {border:3px solid var(--ink) !important; border-radius:4px;
337
+ box-shadow:6px 6px 0 var(--ink);}
338
+ """
339
+
340
+ _HERO = f"""
341
+ <div class='comic-hero'>
342
+ <div class='kicker'>✦ AI Comic Studio &nbsp;·&nbsp; Issue #01</div>
343
+ <h1>Comic <span class='accent'>Book</span><br>Generator</h1>
344
+ <p>Type a story idea and get a full <b>{PAGES}-page, {TOTAL_PANELS}-panel</b> comic β€”
345
+ written by <b>Gemma&nbsp;4</b>, inked by <b>FLUX</b>, start to finish in about a minute.</p>
346
+ <div class='pills'>
347
+ <span class='pill'>{PAGES} Pages</span>
348
+ <span class='pill'>{TOTAL_PANELS} Panels</span>
349
+ <span class='pill'>Consistent Cast</span>
350
+ <span class='pill'>Live Timer</span>
351
+ </div>
352
+ </div>
353
+ """
354
+
355
+ _EXAMPLE_LABELS = ["🏚️ Lighthouse keeper", "πŸ€– Robot painter",
356
+ "πŸ—ΊοΈ Lost city", "🍜 Space-pirate noodle shop"]
357
+ _EXAMPLE_TEXTS = [
358
+ "On a storm-battered coast, the last lighthouse keeper guards a secret that washes ashore. Moody, atmospheric mystery.",
359
+ "A shy robot in a flooded future city learns to paint, and befriends a stray cat that changes everything.",
360
+ "A brave cartographer and her apprentice brave the jungle to find a legendary lost city.",
361
+ "A retired space-pirate runs a tiny noodle shop on a backwater moon, until her old crew comes calling.",
362
+ ]
363
+
364
+
365
+ def build_ui():
366
+ with gr.Blocks(title="Comic Book Generator") as demo:
367
+ comic_state = gr.State(None)
368
+ page_idx = gr.State(0)
369
+
370
+ # ── input / landing view ──
371
+ with gr.Column(visible=True, elem_id="landing") as input_view:
372
+ gr.HTML(_HERO)
373
+ idea = gr.Textbox(
374
+ show_label=False,
375
+ placeholder="Describe your comic… e.g. On a storm-battered coast, the "
376
+ "last lighthouse keeper guards a secret that washes ashore.",
377
+ lines=3, elem_id="idea_box",
378
+ )
379
+ with gr.Row(elem_id="examples"):
380
+ ex_btns = [gr.Button(lbl, size="sm") for lbl in _EXAMPLE_LABELS]
381
+ gen_btn = gr.Button("✨ Generate comic", variant="primary", size="lg",
382
+ elem_id="gen_btn")
383
+ status0 = gr.HTML(_progress_html("Describe a comic, or tap an example, to begin."))
384
+
385
+ # ── generation view (stopwatch) ──
386
+ with gr.Column(visible=False, elem_id="gen_col") as gen_view:
387
+ clock = gr.HTML(_clock_html(0.0, True))
388
+ status = gr.HTML(_progress_html("Starting…", 0.0))
389
+ live_img = gr.Image(type="pil", height=360, interactive=False,
390
+ show_label=True, label="latest panel", elem_id="live_img")
391
+
392
+ # ── reader view ──
393
+ with gr.Column(visible=False, elem_id="reader_col") as reader_view:
394
+ title_html = gr.HTML()
395
+ img1 = gr.Image(type="pil", height=380, interactive=False,
396
+ show_label=False, elem_id="panel_img")
397
+ cap1 = gr.HTML()
398
+ img2 = gr.Image(type="pil", height=380, interactive=False,
399
+ show_label=False, elem_id="panel_img")
400
+ cap2 = gr.HTML()
401
+ with gr.Row():
402
+ label = gr.HTML(_label_html(0))
403
+ prev_btn = gr.Button("←", scale=0, min_width=64)
404
+ next_btn = gr.Button("β†’", scale=0, min_width=64)
405
+ new_btn = gr.Button("οΌ‹ New comic", variant="secondary")
406
+
407
+ gen_timer = gr.Timer(0.1, active=False)
408
+
409
+ # wiring. status (gen view) shows live progress; status0 (input view) shows
410
+ # refusals/empty-input errors, since the gen view is hidden in those cases.
411
+ gen_outputs = [input_view, gen_view, reader_view, gen_btn, clock, status,
412
+ status0, live_img, gen_timer, title_html, img1, cap1, img2, cap2,
413
+ label, comic_state, page_idx]
414
+ gen_btn.click(on_generate, [idea], gen_outputs)
415
+ idea.submit(on_generate, [idea], gen_outputs)
416
+
417
+ # example chips populate the textbox (tap, then Generate)
418
+ for b, text in zip(ex_btns, _EXAMPLE_TEXTS):
419
+ b.click(lambda t=text: t, None, idea)
420
+
421
+ gen_timer.tick(on_clock_tick, None, [clock])
422
+
423
+ nav_outputs = [img1, cap1, img2, cap2, label, page_idx]
424
+ prev_btn.click(on_prev, [comic_state, page_idx], nav_outputs)
425
+ next_btn.click(on_next, [comic_state, page_idx], nav_outputs)
426
+
427
+ new_btn.click(on_new, None,
428
+ [input_view, gen_view, reader_view, idea, gen_btn, gen_timer,
429
+ live_img, comic_state, page_idx])
430
+
431
+ demo.load(on_warm, None, [status0])
432
+
433
+ return demo
434
+
435
+
436
+ if __name__ == "__main__":
437
+ demo = build_ui()
438
+ demo.queue(default_concurrency_limit=8).launch(
439
+ theme=gr.themes.Base(), css=CSS,
440
+ share=os.environ.get("COMIC_SHARE") == "1",
441
+ server_name=os.environ.get("GRADIO_SERVER_NAME", "127.0.0.1"),
442
+ )