AST-1320 commited on
Commit
ab866ca
·
verified ·
1 Parent(s): 5c8fd8b

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +378 -224
app.py CHANGED
@@ -68,7 +68,6 @@ from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
68
 
69
  import Age_classifier
70
 
71
- from esrgan import load_esrgan, upscale_to_pixels
72
  from security import (
73
  FloodGuard,
74
  check_admin_password,
@@ -120,10 +119,40 @@ if torch.cuda.is_available():
120
  MODEL_QWEN = "qwen"
121
  MODEL_FLUX = "flux"
122
 
123
- ASPECT_RATIOS = ["Default", "1:1", "3:2", "2:3", "9:16", "16:9", "3:4", "4:3", "21:9", "9:21"]
124
- RATIO_DEFAULT = "Default"
125
- QWEN_TARGET_PIXELS = 2073600
126
- UPSCALE_PIXELS = {"Basic": 3686400, "Medium": 5760000, "High": 8294400}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
  from diffusers.utils import logging as diffusers_logging
129
 
@@ -219,7 +248,7 @@ def _enable_vae_tiling(p, tile=None):
219
  print("[vae] tiling applied:", {k: getattr(vae, k, None) for k in ("tile_sample_min_height", "tile_sample_min_width", "tile_sample_stride_height", "tile_sample_stride_width")})
220
 
221
 
222
- QWEN_VAE_TILE = int(os.environ.get("QWEN_VAE_TILE", "0"))
223
 
224
 
225
  pipe = QwenImageEditPlusPipeline.from_pretrained(
@@ -256,20 +285,16 @@ if getattr(pipe, "text_encoder", None) is not None:
256
  except Exception as e:
257
  print(f"[qwen] text_encoder quantization FAILED, staying bf16 (~+8GB VRAM): {e}")
258
 
259
- if QWEN_VAE_TILE > 0:
260
- _enable_vae_tiling(pipe, QWEN_VAE_TILE)
261
- else:
262
- print("[vae] Qwen VAE tiling OFF (full-frame decode)")
263
 
264
  pipe.transformer.__class__ = QwenImageTransformer2DModel
265
  pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
266
 
267
  FLUX_REPO = "black-forest-labs/FLUX.2-klein-9B"
268
- FLUX_REFINE_PROMPT = "An ultra-high-resolution masterpiece with exceptional sharpness and detail, featuring only a subtle, barely perceptible amount of noise on the skin’s surface. The image remains entirely unzoomed and unaltered, preserving its original composition and proportions, with lighting and shadows meticulously controlled to remain perfectly faithful to the original."
269
  FLUX_REFINE_GUIDANCE_SCALE = 1
270
  FLUX_NUM_INFERENCE_STEPS = 4
271
- FLUX_SEED = 42
272
- FLUX_MAX_PIXELS = 2073600
273
 
274
  flux_pipe = Flux2KleinPipeline.from_pretrained(FLUX_REPO, torch_dtype=dtype)
275
 
@@ -288,33 +313,6 @@ try:
288
  except Exception as e:
289
  print(f"[flux] quantization FAILED, continuing in bf16 (roughly 2x the VRAM/transfer time): {e}")
290
 
291
- if hasattr(flux_pipe, "vae") and flux_pipe.vae is not None:
292
- try:
293
- flux_pipe.vae.enable_slicing()
294
- except Exception:
295
- pass
296
-
297
- _flux_ip = getattr(flux_pipe, "image_processor", None)
298
- if _flux_ip is not None and hasattr(_flux_ip, "_resize_to_target_area"):
299
- _orig_resize_to_target_area = _flux_ip._resize_to_target_area
300
-
301
- def _flux_resize_with_higher_cap(image, target_area):
302
- if target_area == 1024 * 1024:
303
- w, h = image.size
304
- if w * h <= FLUX_MAX_PIXELS:
305
- return image
306
- return _orig_resize_to_target_area(image, FLUX_MAX_PIXELS)
307
- return _orig_resize_to_target_area(image, target_area)
308
-
309
- _flux_ip._resize_to_target_area = _flux_resize_with_higher_cap
310
- print(f"[flux] reference-image cap raised: no downscale up to {FLUX_MAX_PIXELS} px")
311
- else:
312
- print("[flux] WARNING: image_processor._resize_to_target_area not found; "
313
- "FLUX will use its built-in ~1MP reference-image downscale")
314
-
315
- esrgan_model = load_esrgan(device)
316
- print("[esrgan] RealESRGAN_x2plus loaded")
317
-
318
  # FLUX VAE tiling is off by default because tiled encode/decode shifted skin colour.
319
  if os.environ.get("FLUX_VAE_TILING", "0") == "1":
320
  _enable_vae_tiling(flux_pipe)
@@ -323,6 +321,12 @@ gc.collect()
323
 
324
 
325
  MAX_SEED = 2**31 - 1
 
 
 
 
 
 
326
  _VALID_FORMATS = ("PNG", "JPG", "WebP")
327
 
328
  GPU_DURATION_MIN = 10
@@ -342,10 +346,6 @@ def _to_int(value, lo, hi, default):
342
  return max(lo, min(hi, v))
343
 
344
 
345
- LIMIT_GUIDANCE_MIN = 1.0
346
- LIMIT_GUIDANCE_MAX = 2.0
347
-
348
-
349
  def _to_float(value, lo, hi, default):
350
  try:
351
  v = float(value)
@@ -356,38 +356,42 @@ def _to_float(value, lo, hi, default):
356
  return max(lo, min(hi, v))
357
 
358
 
359
- def _round_size(width, height, multiple=8):
360
- return max(multiple, (int(width) // multiple) * multiple), max(multiple, (int(height) // multiple) * multiple)
361
-
 
 
 
 
362
 
363
- def _ratio_size(ratio, pixels=QWEN_TARGET_PIXELS, multiple=16):
364
- width = (pixels * ratio) ** 0.5
365
- height = width / ratio
366
- return (
367
- max(multiple, int(width) // multiple * multiple),
368
- max(multiple, int(height) // multiple * multiple),
369
- )
370
 
371
-
372
- def _image_ratio(image_1, image_2):
373
  src = image_1 if image_1 is not None else image_2
374
  if src is None:
375
- return 1.0
376
  try:
377
  with Image.open(src if isinstance(src, str) else src.name) as im:
378
- return im.width / im.height
379
  except Exception:
380
- return 1.0
 
 
 
 
 
381
 
382
 
383
- def _resolve_size(ratio_label, image_1, image_2):
384
  try:
385
- if ratio_label in ASPECT_RATIOS and ratio_label != RATIO_DEFAULT:
386
- w, h = ratio_label.split(":")
387
- return _ratio_size(int(w) / int(h))
388
- except (TypeError, ValueError, ZeroDivisionError):
 
 
 
389
  pass
390
- return _ratio_size(_image_ratio(image_1, image_2))
391
 
392
 
393
  def _fit_size(width, height, max_pixels=FLUX_MAX_PIXELS, multiple=16):
@@ -403,7 +407,12 @@ def _fit_size(width, height, max_pixels=FLUX_MAX_PIXELS, multiple=16):
403
  def _load_image(img):
404
  if not isinstance(img, Image.Image):
405
  img = Image.open(img if isinstance(img, str) else img.name)
406
- return img.convert("RGB")
 
 
 
 
 
407
 
408
 
409
  def _sanitize_common(seed, gpu_duration, output_format):
@@ -447,6 +456,7 @@ def _generate_core(
447
  num_inference_steps,
448
  height,
449
  width,
 
450
  progress=gr.Progress(track_tqdm=True),
451
  ):
452
  if randomize_seed:
@@ -535,7 +545,6 @@ def _finalize(res, image_1, image_2, prompt, output_format):
535
  else:
536
  img.save(output_path)
537
  output_paths.append(output_path)
538
- print(f"[out] resolution={img.width}x{img.height}")
539
  log_generation(
540
  path=output_path,
541
  prompt=prompt,
@@ -550,49 +559,30 @@ def _finalize(res, image_1, image_2, prompt, output_format):
550
  inputs=input_paths,
551
  )
552
  has_output = bool(output_paths)
553
- return output_paths, seed, gr.update(visible=has_output)
554
 
555
 
556
- def _make_qwen_flux_gpu_variant(duration_seconds, size):
557
- def _qwen_then_flux_gpu_call(qwen_kwargs, scale_up):
558
- _free_vram_cache()
559
- q = _generate_core(MODEL_QWEN, **qwen_kwargs)
560
- if not q["images"]:
561
- return q, None
562
- _free_vram_cache()
563
- img = q["images"][0]
564
- w, h = _round_size(*img.size, multiple=16)
565
- f = _generate_core(
566
- MODEL_FLUX, image_1=img, image_2=None, prompt=FLUX_REFINE_PROMPT,
567
- seed=FLUX_SEED, randomize_seed=False,
568
- true_guidance_scale=FLUX_REFINE_GUIDANCE_SCALE,
569
- num_inference_steps=FLUX_NUM_INFERENCE_STEPS,
570
- height=h, width=w,
571
- progress=qwen_kwargs["progress"],
572
- )
573
- if scale_up and f["images"]:
574
- _free_vram_cache()
575
- f = {**f, "images": [upscale_to_pixels(esrgan_model, f["images"][0], scale_up)]}
576
- return q, f
577
 
578
- _qwen_then_flux_gpu_call.__name__ = f"_qwen_then_flux_gpu_{size}_{duration_seconds}s"
579
- _qwen_then_flux_gpu_call.__qualname__ = _qwen_then_flux_gpu_call.__name__
580
- return spaces.GPU(duration=duration_seconds, size=size)(_qwen_then_flux_gpu_call)
581
 
582
 
583
- _qwen_then_flux_variants = {(s, d): _make_qwen_flux_gpu_variant(d, s) for s in GPU_SIZE_OPTIONS for d in _GPU_DURATION_STEPS}
584
 
585
 
586
- def _pick_qwen_flux_variant(gpu_duration, gpu_size=GPU_SIZE_DEFAULT):
587
  requested = _to_int(gpu_duration, GPU_DURATION_MIN, GPU_DURATION_MAX, GPU_DURATION_DEFAULT)
588
  chosen = next((d for d in _GPU_DURATION_STEPS if d >= requested), _GPU_DURATION_STEPS[-1])
589
  size = gpu_size if gpu_size in GPU_SIZE_OPTIONS else GPU_SIZE_DEFAULT
590
- gpu_fn = _qwen_then_flux_variants[(size, chosen)]
591
 
592
- def _call(image_1, image_2, prompt, output_format, qwen_kwargs, scale_up):
593
- q, f = gpu_fn(qwen_kwargs, scale_up)
594
- final = f if (f is not None and f["images"]) else q
595
- return _finalize({**q, "images": final["images"]}, image_1, image_2, prompt, output_format)
596
 
597
  return _call
598
 
@@ -691,7 +681,7 @@ def _run_limited(request, seed, run):
691
  ok, key, msg = rate_acquire(request)
692
  if not ok:
693
  show_rate_message(msg)
694
- return gr.update(), seed, gr.update()
695
  refund = False
696
  try:
697
  return run()
@@ -702,7 +692,7 @@ def _run_limited(request, seed, run):
702
  rate_release(key, refund=refund)
703
 
704
 
705
- def run_qwen_and_flux(
706
  image_1,
707
  image_2,
708
  prompt,
@@ -712,10 +702,11 @@ def run_qwen_and_flux(
712
  num_inference_steps,
713
  gpu_duration,
714
  gpu_size,
715
- aspect_ratio,
716
- scale_basic,
717
- scale_medium,
718
- scale_high,
 
719
  output_format,
720
  age_confirmed=False,
721
  progress=gr.Progress(track_tqdm=True),
@@ -725,34 +716,77 @@ def run_qwen_and_flux(
725
  raise gr.Error("Please tick the box to confirm you are 18 or older.")
726
 
727
  if not (isinstance(prompt, str) and prompt.strip()):
728
- return gr.update(), seed, gr.update()
729
 
730
  def run():
731
  clean_seed, duration, fmt = _sanitize_common(seed, gpu_duration, output_format)
732
- width, height = _resolve_size(aspect_ratio, image_1, image_2)
733
  clean_prompt = prompt if isinstance(prompt, str) else ""
734
- qwen_kwargs = dict(
735
- image_1=image_1, image_2=image_2, prompt=clean_prompt,
736
- seed=clean_seed, randomize_seed=bool(randomize_seed),
737
- true_guidance_scale=_to_float(true_guidance_scale, LIMIT_GUIDANCE_MIN, LIMIT_GUIDANCE_MAX, 1.0),
738
- num_inference_steps=int(num_inference_steps),
739
- height=height, width=width,
740
- progress=progress,
741
- )
742
- upscale_pixels = next(
743
- (UPSCALE_PIXELS[name] for name, on in (("High", scale_high), ("Medium", scale_medium), ("Basic", scale_basic)) if on),
744
- 0,
745
- )
746
  try:
747
  _screen_inputs(request, clean_prompt, image_1, image_2)
748
- return _screen_outputs(
749
- _pick_qwen_flux_variant(duration, gpu_size)(image_1, image_2, clean_prompt, fmt, qwen_kwargs, upscale_pixels),
750
- request,
751
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
752
  except gr.Error as exc:
753
  if not _is_age_block(exc):
754
  raise
755
- return [_block_image()], seed, gr.update(visible=False)
756
 
757
  return _run_limited(request, seed, run)
758
 
@@ -764,6 +798,11 @@ def use_output_as_input(output_images):
764
 
765
 
766
  def _sync_result_followups(output_images):
 
 
 
 
 
767
  return gr.update(visible=bool(output_images) and not _is_block_image(output_images))
768
 
769
 
@@ -781,6 +820,23 @@ css = """
781
  }
782
  #logo-title {
783
  text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
784
  }
785
 
786
  @keyframes ast-title-flash {
@@ -802,11 +858,6 @@ css = """
802
  @media (max-width: 480px) {
803
  #ast-main-title { font-size: 5.8vw !important; }
804
  }
805
- #logo-title h1#ast-nfaa-title {
806
- margin: 0 -0.3em 0 0 !important;
807
- padding: 0 0.3em 0 0 !important;
808
- line-height: 1.15 !important;
809
- }
810
  #ast-nfaa-title {
811
  font-style: italic;
812
  display: inline-block;
@@ -826,7 +877,7 @@ css = """
826
  #ast-notice {
827
  width: 100%;
828
  max-width: 560px;
829
- margin: 11px auto 0 !important;
830
  padding: 1px 0 1px 14px !important;
831
  border-left: 2px solid transparent;
832
  border-image: linear-gradient(180deg, #FF3CAC, #7B2FF7, #00E5FF) 1;
@@ -875,57 +926,63 @@ css = """
875
  background-color: #FF1010 !important;
876
  border-color: #FF1010 !important;
877
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
878
  #seed-slider .wrap.generating,
879
  #seed-slider .wrap.pending,
880
  #seed-slider .progress-text,
881
  #seed-slider .progress-level {
882
  display: none !important;
883
  }
884
- """
885
-
886
- _RATIO_ICON_MAX = 30
887
- _RATIO_ICON_BOX = 34
888
- _RATIO_ACCENT = "var(--checkbox-background-color-selected, var(--color-accent, var(--primary-500)))"
889
-
890
-
891
- def _ratio_css():
892
- acc = _RATIO_ACCENT
893
- rules = [
894
- "#aspect-ratio .wrap { display: grid !important; grid-template-columns: repeat(5, minmax(0, 1fr)) !important; gap: 6px !important; }",
895
- "#aspect-ratio label { display: flex !important; flex-direction: column !important; align-items: center !important; "
896
- "justify-content: center !important; gap: 0 !important; width: 100% !important; min-width: 0 !important; "
897
- "margin: 0 !important; padding: 8px 2px 6px !important; box-sizing: border-box !important; "
898
- "border-radius: 12px !important; cursor: pointer; }",
899
- "#aspect-ratio label input { display: none !important; }",
900
- "#aspect-ratio label span { font-size: 13px !important; line-height: 1.2 !important; white-space: nowrap; margin: 0 !important; padding: 0 !important; text-indent: 0 !important; text-align: center !important; }",
901
- "#aspect-ratio label::before { content: \"\"; display: block; flex: none; border-radius: 4px; background: rgba(128, 128, 128, 0.38); }",
902
- f"#aspect-ratio label:has(input:checked), #aspect-ratio label.selected {{ border-color: color-mix(in srgb, {acc} 90%, transparent) !important; background: color-mix(in srgb, {acc} 14%, transparent) !important; }}",
903
- f"#aspect-ratio label:has(input:checked)::before, #aspect-ratio label.selected::before {{ background: color-mix(in srgb, {acc} 70%, transparent); }}",
904
- '#aspect-ratio label:has(input[value="Default"])::before { background: transparent !important; border: 1.5px dashed rgba(150, 150, 150, 0.8); box-sizing: border-box; }',
905
- f'#aspect-ratio label:has(input[value="Default"]:checked)::before, #aspect-ratio label.selected:has(input[value="Default"])::before {{ border-color: color-mix(in srgb, {acc} 95%, transparent); }}',
906
- ]
907
- for label in ASPECT_RATIOS:
908
- w, h = (4, 3) if label == RATIO_DEFAULT else (int(x) for x in label.split(":"))
909
- k = _RATIO_ICON_MAX / max(w, h)
910
- iw, ih = round(w * k), round(h * k)
911
- pad = (_RATIO_ICON_BOX - ih) / 2
912
- rules.append(
913
- f'#aspect-ratio label:has(input[value="{label}"])::before '
914
- f'{{ width: {iw}px; height: {ih}px; margin: {pad:g}px 0; }}'
915
- )
916
- return "\n".join(rules) + "\n"
917
-
918
-
919
- css += _ratio_css()
920
- css += f"""
921
- #output-format {{ background: transparent !important; border: none !important; box-shadow: none !important; padding: 0 !important; }}
922
- #output-format .wrap {{ display: flex !important; justify-content: center !important; gap: 1px !important; background: transparent !important; padding: 0 !important; margin: 0 !important; }}
923
- #output-format label {{ flex: 1 1 0 !important; min-width: 0 !important; justify-content: center !important; align-items: center !important; height: 38px !important; min-height: 0 !important; margin: 0 !important; padding: 0 8px !important; border: none !important; border-radius: 0 !important; box-shadow: none !important; background: var(--block-background-fill) !important; }}
924
- #output-format label:has(input:checked), #output-format label.selected, #lanczos-row .block:has(input:checked) {{ background-image: linear-gradient(color-mix(in srgb, {_RATIO_ACCENT} 14%, transparent), color-mix(in srgb, {_RATIO_ACCENT} 14%, transparent)) !important; }}
925
- .lanczos-title {{ padding: 4px 0 3px 6px !important; background: var(--block-background-fill) !important; }}
926
- """
927
-
928
- css += """
929
  #gpu-size .wrap {
930
  display: grid !important;
931
  grid-template-columns: repeat(2, 1fr) !important;
@@ -958,8 +1015,32 @@ css += """
958
  line-height: 1.5 !important;
959
  opacity: 0.7;
960
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
961
  """
962
 
 
 
 
 
 
 
 
 
 
 
963
  _theme_dir = os.path.dirname(os.path.abspath(__file__))
964
  _theme_pick = os.environ.get("UI_THEME", "")
965
  if _theme_pick == "off":
@@ -980,8 +1061,7 @@ with gr.Blocks(**({} if _GR6 else {"css": css})) as demo:
980
  gr.HTML(
981
  '<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600&display=swap">'
982
  '<div id="logo-title">'
983
- '<h1 id="ast-main-title">Multi Ratio Image Editor</h1>'
984
- '<br>'
985
  '<h1 id="ast-nfaa-title">Not For All Audiences</h1>'
986
  '<div id="ast-notice">'
987
  '<p><b>Adults only:</b> Zero tolerance for any content involving minors. '
@@ -997,7 +1077,8 @@ with gr.Blocks(**({} if _GR6 else {"css": css})) as demo:
997
  with gr.Column():
998
  with gr.Row():
999
  image_1 = gr.Image(label="Image 1", type="filepath", interactive=True)
1000
- image_2 = gr.Image(label="Image 2 (optional)", type="filepath", interactive=True)
 
1001
 
1002
  prompt = gr.Text(
1003
  label="Prompt 🪄",
@@ -1012,26 +1093,8 @@ with gr.Blocks(**({} if _GR6 else {"css": css})) as demo:
1012
  )
1013
 
1014
  with gr.Group():
1015
- aspect_ratio = gr.Radio(
1016
- label="Aspect Ratio",
1017
- show_label=True,
1018
- choices=ASPECT_RATIOS,
1019
- value=RATIO_DEFAULT,
1020
- elem_id="aspect-ratio",
1021
- )
1022
-
1023
- with gr.Group():
1024
- gr.Markdown("Lanczos Upscale", elem_classes=["lanczos-title"])
1025
- with gr.Row(elem_id="lanczos-row"):
1026
- scale_basic = gr.Checkbox(label="Basic", value=False, min_width=0)
1027
- scale_medium = gr.Checkbox(label="Medium", value=False, min_width=0)
1028
- scale_high = gr.Checkbox(label="High", value=False, min_width=0)
1029
-
1030
- with gr.Group():
1031
- gr.Markdown("Output Format", elem_classes=["lanczos-title"])
1032
  output_format = gr.Radio(
1033
  label="Output Format",
1034
- show_label=False,
1035
  choices=["PNG", "JPG", "WebP"],
1036
  value="PNG",
1037
  elem_id="output-format",
@@ -1076,10 +1139,50 @@ with gr.Blocks(**({} if _GR6 else {"css": css})) as demo:
1076
  elem_id="gpu-size-note",
1077
  )
1078
 
1079
- with gr.Column():
1080
- result = gr.Gallery(label="Result", show_label=False, type="filepath")
1081
- use_output_btn = gr.Button("↗️ Use as input", variant="secondary", size="sm", visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1082
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1083
 
1084
  with gr.Tab("🔒", visible=False) as admin_tab:
1085
  with gr.Group(visible=True) as admin_lock_group:
@@ -1136,23 +1239,9 @@ with gr.Blocks(**({} if _GR6 else {"css": css})) as demo:
1136
  **_PRIVATE,
1137
  )
1138
 
1139
- for _box, _js in (
1140
- (scale_basic, "(on) => [on, false, false]"),
1141
- (scale_medium, "(on) => [false, on, false]"),
1142
- (scale_high, "(on) => [false, false, on]"),
1143
- ):
1144
- _box.input(
1145
- fn=None,
1146
- inputs=[_box],
1147
- outputs=[scale_basic, scale_medium, scale_high],
1148
- js=_js,
1149
- show_progress="hidden",
1150
- **_PRIVATE,
1151
- )
1152
-
1153
- edit_event = gr.on(
1154
  triggers=[run_button.click, prompt.submit],
1155
- fn=run_qwen_and_flux,
1156
  inputs=[
1157
  image_1,
1158
  image_2,
@@ -1163,28 +1252,54 @@ with gr.Blocks(**({} if _GR6 else {"css": css})) as demo:
1163
  num_inference_steps,
1164
  gpu_duration,
1165
  gpu_size,
1166
- aspect_ratio,
1167
- scale_basic,
1168
- scale_medium,
1169
- scale_high,
 
1170
  output_format,
1171
  age_confirm,
1172
  ],
1173
- outputs=[result, seed, use_output_btn],
1174
  **({"show_progress_on": [result]} if _SHOW_PROGRESS_ON else {}),
1175
- )
1176
- edit_event.then(
1177
  fn=_sync_result_followups,
1178
  inputs=result,
1179
- outputs=use_output_btn,
1180
  show_progress="hidden",
1181
  )
1182
 
1183
  use_output_btn.click(fn=use_output_as_input, inputs=[result], outputs=[image_1])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1184
  result.change(
1185
  fn=_sync_result_followups,
1186
  inputs=result,
1187
- outputs=use_output_btn,
1188
  show_progress="hidden",
1189
  )
1190
 
@@ -1206,6 +1321,45 @@ with gr.Blocks(**({} if _GR6 else {"css": css})) as demo:
1206
  **_PRIVATE,
1207
  )
1208
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1209
  demo.load(fn=mark_browser_session, inputs=None, outputs=None, **_PRIVATE)
1210
  demo.load(fn=check_admin_tab_visibility, inputs=None, outputs=[admin_tab], **_PRIVATE)
1211
 
 
68
 
69
  import Age_classifier
70
 
 
71
  from security import (
72
  FloodGuard,
73
  check_admin_password,
 
119
  MODEL_QWEN = "qwen"
120
  MODEL_FLUX = "flux"
121
 
122
+ PRESET_RESOLUTIONS = [
123
+ ("720×1280 (9:16)", 720, 1280),
124
+ ("864×1536 (9:16)", 864, 1536),
125
+ ("1080×1920 (9:16)", 1080, 1920),
126
+ ("1440×2560 (9:16)", 1440, 2560),
127
+ ("1600×2560 (10:16)", 1600, 2560),
128
+ ("1536×2728 (9:16)", 1536, 2728),
129
+ ("1280×720 (16:9)", 1280, 720),
130
+ ("1536×864 (16:9)", 1536, 864),
131
+ ("1920×1080 (16:9)", 1920, 1080),
132
+ ("2560×1440 (16:9)", 2560, 1440),
133
+ ("2560×1600 (16:10)", 2560, 1600),
134
+ ("2728×1536 (16:9)", 2728, 1536),
135
+ ("768×1024 (3:4)", 768, 1024),
136
+ ("1080×1440 (3:4)", 1080, 1440),
137
+ ("1536×2048 (3:4)", 1536, 2048),
138
+ ("1024×1536 (2:3)", 1024, 1536),
139
+ ("1440×1800 (4:5)", 1440, 1800),
140
+ ("1080×2400 (9:20)", 1080, 2400),
141
+ ("1024×768 (4:3)", 1024, 768),
142
+ ("1440×1080 (4:3)", 1440, 1080),
143
+ ("2048×1536 (4:3)", 2048, 1536),
144
+ ("1536×1024 (3:2)", 1536, 1024),
145
+ ("1800×1440 (5:4)", 1800, 1440),
146
+ ("2560×1080 (21:9)", 2560, 1080),
147
+ ]
148
+ PRESET_RESOLUTION_MAP = {label: (w, h) for label, w, h in PRESET_RESOLUTIONS}
149
+ PRESET_TALL = [label for label, w, h in PRESET_RESOLUTIONS if w < h]
150
+ PRESET_WIDE = [label for label, w, h in PRESET_RESOLUTIONS if w > h]
151
+ PRESET_ALL = PRESET_TALL + PRESET_WIDE
152
+ PRESET_DEFAULT = "720×1280 (9:16)"
153
+ AUTO_SCALES = ["1x", "2x", "3x", "4x"]
154
+ AUTO_BASE_PIXELS = 1024 * 1024
155
+ AUTO_MAX_PIXELS = 4194304
156
 
157
  from diffusers.utils import logging as diffusers_logging
158
 
 
248
  print("[vae] tiling applied:", {k: getattr(vae, k, None) for k in ("tile_sample_min_height", "tile_sample_min_width", "tile_sample_stride_height", "tile_sample_stride_width")})
249
 
250
 
251
+ QWEN_VAE_TILE = int(os.environ.get("QWEN_VAE_TILE", "1600"))
252
 
253
 
254
  pipe = QwenImageEditPlusPipeline.from_pretrained(
 
285
  except Exception as e:
286
  print(f"[qwen] text_encoder quantization FAILED, staying bf16 (~+8GB VRAM): {e}")
287
 
288
+ _enable_vae_tiling(pipe, QWEN_VAE_TILE)
 
 
 
289
 
290
  pipe.transformer.__class__ = QwenImageTransformer2DModel
291
  pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
292
 
293
  FLUX_REPO = "black-forest-labs/FLUX.2-klein-9B"
294
+ FLUX_REFINE_PROMPT = "An ultra-high-resolution masterpiece with exceptional sharpness and detail, featuring only a subtle, barely perceptible amount of noise on the skin’s surface. The image remains entirely unzoomed and unaltered, preserving its original composition and proportions, with lighting and shadows meticulously controlled to remain perfectly faithful to the original"
295
  FLUX_REFINE_GUIDANCE_SCALE = 1
296
  FLUX_NUM_INFERENCE_STEPS = 4
297
+ FLUX_MAX_PIXELS = 2048 * 2048
 
298
 
299
  flux_pipe = Flux2KleinPipeline.from_pretrained(FLUX_REPO, torch_dtype=dtype)
300
 
 
313
  except Exception as e:
314
  print(f"[flux] quantization FAILED, continuing in bf16 (roughly 2x the VRAM/transfer time): {e}")
315
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  # FLUX VAE tiling is off by default because tiled encode/decode shifted skin colour.
317
  if os.environ.get("FLUX_VAE_TILING", "0") == "1":
318
  _enable_vae_tiling(flux_pipe)
 
321
 
322
 
323
  MAX_SEED = 2**31 - 1
324
+ LIMIT_STEPS_MAX = 20
325
+ LIMIT_GUIDANCE_MIN = 1.0
326
+ LIMIT_GUIDANCE_MAX = 2.0
327
+ LIMIT_SIDE_MAX = 2728
328
+ LIMIT_PIXELS_MAX = 2048 * 2048
329
+ LIMIT_INPUT_PIXELS = 2560 * 2560
330
  _VALID_FORMATS = ("PNG", "JPG", "WebP")
331
 
332
  GPU_DURATION_MIN = 10
 
346
  return max(lo, min(hi, v))
347
 
348
 
 
 
 
 
349
  def _to_float(value, lo, hi, default):
350
  try:
351
  v = float(value)
 
356
  return max(lo, min(hi, v))
357
 
358
 
359
+ def _clamp_size(width, height, multiple=8, lo=64):
360
+ w = _to_int(width, lo, LIMIT_SIDE_MAX, lo)
361
+ h = _to_int(height, lo, LIMIT_SIDE_MAX, lo)
362
+ if w * h > LIMIT_PIXELS_MAX:
363
+ scale = (LIMIT_PIXELS_MAX / (w * h)) ** 0.5
364
+ w, h = int(w * scale), int(h * scale)
365
+ return max(lo, (w // multiple) * multiple), max(lo, (h // multiple) * multiple)
366
 
 
 
 
 
 
 
 
367
 
368
+ def _auto_size(scale_choice, image_1, image_2):
 
369
  src = image_1 if image_1 is not None else image_2
370
  if src is None:
371
+ return None, None
372
  try:
373
  with Image.open(src if isinstance(src, str) else src.name) as im:
374
+ width, height = im.size
375
  except Exception:
376
+ return None, None
377
+ factor = _to_int(str(scale_choice).rstrip("x"), 1, 4, 1)
378
+ pixels = min(AUTO_BASE_PIXELS * factor, AUTO_MAX_PIXELS)
379
+ ratio = width / height
380
+ out_w = (pixels * ratio) ** 0.5
381
+ return _clamp_size(int(out_w), int(out_w / ratio), multiple=16)
382
 
383
 
384
+ def _resolve_size(mode, preset, custom_width, custom_height, auto_scale, image_1, image_2):
385
  try:
386
+ if mode == "Preset" and preset in PRESET_RESOLUTION_MAP:
387
+ return _clamp_size(*PRESET_RESOLUTION_MAP[preset])
388
+ if mode == "Custom":
389
+ return _clamp_size(int(custom_width), int(custom_height))
390
+ if mode == "Auto":
391
+ return _auto_size(auto_scale, image_1, image_2)
392
+ except (TypeError, ValueError, OverflowError):
393
  pass
394
+ return None, None
395
 
396
 
397
  def _fit_size(width, height, max_pixels=FLUX_MAX_PIXELS, multiple=16):
 
407
  def _load_image(img):
408
  if not isinstance(img, Image.Image):
409
  img = Image.open(img if isinstance(img, str) else img.name)
410
+ img = img.convert("RGB")
411
+ w, h = img.size
412
+ if w * h > LIMIT_INPUT_PIXELS:
413
+ scale = (LIMIT_INPUT_PIXELS / (w * h)) ** 0.5
414
+ img = img.resize((max(1, int(w * scale)), max(1, int(h * scale))), Image.LANCZOS)
415
+ return img
416
 
417
 
418
  def _sanitize_common(seed, gpu_duration, output_format):
 
456
  num_inference_steps,
457
  height,
458
  width,
459
+ output_format,
460
  progress=gr.Progress(track_tqdm=True),
461
  ):
462
  if randomize_seed:
 
545
  else:
546
  img.save(output_path)
547
  output_paths.append(output_path)
 
548
  log_generation(
549
  path=output_path,
550
  prompt=prompt,
 
559
  inputs=input_paths,
560
  )
561
  has_output = bool(output_paths)
562
+ return output_paths, seed, gr.update(visible=has_output), gr.update(visible=has_output)
563
 
564
 
565
+ def _make_gpu_variant(duration_seconds, size):
566
+ def _infer_gpu_call(*args, **kwargs):
567
+ return _generate_core(*args, **kwargs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
568
 
569
+ _infer_gpu_call.__name__ = f"_infer_impl_gpu_{size}_{duration_seconds}s"
570
+ _infer_gpu_call.__qualname__ = _infer_gpu_call.__name__
571
+ return spaces.GPU(duration=duration_seconds, size=size)(_infer_gpu_call)
572
 
573
 
574
+ _gpu_variants = {(s, d): _make_gpu_variant(d, s) for s in GPU_SIZE_OPTIONS for d in _GPU_DURATION_STEPS}
575
 
576
 
577
+ def _pick_gpu_variant(gpu_duration, gpu_size=GPU_SIZE_DEFAULT):
578
  requested = _to_int(gpu_duration, GPU_DURATION_MIN, GPU_DURATION_MAX, GPU_DURATION_DEFAULT)
579
  chosen = next((d for d in _GPU_DURATION_STEPS if d >= requested), _GPU_DURATION_STEPS[-1])
580
  size = gpu_size if gpu_size in GPU_SIZE_OPTIONS else GPU_SIZE_DEFAULT
581
+ gpu_fn = _gpu_variants[(size, chosen)]
582
 
583
+ def _call(model_choice, image_1, image_2, prompt, *args, output_format="PNG", **kwargs):
584
+ res = gpu_fn(model_choice, image_1, image_2, prompt, *args, output_format=output_format, **kwargs)
585
+ return _finalize(res, image_1, image_2, prompt, output_format)
 
586
 
587
  return _call
588
 
 
681
  ok, key, msg = rate_acquire(request)
682
  if not ok:
683
  show_rate_message(msg)
684
+ return gr.update(), seed, gr.update(), gr.update()
685
  refund = False
686
  try:
687
  return run()
 
692
  rate_release(key, refund=refund)
693
 
694
 
695
+ def infer_qwen_only(
696
  image_1,
697
  image_2,
698
  prompt,
 
702
  num_inference_steps,
703
  gpu_duration,
704
  gpu_size,
705
+ resolution_mode,
706
+ resolution_preset,
707
+ custom_width,
708
+ custom_height,
709
+ auto_scale,
710
  output_format,
711
  age_confirmed=False,
712
  progress=gr.Progress(track_tqdm=True),
 
716
  raise gr.Error("Please tick the box to confirm you are 18 or older.")
717
 
718
  if not (isinstance(prompt, str) and prompt.strip()):
719
+ return gr.update(), seed, gr.update(), gr.update()
720
 
721
  def run():
722
  clean_seed, duration, fmt = _sanitize_common(seed, gpu_duration, output_format)
723
+ width, height = _resolve_size(resolution_mode, resolution_preset, custom_width, custom_height, auto_scale, image_1, image_2)
724
  clean_prompt = prompt if isinstance(prompt, str) else ""
 
 
 
 
 
 
 
 
 
 
 
 
725
  try:
726
  _screen_inputs(request, clean_prompt, image_1, image_2)
727
+ return _screen_outputs(_pick_gpu_variant(duration, gpu_size)(
728
+ MODEL_QWEN,
729
+ image_1,
730
+ image_2,
731
+ clean_prompt,
732
+ seed=clean_seed,
733
+ randomize_seed=bool(randomize_seed),
734
+ true_guidance_scale=_to_float(true_guidance_scale, LIMIT_GUIDANCE_MIN, LIMIT_GUIDANCE_MAX, 1.0),
735
+ num_inference_steps=_to_int(num_inference_steps, 1, LIMIT_STEPS_MAX, 4),
736
+ height=height,
737
+ width=width,
738
+ output_format=fmt,
739
+ progress=progress,
740
+ ), request)
741
+ except gr.Error as exc:
742
+ if not _is_age_block(exc):
743
+ raise
744
+ return [_block_image()], seed, gr.update(visible=False), gr.update(visible=False)
745
+
746
+ return _run_limited(request, seed, run)
747
+
748
+
749
+ def refine_with_flux(
750
+ output_images,
751
+ seed,
752
+ randomize_seed,
753
+ gpu_duration,
754
+ gpu_size,
755
+ output_format,
756
+ progress=gr.Progress(track_tqdm=True),
757
+ request: gr.Request = None,
758
+ ):
759
+ if not output_images:
760
+ return gr.update(), seed, gr.update(visible=False), gr.update(visible=False)
761
+
762
+ def run():
763
+ clean_seed, duration, fmt = _sanitize_common(seed, gpu_duration, output_format)
764
+ path = _first_path(output_images)
765
+ try:
766
+ with Image.open(path) as im:
767
+ width, height = _clamp_size(*im.size, multiple=16)
768
+ except Exception:
769
+ width = height = None
770
+ try:
771
+ _screen_inputs(request, "", path)
772
+ return _screen_outputs(_pick_gpu_variant(duration, gpu_size)(
773
+ MODEL_FLUX,
774
+ path,
775
+ None,
776
+ FLUX_REFINE_PROMPT,
777
+ seed=clean_seed,
778
+ randomize_seed=bool(randomize_seed),
779
+ true_guidance_scale=FLUX_REFINE_GUIDANCE_SCALE,
780
+ num_inference_steps=FLUX_NUM_INFERENCE_STEPS,
781
+ height=height,
782
+ width=width,
783
+ output_format=fmt,
784
+ progress=progress,
785
+ ), request)
786
  except gr.Error as exc:
787
  if not _is_age_block(exc):
788
  raise
789
+ return [_block_image()], seed, gr.update(visible=False), gr.update()
790
 
791
  return _run_limited(request, seed, run)
792
 
 
798
 
799
 
800
  def _sync_result_followups(output_images):
801
+ visible = bool(output_images) and not _is_block_image(output_images)
802
+ return gr.update(visible=visible), gr.update(visible=visible)
803
+
804
+
805
+ def _sync_flux_result_followups(output_images):
806
  return gr.update(visible=bool(output_images) and not _is_block_image(output_images))
807
 
808
 
 
820
  }
821
  #logo-title {
822
  text-align: center;
823
+ display: flex;
824
+ flex-direction: column;
825
+ align-items: center;
826
+ gap: 14px;
827
+ margin-bottom: 10px;
828
+ }
829
+ #logo-title h1 {
830
+ margin: 0 !important;
831
+ padding: 0 !important;
832
+ line-height: 1.15 !important;
833
+ }
834
+ /* Italic + background-clip:text clips the slanted tail of the last glyph; extra right padding
835
+ gives it room, and the negative margin keeps the title visually centered. */
836
+ #logo-title h1#ast-main-title,
837
+ #logo-title h1#ast-nfaa-title {
838
+ padding-right: 0.3em !important;
839
+ margin-right: -0.3em !important;
840
  }
841
 
842
  @keyframes ast-title-flash {
 
858
  @media (max-width: 480px) {
859
  #ast-main-title { font-size: 5.8vw !important; }
860
  }
 
 
 
 
 
861
  #ast-nfaa-title {
862
  font-style: italic;
863
  display: inline-block;
 
877
  #ast-notice {
878
  width: 100%;
879
  max-width: 560px;
880
+ margin: 0 auto !important;
881
  padding: 1px 0 1px 14px !important;
882
  border-left: 2px solid transparent;
883
  border-image: linear-gradient(180deg, #FF3CAC, #7B2FF7, #00E5FF) 1;
 
926
  background-color: #FF1010 !important;
927
  border-color: #FF1010 !important;
928
  }
929
+ /* Force a 2-column grid for the preset radio (Gradio's default flex-wrap stacks into one column on mobile). */
930
+ .preset-grid > .wrap,
931
+ .preset-grid .wrap {
932
+ display: grid !important;
933
+ grid-template-columns: 1fr 1fr !important;
934
+ gap: 8px !important;
935
+ }
936
+ #adv-settings:has(#res-mode input[value="Preset"]:checked) #res-custom-height,
937
+ #adv-settings:has(#res-mode input[value="Preset"]:checked) #res-custom-width,
938
+ #adv-settings:has(#res-mode input[value="Preset"]:checked) #res-auto-scale,
939
+ #adv-settings:has(#res-mode input[value="Custom"]:checked) #res-preset,
940
+ #adv-settings:has(#res-mode input[value="Custom"]:checked) #res-auto-scale,
941
+ #adv-settings:has(#res-mode input[value="Auto"]:checked) #res-preset,
942
+ #adv-settings:has(#res-mode input[value="Auto"]:checked) #res-custom-height,
943
+ #adv-settings:has(#res-mode input[value="Auto"]:checked) #res-custom-width {
944
+ display: none !important;
945
+ }
946
+ /* Seed is an output component: hide Gradio's progress/status overlay so it never covers the number box. */
947
  #seed-slider .wrap.generating,
948
  #seed-slider .wrap.pending,
949
  #seed-slider .progress-text,
950
  #seed-slider .progress-level {
951
  display: none !important;
952
  }
953
+ #flux-seed-slider .wrap.generating,
954
+ #flux-seed-slider .wrap.pending,
955
+ #flux-seed-slider .progress-text,
956
+ #flux-seed-slider .progress-level {
957
+ display: none !important;
958
+ }
959
+ .preset-grid label {
960
+ width: 100% !important;
961
+ margin: 0 !important;
962
+ box-sizing: border-box !important;
963
+ }
964
+ #res-mode .wrap {
965
+ display: grid !important;
966
+ grid-template-columns: repeat(3, 1fr) !important;
967
+ gap: 8px !important;
968
+ }
969
+ #res-mode label {
970
+ width: 100% !important;
971
+ margin: 0 !important;
972
+ box-sizing: border-box !important;
973
+ justify-content: center !important;
974
+ }
975
+ #output-format .wrap {
976
+ display: grid !important;
977
+ grid-template-columns: repeat(3, 1fr) !important;
978
+ gap: 8px !important;
979
+ }
980
+ #output-format label {
981
+ width: 100% !important;
982
+ margin: 0 !important;
983
+ box-sizing: border-box !important;
984
+ justify-content: center !important;
985
+ }
 
 
 
 
 
 
 
 
 
 
 
 
986
  #gpu-size .wrap {
987
  display: grid !important;
988
  grid-template-columns: repeat(2, 1fr) !important;
 
1015
  line-height: 1.5 !important;
1016
  opacity: 0.7;
1017
  }
1018
+ #res-auto-scale .wrap {
1019
+ display: grid !important;
1020
+ grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
1021
+ gap: 6px !important;
1022
+ }
1023
+ #res-auto-scale label {
1024
+ width: 100% !important;
1025
+ min-width: 0 !important;
1026
+ margin: 0 !important;
1027
+ padding-left: 8px !important;
1028
+ padding-right: 8px !important;
1029
+ box-sizing: border-box !important;
1030
+ justify-content: center !important;
1031
+ }
1032
  """
1033
 
1034
+ _div_a = f'.preset-grid label:has(input[value="{PRESET_WIDE[0]}"])'
1035
+ _div_b = f'.preset-grid label:has(input[value="{PRESET_WIDE[1]}"])'
1036
+ css += (
1037
+ f"{_div_a}, {_div_b} {{ position: relative !important; margin-top: 12px !important; overflow: visible !important; }}\n"
1038
+ f"{_div_a}::before, {_div_b}::before {{ content: \"\"; position: absolute; top: -10px; height: 1px; "
1039
+ f"background: rgba(128, 128, 128, 0.55); pointer-events: none; }}\n"
1040
+ f"{_div_a}::before {{ left: 0; right: -4px; }}\n"
1041
+ f"{_div_b}::before {{ left: -4px; right: 0; }}\n"
1042
+ )
1043
+
1044
  _theme_dir = os.path.dirname(os.path.abspath(__file__))
1045
  _theme_pick = os.environ.get("UI_THEME", "")
1046
  if _theme_pick == "off":
 
1061
  gr.HTML(
1062
  '<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600&display=swap">'
1063
  '<div id="logo-title">'
1064
+ '<h1 id="ast-main-title">Qwen 2511 + FLUX.2</h1>'
 
1065
  '<h1 id="ast-nfaa-title">Not For All Audiences</h1>'
1066
  '<div id="ast-notice">'
1067
  '<p><b>Adults only:</b> Zero tolerance for any content involving minors. '
 
1077
  with gr.Column():
1078
  with gr.Row():
1079
  image_1 = gr.Image(label="Image 1", type="filepath", interactive=True)
1080
+ with gr.Column():
1081
+ image_2 = gr.Image(label="Image 2 (optional)", type="filepath", interactive=True)
1082
 
1083
  prompt = gr.Text(
1084
  label="Prompt 🪄",
 
1093
  )
1094
 
1095
  with gr.Group():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1096
  output_format = gr.Radio(
1097
  label="Output Format",
 
1098
  choices=["PNG", "JPG", "WebP"],
1099
  value="PNG",
1100
  elem_id="output-format",
 
1139
  elem_id="gpu-size-note",
1140
  )
1141
 
1142
+ with gr.Group():
1143
+ resolution_mode = gr.Radio(
1144
+ label="Resolution mode",
1145
+ choices=["Auto", "Preset", "Custom"],
1146
+ value="Auto",
1147
+ elem_id="res-mode",
1148
+ )
1149
+ resolution_preset = gr.Radio(
1150
+ label="Resolution",
1151
+ choices=PRESET_ALL,
1152
+ value=PRESET_DEFAULT,
1153
+ elem_classes=["preset-grid"],
1154
+ elem_id="res-preset",
1155
+ )
1156
+ custom_height = gr.Slider(
1157
+ label="Height", minimum=256, maximum=2048, step=8, value=256,
1158
+ buttons=[], elem_id="res-custom-height",
1159
+ )
1160
+ custom_width = gr.Slider(
1161
+ label="Width", minimum=256, maximum=2048, step=8, value=256,
1162
+ buttons=[], elem_id="res-custom-width",
1163
+ )
1164
+ auto_scale = gr.Radio(
1165
+ label="Auto resolution",
1166
+ choices=AUTO_SCALES,
1167
+ value="1x",
1168
+ elem_id="res-auto-scale",
1169
+ )
1170
 
1171
+ with gr.Column():
1172
+ result = gr.Gallery(label="Result", show_label=False, type="filepath", interactive=True, elem_id="result-gallery")
1173
+ with gr.Column(visible=False) as refine_group:
1174
+ with gr.Row():
1175
+ use_output_btn = gr.Button("↗️ Use as input", variant="secondary", size="sm", visible=False)
1176
+ refine_with_flux_btn = gr.Button(
1177
+ "Refine with FLUX.2-klein-9B", variant="primary", size="lg",
1178
+ )
1179
+ flux_seed = gr.Slider(label="FLUX seed", minimum=0, maximum=MAX_SEED, step=1, value=42, elem_id="flux-seed-slider")
1180
+ flux_randomize_seed = gr.Checkbox(label="Randomize FLUX seed", value=False)
1181
+ flux_result = gr.Gallery(label="FLUX Result", show_label=True, type="filepath", interactive=True, elem_id="flux-result-gallery")
1182
+ with gr.Row():
1183
+ use_flux_output_btn = gr.Button(
1184
+ "↗️ Use FLUX output as input", variant="secondary", size="sm", visible=False,
1185
+ )
1186
 
1187
  with gr.Tab("🔒", visible=False) as admin_tab:
1188
  with gr.Group(visible=True) as admin_lock_group:
 
1239
  **_PRIVATE,
1240
  )
1241
 
1242
+ gr.on(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1243
  triggers=[run_button.click, prompt.submit],
1244
+ fn=infer_qwen_only,
1245
  inputs=[
1246
  image_1,
1247
  image_2,
 
1252
  num_inference_steps,
1253
  gpu_duration,
1254
  gpu_size,
1255
+ resolution_mode,
1256
+ resolution_preset,
1257
+ custom_width,
1258
+ custom_height,
1259
+ auto_scale,
1260
  output_format,
1261
  age_confirm,
1262
  ],
1263
+ outputs=[result, seed, use_output_btn, refine_group],
1264
  **({"show_progress_on": [result]} if _SHOW_PROGRESS_ON else {}),
1265
+ ).then(
 
1266
  fn=_sync_result_followups,
1267
  inputs=result,
1268
+ outputs=[use_output_btn, refine_group],
1269
  show_progress="hidden",
1270
  )
1271
 
1272
  use_output_btn.click(fn=use_output_as_input, inputs=[result], outputs=[image_1])
1273
+
1274
+ refine_with_flux_btn.click(
1275
+ fn=refine_with_flux,
1276
+ inputs=[result, flux_seed, flux_randomize_seed, gpu_duration, gpu_size, output_format],
1277
+ outputs=[flux_result, flux_seed, use_flux_output_btn, refine_group],
1278
+ **({"show_progress_on": [flux_result]} if _SHOW_PROGRESS_ON else {}),
1279
+ ).then(
1280
+ fn=_sync_result_followups,
1281
+ inputs=result,
1282
+ outputs=[use_output_btn, refine_group],
1283
+ show_progress="hidden",
1284
+ ).then(
1285
+ fn=_sync_flux_result_followups,
1286
+ inputs=flux_result,
1287
+ outputs=use_flux_output_btn,
1288
+ show_progress="hidden",
1289
+ )
1290
+
1291
+ use_flux_output_btn.click(fn=use_output_as_input, inputs=[flux_result], outputs=[image_1])
1292
+
1293
+ flux_result.change(
1294
+ fn=_sync_flux_result_followups,
1295
+ inputs=flux_result,
1296
+ outputs=use_flux_output_btn,
1297
+ show_progress="hidden",
1298
+ )
1299
  result.change(
1300
  fn=_sync_result_followups,
1301
  inputs=result,
1302
+ outputs=[use_output_btn, refine_group],
1303
  show_progress="hidden",
1304
  )
1305
 
 
1321
  **_PRIVATE,
1322
  )
1323
 
1324
+ result.upload(fn=lambda: None, inputs=None, outputs=result, show_progress="hidden", **_PRIVATE)
1325
+ flux_result.upload(fn=lambda: None, inputs=None, outputs=flux_result, show_progress="hidden", **_PRIVATE)
1326
+ demo.load(
1327
+ fn=None, inputs=None, outputs=None,
1328
+ js="""() => {
1329
+ const ids = ["result-gallery", "flux-result-gallery"];
1330
+ const inGallery = (t) => t && t.closest && ids.some((id) => t.closest("#" + id));
1331
+ const lock = () => ids.forEach((id) => {
1332
+ const root = document.getElementById(id);
1333
+ if (!root) return;
1334
+ root.querySelectorAll("input[type=file]").forEach((i) => {
1335
+ i.disabled = true;
1336
+ const wrap = i.closest("button");
1337
+ if (wrap) wrap.style.visibility = "hidden";
1338
+ });
1339
+ });
1340
+ ["dragenter", "dragover", "drop"].forEach((ev) =>
1341
+ document.addEventListener(ev, (e) => {
1342
+ if (inGallery(e.target)) { e.preventDefault(); e.stopPropagation(); }
1343
+ }, true));
1344
+ new MutationObserver(lock).observe(document.body, { childList: true, subtree: true });
1345
+ lock();
1346
+ }""",
1347
+ **_PRIVATE,
1348
+ )
1349
+
1350
+ resolution_mode.change(
1351
+ fn=None, inputs=None, outputs=None,
1352
+ js="""() => {
1353
+ const nudge = () => {
1354
+ window.dispatchEvent(new Event("resize"));
1355
+ try { if (window.parentIFrame && window.parentIFrame.size) window.parentIFrame.size(); } catch (e) {}
1356
+ };
1357
+ [0, 100, 300, 800].forEach((ms) => setTimeout(nudge, ms));
1358
+ }""",
1359
+ show_progress="hidden",
1360
+ **_PRIVATE,
1361
+ )
1362
+
1363
  demo.load(fn=mark_browser_session, inputs=None, outputs=None, **_PRIVATE)
1364
  demo.load(fn=check_admin_tab_visibility, inputs=None, outputs=[admin_tab], **_PRIVATE)
1365