prithivMLmods commited on
Commit
698c34a
·
verified ·
1 Parent(s): 645bfee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +428 -407
app.py CHANGED
@@ -10,17 +10,15 @@ import base64
10
  import json
11
  import html as html_lib
12
  from io import BytesIO
13
- from diffusers import Flux2KleinPipeline, AutoencoderKLFlux2
14
  from PIL import Image
15
- from typing import Iterable
 
16
 
17
  MAX_SEED = np.iinfo(np.int32).max
18
  MAX_IMAGE_SIZE = 1024
19
  dtype = torch.bfloat16
20
  device = "cuda" if torch.cuda.is_available() else "cpu"
21
 
22
- LANCZOS = getattr(Image, "Resampling", Image).LANCZOS
23
-
24
  print("Loading 4B Distilled model (Standard VAE)...")
25
  pipe_standard = Flux2KleinPipeline.from_pretrained(
26
  "black-forest-labs/FLUX.2-klein-4B",
@@ -49,6 +47,7 @@ EXAMPLES_CONFIG = [
49
  {"images": ["examples/4.jpg"], "prompt": "Make the texture high-resolution."},
50
  ]
51
 
 
52
 
53
  def make_thumb_b64(path, max_dim=220):
54
  if not os.path.exists(path):
@@ -63,7 +62,6 @@ def make_thumb_b64(path, max_dim=220):
63
  print(f"Thumbnail error for {path}: {e}")
64
  return ""
65
 
66
-
67
  def encode_full_image(path):
68
  if not os.path.exists(path):
69
  return ""
@@ -77,7 +75,6 @@ def encode_full_image(path):
77
  print(f"Encode error for {path}: {e}")
78
  return ""
79
 
80
-
81
  def build_example_cards_html():
82
  cards = ""
83
  for i, ex in enumerate(EXAMPLES_CONFIG):
@@ -100,7 +97,6 @@ def build_example_cards_html():
100
  </div>'''
101
  return cards
102
 
103
-
104
  def load_example_data(idx_str):
105
  try:
106
  idx = int(float(idx_str)) if idx_str and idx_str.strip() else -1
@@ -117,12 +113,10 @@ def load_example_data(idx_str):
117
  names.append(os.path.basename(path))
118
  return json.dumps({"images": b64_list, "prompt": ex["prompt"], "names": names, "status": "ok"})
119
 
120
-
121
  print("Building example thumbnails...")
122
  EXAMPLE_CARDS_HTML = build_example_cards_html()
123
  print(f"Built {len(EXAMPLES_CONFIG)} example cards.")
124
 
125
-
126
  def b64_to_pil_list(b64_json_str):
127
  if not b64_json_str or b64_json_str.strip() in ("", "[]"):
128
  return []
@@ -145,15 +139,6 @@ def b64_to_pil_list(b64_json_str):
145
  print(f"Error decoding image: {e}")
146
  return pil_images
147
 
148
-
149
- def format_time(seconds: float) -> str:
150
- if seconds < 60:
151
- return f"{seconds:.2f}s"
152
- minutes = int(seconds // 60)
153
- secs = seconds % 60
154
- return f"{minutes}m {secs:.2f}s"
155
-
156
-
157
  def update_dimensions_on_upload(pil_images):
158
  if not pil_images:
159
  return 1024, 1024
@@ -165,8 +150,14 @@ def update_dimensions_on_upload(pil_images):
165
  else:
166
  nh = 1024
167
  nw = int(1024 * iw / ih)
168
- return max(256, min(1024, (nw // 8) * 8)), max(256, min(1024, (nh // 8) * 8))
169
 
 
 
 
 
 
 
170
 
171
  @spaces.GPU(duration=240)
172
  def infer(
@@ -176,7 +167,7 @@ def infer(
176
  randomize_seed,
177
  width,
178
  height,
179
- steps,
180
  guidance_scale,
181
  progress=gr.Progress(track_tqdm=True),
182
  ):
@@ -197,48 +188,49 @@ def infer(
197
  prompt=prompt,
198
  height=height,
199
  width=width,
200
- num_inference_steps=steps,
201
  guidance_scale=guidance_scale,
202
  )
203
  if pil_images:
204
  shared_kwargs["image"] = pil_images
205
 
206
- progress(0.05, desc="Running Standard VAE generation...")
207
  t0_std = time.perf_counter()
208
  gen_std = torch.Generator(device="cpu").manual_seed(seed)
209
  out_standard = pipe_standard(**shared_kwargs, generator=gen_std).images[0]
210
- time_std_str = format_time(time.perf_counter() - t0_std)
 
211
  print(f"Standard VAE done in {time_std_str}")
212
 
 
213
  gc.collect()
214
  torch.cuda.empty_cache()
215
 
216
- progress(0.55, desc=f"Standard VAE done in {time_std_str} — now running Small Decoder VAE...")
217
  t0_small = time.perf_counter()
218
  gen_small = torch.Generator(device="cpu").manual_seed(seed)
219
  out_small = pipe_small_decoder(**shared_kwargs, generator=gen_small).images[0]
220
- time_small_str = format_time(time.perf_counter() - t0_small)
 
221
  print(f"Small Decoder VAE done in {time_small_str}")
222
 
 
223
  gc.collect()
224
  torch.cuda.empty_cache()
225
 
226
- label_std = f"Standard VAE — {time_std_str}"
227
- label_small = f"Small Decoder VAE — {time_small_str}"
228
 
229
  return out_standard, out_small, seed, label_std, label_small
230
 
231
-
232
- # ── SVG / Icon constants ───────────────────────────────────────────────────────
233
-
234
- FLUX_LOGO_SVG = '''<svg viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg">
235
- <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
236
- </svg>'''
237
 
238
  UPLOAD_SVG = '<svg class="tb-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>'
239
- REMOVE_SVG = '<svg class="tb-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>'
240
  CLEAR_SVG = '<svg class="tb-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>'
241
- DOWNLOAD_SVG = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 16l-5-5h3V4h4v7h3l-5 5z"/><path d="M20 18H4v2h16v-2z"/></svg>'
 
 
 
 
242
 
243
  css = r"""
244
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap');
@@ -258,18 +250,21 @@ footer{display:none!important}
258
  position:absolute;left:-9999px;top:-9999px;width:1px;height:1px;opacity:0.01;pointer-events:none;
259
  }
260
 
 
261
  .app-shell{
262
  background:#18181b;border:1px solid #27272a;border-radius:16px;
263
  margin:12px auto;max-width:1500px;overflow:hidden;
264
  box-shadow:0 25px 50px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.03);
265
  }
 
 
266
  .app-header{
267
  background:linear-gradient(135deg,#18181b,#1e1e24);border-bottom:1px solid #27272a;
268
  padding:14px 24px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;
269
  }
270
  .app-header-left{display:flex;align-items:center;gap:12px}
271
  .app-logo{
272
- width:36px;height:36px;background:linear-gradient(135deg,#FF6B00,#FF4500,#FF8C00);
273
  border-radius:10px;display:flex;align-items:center;justify-content:center;
274
  box-shadow:0 4px 12px rgba(255,69,0,.35);
275
  }
@@ -280,11 +275,12 @@ footer{display:none!important}
280
  }
281
  .app-badge{
282
  font-size:11px;font-weight:600;padding:3px 10px;border-radius:20px;
283
- background:rgba(255,69,0,.15);color:#FF8C00;border:1px solid rgba(255,69,0,.25);letter-spacing:.3px;
284
  }
285
- .app-badge.std{background:rgba(30,100,255,.12);color:#60A5FA;border:1px solid rgba(30,100,255,.25)}
286
- .app-badge.small{background:rgba(34,197,94,.12);color:#4ade80;border:1px solid rgba(34,197,94,.25)}
287
 
 
288
  .app-toolbar{
289
  background:#18181b;border-bottom:1px solid #27272a;padding:8px 16px;
290
  display:flex;gap:4px;align-items:center;flex-wrap:wrap;
@@ -304,26 +300,28 @@ footer{display:none!important}
304
  .modern-tb-btn .tb-svg,.modern-tb-btn .tb-svg *{stroke:#ffffff!important;fill:none!important}
305
  .tb-info{font-family:'JetBrains Mono',monospace;font-size:12px;color:#71717a;padding:0 8px;display:flex;align-items:center}
306
 
 
307
  .app-main-row{display:flex;gap:0;flex:1;overflow:hidden}
308
- .app-main-left{flex:0 0 360px;display:flex;flex-direction:column;min-width:0;border-right:1px solid #27272a;background:#18181b}
309
- .app-main-right{flex:1;display:flex;flex-direction:column;min-width:0;background:#0f0f13}
310
 
311
- /* ── Input panel ── */
312
- #gallery-drop-zone{position:relative;background:#09090b;min-height:260px;overflow:auto}
313
  #gallery-drop-zone.drag-over{outline:2px solid #FF4500;outline-offset:-2px;background:rgba(255,69,0,.04)}
314
- .upload-prompt-modern{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:20;width:90%}
315
  .upload-click-area{
316
  display:flex;flex-direction:column;align-items:center;justify-content:center;
317
- cursor:pointer;padding:28px 24px;border:2px dashed #3f3f46;border-radius:16px;
318
  background:rgba(255,69,0,.03);transition:all .2s ease;gap:8px;
319
  }
320
- .upload-click-area:hover{background:rgba(255,69,0,.08);border-color:#FF4500;transform:scale(1.02)}
321
- .upload-click-area svg{width:64px;height:64px}
322
- .upload-main-text{color:#71717a;font-size:13px;font-weight:500;margin-top:4px;text-align:center}
323
- .upload-sub-text{color:#52525b;font-size:11px;text-align:center}
 
324
  .image-gallery-grid{
325
- display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));
326
- gap:10px;padding:12px;align-content:start;
327
  }
328
  .gallery-thumb{
329
  position:relative;aspect-ratio:1;border-radius:10px;overflow:hidden;
@@ -352,26 +350,72 @@ footer{display:none!important}
352
  .gallery-add-card .add-icon{font-size:28px;color:#71717a;font-weight:300}
353
  .gallery-add-card .add-text{font-size:12px;color:#71717a;font-weight:500}
354
 
 
355
  .hint-bar{
356
  background:rgba(255,69,0,.06);border-top:1px solid #27272a;border-bottom:1px solid #27272a;
357
- padding:8px 16px;font-size:12px;color:#a1a1aa;line-height:1.6;
358
  }
359
- .hint-bar b{color:#FF8C00;font-weight:600}
360
  .hint-bar kbd{
361
  display:inline-block;padding:1px 6px;background:#27272a;border:1px solid #3f3f46;
362
  border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:11px;color:#a1a1aa;
363
  }
364
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  .panel-card{border-bottom:1px solid #27272a}
366
  .panel-card-title{
367
- padding:10px 16px;font-size:12px;font-weight:600;color:#71717a;
368
  text-transform:uppercase;letter-spacing:.8px;border-bottom:1px solid rgba(39,39,42,.6);
369
  }
370
- .panel-card-body{padding:12px 16px;display:flex;flex-direction:column;gap:8px}
371
  .modern-label{font-size:13px;font-weight:500;color:#a1a1aa;margin-bottom:4px;display:block}
372
  .modern-textarea{
373
  width:100%;background:#09090b;border:1px solid #27272a;border-radius:8px;
374
- padding:10px 14px;font-family:'Inter',sans-serif;font-size:13px;color:#e4e4e7;
375
  resize:vertical;outline:none;min-height:80px;transition:border-color .2s;
376
  }
377
  .modern-textarea:focus{border-color:#FF4500;box-shadow:0 0 0 3px rgba(255,69,0,.15)}
@@ -381,170 +425,120 @@ footer{display:none!important}
381
  }
382
  @keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-4px)}40%,80%{transform:translateX(4px)}}
383
 
 
384
  .btn-run{
385
  display:flex;align-items:center;justify-content:center;gap:8px;width:100%;
386
- background:linear-gradient(135deg,#FF4500,#CC3700);border:none;border-radius:10px;
387
  padding:12px 24px;cursor:pointer;font-size:15px;font-weight:600;font-family:'Inter',sans-serif;
388
- color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;transition:all .2s ease;
389
  box-shadow:0 4px 16px rgba(255,69,0,.3),inset 0 1px 0 rgba(255,255,255,.1);
390
  }
391
  .btn-run:hover{
392
- background:linear-gradient(135deg,#FF6B00,#FF4500);transform:translateY(-1px);
393
  box-shadow:0 6px 24px rgba(255,69,0,.45),inset 0 1px 0 rgba(255,255,255,.15);
394
  }
395
  .btn-run:active{transform:translateY(0);box-shadow:0 2px 8px rgba(255,69,0,.3)}
396
  .btn-run svg{width:18px;height:18px;fill:#ffffff!important}
397
 
398
- /* ── Output panels ── */
399
- .output-area{flex:1;display:flex;flex-direction:column;gap:0;overflow:hidden}
400
- .output-cols{display:flex;flex:1;gap:0;overflow:hidden}
401
- .output-col{
402
- flex:1;display:flex;flex-direction:column;border-left:1px solid #27272a;
403
- background:#0f0f13;min-width:0;
404
- }
405
- .output-col:first-child{border-left:none}
406
- .out-col-header{
407
- padding:10px 16px;border-bottom:1px solid #27272a;
408
- display:flex;align-items:center;justify-content:space-between;background:#18181b;
409
  }
410
- .out-col-title{font-size:12px;font-weight:700;letter-spacing:.5px;text-transform:uppercase}
411
- .out-col-title.std{color:#60A5FA}
412
- .out-col-title.small{color:#4ade80}
413
- .out-col-badge{
414
- font-size:10px;font-weight:600;padding:2px 8px;border-radius:10px;
415
- font-family:'JetBrains Mono',monospace;white-space:nowrap;
 
 
 
 
 
416
  }
417
- .out-col-badge.std{background:rgba(30,100,255,.12);color:#60A5FA;border:1px solid rgba(30,100,255,.25)}
418
- .out-col-badge.small{background:rgba(34,197,94,.12);color:#4ade80;border:1px solid rgba(34,197,94,.25)}
419
- .out-col-body{
420
- flex:1;background:#09090b;display:flex;align-items:center;justify-content:center;
421
- overflow:hidden;min-height:300px;position:relative;
422
  }
423
- .out-col-body img.modern-out-img{max-width:100%;max-height:500px;image-rendering:auto;display:block}
424
- .out-col-placeholder{color:#3f3f46;font-size:12px;text-align:center;padding:16px}
425
- .out-col-footer{
426
- padding:8px 16px;border-top:1px solid #27272a;background:#18181b;
427
- display:flex;align-items:center;justify-content:space-between;min-height:38px;
 
428
  }
429
- .timing-display{
430
- font-family:'JetBrains Mono',monospace;font-size:11px;color:#71717a;
 
 
 
 
431
  }
432
  .out-download-btn{
433
  display:none;align-items:center;justify-content:center;background:rgba(255,69,0,.1);
434
  border:1px solid rgba(255,69,0,.2);border-radius:6px;cursor:pointer;padding:3px 10px;
435
- font-size:11px;font-weight:500;color:#FF8C00!important;gap:4px;height:24px;transition:all .15s;
436
  }
437
  .out-download-btn:hover{background:rgba(255,69,0,.2);border-color:rgba(255,69,0,.35);color:#ffffff!important}
438
  .out-download-btn.visible{display:inline-flex}
439
- .out-download-btn svg{width:12px;height:12px;fill:#FF8C00}
440
 
441
  /* ── Loader ── */
442
  .modern-loader{
443
  display:none;position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(9,9,11,.92);
444
- z-index:15;flex-direction:column;align-items:center;justify-content:center;gap:16px;backdrop-filter:blur(4px);
445
  }
446
  .modern-loader.active{display:flex}
447
  .modern-loader .loader-spinner{
448
- width:36px;height:36px;border:3px solid #27272a;border-top-color:#FF4500;
449
  border-radius:50%;animation:spin .8s linear infinite;
450
  }
451
  @keyframes spin{to{transform:rotate(360deg)}}
452
- .modern-loader .loader-text{font-size:12px;color:#a1a1aa;font-weight:500;text-align:center}
453
- .loader-bar-track{width:160px;height:4px;background:#27272a;border-radius:2px;overflow:hidden}
454
  .loader-bar-fill{
455
- height:100%;background:linear-gradient(90deg,#FF4500,#FF8C00,#FF4500);
456
  background-size:200% 100%;animation:shimmer 1.5s ease-in-out infinite;border-radius:2px;
457
  }
458
  @keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
459
 
460
- /* ── Toast ── */
461
- .toast-notification{
462
- position:fixed;top:24px;left:50%;transform:translateX(-50%) translateY(-120%);
463
- z-index:9999;padding:10px 24px;border-radius:10px;font-family:'Inter',sans-serif;
464
- font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px;
465
- box-shadow:0 8px 24px rgba(0,0,0,.5);
466
- transition:transform .35s cubic-bezier(.34,1.56,.64,1),opacity .35s ease;opacity:0;pointer-events:none;
467
- }
468
- .toast-notification.visible{transform:translateX(-50%) translateY(0);opacity:1;pointer-events:auto}
469
- .toast-notification.error{background:linear-gradient(135deg,#dc2626,#b91c1c);color:#fff;border:1px solid rgba(255,255,255,.15)}
470
- .toast-notification.warning{background:linear-gradient(135deg,#d97706,#b45309);color:#fff;border:1px solid rgba(255,255,255,.15)}
471
- .toast-notification.info{background:linear-gradient(135deg,#FF4500,#CC3700);color:#fff;border:1px solid rgba(255,255,255,.15)}
472
- .toast-notification .toast-icon{font-size:16px;line-height:1}
473
- .toast-notification .toast-text{line-height:1.3}
474
-
475
- /* ── Settings ── */
476
- .settings-group{border:1px solid #27272a;border-radius:10px;margin:12px 16px;padding:0;overflow:hidden}
477
  .settings-group-title{
478
  font-size:12px;font-weight:600;color:#71717a;text-transform:uppercase;letter-spacing:.8px;
479
- padding:8px 14px;border-bottom:1px solid #27272a;background:rgba(24,24,27,.5);
480
  }
481
- .settings-group-body{padding:12px 14px;display:flex;flex-direction:column;gap:10px}
482
- .slider-row{display:flex;align-items:center;gap:10px;min-height:26px}
483
- .slider-row label{font-size:12px;font-weight:500;color:#a1a1aa;min-width:80px;flex-shrink:0}
484
  .slider-row input[type="range"]{
485
  flex:1;-webkit-appearance:none;appearance:none;height:6px;background:#27272a;
486
  border-radius:3px;outline:none;min-width:0;
487
  }
488
  .slider-row input[type="range"]::-webkit-slider-thumb{
489
- -webkit-appearance:none;width:16px;height:16px;background:linear-gradient(135deg,#FF4500,#CC3700);
490
  border-radius:50%;cursor:pointer;box-shadow:0 2px 6px rgba(255,69,0,.4);transition:transform .15s;
491
  }
492
  .slider-row input[type="range"]::-webkit-slider-thumb:hover{transform:scale(1.2)}
493
  .slider-row input[type="range"]::-moz-range-thumb{
494
- width:16px;height:16px;background:linear-gradient(135deg,#FF4500,#CC3700);
495
  border-radius:50%;cursor:pointer;border:none;
496
  }
497
  .slider-row .slider-val{
498
- min-width:52px;text-align:right;font-family:'JetBrains Mono',monospace;font-size:11px;
499
  font-weight:500;padding:3px 8px;background:#09090b;border:1px solid #27272a;
500
  border-radius:6px;color:#a1a1aa;flex-shrink:0;
501
  }
502
  .checkbox-row{display:flex;align-items:center;gap:8px;font-size:13px;color:#a1a1aa}
503
  .checkbox-row input[type="checkbox"]{accent-color:#FF4500;width:16px;height:16px;cursor:pointer}
 
504
 
505
- /* ── Suggestions / Examples ── */
506
- .suggestions-section{border-top:1px solid #27272a;padding:10px 14px}
507
- .suggestions-title,.examples-title{
508
- font-size:11px;font-weight:600;color:#71717a;text-transform:uppercase;
509
- letter-spacing:.8px;margin-bottom:8px;
510
- }
511
- .suggestions-wrap{display:flex;flex-wrap:wrap;gap:5px}
512
- .suggestion-chip{
513
- display:inline-flex;align-items:center;gap:4px;padding:4px 10px;
514
- background:rgba(255,69,0,.08);border:1px solid rgba(255,69,0,.2);border-radius:20px;
515
- color:#FF8C00;font-size:11px;font-weight:500;font-family:'Inter',sans-serif;
516
- cursor:pointer;transition:all .15s;white-space:nowrap;
517
- }
518
- .suggestion-chip:hover{background:rgba(255,69,0,.15);border-color:rgba(255,69,0,.35);color:#FFB366;transform:translateY(-1px)}
519
-
520
- .examples-section{border-top:1px solid #27272a;padding:10px 14px}
521
- .examples-scroll{display:flex;gap:8px;overflow-x:auto;padding-bottom:6px}
522
- .examples-scroll::-webkit-scrollbar{height:4px}
523
- .examples-scroll::-webkit-scrollbar-track{background:#09090b;border-radius:3px}
524
- .examples-scroll::-webkit-scrollbar-thumb{background:#27272a;border-radius:3px}
525
- .example-card{
526
- flex-shrink:0;width:180px;background:#09090b;border:1px solid #27272a;
527
- border-radius:10px;overflow:hidden;cursor:pointer;transition:all .2s ease;
528
- }
529
- .example-card:hover{border-color:#FF4500;transform:translateY(-2px);box-shadow:0 4px 12px rgba(255,69,0,.15)}
530
- .example-card.loading{opacity:.5;pointer-events:none}
531
- .example-thumbs{display:flex;height:90px;overflow:hidden;background:#18181b}
532
- .example-thumbs img{flex:1;object-fit:cover;min-width:0;border-bottom:1px solid #27272a}
533
- .example-thumb-placeholder{
534
- flex:1;display:flex;align-items:center;justify-content:center;
535
- background:#18181b;color:#3f3f46;font-size:11px;min-width:0;
536
- }
537
- .example-meta{padding:5px 8px;display:flex;align-items:center;gap:6px}
538
- .example-badge{
539
- display:inline-flex;padding:2px 6px;background:rgba(255,69,0,.1);border-radius:4px;
540
- font-size:10px;font-weight:600;color:#FF8C00;font-family:'JetBrains Mono',monospace;white-space:nowrap;
541
- }
542
- .example-prompt-text{
543
- padding:0 8px 7px;font-size:10px;color:#a1a1aa;line-height:1.4;
544
- display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
545
- }
546
-
547
- /* ── Statusbar ── */
548
  .app-statusbar{
549
  background:#18181b;border-top:1px solid #27272a;padding:6px 20px;
550
  display:flex;gap:12px;height:34px;align-items:center;font-size:12px;
@@ -555,10 +549,10 @@ footer{display:none!important}
555
  }
556
  .app-statusbar .sb-section.sb-fixed{
557
  flex:0 0 auto;min-width:90px;text-align:center;justify-content:center;
558
- padding:3px 12px;background:rgba(255,69,0,.08);border-radius:6px;color:#FF8C00;font-weight:500;
559
  }
560
  .exp-note{padding:10px 20px;font-size:12px;color:#52525b;border-top:1px solid #27272a;text-align:center}
561
- .exp-note a{color:#FF8C00;text-decoration:none}
562
  .exp-note a:hover{text-decoration:underline}
563
 
564
  ::-webkit-scrollbar{width:8px;height:8px}
@@ -568,12 +562,14 @@ footer{display:none!important}
568
 
569
  @media(max-width:900px){
570
  .app-main-row{flex-direction:column}
571
- .app-main-left{flex:0 0 auto;border-right:none;border-bottom:1px solid #27272a}
572
- .output-cols{flex-direction:column}
573
- .output-col{border-left:none;border-top:1px solid #27272a}
574
  }
575
  """
576
 
 
 
577
  gallery_js = r"""
578
  () => {
579
  function init() {
@@ -613,7 +609,9 @@ function init() {
613
  const icon = toast.querySelector('.toast-icon');
614
  const text = toast.querySelector('.toast-text');
615
  toast.className = 'toast-notification ' + (type || 'error');
616
- icon.textContent = type === 'warning' ? '\u26A0' : type === 'info' ? '\u2139' : '\u2717';
 
 
617
  text.textContent = message;
618
  if (toastTimer) clearTimeout(toastTimer);
619
  void toast.offsetWidth;
@@ -660,11 +658,11 @@ function init() {
660
  const n = images.length;
661
  const txt = n > 0 ? n + ' image' + (n > 1 ? 's' : '') : 'No images';
662
  if (imgCountTb) imgCountTb.textContent = txt;
663
- if (imgCountSb) imgCountSb.textContent = n > 0 ? txt + ' uploaded' : 'No images uploaded';
664
  }
665
 
666
  function addImage(b64, name) {
667
- images.push({id: Date.now() + Math.random(), b64, name});
668
  renderGallery();
669
  syncImagesToGradio();
670
  }
@@ -687,6 +685,11 @@ function init() {
687
  }
688
  window.__clearAll = clearAll;
689
 
 
 
 
 
 
690
  function renderGallery() {
691
  if (images.length === 0) {
692
  galleryGrid.innerHTML = '';
@@ -696,30 +699,30 @@ function init() {
696
  }
697
  if (uploadPrompt) uploadPrompt.style.display = 'none';
698
  galleryGrid.style.display = 'grid';
699
-
700
  let html = '';
701
  images.forEach((img, i) => {
702
  const sel = i === selectedIdx ? ' selected' : '';
703
- html += `<div class="gallery-thumb${sel}" data-idx="${i}">
704
- <img src="${img.b64}" alt="${img.name||'image'}">
705
- <span class="thumb-badge">#${i+1}</span>
706
- <button class="thumb-remove" data-remove="${i}">\u2715</button>
707
- </div>`;
708
  });
709
- html += `<div class="gallery-add-card" id="gallery-add-card">
710
- <span class="add-icon">+</span><span class="add-text">Add</span>
711
- </div>`;
712
  galleryGrid.innerHTML = html;
713
 
714
  galleryGrid.querySelectorAll('.gallery-thumb').forEach(thumb => {
715
- thumb.addEventListener('click', e => {
716
  if (e.target.closest('.thumb-remove')) return;
717
- selectedIdx = selectedIdx === parseInt(thumb.dataset.idx) ? -1 : parseInt(thumb.dataset.idx);
718
- renderGallery();
719
  });
720
  });
721
  galleryGrid.querySelectorAll('.thumb-remove').forEach(btn => {
722
- btn.addEventListener('click', e => { e.stopPropagation(); removeImage(parseInt(btn.dataset.remove)); });
 
 
 
723
  });
724
  const addCard = document.getElementById('gallery-add-card');
725
  if (addCard) addCard.addEventListener('click', () => fileInput.click());
@@ -729,20 +732,22 @@ function init() {
729
  Array.from(files).forEach(file => {
730
  if (!file.type.startsWith('image/')) return;
731
  const reader = new FileReader();
732
- reader.onload = e => addImage(e.target.result, file.name);
733
  reader.readAsDataURL(file);
734
  });
735
  }
736
 
737
- fileInput.addEventListener('change', e => { processFiles(e.target.files); e.target.value = ''; });
738
  if (uploadClick) uploadClick.addEventListener('click', () => fileInput.click());
739
  if (btnUpload) btnUpload.addEventListener('click', () => fileInput.click());
740
- if (btnRemove) btnRemove.addEventListener('click', () => { if (selectedIdx >= 0) removeImage(selectedIdx); });
 
 
741
  if (btnClear) btnClear.addEventListener('click', clearAll);
742
 
743
- dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('drag-over'); });
744
- dropZone.addEventListener('dragleave', e => { e.preventDefault(); dropZone.classList.remove('drag-over'); });
745
- dropZone.addEventListener('drop', e => {
746
  e.preventDefault(); dropZone.classList.remove('drag-over');
747
  if (e.dataTransfer.files.length) processFiles(e.dataTransfer.files);
748
  });
@@ -753,7 +758,7 @@ function init() {
753
  if (promptInput) { promptInput.value = text; syncPromptToGradio(); }
754
  };
755
 
756
- // Example cards
757
  document.querySelectorAll('.example-card[data-idx]').forEach(card => {
758
  card.addEventListener('click', () => {
759
  const idx = card.getAttribute('data-idx');
@@ -770,13 +775,13 @@ function init() {
770
  });
771
  });
772
 
773
- // Sliders
774
  function syncSlider(customId, gradioId) {
775
  const slider = document.getElementById(customId);
776
  const valSpan = document.getElementById(customId + '-val');
777
  if (!slider) return;
778
  slider.addEventListener('input', () => {
779
- if (valSpan) valSpan.textContent = slider.value;
780
  const container = document.getElementById(gradioId);
781
  if (!container) return;
782
  container.querySelectorAll('input[type="range"],input[type="number"]').forEach(el => {
@@ -789,34 +794,43 @@ function init() {
789
  });
790
  });
791
  }
792
- syncSlider('custom-seed', 'gradio-seed');
793
  syncSlider('custom-guidance', 'gradio-guidance');
794
- syncSlider('custom-steps', 'gradio-steps');
795
- syncSlider('custom-width', 'gradio-width');
796
- syncSlider('custom-height', 'gradio-height');
797
 
798
  const randCheck = document.getElementById('custom-randomize');
799
- if (randCheck) randCheck.addEventListener('change', () => {
800
- const container = document.getElementById('gradio-randomize');
801
- if (!container) return;
802
- const cb = container.querySelector('input[type="checkbox"]');
803
- if (cb && cb.checked !== randCheck.checked) cb.click();
804
- });
 
 
805
 
806
- // Loader
807
  function showLoader() {
808
- document.querySelectorAll('.modern-loader').forEach(l => l.classList.add('active'));
 
 
 
809
  const sb = document.querySelector('.sb-fixed');
810
  if (sb) sb.textContent = 'Processing...';
811
  }
812
  function hideLoader() {
813
- document.querySelectorAll('.modern-loader').forEach(l => l.classList.remove('active'));
 
 
 
814
  const sb = document.querySelector('.sb-fixed');
815
  if (sb) sb.textContent = 'Done';
816
  }
817
  window.__showLoader = showLoader;
818
  window.__hideLoader = hideLoader;
819
 
 
820
  function validateBeforeRun() {
821
  const promptVal = promptInput ? promptInput.value.trim() : '';
822
  if (!promptVal) { showToast('Please enter a prompt', 'warning'); flashPromptError(); return false; }
@@ -846,79 +860,77 @@ init();
846
  wire_outputs_js = r"""
847
  () => {
848
  function watchOutputs() {
849
- // Watch both result containers
850
- const containers = [
851
- {id: 'gradio-result-std', bodyId: 'output-std-body', dlId: 'dl-btn-std', timingId: 'timing-std', suffix: 'std_output.png'},
852
- {id: 'gradio-result-small', bodyId: 'output-small-body', dlId: 'dl-btn-small', timingId: 'timing-small', suffix: 'small_output.png'},
853
- ];
854
-
855
- let ready = containers.every(c => document.getElementById(c.id) && document.getElementById(c.bodyId));
856
- if (!ready) { setTimeout(watchOutputs, 500); return; }
857
-
858
- containers.forEach(c => {
859
- const dlBtn = document.getElementById(c.dlId);
860
- if (dlBtn) {
861
- dlBtn.addEventListener('click', e => {
862
- e.stopPropagation();
863
- const img = document.getElementById(c.bodyId).querySelector('img.modern-out-img');
864
- if (img && img.src) {
865
- const a = document.createElement('a');
866
- a.href = img.src; a.download = c.suffix;
867
- document.body.appendChild(a); a.click(); document.body.removeChild(a);
868
- }
869
- });
870
- }
871
- });
872
 
873
- function syncImage(c) {
874
- const resultContainer = document.getElementById(c.id);
875
- const outBody = document.getElementById(c.bodyId);
876
- if (!resultContainer || !outBody) return;
877
- const resultImg = resultContainer.querySelector('img');
878
  if (resultImg && resultImg.src) {
879
- const ph = outBody.querySelector('.out-col-placeholder');
880
  if (ph) ph.style.display = 'none';
881
- let existing = outBody.querySelector('img.modern-out-img');
882
- if (!existing) { existing = document.createElement('img'); existing.className = 'modern-out-img'; outBody.appendChild(existing); }
883
  if (existing.src !== resultImg.src) {
884
  existing.src = resultImg.src;
885
- const dlBtn = document.getElementById(c.dlId);
886
  if (dlBtn) dlBtn.classList.add('visible');
887
- if (window.__hideLoader) window.__hideLoader();
 
888
  }
889
  }
890
  }
891
 
892
- containers.forEach(c => {
893
- const resultContainer = document.getElementById(c.id);
894
- if (!resultContainer) return;
895
- const observer = new MutationObserver(() => syncImage(c));
896
- observer.observe(resultContainer, {childList:true, subtree:true, attributes:true, attributeFilter:['src']});
897
- setInterval(() => syncImage(c), 800);
898
- });
 
 
899
  }
900
  watchOutputs();
901
 
902
  function watchTimings() {
903
- const timingMap = [
904
- {gradioId: 'gradio-timing-std', displayId: 'timing-std-display'},
905
- {gradioId: 'gradio-timing-small', displayId: 'timing-small-display'},
906
- ];
907
- timingMap.forEach(t => {
908
- function sync() {
909
- const container = document.getElementById(t.gradioId);
910
- const display = document.getElementById(t.displayId);
911
- if (!container || !display) return;
912
- const el = container.querySelector('textarea, input');
913
- if (el && el.value && el.value.trim()) display.textContent = el.value;
914
- }
915
- const container = document.getElementById(t.gradioId);
916
- if (container) {
917
- const obs = new MutationObserver(sync);
918
- obs.observe(container, {childList:true, subtree:true, characterData:true, attributes:true});
919
- }
920
- setInterval(sync, 600);
921
- });
922
  }
923
  watchTimings();
924
 
@@ -931,8 +943,7 @@ function watchSeed() {
931
  const el = seedContainer.querySelector('input[type="range"],input[type="number"]');
932
  if (el && el.value) { seedSlider.value = el.value; if (seedVal) seedVal.textContent = el.value; }
933
  }
934
- const obs = new MutationObserver(sync);
935
- obs.observe(seedContainer, {childList:true, subtree:true, attributes:true});
936
  setInterval(sync, 1000);
937
  }
938
  watchSeed();
@@ -964,59 +975,61 @@ function watchExampleResults() {
964
  document.querySelectorAll('.example-card.loading').forEach(c => c.classList.remove('loading'));
965
  if (window.__showToast) window.__showToast('Could not load example images', 'error');
966
  }
967
- } catch(e) {}
968
  }
969
- const obs = new MutationObserver(checkResult);
970
- obs.observe(container, {childList:true, subtree:true, characterData:true, attributes:true});
971
  setInterval(checkResult, 500);
972
  }
973
  watchExampleResults();
974
  }
975
  """
976
 
 
 
977
  with gr.Blocks(css=css) as demo:
978
 
979
- # ── Hidden Gradio state ───────────────────────────────────────────────────
980
- hidden_images_b64 = gr.Textbox(value="[]", elem_id="hidden-images-b64", elem_classes="hidden-input", container=False)
981
- prompt_hidden = gr.Textbox(value="", elem_id="prompt-gradio-input", elem_classes="hidden-input", container=False)
982
- seed_slider = gr.Slider(minimum=0, maximum=MAX_SEED, step=1, value=0, elem_id="gradio-seed", elem_classes="hidden-input", container=False)
983
- randomize_seed = gr.Checkbox(value=True, elem_id="gradio-randomize", elem_classes="hidden-input", container=False)
984
- guidance_scale = gr.Slider(minimum=0.0, maximum=10.0, step=0.1, value=1.0, elem_id="gradio-guidance", elem_classes="hidden-input", container=False)
985
- steps_slider = gr.Slider(minimum=1, maximum=20, step=1, value=4, elem_id="gradio-steps", elem_classes="hidden-input", container=False)
986
- width_slider = gr.Slider(minimum=256, maximum=1024, step=8, value=1024, elem_id="gradio-width", elem_classes="hidden-input", container=False)
987
- height_slider = gr.Slider(minimum=256, maximum=1024, step=8, value=1024, elem_id="gradio-height", elem_classes="hidden-input", container=False)
988
-
989
- result_std = gr.Image(elem_id="gradio-result-std", elem_classes="hidden-input", container=False, format="png")
990
- result_small_dec = gr.Image(elem_id="gradio-result-small", elem_classes="hidden-input", container=False, format="png")
991
- seed_used = gr.Number(value=0, elem_id="gradio-seed-used", elem_classes="hidden-input", container=False)
992
- timing_std_hidden = gr.Textbox(value="", elem_id="gradio-timing-std", elem_classes="hidden-input", container=False)
993
- timing_small_hidden= gr.Textbox(value="", elem_id="gradio-timing-small", elem_classes="hidden-input", container=False)
994
-
995
- example_idx = gr.Textbox(value="", elem_id="example-idx-input", elem_classes="hidden-input", container=False)
996
- example_result = gr.Textbox(value="", elem_id="example-result-data", elem_classes="hidden-input", container=False)
997
- example_load_btn = gr.Button("Load Example", elem_id="example-load-btn")
998
 
999
  gr.HTML(f"""
1000
  <div class="app-shell">
1001
 
 
1002
  <div class="app-header">
1003
  <div class="app-header-left">
1004
- <div class="app-logo">{FLUX_LOGO_SVG}</div>
1005
- <span class="app-title">FLUX.2-klein-4B Comparator</span>
1006
- <span class="app-badge">v1.0</span>
1007
- <span class="app-badge std">Standard VAE</span>
1008
- <span class="app-badge small">Small Decoder</span>
1009
  </div>
1010
  </div>
1011
 
 
1012
  <div class="app-toolbar">
1013
- <button id="tb-upload" class="modern-tb-btn" title="Upload images">
1014
  {UPLOAD_SVG}<span class="tb-label">Upload</span>
1015
  </button>
1016
- <button id="tb-remove" class="modern-tb-btn" title="Remove selected">
1017
  {REMOVE_SVG}<span class="tb-label">Remove</span>
1018
  </button>
1019
- <button id="tb-clear" class="modern-tb-btn" title="Clear all">
1020
  {CLEAR_SVG}<span class="tb-label">Clear All</span>
1021
  </button>
1022
  <div class="tb-sep"></div>
@@ -1025,7 +1038,7 @@ with gr.Blocks(css=css) as demo:
1025
 
1026
  <div class="app-main-row">
1027
 
1028
- <!-- LEFT: Input Panel -->
1029
  <div class="app-main-left">
1030
 
1031
  <div id="gallery-drop-zone">
@@ -1036,8 +1049,8 @@ with gr.Blocks(css=css) as demo:
1036
  <polygon points="12,62 30,40 42,50 54,34 68,62" fill="rgba(255,69,0,0.15)" stroke="#FF4500" stroke-width="1.5"/>
1037
  <circle cx="28" cy="30" r="6" fill="rgba(255,69,0,0.2)" stroke="#FF4500" stroke-width="1.5"/>
1038
  </svg>
1039
- <span class="upload-main-text">Click or drag image here</span>
1040
- <span class="upload-sub-text">Optional reference image for editing tasks</span>
1041
  </div>
1042
  </div>
1043
  <input id="custom-file-input" type="file" accept="image/*" multiple style="display:none;" />
@@ -1045,49 +1058,105 @@ with gr.Blocks(css=css) as demo:
1045
  </div>
1046
 
1047
  <div class="hint-bar">
1048
- <b>Optional:</b> Upload a reference image for editing &nbsp;&middot;&nbsp;
1049
- Text-to-image also works without any input &nbsp;&middot;&nbsp;
1050
- <kbd>Clear All</kbd> to reset
1051
  </div>
1052
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1053
  <div class="panel-card">
1054
- <div class="panel-card-title">Prompt</div>
1055
  <div class="panel-card-body">
 
1056
  <textarea id="custom-prompt-input" class="modern-textarea" rows="4"
1057
  placeholder="e.g., Transform the scene into a snowy winter day..."></textarea>
1058
  </div>
1059
  </div>
1060
 
1061
- <div style="padding:12px 16px;">
1062
  <button id="custom-run-btn" class="btn-run">
1063
  <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
1064
- <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" fill="white"/>
1065
  </svg>
1066
- <span>Run Comparison</span>
1067
  </button>
1068
  </div>
1069
 
1070
- <div class="suggestions-section">
1071
- <div class="suggestions-title">Quick Prompts</div>
1072
- <div class="suggestions-wrap">
1073
- <button class="suggestion-chip" onclick="window.__setPrompt('Change the weather to stormy.')">Stormy Weather</button>
1074
- <button class="suggestion-chip" onclick="window.__setPrompt('Transform the scene into a snowy winter day.')">Snowy Winter</button>
1075
- <button class="suggestion-chip" onclick="window.__setPrompt('Relight with soft golden sunset lighting.')">Golden Sunset</button>
1076
- <button class="suggestion-chip" onclick="window.__setPrompt('Make the texture high-resolution and detailed.')">HD Texture</button>
1077
- <button class="suggestion-chip" onclick="window.__setPrompt('Convert to oil painting style.')">Oil Painting</button>
1078
- <button class="suggestion-chip" onclick="window.__setPrompt('Add cinematic film grain and color grading.')">Cinematic</button>
1079
- <button class="suggestion-chip" onclick="window.__setPrompt('Transform into anime illustration style.')">Anime Style</button>
1080
- <button class="suggestion-chip" onclick="window.__setPrompt('Apply dramatic noir black and white style.')">Noir B&W</button>
1081
- </div>
1082
- </div>
1083
 
1084
- <div class="examples-section">
1085
- <div class="examples-title">Quick Examples</div>
1086
- <div class="examples-scroll">
1087
- {EXAMPLE_CARDS_HTML}
1088
- </div>
1089
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1090
 
 
1091
  <div class="settings-group">
1092
  <div class="settings-group-title">Advanced Settings</div>
1093
  <div class="settings-group-body">
@@ -1098,7 +1167,7 @@ with gr.Blocks(css=css) as demo:
1098
  </div>
1099
  <div class="checkbox-row">
1100
  <input type="checkbox" id="custom-randomize" checked>
1101
- <label for="custom-randomize">Randomize seed</label>
1102
  </div>
1103
  <div class="slider-row">
1104
  <label>Guidance</label>
@@ -1110,88 +1179,38 @@ with gr.Blocks(css=css) as demo:
1110
  <input type="range" id="custom-steps" min="1" max="20" step="1" value="4">
1111
  <span class="slider-val" id="custom-steps-val">4</span>
1112
  </div>
1113
- <div class="slider-row">
1114
- <label>Width</label>
1115
- <input type="range" id="custom-width" min="256" max="1024" step="8" value="1024">
1116
- <span class="slider-val" id="custom-width-val">1024</span>
1117
- </div>
1118
- <div class="slider-row">
1119
- <label>Height</label>
1120
- <input type="range" id="custom-height" min="256" max="1024" step="8" value="1024">
1121
- <span class="slider-val" id="custom-height-val">1024</span>
1122
- </div>
1123
- </div>
1124
- </div>
1125
-
1126
- </div>
1127
-
1128
- <!-- RIGHT: Output Area -->
1129
- <div class="app-main-right">
1130
- <div class="output-area">
1131
-
1132
- <div class="output-cols">
1133
-
1134
- <!-- Standard VAE -->
1135
- <div class="output-col">
1136
- <div class="out-col-header">
1137
- <span class="out-col-title std">&#128310; Standard VAE</span>
1138
- <span class="out-col-badge std">Generated First</span>
1139
- </div>
1140
- <div class="out-col-body" id="output-std-body">
1141
- <div class="modern-loader" id="output-loader-std">
1142
- <div class="loader-spinner"></div>
1143
- <div class="loader-text">Running Standard VAE...</div>
1144
- <div class="loader-bar-track"><div class="loader-bar-fill"></div></div>
1145
- </div>
1146
- <div class="out-col-placeholder">Standard VAE output will appear here</div>
1147
- </div>
1148
- <div class="out-col-footer">
1149
- <span class="timing-display" id="timing-std-display">⏱ Waiting...</span>
1150
- <span id="dl-btn-std" class="out-download-btn" title="Download">
1151
- {DOWNLOAD_SVG} Save
1152
- </span>
1153
- </div>
1154
- </div>
1155
-
1156
- <!-- Small Decoder VAE -->
1157
- <div class="output-col">
1158
- <div class="out-col-header">
1159
- <span class="out-col-title small">&#128312; Small Decoder VAE</span>
1160
- <span class="out-col-badge small">Generated Second</span>
1161
- </div>
1162
- <div class="out-col-body" id="output-small-body">
1163
- <div class="modern-loader" id="output-loader-small">
1164
- <div class="loader-spinner"></div>
1165
- <div class="loader-text">Running Small Decoder VAE...</div>
1166
- <div class="loader-bar-track"><div class="loader-bar-fill"></div></div>
1167
- </div>
1168
- <div class="out-col-placeholder">Small Decoder VAE output will appear here</div>
1169
  </div>
1170
- <div class="out-col-footer">
1171
- <span class="timing-display" id="timing-small-display">⏱ Waiting...</span>
1172
- <span id="dl-btn-small" class="out-download-btn" title="Download">
1173
- {DOWNLOAD_SVG} Save
1174
- </span>
1175
  </div>
1176
  </div>
1177
-
1178
  </div>
1179
  </div>
1180
- </div>
1181
 
1182
- </div>
 
1183
 
1184
  <div class="exp-note">
1185
- Experimental Space — <a href="https://huggingface.co/black-forest-labs/FLUX.2-klein-4B" target="_blank">FLUX.2-klein-4B</a>
1186
- &middot; <a href="https://huggingface.co/black-forest-labs/FLUX.2-small-decoder" target="_blank">FLUX.2-small-decoder</a>
 
 
 
1187
  </div>
1188
 
1189
  <div class="app-statusbar">
1190
- <div class="sb-section" id="sb-image-count">No images uploaded</div>
1191
  <div class="sb-section sb-fixed">Ready</div>
1192
  </div>
1193
 
1194
- </div>
1195
  """)
1196
 
1197
  run_btn = gr.Button("Run", elem_id="gradio-run-btn")
@@ -1201,24 +1220,27 @@ with gr.Blocks(css=css) as demo:
1201
 
1202
  run_btn.click(
1203
  fn=infer,
1204
- inputs=[
1205
- hidden_images_b64,
1206
- prompt_hidden,
1207
- seed_slider,
1208
- randomize_seed,
1209
- width_slider,
1210
- height_slider,
1211
- steps_slider,
1212
- guidance_scale,
1213
- ],
1214
- outputs=[result_std, result_small_dec, seed_used, timing_std_hidden, timing_small_hidden],
1215
  js=r"""(imgs, p, s, rs, w, h, st, gs) => {
1216
  const images = window.__uploadedImages || [];
1217
  const b64Array = images.map(img => img.b64);
1218
  const imgsJson = JSON.stringify(b64Array);
1219
  const promptEl = document.getElementById('custom-prompt-input');
1220
  const promptVal = promptEl ? promptEl.value : p;
1221
- return [imgsJson, promptVal, s, rs, w, h, st, gs];
 
 
 
 
 
 
 
 
 
 
 
 
1222
  }""",
1223
  )
1224
 
@@ -1231,7 +1253,6 @@ with gr.Blocks(css=css) as demo:
1231
 
1232
  if __name__ == "__main__":
1233
  demo.queue(max_size=20).launch(
1234
- css=css,
1235
  ssr_mode=False,
1236
  show_error=True,
1237
  allowed_paths=["examples"],
 
10
  import json
11
  import html as html_lib
12
  from io import BytesIO
 
13
  from PIL import Image
14
+ from pathlib import Path
15
+ from diffusers import Flux2KleinPipeline, AutoencoderKLFlux2
16
 
17
  MAX_SEED = np.iinfo(np.int32).max
18
  MAX_IMAGE_SIZE = 1024
19
  dtype = torch.bfloat16
20
  device = "cuda" if torch.cuda.is_available() else "cpu"
21
 
 
 
22
  print("Loading 4B Distilled model (Standard VAE)...")
23
  pipe_standard = Flux2KleinPipeline.from_pretrained(
24
  "black-forest-labs/FLUX.2-klein-4B",
 
47
  {"images": ["examples/4.jpg"], "prompt": "Make the texture high-resolution."},
48
  ]
49
 
50
+ LANCZOS = getattr(Image, "Resampling", Image).LANCZOS
51
 
52
  def make_thumb_b64(path, max_dim=220):
53
  if not os.path.exists(path):
 
62
  print(f"Thumbnail error for {path}: {e}")
63
  return ""
64
 
 
65
  def encode_full_image(path):
66
  if not os.path.exists(path):
67
  return ""
 
75
  print(f"Encode error for {path}: {e}")
76
  return ""
77
 
 
78
  def build_example_cards_html():
79
  cards = ""
80
  for i, ex in enumerate(EXAMPLES_CONFIG):
 
97
  </div>'''
98
  return cards
99
 
 
100
  def load_example_data(idx_str):
101
  try:
102
  idx = int(float(idx_str)) if idx_str and idx_str.strip() else -1
 
113
  names.append(os.path.basename(path))
114
  return json.dumps({"images": b64_list, "prompt": ex["prompt"], "names": names, "status": "ok"})
115
 
 
116
  print("Building example thumbnails...")
117
  EXAMPLE_CARDS_HTML = build_example_cards_html()
118
  print(f"Built {len(EXAMPLES_CONFIG)} example cards.")
119
 
 
120
  def b64_to_pil_list(b64_json_str):
121
  if not b64_json_str or b64_json_str.strip() in ("", "[]"):
122
  return []
 
139
  print(f"Error decoding image: {e}")
140
  return pil_images
141
 
 
 
 
 
 
 
 
 
 
142
  def update_dimensions_on_upload(pil_images):
143
  if not pil_images:
144
  return 1024, 1024
 
150
  else:
151
  nh = 1024
152
  nw = int(1024 * iw / ih)
153
+ return (max(256, (nw // 8) * 8), max(256, (nh // 8) * 8))
154
 
155
+ def format_time(seconds: float) -> str:
156
+ if seconds < 60:
157
+ return f"{seconds:.2f}s"
158
+ minutes = int(seconds // 60)
159
+ secs = seconds % 60
160
+ return f"{minutes}m {secs:.2f}s"
161
 
162
  @spaces.GPU(duration=240)
163
  def infer(
 
167
  randomize_seed,
168
  width,
169
  height,
170
+ num_inference_steps,
171
  guidance_scale,
172
  progress=gr.Progress(track_tqdm=True),
173
  ):
 
188
  prompt=prompt,
189
  height=height,
190
  width=width,
191
+ num_inference_steps=num_inference_steps,
192
  guidance_scale=guidance_scale,
193
  )
194
  if pil_images:
195
  shared_kwargs["image"] = pil_images
196
 
197
+ progress(0.05, desc="🟦 Running Standard VAE generation...")
198
  t0_std = time.perf_counter()
199
  gen_std = torch.Generator(device="cpu").manual_seed(seed)
200
  out_standard = pipe_standard(**shared_kwargs, generator=gen_std).images[0]
201
+ t1_std = time.perf_counter()
202
+ time_std_str = format_time(t1_std - t0_std)
203
  print(f"Standard VAE done in {time_std_str}")
204
 
205
+ progress(0.55, desc=f"🟦 Standard VAE done in {time_std_str} — now running 🟩 Small Decoder VAE...")
206
  gc.collect()
207
  torch.cuda.empty_cache()
208
 
 
209
  t0_small = time.perf_counter()
210
  gen_small = torch.Generator(device="cpu").manual_seed(seed)
211
  out_small = pipe_small_decoder(**shared_kwargs, generator=gen_small).images[0]
212
+ t1_small = time.perf_counter()
213
+ time_small_str = format_time(t1_small - t0_small)
214
  print(f"Small Decoder VAE done in {time_small_str}")
215
 
216
+ progress(1.0, desc=f"✅ Both done! Standard: {time_std_str} | Small: {time_small_str}")
217
  gc.collect()
218
  torch.cuda.empty_cache()
219
 
220
+ label_std = f"🟦 Standard VAE — {time_std_str}"
221
+ label_small = f"🟩 Small Decoder VAE — {time_small_str}"
222
 
223
  return out_standard, out_small, seed, label_std, label_small
224
 
225
+ # ── SVG Icons ─────────────────────────────────────────────────────────────────
 
 
 
 
 
226
 
227
  UPLOAD_SVG = '<svg class="tb-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>'
 
228
  CLEAR_SVG = '<svg class="tb-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>'
229
+ REMOVE_SVG = '<svg class="tb-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>'
230
+ DL_SVG = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 16l-5-5h3V4h4v7h3l-5 5z"/><path d="M20 18H4v2h16v-2z"/></svg>'
231
+ FLUX_LOGO = '<svg viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>'
232
+
233
+ # ── CSS ───────────────────────────────────────────────────────────────────────
234
 
235
  css = r"""
236
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap');
 
250
  position:absolute;left:-9999px;top:-9999px;width:1px;height:1px;opacity:0.01;pointer-events:none;
251
  }
252
 
253
+ /* ── Shell ── */
254
  .app-shell{
255
  background:#18181b;border:1px solid #27272a;border-radius:16px;
256
  margin:12px auto;max-width:1500px;overflow:hidden;
257
  box-shadow:0 25px 50px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.03);
258
  }
259
+
260
+ /* ── Header ── */
261
  .app-header{
262
  background:linear-gradient(135deg,#18181b,#1e1e24);border-bottom:1px solid #27272a;
263
  padding:14px 24px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;
264
  }
265
  .app-header-left{display:flex;align-items:center;gap:12px}
266
  .app-logo{
267
+ width:36px;height:36px;background:linear-gradient(135deg,#FF4500,#FF8533,#FFB366);
268
  border-radius:10px;display:flex;align-items:center;justify-content:center;
269
  box-shadow:0 4px 12px rgba(255,69,0,.35);
270
  }
 
275
  }
276
  .app-badge{
277
  font-size:11px;font-weight:600;padding:3px 10px;border-radius:20px;
278
+ background:rgba(255,69,0,.15);color:#FF8533;border:1px solid rgba(255,69,0,.25);letter-spacing:.3px;
279
  }
280
+ .app-badge.klein{background:rgba(255,69,0,.12);color:#FF6A33;border:1px solid rgba(255,69,0,.25)}
281
+ .app-badge.fast{background:rgba(34,197,94,.12);color:#4ade80;border:1px solid rgba(34,197,94,.25)}
282
 
283
+ /* ── Toolbar ── */
284
  .app-toolbar{
285
  background:#18181b;border-bottom:1px solid #27272a;padding:8px 16px;
286
  display:flex;gap:4px;align-items:center;flex-wrap:wrap;
 
300
  .modern-tb-btn .tb-svg,.modern-tb-btn .tb-svg *{stroke:#ffffff!important;fill:none!important}
301
  .tb-info{font-family:'JetBrains Mono',monospace;font-size:12px;color:#71717a;padding:0 8px;display:flex;align-items:center}
302
 
303
+ /* ── Main Layout ── */
304
  .app-main-row{display:flex;gap:0;flex:1;overflow:hidden}
305
+ .app-main-left{flex:1;display:flex;flex-direction:column;min-width:0;border-right:1px solid #27272a}
306
+ .app-main-right{width:460px;display:flex;flex-direction:column;flex-shrink:0;background:#18181b}
307
 
308
+ /* ── Gallery / Drop Zone ── */
309
+ #gallery-drop-zone{position:relative;background:#09090b;min-height:320px;overflow:auto}
310
  #gallery-drop-zone.drag-over{outline:2px solid #FF4500;outline-offset:-2px;background:rgba(255,69,0,.04)}
311
+ .upload-prompt-modern{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:20}
312
  .upload-click-area{
313
  display:flex;flex-direction:column;align-items:center;justify-content:center;
314
+ cursor:pointer;padding:36px 52px;border:2px dashed #3f3f46;border-radius:16px;
315
  background:rgba(255,69,0,.03);transition:all .2s ease;gap:8px;
316
  }
317
+ .upload-click-area:hover{background:rgba(255,69,0,.08);border-color:#FF4500;transform:scale(1.03)}
318
+ .upload-click-area:active{background:rgba(255,69,0,.12);transform:scale(.98)}
319
+ .upload-click-area svg{width:80px;height:80px}
320
+ .upload-main-text{color:#71717a;font-size:14px;font-weight:500;margin-top:4px}
321
+ .upload-sub-text{color:#52525b;font-size:12px}
322
  .image-gallery-grid{
323
+ display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));
324
+ gap:12px;padding:16px;align-content:start;
325
  }
326
  .gallery-thumb{
327
  position:relative;aspect-ratio:1;border-radius:10px;overflow:hidden;
 
350
  .gallery-add-card .add-icon{font-size:28px;color:#71717a;font-weight:300}
351
  .gallery-add-card .add-text{font-size:12px;color:#71717a;font-weight:500}
352
 
353
+ /* ── Hint bar ── */
354
  .hint-bar{
355
  background:rgba(255,69,0,.06);border-top:1px solid #27272a;border-bottom:1px solid #27272a;
356
+ padding:10px 20px;font-size:13px;color:#a1a1aa;line-height:1.7;
357
  }
358
+ .hint-bar b{color:#FF8533;font-weight:600}
359
  .hint-bar kbd{
360
  display:inline-block;padding:1px 6px;background:#27272a;border:1px solid #3f3f46;
361
  border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:11px;color:#a1a1aa;
362
  }
363
 
364
+ /* ── Quick Prompts ── */
365
+ .suggestions-section{border-top:1px solid #27272a;padding:12px 16px}
366
+ .suggestions-title,.examples-title{
367
+ font-size:12px;font-weight:600;color:#71717a;text-transform:uppercase;
368
+ letter-spacing:.8px;margin-bottom:10px;
369
+ }
370
+ .suggestions-wrap{display:flex;flex-wrap:wrap;gap:6px}
371
+ .suggestion-chip{
372
+ display:inline-flex;align-items:center;gap:4px;padding:5px 12px;
373
+ background:rgba(255,69,0,.08);border:1px solid rgba(255,69,0,.2);border-radius:20px;
374
+ color:#FF8533;font-size:12px;font-weight:500;font-family:'Inter',sans-serif;
375
+ cursor:pointer;transition:all .15s;white-space:nowrap;
376
+ }
377
+ .suggestion-chip:hover{background:rgba(255,69,0,.15);border-color:rgba(255,69,0,.35);color:#FFB366;transform:translateY(-1px)}
378
+
379
+ /* ── Example Cards ── */
380
+ .examples-section{border-top:1px solid #27272a;padding:12px 16px}
381
+ .examples-scroll{display:flex;gap:10px;overflow-x:auto;padding-bottom:8px}
382
+ .examples-scroll::-webkit-scrollbar{height:6px}
383
+ .examples-scroll::-webkit-scrollbar-track{background:#09090b;border-radius:3px}
384
+ .examples-scroll::-webkit-scrollbar-thumb{background:#27272a;border-radius:3px}
385
+ .examples-scroll::-webkit-scrollbar-thumb:hover{background:#3f3f46}
386
+ .example-card{
387
+ flex-shrink:0;width:210px;background:#09090b;border:1px solid #27272a;
388
+ border-radius:10px;overflow:hidden;cursor:pointer;transition:all .2s ease;
389
+ }
390
+ .example-card:hover{border-color:#FF4500;transform:translateY(-2px);box-shadow:0 4px 12px rgba(255,69,0,.15)}
391
+ .example-card.loading{opacity:.5;pointer-events:none}
392
+ .example-thumbs{display:flex;height:110px;overflow:hidden;background:#18181b}
393
+ .example-thumbs img{flex:1;object-fit:cover;min-width:0;border-bottom:1px solid #27272a}
394
+ .example-thumb-placeholder{
395
+ flex:1;display:flex;align-items:center;justify-content:center;
396
+ background:#18181b;color:#3f3f46;font-size:11px;min-width:0;
397
+ }
398
+ .example-meta{padding:6px 10px;display:flex;align-items:center;gap:6px}
399
+ .example-badge{
400
+ display:inline-flex;padding:2px 7px;background:rgba(255,69,0,.1);border-radius:4px;
401
+ font-size:10px;font-weight:600;color:#FF8533;font-family:'JetBrains Mono',monospace;white-space:nowrap;
402
+ }
403
+ .example-prompt-text{
404
+ padding:0 10px 8px;font-size:11px;color:#a1a1aa;line-height:1.4;
405
+ display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
406
+ }
407
+
408
+ /* ── Right Panel ── */
409
  .panel-card{border-bottom:1px solid #27272a}
410
  .panel-card-title{
411
+ padding:12px 20px;font-size:12px;font-weight:600;color:#71717a;
412
  text-transform:uppercase;letter-spacing:.8px;border-bottom:1px solid rgba(39,39,42,.6);
413
  }
414
+ .panel-card-body{padding:16px 20px;display:flex;flex-direction:column;gap:8px}
415
  .modern-label{font-size:13px;font-weight:500;color:#a1a1aa;margin-bottom:4px;display:block}
416
  .modern-textarea{
417
  width:100%;background:#09090b;border:1px solid #27272a;border-radius:8px;
418
+ padding:10px 14px;font-family:'Inter',sans-serif;font-size:14px;color:#e4e4e7;
419
  resize:vertical;outline:none;min-height:80px;transition:border-color .2s;
420
  }
421
  .modern-textarea:focus{border-color:#FF4500;box-shadow:0 0 0 3px rgba(255,69,0,.15)}
 
425
  }
426
  @keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-4px)}40%,80%{transform:translateX(4px)}}
427
 
428
+ /* ── Run Button ── */
429
  .btn-run{
430
  display:flex;align-items:center;justify-content:center;gap:8px;width:100%;
431
+ background:linear-gradient(135deg,#FF4500,#E63E00);border:none;border-radius:10px;
432
  padding:12px 24px;cursor:pointer;font-size:15px;font-weight:600;font-family:'Inter',sans-serif;
433
+ color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;transition:all .2s ease;letter-spacing:-.2px;
434
  box-shadow:0 4px 16px rgba(255,69,0,.3),inset 0 1px 0 rgba(255,255,255,.1);
435
  }
436
  .btn-run:hover{
437
+ background:linear-gradient(135deg,#FF6A33,#FF4500);transform:translateY(-1px);
438
  box-shadow:0 6px 24px rgba(255,69,0,.45),inset 0 1px 0 rgba(255,255,255,.15);
439
  }
440
  .btn-run:active{transform:translateY(0);box-shadow:0 2px 8px rgba(255,69,0,.3)}
441
  .btn-run svg{width:18px;height:18px;fill:#ffffff!important}
442
 
443
+ /* ── Toast ── */
444
+ .toast-notification{
445
+ position:fixed;top:24px;left:50%;transform:translateX(-50%) translateY(-120%);
446
+ z-index:9999;padding:10px 24px;border-radius:10px;font-family:'Inter',sans-serif;
447
+ font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px;
448
+ box-shadow:0 8px 24px rgba(0,0,0,.5);
449
+ transition:transform .35s cubic-bezier(.34,1.56,.64,1),opacity .35s ease;opacity:0;pointer-events:none;
 
 
 
 
450
  }
451
+ .toast-notification.visible{transform:translateX(-50%) translateY(0);opacity:1;pointer-events:auto}
452
+ .toast-notification.error{background:linear-gradient(135deg,#dc2626,#b91c1c);color:#fff;border:1px solid rgba(255,255,255,.15)}
453
+ .toast-notification.warning{background:linear-gradient(135deg,#d97706,#b45309);color:#fff;border:1px solid rgba(255,255,255,.15)}
454
+ .toast-notification.info{background:linear-gradient(135deg,#FF4500,#E63E00);color:#fff;border:1px solid rgba(255,255,255,.15)}
455
+
456
+ /* ── Output Panel ── */
457
+ .output-panel{padding:16px;display:flex;flex-direction:column;gap:12px;flex:1}
458
+ .output-compare-row{display:grid;grid-template-columns:1fr 1fr;gap:12px}
459
+ .output-card{
460
+ background:#09090b;border:1px solid #27272a;border-radius:10px;overflow:hidden;
461
+ display:flex;flex-direction:column;transition:border-color .2s;
462
  }
463
+ .output-card:hover{border-color:#3f3f46}
464
+ .output-card-header{
465
+ padding:8px 12px;border-bottom:1px solid #27272a;display:flex;align-items:center;
466
+ justify-content:space-between;background:rgba(24,24,27,.8);
 
467
  }
468
+ .output-card-label{font-size:12px;font-weight:700;color:#ffffff!important;-webkit-text-fill-color:#ffffff!important}
469
+ .output-card-label.std{color:#60A5FA!important;-webkit-text-fill-color:#60A5FA!important}
470
+ .output-card-label.small{color:#4ADE80!important;-webkit-text-fill-color:#4ADE80!important}
471
+ .output-card-body{
472
+ flex:1;display:flex;align-items:center;justify-content:center;
473
+ background:#09090b;overflow:hidden;min-height:200px;position:relative;
474
  }
475
+ .output-card-body img{max-width:100%;max-height:300px;object-fit:contain}
476
+ .out-placeholder-text{color:#3f3f46;font-size:12px;text-align:center}
477
+ .timing-badge{
478
+ font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:600;
479
+ padding:2px 8px;border-radius:4px;background:rgba(255,255,255,.05);color:#71717a;
480
+ border:1px solid #27272a;white-space:nowrap;
481
  }
482
  .out-download-btn{
483
  display:none;align-items:center;justify-content:center;background:rgba(255,69,0,.1);
484
  border:1px solid rgba(255,69,0,.2);border-radius:6px;cursor:pointer;padding:3px 10px;
485
+ font-size:11px;font-weight:500;color:#FF8533!important;gap:4px;height:24px;transition:all .15s;
486
  }
487
  .out-download-btn:hover{background:rgba(255,69,0,.2);border-color:rgba(255,69,0,.35);color:#ffffff!important}
488
  .out-download-btn.visible{display:inline-flex}
489
+ .out-download-btn svg{width:12px;height:12px;fill:#FF8533}
490
 
491
  /* ── Loader ── */
492
  .modern-loader{
493
  display:none;position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(9,9,11,.92);
494
+ z-index:15;flex-direction:column;align-items:center;justify-content:center;gap:12px;backdrop-filter:blur(4px);
495
  }
496
  .modern-loader.active{display:flex}
497
  .modern-loader .loader-spinner{
498
+ width:32px;height:32px;border:3px solid #27272a;border-top-color:#FF4500;
499
  border-radius:50%;animation:spin .8s linear infinite;
500
  }
501
  @keyframes spin{to{transform:rotate(360deg)}}
502
+ .modern-loader .loader-text{font-size:12px;color:#a1a1aa;font-weight:500}
503
+ .loader-bar-track{width:180px;height:4px;background:#27272a;border-radius:2px;overflow:hidden}
504
  .loader-bar-fill{
505
+ height:100%;background:linear-gradient(90deg,#FF4500,#FF8533,#FF4500);
506
  background-size:200% 100%;animation:shimmer 1.5s ease-in-out infinite;border-radius:2px;
507
  }
508
  @keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
509
 
510
+ /* ── Settings Group ── */
511
+ .settings-group{border:1px solid #27272a;border-radius:10px;margin:12px 16px;overflow:hidden}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
  .settings-group-title{
513
  font-size:12px;font-weight:600;color:#71717a;text-transform:uppercase;letter-spacing:.8px;
514
+ padding:10px 16px;border-bottom:1px solid #27272a;background:rgba(24,24,27,.5);
515
  }
516
+ .settings-group-body{padding:14px 16px;display:flex;flex-direction:column;gap:12px}
517
+ .slider-row{display:flex;align-items:center;gap:10px;min-height:28px}
518
+ .slider-row label{font-size:13px;font-weight:500;color:#a1a1aa;min-width:72px;flex-shrink:0}
519
  .slider-row input[type="range"]{
520
  flex:1;-webkit-appearance:none;appearance:none;height:6px;background:#27272a;
521
  border-radius:3px;outline:none;min-width:0;
522
  }
523
  .slider-row input[type="range"]::-webkit-slider-thumb{
524
+ -webkit-appearance:none;width:16px;height:16px;background:linear-gradient(135deg,#FF4500,#E63E00);
525
  border-radius:50%;cursor:pointer;box-shadow:0 2px 6px rgba(255,69,0,.4);transition:transform .15s;
526
  }
527
  .slider-row input[type="range"]::-webkit-slider-thumb:hover{transform:scale(1.2)}
528
  .slider-row input[type="range"]::-moz-range-thumb{
529
+ width:16px;height:16px;background:linear-gradient(135deg,#FF4500,#E63E00);
530
  border-radius:50%;cursor:pointer;border:none;
531
  }
532
  .slider-row .slider-val{
533
+ min-width:52px;text-align:right;font-family:'JetBrains Mono',monospace;font-size:12px;
534
  font-weight:500;padding:3px 8px;background:#09090b;border:1px solid #27272a;
535
  border-radius:6px;color:#a1a1aa;flex-shrink:0;
536
  }
537
  .checkbox-row{display:flex;align-items:center;gap:8px;font-size:13px;color:#a1a1aa}
538
  .checkbox-row input[type="checkbox"]{accent-color:#FF4500;width:16px;height:16px;cursor:pointer}
539
+ .slider-row-2col{display:grid;grid-template-columns:1fr 1fr;gap:12px}
540
 
541
+ /* ── Status Bar ── */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
542
  .app-statusbar{
543
  background:#18181b;border-top:1px solid #27272a;padding:6px 20px;
544
  display:flex;gap:12px;height:34px;align-items:center;font-size:12px;
 
549
  }
550
  .app-statusbar .sb-section.sb-fixed{
551
  flex:0 0 auto;min-width:90px;text-align:center;justify-content:center;
552
+ padding:3px 12px;background:rgba(255,69,0,.08);border-radius:6px;color:#FF8533;font-weight:500;
553
  }
554
  .exp-note{padding:10px 20px;font-size:12px;color:#52525b;border-top:1px solid #27272a;text-align:center}
555
+ .exp-note a{color:#FF8533;text-decoration:none}
556
  .exp-note a:hover{text-decoration:underline}
557
 
558
  ::-webkit-scrollbar{width:8px;height:8px}
 
562
 
563
  @media(max-width:900px){
564
  .app-main-row{flex-direction:column}
565
+ .app-main-right{width:100%}
566
+ .app-main-left{border-right:none;border-bottom:1px solid #27272a}
567
+ .output-compare-row{grid-template-columns:1fr}
568
  }
569
  """
570
 
571
+ # ── Gallery JS ─────────────────────────────────────────────────────────────────
572
+
573
  gallery_js = r"""
574
  () => {
575
  function init() {
 
609
  const icon = toast.querySelector('.toast-icon');
610
  const text = toast.querySelector('.toast-text');
611
  toast.className = 'toast-notification ' + (type || 'error');
612
+ if (type === 'warning') icon.textContent = '\u26A0';
613
+ else if (type === 'info') icon.textContent = '\u2139';
614
+ else icon.textContent = '\u2717';
615
  text.textContent = message;
616
  if (toastTimer) clearTimeout(toastTimer);
617
  void toast.offsetWidth;
 
658
  const n = images.length;
659
  const txt = n > 0 ? n + ' image' + (n > 1 ? 's' : '') : 'No images';
660
  if (imgCountTb) imgCountTb.textContent = txt;
661
+ if (imgCountSb) imgCountSb.textContent = n > 0 ? txt + ' loaded' : 'No images loaded';
662
  }
663
 
664
  function addImage(b64, name) {
665
+ images.push({id: Date.now() + Math.random(), b64: b64, name: name});
666
  renderGallery();
667
  syncImagesToGradio();
668
  }
 
685
  }
686
  window.__clearAll = clearAll;
687
 
688
+ function selectImage(idx) {
689
+ selectedIdx = (selectedIdx === idx) ? -1 : idx;
690
+ renderGallery();
691
+ }
692
+
693
  function renderGallery() {
694
  if (images.length === 0) {
695
  galleryGrid.innerHTML = '';
 
699
  }
700
  if (uploadPrompt) uploadPrompt.style.display = 'none';
701
  galleryGrid.style.display = 'grid';
 
702
  let html = '';
703
  images.forEach((img, i) => {
704
  const sel = i === selectedIdx ? ' selected' : '';
705
+ html += '<div class="gallery-thumb' + sel + '" data-idx="' + i + '">'
706
+ + '<img src="' + img.b64 + '" alt="' + (img.name||'image') + '">'
707
+ + '<span class="thumb-badge">#' + (i+1) + '</span>'
708
+ + '<button class="thumb-remove" data-remove="' + i + '">\u2715</button>'
709
+ + '</div>';
710
  });
711
+ html += '<div class="gallery-add-card" id="gallery-add-card">'
712
+ + '<span class="add-icon">+</span><span class="add-text">Add</span></div>';
 
713
  galleryGrid.innerHTML = html;
714
 
715
  galleryGrid.querySelectorAll('.gallery-thumb').forEach(thumb => {
716
+ thumb.addEventListener('click', (e) => {
717
  if (e.target.closest('.thumb-remove')) return;
718
+ selectImage(parseInt(thumb.dataset.idx));
 
719
  });
720
  });
721
  galleryGrid.querySelectorAll('.thumb-remove').forEach(btn => {
722
+ btn.addEventListener('click', (e) => {
723
+ e.stopPropagation();
724
+ removeImage(parseInt(btn.dataset.remove));
725
+ });
726
  });
727
  const addCard = document.getElementById('gallery-add-card');
728
  if (addCard) addCard.addEventListener('click', () => fileInput.click());
 
732
  Array.from(files).forEach(file => {
733
  if (!file.type.startsWith('image/')) return;
734
  const reader = new FileReader();
735
+ reader.onload = (e) => addImage(e.target.result, file.name);
736
  reader.readAsDataURL(file);
737
  });
738
  }
739
 
740
+ fileInput.addEventListener('change', (e) => { processFiles(e.target.files); e.target.value = ''; });
741
  if (uploadClick) uploadClick.addEventListener('click', () => fileInput.click());
742
  if (btnUpload) btnUpload.addEventListener('click', () => fileInput.click());
743
+ if (btnRemove) btnRemove.addEventListener('click', () => {
744
+ if (selectedIdx >= 0 && selectedIdx < images.length) removeImage(selectedIdx);
745
+ });
746
  if (btnClear) btnClear.addEventListener('click', clearAll);
747
 
748
+ dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('drag-over'); });
749
+ dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); });
750
+ dropZone.addEventListener('drop', (e) => {
751
  e.preventDefault(); dropZone.classList.remove('drag-over');
752
  if (e.dataTransfer.files.length) processFiles(e.dataTransfer.files);
753
  });
 
758
  if (promptInput) { promptInput.value = text; syncPromptToGradio(); }
759
  };
760
 
761
+ /* ── Example cards ── */
762
  document.querySelectorAll('.example-card[data-idx]').forEach(card => {
763
  card.addEventListener('click', () => {
764
  const idx = card.getAttribute('data-idx');
 
775
  });
776
  });
777
 
778
+ /* ── Slider sync ── */
779
  function syncSlider(customId, gradioId) {
780
  const slider = document.getElementById(customId);
781
  const valSpan = document.getElementById(customId + '-val');
782
  if (!slider) return;
783
  slider.addEventListener('input', () => {
784
+ if (valSpan) valSpan.textContent = parseFloat(slider.value).toFixed(slider.step && slider.step < 1 ? 1 : 0);
785
  const container = document.getElementById(gradioId);
786
  if (!container) return;
787
  container.querySelectorAll('input[type="range"],input[type="number"]').forEach(el => {
 
794
  });
795
  });
796
  }
797
+ syncSlider('custom-seed', 'gradio-seed');
798
  syncSlider('custom-guidance', 'gradio-guidance');
799
+ syncSlider('custom-steps', 'gradio-steps');
800
+ syncSlider('custom-width', 'gradio-width');
801
+ syncSlider('custom-height', 'gradio-height');
802
 
803
  const randCheck = document.getElementById('custom-randomize');
804
+ if (randCheck) {
805
+ randCheck.addEventListener('change', () => {
806
+ const container = document.getElementById('gradio-randomize');
807
+ if (!container) return;
808
+ const cb = container.querySelector('input[type="checkbox"]');
809
+ if (cb && cb.checked !== randCheck.checked) cb.click();
810
+ });
811
+ }
812
 
813
+ /* ── Loader ── */
814
  function showLoader() {
815
+ ['output-loader-std','output-loader-small'].forEach(id => {
816
+ const l = document.getElementById(id);
817
+ if (l) l.classList.add('active');
818
+ });
819
  const sb = document.querySelector('.sb-fixed');
820
  if (sb) sb.textContent = 'Processing...';
821
  }
822
  function hideLoader() {
823
+ ['output-loader-std','output-loader-small'].forEach(id => {
824
+ const l = document.getElementById(id);
825
+ if (l) l.classList.remove('active');
826
+ });
827
  const sb = document.querySelector('.sb-fixed');
828
  if (sb) sb.textContent = 'Done';
829
  }
830
  window.__showLoader = showLoader;
831
  window.__hideLoader = hideLoader;
832
 
833
+ /* ── Validate & run ── */
834
  function validateBeforeRun() {
835
  const promptVal = promptInput ? promptInput.value.trim() : '';
836
  if (!promptVal) { showToast('Please enter a prompt', 'warning'); flashPromptError(); return false; }
 
860
  wire_outputs_js = r"""
861
  () => {
862
  function watchOutputs() {
863
+ const stdContainer = document.getElementById('gradio-result-std');
864
+ const smallContainer = document.getElementById('gradio-result-small');
865
+ const stdBody = document.getElementById('output-body-std');
866
+ const smallBody = document.getElementById('output-body-small');
867
+ const dlStd = document.getElementById('dl-btn-std');
868
+ const dlSmall = document.getElementById('dl-btn-small');
869
+
870
+ if (!stdContainer || !smallContainer) { setTimeout(watchOutputs, 500); return; }
871
+
872
+ function makeDownload(btn, bodyEl, filename) {
873
+ if (!btn) return;
874
+ btn.addEventListener('click', (e) => {
875
+ e.stopPropagation();
876
+ const img = bodyEl.querySelector('img.modern-out-img');
877
+ if (img && img.src) {
878
+ const a = document.createElement('a');
879
+ a.href = img.src; a.download = filename;
880
+ document.body.appendChild(a); a.click(); document.body.removeChild(a);
881
+ }
882
+ });
883
+ }
884
+ makeDownload(dlStd, stdBody, 'flux_standard_vae.png');
885
+ makeDownload(dlSmall, smallBody, 'flux_small_decoder.png');
886
 
887
+ function syncOne(container, body, loaderId, dlBtn) {
888
+ const resultImg = container.querySelector('img');
 
 
 
889
  if (resultImg && resultImg.src) {
890
+ const ph = body.querySelector('.out-placeholder-text');
891
  if (ph) ph.style.display = 'none';
892
+ let existing = body.querySelector('img.modern-out-img');
893
+ if (!existing) { existing = document.createElement('img'); existing.className = 'modern-out-img'; body.appendChild(existing); }
894
  if (existing.src !== resultImg.src) {
895
  existing.src = resultImg.src;
 
896
  if (dlBtn) dlBtn.classList.add('visible');
897
+ const l = document.getElementById(loaderId);
898
+ if (l) l.classList.remove('active');
899
  }
900
  }
901
  }
902
 
903
+ function syncAll() {
904
+ syncOne(stdContainer, stdBody, 'output-loader-std', dlStd);
905
+ syncOne(smallContainer, smallBody, 'output-loader-small', dlSmall);
906
+ }
907
+
908
+ const obs = new MutationObserver(syncAll);
909
+ obs.observe(stdContainer, {childList:true,subtree:true,attributes:true,attributeFilter:['src']});
910
+ obs.observe(smallContainer, {childList:true,subtree:true,attributes:true,attributeFilter:['src']});
911
+ setInterval(syncAll, 800);
912
  }
913
  watchOutputs();
914
 
915
  function watchTimings() {
916
+ function syncTiming(gradioId, badgeId) {
917
+ const container = document.getElementById(gradioId);
918
+ const badge = document.getElementById(badgeId);
919
+ if (!container || !badge) return;
920
+ const obs = new MutationObserver(() => {
921
+ const el = container.querySelector('p, span, div');
922
+ if (el && el.textContent && el.textContent.trim()) {
923
+ badge.textContent = el.textContent.trim();
924
+ }
925
+ });
926
+ obs.observe(container, {childList:true, subtree:true, characterData:true});
927
+ setInterval(() => {
928
+ const el = container.querySelector('p, span, div');
929
+ if (el && el.textContent && el.textContent.trim()) badge.textContent = el.textContent.trim();
930
+ }, 800);
931
+ }
932
+ syncTiming('gradio-timing-std', 'timing-badge-std');
933
+ syncTiming('gradio-timing-small', 'timing-badge-small');
 
934
  }
935
  watchTimings();
936
 
 
943
  const el = seedContainer.querySelector('input[type="range"],input[type="number"]');
944
  if (el && el.value) { seedSlider.value = el.value; if (seedVal) seedVal.textContent = el.value; }
945
  }
946
+ new MutationObserver(sync).observe(seedContainer, {childList:true,subtree:true,attributes:true});
 
947
  setInterval(sync, 1000);
948
  }
949
  watchSeed();
 
975
  document.querySelectorAll('.example-card.loading').forEach(c => c.classList.remove('loading'));
976
  if (window.__showToast) window.__showToast('Could not load example images', 'error');
977
  }
978
+ } catch(e) { console.error('Example parse error:', e); }
979
  }
980
+ new MutationObserver(checkResult).observe(container, {childList:true,subtree:true,characterData:true,attributes:true});
 
981
  setInterval(checkResult, 500);
982
  }
983
  watchExampleResults();
984
  }
985
  """
986
 
987
+ # ── Gradio App ────────────────────────────────────────────────────────────────
988
+
989
  with gr.Blocks(css=css) as demo:
990
 
991
+ # Hidden Gradio state components
992
+ hidden_images_b64 = gr.Textbox(value="[]", elem_id="hidden-images-b64", elem_classes="hidden-input", container=False)
993
+ prompt = gr.Textbox(value="", elem_id="prompt-gradio-input", elem_classes="hidden-input", container=False)
994
+ seed = gr.Slider(minimum=0, maximum=MAX_SEED, step=1, value=0, elem_id="gradio-seed", elem_classes="hidden-input", container=False)
995
+ randomize_seed = gr.Checkbox(value=True, elem_id="gradio-randomize", elem_classes="hidden-input", container=False)
996
+ guidance_scale = gr.Slider(minimum=0.0, maximum=10.0, step=0.1, value=1.0,elem_id="gradio-guidance", elem_classes="hidden-input", container=False)
997
+ steps = gr.Slider(minimum=1, maximum=20, step=1, value=4, elem_id="gradio-steps", elem_classes="hidden-input", container=False)
998
+ width_gr = gr.Slider(minimum=256, maximum=1024, step=8, value=1024, elem_id="gradio-width", elem_classes="hidden-input", container=False)
999
+ height_gr = gr.Slider(minimum=256, maximum=1024, step=8, value=1024, elem_id="gradio-height", elem_classes="hidden-input", container=False)
1000
+
1001
+ result_std = gr.Image(elem_id="gradio-result-std", elem_classes="hidden-input", container=False, format="png")
1002
+ result_small_gr = gr.Image(elem_id="gradio-result-small", elem_classes="hidden-input", container=False, format="png")
1003
+ seed_output = gr.Number(elem_id="gradio-seed-out", elem_classes="hidden-input", container=False)
1004
+ timing_std_gr = gr.Markdown(elem_id="gradio-timing-std", elem_classes="hidden-input")
1005
+ timing_small_gr = gr.Markdown(elem_id="gradio-timing-small", elem_classes="hidden-input")
1006
+
1007
+ example_idx = gr.Textbox(value="", elem_id="example-idx-input", elem_classes="hidden-input", container=False)
1008
+ example_result = gr.Textbox(value="", elem_id="example-result-data", elem_classes="hidden-input", container=False)
1009
+ example_load_btn = gr.Button("Load Example", elem_id="example-load-btn")
1010
 
1011
  gr.HTML(f"""
1012
  <div class="app-shell">
1013
 
1014
+ <!-- Header -->
1015
  <div class="app-header">
1016
  <div class="app-header-left">
1017
+ <div class="app-logo">{FLUX_LOGO}</div>
1018
+ <span class="app-title">FLUX.2 Klein VAE Comparator</span>
1019
+ <span class="app-badge klein">4B Distilled</span>
1020
+ <span class="app-badge fast">4-Step Fast</span>
 
1021
  </div>
1022
  </div>
1023
 
1024
+ <!-- Toolbar -->
1025
  <div class="app-toolbar">
1026
+ <button id="tb-upload" class="modern-tb-btn" title="Upload reference images">
1027
  {UPLOAD_SVG}<span class="tb-label">Upload</span>
1028
  </button>
1029
+ <button id="tb-remove" class="modern-tb-btn" title="Remove selected image">
1030
  {REMOVE_SVG}<span class="tb-label">Remove</span>
1031
  </button>
1032
+ <button id="tb-clear" class="modern-tb-btn" title="Clear all images">
1033
  {CLEAR_SVG}<span class="tb-label">Clear All</span>
1034
  </button>
1035
  <div class="tb-sep"></div>
 
1038
 
1039
  <div class="app-main-row">
1040
 
1041
+ <!-- LEFT: gallery + prompts + examples -->
1042
  <div class="app-main-left">
1043
 
1044
  <div id="gallery-drop-zone">
 
1049
  <polygon points="12,62 30,40 42,50 54,34 68,62" fill="rgba(255,69,0,0.15)" stroke="#FF4500" stroke-width="1.5"/>
1050
  <circle cx="28" cy="30" r="6" fill="rgba(255,69,0,0.2)" stroke="#FF4500" stroke-width="1.5"/>
1051
  </svg>
1052
+ <span class="upload-main-text">Click or drag reference images here (optional)</span>
1053
+ <span class="upload-sub-text">Supports multiple images for image-guided generation</span>
1054
  </div>
1055
  </div>
1056
  <input id="custom-file-input" type="file" accept="image/*" multiple style="display:none;" />
 
1058
  </div>
1059
 
1060
  <div class="hint-bar">
1061
+ <b>Text-to-Image:</b> Leave gallery empty for pure text generation &nbsp;&middot;&nbsp;
1062
+ <b>Image-guided:</b> Upload reference images to condition generation &nbsp;&middot;&nbsp;
1063
+ <kbd>Clear All</kbd> removes all references
1064
  </div>
1065
 
1066
+ <div class="suggestions-section">
1067
+ <div class="suggestions-title">Quick Prompts</div>
1068
+ <div class="suggestions-wrap">
1069
+ <button class="suggestion-chip" onclick="window.__setPrompt('Change the weather to stormy with dark clouds and lightning.')">Stormy Weather</button>
1070
+ <button class="suggestion-chip" onclick="window.__setPrompt('Transform the scene into a snowy winter day.')">Snowy Winter</button>
1071
+ <button class="suggestion-chip" onclick="window.__setPrompt('Relight with soft golden sunset lighting.')">Sunset Lighting</button>
1072
+ <button class="suggestion-chip" onclick="window.__setPrompt('Make the texture high-resolution and ultra-detailed.')">HD Texture</button>
1073
+ <button class="suggestion-chip" onclick="window.__setPrompt('Convert to cinematic film look with anamorphic lens flares.')">Cinematic</button>
1074
+ <button class="suggestion-chip" onclick="window.__setPrompt('Transform into anime style illustration with vibrant colors.')">Anime Style</button>
1075
+ <button class="suggestion-chip" onclick="window.__setPrompt('Apply dramatic neon cyberpunk lighting and atmosphere.')">Cyberpunk</button>
1076
+ <button class="suggestion-chip" onclick="window.__setPrompt('Render as hyperrealistic oil painting with visible brush strokes.')">Oil Painting</button>
1077
+ <button class="suggestion-chip" onclick="window.__setPrompt('Add dramatic fog and moody atmospheric perspective.')">Foggy Mood</button>
1078
+ <button class="suggestion-chip" onclick="window.__setPrompt('Transform into a vibrant watercolor painting with soft edges.')">Watercolor</button>
1079
+ <button class="suggestion-chip" onclick="window.__setPrompt('Apply vintage retro film look with faded colors and grain.')">Vintage Retro</button>
1080
+ <button class="suggestion-chip" onclick="window.__setPrompt('Render with dramatic volumetric god rays and dust particles.')">God Rays</button>
1081
+ </div>
1082
+ </div>
1083
+
1084
+ <div class="examples-section">
1085
+ <div class="examples-title">Quick Examples</div>
1086
+ <div class="examples-scroll">
1087
+ {EXAMPLE_CARDS_HTML}
1088
+ </div>
1089
+ </div>
1090
+
1091
+ </div><!-- end app-main-left -->
1092
+
1093
+ <!-- RIGHT: prompt + run + outputs + settings -->
1094
+ <div class="app-main-right">
1095
+
1096
  <div class="panel-card">
1097
+ <div class="panel-card-title">Generation Prompt</div>
1098
  <div class="panel-card-body">
1099
+ <label class="modern-label" for="custom-prompt-input">Prompt</label>
1100
  <textarea id="custom-prompt-input" class="modern-textarea" rows="4"
1101
  placeholder="e.g., Transform the scene into a snowy winter day..."></textarea>
1102
  </div>
1103
  </div>
1104
 
1105
+ <div style="padding:12px 20px;">
1106
  <button id="custom-run-btn" class="btn-run">
1107
  <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
1108
+ <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" fill="#ffffff"/>
1109
  </svg>
1110
+ <span id="run-btn-label">Run Comparison</span>
1111
  </button>
1112
  </div>
1113
 
1114
+ <!-- Dual output cards -->
1115
+ <div class="output-panel">
1116
+ <div class="output-compare-row">
 
 
 
 
 
 
 
 
 
 
1117
 
1118
+ <!-- Standard VAE -->
1119
+ <div class="output-card">
1120
+ <div class="output-card-header">
1121
+ <span class="output-card-label std">🟦 Standard VAE</span>
1122
+ <span id="timing-badge-std" class="timing-badge">⏱ —</span>
1123
+ <span id="dl-btn-std" class="out-download-btn" title="Download">
1124
+ {DL_SVG} Save
1125
+ </span>
1126
+ </div>
1127
+ <div class="output-card-body" id="output-body-std">
1128
+ <div class="modern-loader" id="output-loader-std">
1129
+ <div class="loader-spinner"></div>
1130
+ <div class="loader-text">Generating...</div>
1131
+ <div class="loader-bar-track"><div class="loader-bar-fill"></div></div>
1132
+ </div>
1133
+ <div class="out-placeholder-text">Standard VAE result</div>
1134
+ </div>
1135
+ </div>
1136
+
1137
+ <!-- Small Decoder VAE -->
1138
+ <div class="output-card">
1139
+ <div class="output-card-header">
1140
+ <span class="output-card-label small">🟩 Small Decoder</span>
1141
+ <span id="timing-badge-small" class="timing-badge">⏱ —</span>
1142
+ <span id="dl-btn-small" class="out-download-btn" title="Download">
1143
+ {DL_SVG} Save
1144
+ </span>
1145
+ </div>
1146
+ <div class="output-card-body" id="output-body-small">
1147
+ <div class="modern-loader" id="output-loader-small">
1148
+ <div class="loader-spinner"></div>
1149
+ <div class="loader-text">Generating...</div>
1150
+ <div class="loader-bar-track"><div class="loader-bar-fill"></div></div>
1151
+ </div>
1152
+ <div class="out-placeholder-text">Small Decoder result</div>
1153
+ </div>
1154
+ </div>
1155
+
1156
+ </div><!-- end output-compare-row -->
1157
+ </div><!-- end output-panel -->
1158
 
1159
+ <!-- Advanced Settings -->
1160
  <div class="settings-group">
1161
  <div class="settings-group-title">Advanced Settings</div>
1162
  <div class="settings-group-body">
 
1167
  </div>
1168
  <div class="checkbox-row">
1169
  <input type="checkbox" id="custom-randomize" checked>
1170
+ <label for="custom-randomize">Randomize seed each run</label>
1171
  </div>
1172
  <div class="slider-row">
1173
  <label>Guidance</label>
 
1179
  <input type="range" id="custom-steps" min="1" max="20" step="1" value="4">
1180
  <span class="slider-val" id="custom-steps-val">4</span>
1181
  </div>
1182
+ <div class="slider-row-2col">
1183
+ <div class="slider-row" style="flex:1">
1184
+ <label>Width</label>
1185
+ <input type="range" id="custom-width" min="256" max="1024" step="8" value="1024">
1186
+ <span class="slider-val" id="custom-width-val">1024</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1187
  </div>
1188
+ <div class="slider-row" style="flex:1">
1189
+ <label>Height</label>
1190
+ <input type="range" id="custom-height" min="256" max="1024" step="8" value="1024">
1191
+ <span class="slider-val" id="custom-height-val">1024</span>
 
1192
  </div>
1193
  </div>
 
1194
  </div>
1195
  </div>
 
1196
 
1197
+ </div><!-- end app-main-right -->
1198
+ </div><!-- end app-main-row -->
1199
 
1200
  <div class="exp-note">
1201
+ Experimental Space —
1202
+ <a href="https://huggingface.co/black-forest-labs/FLUX.2-klein-4B" target="_blank">FLUX.2-klein-4B</a>
1203
+ &middot;
1204
+ <a href="https://huggingface.co/black-forest-labs/FLUX.2-small-decoder" target="_blank">FLUX.2-small-decoder</a>
1205
+ &middot; Compare Standard vs Small Decoder VAE side-by-side
1206
  </div>
1207
 
1208
  <div class="app-statusbar">
1209
+ <div class="sb-section" id="sb-image-count">No images loaded</div>
1210
  <div class="sb-section sb-fixed">Ready</div>
1211
  </div>
1212
 
1213
+ </div><!-- end app-shell -->
1214
  """)
1215
 
1216
  run_btn = gr.Button("Run", elem_id="gradio-run-btn")
 
1220
 
1221
  run_btn.click(
1222
  fn=infer,
1223
+ inputs=[hidden_images_b64, prompt, seed, randomize_seed, width_gr, height_gr, steps, guidance_scale],
1224
+ outputs=[result_std, result_small_gr, seed_output, timing_std_gr, timing_small_gr],
 
 
 
 
 
 
 
 
 
1225
  js=r"""(imgs, p, s, rs, w, h, st, gs) => {
1226
  const images = window.__uploadedImages || [];
1227
  const b64Array = images.map(img => img.b64);
1228
  const imgsJson = JSON.stringify(b64Array);
1229
  const promptEl = document.getElementById('custom-prompt-input');
1230
  const promptVal = promptEl ? promptEl.value : p;
1231
+ const seedEl = document.getElementById('custom-seed');
1232
+ const sv = seedEl ? parseFloat(seedEl.value) : s;
1233
+ const randEl = document.getElementById('custom-randomize');
1234
+ const rv = randEl ? randEl.checked : rs;
1235
+ const wEl = document.getElementById('custom-width');
1236
+ const hEl = document.getElementById('custom-height');
1237
+ const wv = wEl ? parseFloat(wEl.value) : w;
1238
+ const hv = hEl ? parseFloat(hEl.value) : h;
1239
+ const stEl = document.getElementById('custom-steps');
1240
+ const stv = stEl ? parseFloat(stEl.value) : st;
1241
+ const gsEl = document.getElementById('custom-guidance');
1242
+ const gsv = gsEl ? parseFloat(gsEl.value) : gs;
1243
+ return [imgsJson, promptVal, sv, rv, wv, hv, stv, gsv];
1244
  }""",
1245
  )
1246
 
 
1253
 
1254
  if __name__ == "__main__":
1255
  demo.queue(max_size=20).launch(
 
1256
  ssr_mode=False,
1257
  show_error=True,
1258
  allowed_paths=["examples"],