beznogim666 commited on
Commit
6714888
·
verified ·
1 Parent(s): 0585231

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -5
app.py CHANGED
@@ -27,6 +27,13 @@ COMFY_PORT = int(os.environ.get("COMFYUI_PORT", "8188"))
27
  COMFY_URL = f"http://{COMFY_HOST}:{COMFY_PORT}"
28
  MAX_SEED = 2**31 - 1
29
 
 
 
 
 
 
 
 
30
  # ZeroGPU per-call duration cap. Krea2-Turbo itself generates in ~10-20s once
31
  # ComfyUI is warm; the slider lets you dial the *requested* quota down to match
32
  # reality instead of the developer's original 900s+ hardcoded guess, which blew
@@ -62,7 +69,16 @@ def _validate_comfyui():
62
  response = requests.get(f"{COMFY_URL}/object_info", timeout=30)
63
  response.raise_for_status()
64
  object_info = response.json()
65
- required_nodes = ["UNETLoader", "CLIPLoader", "VAELoader", "CLIPTextEncode", "KSampler", "VAEDecode", "SaveImage"]
 
 
 
 
 
 
 
 
 
66
  missing = [node for node in required_nodes if node not in object_info]
67
  if missing:
68
  raise RuntimeError(f"ComfyUI is missing required nodes: {', '.join(missing)}")
@@ -77,6 +93,10 @@ def _validate_comfyui():
77
  if VAE_FILE not in vae_info:
78
  raise RuntimeError(f"Krea2 VAE is not visible to ComfyUI. First VAEs: {', '.join(vae_info[:10])}")
79
 
 
 
 
 
80
 
81
  def _ensure_comfyui():
82
  """Pure CPU/network setup: clone repo, pip install, download weights.
@@ -93,15 +113,23 @@ def _ensure_comfyui():
93
  diffusion_dir = COMFY_DIR / "models" / "diffusion_models"
94
  text_encoder_dir = COMFY_DIR / "models" / "text_encoders"
95
  vae_dir = COMFY_DIR / "models" / "vae"
 
96
  diffusion_dir.mkdir(parents=True, exist_ok=True)
97
  text_encoder_dir.mkdir(parents=True, exist_ok=True)
98
  vae_dir.mkdir(parents=True, exist_ok=True)
 
99
  hf_hub_download(
100
  repo_id=MODEL_REPO,
101
  filename=MODEL_FILE,
102
  local_dir=str(diffusion_dir),
103
  token=os.environ.get("HF_TOKEN"),
104
  )
 
 
 
 
 
 
105
  hf_hub_download(
106
  repo_id=COMFY_KREA_REPO,
107
  filename=f"text_encoders/{TEXT_ENCODER_FILE}",
@@ -147,7 +175,59 @@ def _start_comfyui():
147
  _validate_comfyui()
148
 
149
 
150
- def _build_workflow(prompt, negative_prompt, width, height, steps, cfg, seed, sampler, scheduler):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  workflow = {
152
  "1": {
153
  "class_type": "UNETLoader",
@@ -204,6 +284,9 @@ def _build_workflow(prompt, negative_prompt, width, height, steps, cfg, seed, sa
204
  "class_type": "ConditioningZeroOut",
205
  "inputs": {"conditioning": ["2", 0]},
206
  }
 
 
 
207
  return workflow
208
 
209
 
@@ -234,7 +317,19 @@ def _resize_for_edit(image, width, height):
234
  return image.convert("RGB").resize((width, height), Image.LANCZOS)
235
 
236
 
237
- def _build_edit_workflow(input_filename, prompt, negative_prompt, steps, cfg, seed, sampler, scheduler, denoise):
 
 
 
 
 
 
 
 
 
 
 
 
238
  workflow = {
239
  "1": {
240
  "class_type": "UNETLoader",
@@ -295,6 +390,9 @@ def _build_edit_workflow(input_filename, prompt, negative_prompt, steps, cfg, se
295
  "class_type": "ConditioningZeroOut",
296
  "inputs": {"conditioning": ["2", 0]},
297
  }
 
 
 
298
  return workflow
299
 
300
 
@@ -354,7 +452,19 @@ def _clamp_duration(value):
354
  # decorated function and returns the requested duration. gpu_duration is just
355
  # the slider value passed straight through - no more 900s+megapixels guessing.
356
  def _duration(
357
- prompt, negative_prompt, width, height, steps, cfg, seed, randomize_seed, sampler, scheduler, gpu_duration
 
 
 
 
 
 
 
 
 
 
 
 
358
  ):
359
  return _clamp_duration(gpu_duration)
360
 
@@ -372,6 +482,8 @@ def _edit_duration(
372
  randomize_seed,
373
  sampler,
374
  scheduler,
 
 
375
  gpu_duration,
376
  ):
377
  return _clamp_duration(gpu_duration)
@@ -389,6 +501,8 @@ def generate(
389
  randomize_seed=True,
390
  sampler="er_sde",
391
  scheduler="simple",
 
 
392
  gpu_duration=DURATION_DEFAULT,
393
  ):
394
  if not prompt or not prompt.strip():
@@ -398,7 +512,19 @@ def generate(
398
 
399
  try:
400
  _start_comfyui()
401
- workflow = _build_workflow(prompt, negative_prompt, width, height, steps, cfg, seed, sampler, scheduler)
 
 
 
 
 
 
 
 
 
 
 
 
402
  prompt_id = _queue_prompt(workflow)
403
  history_item = _wait_for_history(prompt_id)
404
  return _load_output_image(history_item), seed
@@ -420,6 +546,8 @@ def edit_image(
420
  randomize_seed=True,
421
  sampler="er_sde",
422
  scheduler="simple",
 
 
423
  gpu_duration=DURATION_DEFAULT,
424
  ):
425
  if input_image is None:
@@ -443,6 +571,8 @@ def edit_image(
443
  sampler,
444
  scheduler,
445
  denoise,
 
 
446
  )
447
  prompt_id = _queue_prompt(workflow)
448
  history_item = _wait_for_history(prompt_id)
@@ -451,6 +581,12 @@ def edit_image(
451
  raise gr.Error(str(exc)) from exc
452
 
453
 
 
 
 
 
 
 
454
 
455
  CSS = """
456
  .gradio-container { max-width: 1120px !important; margin: 0 auto !important; }
@@ -468,6 +604,11 @@ DURATION_INFO = (
468
  "and the model are still loading)."
469
  )
470
 
 
 
 
 
 
471
  with gr.Blocks(title="Redcraft Krea2", css=CSS) as demo:
472
  gr.Markdown("# Redcraft Krea2")
473
  gr.Markdown("ComfyUI-native Redcraft Krea2 generation and image editing.")
@@ -487,6 +628,17 @@ with gr.Blocks(title="Redcraft Krea2", css=CSS) as demo:
487
  with gr.Row():
488
  seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
489
  randomize_seed = gr.Checkbox(value=True, label="Randomize seed")
 
 
 
 
 
 
 
 
 
 
 
490
  gpu_duration = gr.Slider(
491
  DURATION_MIN,
492
  DURATION_MAX,
@@ -517,10 +669,13 @@ with gr.Blocks(title="Redcraft Krea2", css=CSS) as demo:
517
  randomize_seed,
518
  sampler,
519
  scheduler,
 
 
520
  gpu_duration,
521
  ]
522
  run.click(generate, inputs, [output, seed])
523
  prompt.submit(generate, inputs, [output, seed])
 
524
 
525
  with gr.Tab("Edit Image"):
526
  with gr.Row():
@@ -545,6 +700,17 @@ with gr.Blocks(title="Redcraft Krea2", css=CSS) as demo:
545
  with gr.Row():
546
  edit_seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
547
  edit_randomize_seed = gr.Checkbox(value=True, label="Randomize seed")
 
 
 
 
 
 
 
 
 
 
 
548
  edit_gpu_duration = gr.Slider(
549
  DURATION_MIN,
550
  DURATION_MAX,
@@ -577,10 +743,13 @@ with gr.Blocks(title="Redcraft Krea2", css=CSS) as demo:
577
  edit_randomize_seed,
578
  edit_sampler,
579
  edit_scheduler,
 
 
580
  edit_gpu_duration,
581
  ]
582
  edit_run.click(edit_image, edit_inputs, [edit_output, edit_seed])
583
  edit_prompt.submit(edit_image, edit_inputs, [edit_output, edit_seed])
 
584
 
585
 
586
  _ensure_comfyui()
 
27
  COMFY_URL = f"http://{COMFY_HOST}:{COMFY_PORT}"
28
  MAX_SEED = 2**31 - 1
29
 
30
+ # LoRA lives in the same HF repo as the main checkpoint and gets pulled down
31
+ # at startup, same as MODEL_FILE below. "none" in the dropdown always means
32
+ # "skip LoraLoader entirely" - everything else is downloaded automatically.
33
+ LORA_REPO = "beznogim666/test2"
34
+ LORA_FILE = "lora.safetensors"
35
+ LORA_NONE = "none"
36
+
37
  # ZeroGPU per-call duration cap. Krea2-Turbo itself generates in ~10-20s once
38
  # ComfyUI is warm; the slider lets you dial the *requested* quota down to match
39
  # reality instead of the developer's original 900s+ hardcoded guess, which blew
 
69
  response = requests.get(f"{COMFY_URL}/object_info", timeout=30)
70
  response.raise_for_status()
71
  object_info = response.json()
72
+ required_nodes = [
73
+ "UNETLoader",
74
+ "CLIPLoader",
75
+ "VAELoader",
76
+ "CLIPTextEncode",
77
+ "KSampler",
78
+ "VAEDecode",
79
+ "SaveImage",
80
+ "LoraLoaderModelOnly",
81
+ ]
82
  missing = [node for node in required_nodes if node not in object_info]
83
  if missing:
84
  raise RuntimeError(f"ComfyUI is missing required nodes: {', '.join(missing)}")
 
93
  if VAE_FILE not in vae_info:
94
  raise RuntimeError(f"Krea2 VAE is not visible to ComfyUI. First VAEs: {', '.join(vae_info[:10])}")
95
 
96
+ lora_info = object_info["LoraLoaderModelOnly"]["input"]["required"]["lora_name"][0]
97
+ if LORA_FILE not in lora_info:
98
+ raise RuntimeError(f"LoRA is not visible to ComfyUI. First LoRAs: {', '.join(lora_info[:10])}")
99
+
100
 
101
  def _ensure_comfyui():
102
  """Pure CPU/network setup: clone repo, pip install, download weights.
 
113
  diffusion_dir = COMFY_DIR / "models" / "diffusion_models"
114
  text_encoder_dir = COMFY_DIR / "models" / "text_encoders"
115
  vae_dir = COMFY_DIR / "models" / "vae"
116
+ lora_dir = COMFY_DIR / "models" / "loras"
117
  diffusion_dir.mkdir(parents=True, exist_ok=True)
118
  text_encoder_dir.mkdir(parents=True, exist_ok=True)
119
  vae_dir.mkdir(parents=True, exist_ok=True)
120
+ lora_dir.mkdir(parents=True, exist_ok=True)
121
  hf_hub_download(
122
  repo_id=MODEL_REPO,
123
  filename=MODEL_FILE,
124
  local_dir=str(diffusion_dir),
125
  token=os.environ.get("HF_TOKEN"),
126
  )
127
+ hf_hub_download(
128
+ repo_id=LORA_REPO,
129
+ filename=LORA_FILE,
130
+ local_dir=str(lora_dir),
131
+ token=os.environ.get("HF_TOKEN"),
132
+ )
133
  hf_hub_download(
134
  repo_id=COMFY_KREA_REPO,
135
  filename=f"text_encoders/{TEXT_ENCODER_FILE}",
 
175
  _validate_comfyui()
176
 
177
 
178
+ def _list_loras():
179
+ """Query ComfyUI for whatever .safetensors currently sit in models/loras.
180
+ Falls back to a plain directory scan if the server isn't up yet (e.g. a
181
+ 'Refresh' click before the first generation warmed anything up)."""
182
+ try:
183
+ response = requests.get(f"{COMFY_URL}/object_info/LoraLoaderModelOnly", timeout=5)
184
+ if response.ok:
185
+ info = response.json()
186
+ names = info["LoraLoaderModelOnly"]["input"]["required"]["lora_name"][0]
187
+ return [LORA_NONE] + list(names)
188
+ except Exception:
189
+ pass
190
+
191
+ lora_dir = COMFY_DIR / "models" / "loras"
192
+ if lora_dir.exists():
193
+ names = sorted(p.name for p in lora_dir.glob("*.safetensors"))
194
+ return [LORA_NONE] + names
195
+ return [LORA_NONE]
196
+
197
+
198
+ def _add_lora_to_workflow(workflow, model_node_ref, lora_name, lora_strength):
199
+ """Inserts a LoraLoaderModelOnly node between the diffusion model and the
200
+ sampler when a LoRA is selected. Uses *ModelOnly* on purpose: the text
201
+ encoder here (qwen3vl) is loaded separately via CLIPLoader and isn't part
202
+ of the UNET checkpoint, so a regular LoraLoader (which also patches CLIP)
203
+ would be reaching for a CLIP input that doesn't exist in this graph."""
204
+ if not lora_name or lora_name == LORA_NONE:
205
+ return model_node_ref
206
+
207
+ workflow["20"] = {
208
+ "class_type": "LoraLoaderModelOnly",
209
+ "inputs": {
210
+ "model": model_node_ref,
211
+ "lora_name": lora_name,
212
+ "strength_model": float(lora_strength),
213
+ },
214
+ }
215
+ return ["20", 0]
216
+
217
+
218
+ def _build_workflow(
219
+ prompt,
220
+ negative_prompt,
221
+ width,
222
+ height,
223
+ steps,
224
+ cfg,
225
+ seed,
226
+ sampler,
227
+ scheduler,
228
+ lora_name=LORA_NONE,
229
+ lora_strength=1.0,
230
+ ):
231
  workflow = {
232
  "1": {
233
  "class_type": "UNETLoader",
 
284
  "class_type": "ConditioningZeroOut",
285
  "inputs": {"conditioning": ["2", 0]},
286
  }
287
+
288
+ model_ref = _add_lora_to_workflow(workflow, ["1", 0], lora_name, lora_strength)
289
+ workflow["5"]["inputs"]["model"] = model_ref
290
  return workflow
291
 
292
 
 
317
  return image.convert("RGB").resize((width, height), Image.LANCZOS)
318
 
319
 
320
+ def _build_edit_workflow(
321
+ input_filename,
322
+ prompt,
323
+ negative_prompt,
324
+ steps,
325
+ cfg,
326
+ seed,
327
+ sampler,
328
+ scheduler,
329
+ denoise,
330
+ lora_name=LORA_NONE,
331
+ lora_strength=1.0,
332
+ ):
333
  workflow = {
334
  "1": {
335
  "class_type": "UNETLoader",
 
390
  "class_type": "ConditioningZeroOut",
391
  "inputs": {"conditioning": ["2", 0]},
392
  }
393
+
394
+ model_ref = _add_lora_to_workflow(workflow, ["1", 0], lora_name, lora_strength)
395
+ workflow["5"]["inputs"]["model"] = model_ref
396
  return workflow
397
 
398
 
 
452
  # decorated function and returns the requested duration. gpu_duration is just
453
  # the slider value passed straight through - no more 900s+megapixels guessing.
454
  def _duration(
455
+ prompt,
456
+ negative_prompt,
457
+ width,
458
+ height,
459
+ steps,
460
+ cfg,
461
+ seed,
462
+ randomize_seed,
463
+ sampler,
464
+ scheduler,
465
+ lora_name,
466
+ lora_strength,
467
+ gpu_duration,
468
  ):
469
  return _clamp_duration(gpu_duration)
470
 
 
482
  randomize_seed,
483
  sampler,
484
  scheduler,
485
+ lora_name,
486
+ lora_strength,
487
  gpu_duration,
488
  ):
489
  return _clamp_duration(gpu_duration)
 
501
  randomize_seed=True,
502
  sampler="er_sde",
503
  scheduler="simple",
504
+ lora_name=LORA_NONE,
505
+ lora_strength=1.0,
506
  gpu_duration=DURATION_DEFAULT,
507
  ):
508
  if not prompt or not prompt.strip():
 
512
 
513
  try:
514
  _start_comfyui()
515
+ workflow = _build_workflow(
516
+ prompt,
517
+ negative_prompt,
518
+ width,
519
+ height,
520
+ steps,
521
+ cfg,
522
+ seed,
523
+ sampler,
524
+ scheduler,
525
+ lora_name,
526
+ lora_strength,
527
+ )
528
  prompt_id = _queue_prompt(workflow)
529
  history_item = _wait_for_history(prompt_id)
530
  return _load_output_image(history_item), seed
 
546
  randomize_seed=True,
547
  sampler="er_sde",
548
  scheduler="simple",
549
+ lora_name=LORA_NONE,
550
+ lora_strength=1.0,
551
  gpu_duration=DURATION_DEFAULT,
552
  ):
553
  if input_image is None:
 
571
  sampler,
572
  scheduler,
573
  denoise,
574
+ lora_name,
575
+ lora_strength,
576
  )
577
  prompt_id = _queue_prompt(workflow)
578
  history_item = _wait_for_history(prompt_id)
 
581
  raise gr.Error(str(exc)) from exc
582
 
583
 
584
+ def refresh_loras():
585
+ """Button handler: re-scans models/loras (via ComfyUI if it's up, plain
586
+ glob otherwise) and returns a Dropdown update with the fresh choices."""
587
+ choices = _list_loras()
588
+ return gr.update(choices=choices, value=LORA_NONE)
589
+
590
 
591
  CSS = """
592
  .gradio-container { max-width: 1120px !important; margin: 0 auto !important; }
 
604
  "and the model are still loading)."
605
  )
606
 
607
+ LORA_INFO = (
608
+ "Drop .safetensors files into ComfyUI/models/loras in the repo, then hit "
609
+ "Refresh. 'none' skips LoRA entirely."
610
+ )
611
+
612
  with gr.Blocks(title="Redcraft Krea2", css=CSS) as demo:
613
  gr.Markdown("# Redcraft Krea2")
614
  gr.Markdown("ComfyUI-native Redcraft Krea2 generation and image editing.")
 
628
  with gr.Row():
629
  seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
630
  randomize_seed = gr.Checkbox(value=True, label="Randomize seed")
631
+ with gr.Row():
632
+ lora_name = gr.Dropdown(
633
+ choices=[LORA_NONE, LORA_FILE],
634
+ value=LORA_FILE,
635
+ label="LoRA",
636
+ info=LORA_INFO,
637
+ allow_custom_value=True,
638
+ scale=4,
639
+ )
640
+ lora_refresh = gr.Button("Refresh", scale=1)
641
+ lora_strength = gr.Slider(-2.0, 2.0, value=1.0, step=0.05, label="LoRA strength")
642
  gpu_duration = gr.Slider(
643
  DURATION_MIN,
644
  DURATION_MAX,
 
669
  randomize_seed,
670
  sampler,
671
  scheduler,
672
+ lora_name,
673
+ lora_strength,
674
  gpu_duration,
675
  ]
676
  run.click(generate, inputs, [output, seed])
677
  prompt.submit(generate, inputs, [output, seed])
678
+ lora_refresh.click(refresh_loras, None, lora_name)
679
 
680
  with gr.Tab("Edit Image"):
681
  with gr.Row():
 
700
  with gr.Row():
701
  edit_seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
702
  edit_randomize_seed = gr.Checkbox(value=True, label="Randomize seed")
703
+ with gr.Row():
704
+ edit_lora_name = gr.Dropdown(
705
+ choices=[LORA_NONE, LORA_FILE],
706
+ value=LORA_FILE,
707
+ label="LoRA",
708
+ info=LORA_INFO,
709
+ allow_custom_value=True,
710
+ scale=4,
711
+ )
712
+ edit_lora_refresh = gr.Button("Refresh", scale=1)
713
+ edit_lora_strength = gr.Slider(-2.0, 2.0, value=1.0, step=0.05, label="LoRA strength")
714
  edit_gpu_duration = gr.Slider(
715
  DURATION_MIN,
716
  DURATION_MAX,
 
743
  edit_randomize_seed,
744
  edit_sampler,
745
  edit_scheduler,
746
+ edit_lora_name,
747
+ edit_lora_strength,
748
  edit_gpu_duration,
749
  ]
750
  edit_run.click(edit_image, edit_inputs, [edit_output, edit_seed])
751
  edit_prompt.submit(edit_image, edit_inputs, [edit_output, edit_seed])
752
+ edit_lora_refresh.click(refresh_loras, None, edit_lora_name)
753
 
754
 
755
  _ensure_comfyui()