import json import os import random import shutil import subprocess import sys import threading import time import uuid from pathlib import Path import gradio as gr import requests import spaces from huggingface_hub import hf_hub_download from PIL import Image MODEL_REPO = "Daankular/redcraft-krea2-fp8" MODEL_FILE = "redcraftKREA2RedMix_krea2Edition.safetensors" COMFY_KREA_REPO = "Comfy-Org/Krea-2" TEXT_ENCODER_FILE = "qwen3vl_4b_bf16.safetensors" VAE_FILE = "qwen_image_vae.safetensors" IDENTITY_LORA_REPO = "conradlocke/krea2-identity-edit" IDENTITY_LORA_FILE = "krea2_identity_edit_v1_2.safetensors" KREA2EDIT_NODE_REPO = "https://github.com/lbouaraba/comfyui-krea2edit" KREA2EDIT_NODE_DIRNAME = "comfyui-krea2edit" COMFY_REPO = "https://github.com/comfyanonymous/ComfyUI.git" COMFY_DIR = Path(os.environ.get("COMFYUI_DIR", "/tmp/ComfyUI")) COMFY_HOST = "127.0.0.1" COMFY_PORT = int(os.environ.get("COMFYUI_PORT", "8188")) COMFY_URL = f"http://{COMFY_HOST}:{COMFY_PORT}" MAX_SEED = 2**31 - 1 SPACE_LORA_DIR = Path(__file__).resolve().parent / "loras" _comfy_lock = threading.Lock() _comfy_process = None def _run(cmd, cwd=None): print("[setup]", " ".join(map(str, cmd)), flush=True) subprocess.check_call(cmd, cwd=str(cwd) if cwd else None) def _scan_space_loras(): """Return LoRA filenames uploaded to the Space repo under ./loras.""" SPACE_LORA_DIR.mkdir(parents=True, exist_ok=True) return sorted(path.name for path in SPACE_LORA_DIR.glob("*.safetensors") if path.is_file()) def _sync_space_loras_to_comfy(lora_dir: Path): """Copy repo LoRAs into ComfyUI's runtime LoRA folder.""" SPACE_LORA_DIR.mkdir(parents=True, exist_ok=True) lora_dir.mkdir(parents=True, exist_ok=True) for src in SPACE_LORA_DIR.glob("*.safetensors"): if not src.is_file(): continue dst = lora_dir / src.name if (not dst.exists()) or src.stat().st_size != dst.stat().st_size or src.stat().st_mtime > dst.stat().st_mtime: print(f"[lora] syncing {src.name}", flush=True) shutil.copy2(src, dst) def _refresh_lora_choices(): """Refresh the Gradio LoRA dropdown from files in ./loras.""" return gr.update(choices=_scan_space_loras(), value=[]) def _wait_for_comfy(timeout=180): deadline = time.time() + timeout last_error = None while time.time() < deadline: try: response = requests.get(f"{COMFY_URL}/system_stats", timeout=2) if response.ok: return except Exception as exc: last_error = exc time.sleep(1) raise RuntimeError(f"ComfyUI did not start in time: {last_error}") def _validate_comfyui(): response = requests.get(f"{COMFY_URL}/object_info", timeout=30) response.raise_for_status() object_info = response.json() required_nodes = ["UNETLoader", "CLIPLoader", "VAELoader", "CLIPTextEncode", "KSampler", "VAEDecode", "SaveImage"] missing = [node for node in required_nodes if node not in object_info] if missing: raise RuntimeError(f"ComfyUI is missing required nodes: {', '.join(missing)}") unet_info = object_info["UNETLoader"]["input"]["required"]["unet_name"][0] clip_info = object_info["CLIPLoader"]["input"]["required"]["clip_name"][0] vae_info = object_info["VAELoader"]["input"]["required"]["vae_name"][0] if MODEL_FILE not in unet_info: raise RuntimeError(f"Redcraft diffusion model is not visible to ComfyUI. First models: {', '.join(unet_info[:10])}") if TEXT_ENCODER_FILE not in clip_info: raise RuntimeError(f"Krea2 text encoder is not visible to ComfyUI. First encoders: {', '.join(clip_info[:10])}") if VAE_FILE not in vae_info: raise RuntimeError(f"Krea2 VAE is not visible to ComfyUI. First VAEs: {', '.join(vae_info[:10])}") identity_edit_nodes = ["LoraLoaderModelOnly", "Krea2EditModelPatch", "Krea2EditGroundedEncode", "EmptySD3LatentImage"] missing_identity_nodes = [node for node in identity_edit_nodes if node not in object_info] if missing_identity_nodes: raise RuntimeError( f"ComfyUI-Krea2Edit nodes are missing: {', '.join(missing_identity_nodes)}. " f"Check the {KREA2EDIT_NODE_REPO} custom node install." ) lora_info = object_info["LoraLoaderModelOnly"]["input"]["required"]["lora_name"][0] if IDENTITY_LORA_FILE not in lora_info: raise RuntimeError(f"Identity-edit LoRA is not visible to ComfyUI. First loras: {', '.join(lora_info[:10])}") def _ensure_comfyui(): global _comfy_process with _comfy_lock: if _comfy_process is not None and _comfy_process.poll() is None: return try: response = requests.get(f"{COMFY_URL}/system_stats", timeout=2) if response.ok: return except Exception: pass if not COMFY_DIR.exists(): _run(["git", "clone", "--depth", "1", COMFY_REPO, str(COMFY_DIR)]) marker = COMFY_DIR / ".requirements-installed" if not marker.exists(): _run([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"], cwd=COMFY_DIR) marker.write_text("ok", encoding="utf-8") krea2edit_dir = COMFY_DIR / "custom_nodes" / KREA2EDIT_NODE_DIRNAME if not krea2edit_dir.exists(): _run(["git", "clone", "--depth", "1", KREA2EDIT_NODE_REPO, str(krea2edit_dir)]) diffusion_dir = COMFY_DIR / "models" / "diffusion_models" text_encoder_dir = COMFY_DIR / "models" / "text_encoders" vae_dir = COMFY_DIR / "models" / "vae" lora_dir = COMFY_DIR / "models" / "loras" diffusion_dir.mkdir(parents=True, exist_ok=True) text_encoder_dir.mkdir(parents=True, exist_ok=True) vae_dir.mkdir(parents=True, exist_ok=True) lora_dir.mkdir(parents=True, exist_ok=True) # Make every .safetensors uploaded to ./loras available to ComfyUI. _sync_space_loras_to_comfy(lora_dir) hf_hub_download( repo_id=MODEL_REPO, filename=MODEL_FILE, local_dir=str(diffusion_dir), token=os.environ.get("HF_TOKEN"), ) hf_hub_download( repo_id=COMFY_KREA_REPO, filename=f"text_encoders/{TEXT_ENCODER_FILE}", local_dir=str(COMFY_DIR / "models"), token=os.environ.get("HF_TOKEN"), ) hf_hub_download( repo_id=COMFY_KREA_REPO, filename=f"vae/{VAE_FILE}", local_dir=str(COMFY_DIR / "models"), token=os.environ.get("HF_TOKEN"), ) hf_hub_download( repo_id=IDENTITY_LORA_REPO, filename=IDENTITY_LORA_FILE, local_dir=str(lora_dir), token=os.environ.get("HF_TOKEN"), ) def _start_comfyui(): global _comfy_process with _comfy_lock: if _comfy_process is not None and _comfy_process.poll() is None: return try: response = requests.get(f"{COMFY_URL}/system_stats", timeout=2) if response.ok: _validate_comfyui() return except Exception: pass cmd = [ sys.executable, "main.py", "--listen", COMFY_HOST, "--port", str(COMFY_PORT), "--disable-auto-launch", ] _comfy_process = subprocess.Popen(cmd, cwd=str(COMFY_DIR)) _wait_for_comfy() _validate_comfyui() def _build_workflow( prompt, negative_prompt, width, height, steps, cfg, seed, sampler, scheduler, selected_loras=None, lora_strength=0.8, ): if selected_loras is None: selected_loras = [] elif isinstance(selected_loras, str): selected_loras = [selected_loras] available = set(_scan_space_loras()) selected_loras = [name for name in selected_loras if name in available] workflow = { "1": { "class_type": "UNETLoader", "inputs": {"unet_name": MODEL_FILE, "weight_dtype": "default"}, }, "8": { "class_type": "CLIPLoader", "inputs": {"clip_name": TEXT_ENCODER_FILE, "type": "krea2", "device": "default"}, }, "9": { "class_type": "VAELoader", "inputs": {"vae_name": VAE_FILE}, }, "2": { "class_type": "CLIPTextEncode", "inputs": {"text": prompt, "clip": ["8", 0]}, }, "4": { "class_type": "EmptyLatentImage", "inputs": {"width": int(width), "height": int(height), "batch_size": 1}, }, "5": { "class_type": "KSampler", "inputs": { "seed": int(seed), "steps": int(steps), "cfg": float(cfg), "sampler_name": sampler, "scheduler": scheduler, "denoise": 1.0, "model": ["1", 0], "positive": ["2", 0], "negative": ["3", 0], "latent_image": ["4", 0], }, }, "6": { "class_type": "VAEDecode", "inputs": {"samples": ["5", 0], "vae": ["9", 0]}, }, "7": { "class_type": "SaveImage", "inputs": {"filename_prefix": "redcraft", "images": ["6", 0]}, }, } # Chain selected LoRAs model-only, matching the existing Krea2 identity workflow. model_node = ["1", 0] next_node_id = 20 for lora_name in selected_loras: node_id = str(next_node_id) workflow[node_id] = { "class_type": "LoraLoaderModelOnly", "inputs": { "lora_name": lora_name, "strength_model": float(lora_strength), "model": model_node, }, } model_node = [node_id, 0] next_node_id += 1 workflow["5"]["inputs"]["model"] = model_node if negative_prompt and negative_prompt.strip(): workflow["3"] = { "class_type": "CLIPTextEncode", "inputs": {"text": negative_prompt, "clip": ["8", 0]}, } else: workflow["3"] = { "class_type": "ConditioningZeroOut", "inputs": {"conditioning": ["2", 0]}, } return workflow def _upload_image_to_comfy(image): if image is None: raise ValueError("Upload an image to edit.") image = image.convert("RGB") filename = f"redcraft-input-{uuid.uuid4().hex}.png" temp_path = Path("/tmp") / filename image.save(temp_path) with temp_path.open("rb") as handle: response = requests.post( f"{COMFY_URL}/upload/image", files={"image": (filename, handle, "image/png")}, data={"overwrite": "true"}, timeout=120, ) response.raise_for_status() data = response.json() return data.get("name", filename) def _resize_for_edit(image, width, height): width, height = int(width), int(height) if width <= 0 or height <= 0: return image.convert("RGB") return image.convert("RGB").resize((width, height), Image.LANCZOS) def _target_size_from_source(image, max_megapixels=1.0): multiple = 16 width, height = image.size megapixels = (width * height) / 1_000_000 if megapixels > max_megapixels: scale = (max_megapixels / megapixels) ** 0.5 width, height = round(width * scale), round(height * scale) width = max(multiple, (width // multiple) * multiple) height = max(multiple, (height // multiple) * multiple) return width, height def _build_edit_workflow(input_filename, prompt, negative_prompt, steps, cfg, seed, sampler, scheduler, denoise): workflow = { "1": { "class_type": "UNETLoader", "inputs": {"unet_name": MODEL_FILE, "weight_dtype": "default"}, }, "8": { "class_type": "CLIPLoader", "inputs": {"clip_name": TEXT_ENCODER_FILE, "type": "krea2", "device": "default"}, }, "9": { "class_type": "VAELoader", "inputs": {"vae_name": VAE_FILE}, }, "10": { "class_type": "LoadImage", "inputs": {"image": input_filename}, }, "11": { "class_type": "VAEEncode", "inputs": {"pixels": ["10", 0], "vae": ["9", 0]}, }, "2": { "class_type": "CLIPTextEncode", "inputs": {"text": prompt, "clip": ["8", 0]}, }, "5": { "class_type": "KSampler", "inputs": { "seed": int(seed), "steps": int(steps), "cfg": float(cfg), "sampler_name": sampler, "scheduler": scheduler, "denoise": float(denoise), "model": ["1", 0], "positive": ["2", 0], "negative": ["3", 0], "latent_image": ["11", 0], }, }, "6": { "class_type": "VAEDecode", "inputs": {"samples": ["5", 0], "vae": ["9", 0]}, }, "7": { "class_type": "SaveImage", "inputs": {"filename_prefix": "redcraft-edit", "images": ["6", 0]}, }, } if negative_prompt and negative_prompt.strip(): workflow["3"] = { "class_type": "CLIPTextEncode", "inputs": {"text": negative_prompt, "clip": ["8", 0]}, } else: workflow["3"] = { "class_type": "ConditioningZeroOut", "inputs": {"conditioning": ["2", 0]}, } return workflow def _build_identity_edit_workflow(input_filename, prompt, width, height, steps, cfg, seed, ref_boost, grounding_px, sampler, scheduler): return { "1": { "class_type": "UNETLoader", "inputs": {"unet_name": MODEL_FILE, "weight_dtype": "default"}, }, "8": { "class_type": "CLIPLoader", "inputs": {"clip_name": TEXT_ENCODER_FILE, "type": "krea2", "device": "default"}, }, "9": { "class_type": "VAELoader", "inputs": {"vae_name": VAE_FILE}, }, "20": { "class_type": "LoraLoaderModelOnly", "inputs": {"lora_name": IDENTITY_LORA_FILE, "strength_model": 1.0, "model": ["1", 0]}, }, "10": { "class_type": "LoadImage", "inputs": {"image": input_filename}, }, "11": { "class_type": "VAEEncode", "inputs": {"pixels": ["10", 0], "vae": ["9", 0]}, }, "21": { "class_type": "Krea2EditModelPatch", "inputs": { "model": ["20", 0], "source_latent": ["11", 0], "ref_boost": float(ref_boost), "fit_mode": "fit", "vae": ["9", 0], "source_image": ["10", 0], }, }, "2": { "class_type": "Krea2EditGroundedEncode", "inputs": {"clip": ["8", 0], "prompt": prompt, "image": ["10", 0], "grounding_px": int(grounding_px)}, }, "3": { "class_type": "Krea2EditGroundedEncode", "inputs": {"clip": ["8", 0], "prompt": "", "image": ["10", 0], "grounding_px": int(grounding_px)}, }, "4": { "class_type": "EmptySD3LatentImage", "inputs": {"width": int(width), "height": int(height), "batch_size": 1}, }, "5": { "class_type": "KSampler", "inputs": { "seed": int(seed), "steps": int(steps), "cfg": float(cfg), "sampler_name": sampler, "scheduler": scheduler, "denoise": 1.0, "model": ["21", 0], "positive": ["2", 0], "negative": ["3", 0], "latent_image": ["4", 0], }, }, "6": { "class_type": "VAEDecode", "inputs": {"samples": ["5", 0], "vae": ["9", 0]}, }, "7": { "class_type": "SaveImage", "inputs": {"filename_prefix": "identity-edit", "images": ["6", 0]}, }, } def _queue_prompt(workflow): payload = {"prompt": workflow, "client_id": str(uuid.uuid4())} response = requests.post(f"{COMFY_URL}/prompt", json=payload, timeout=30) if not response.ok: raise RuntimeError(f"ComfyUI prompt error {response.status_code}: {response.text[:1000]}") return response.json()["prompt_id"] def _wait_for_history(prompt_id, timeout=900): deadline = time.time() + timeout while time.time() < deadline: response = requests.get(f"{COMFY_URL}/history/{prompt_id}", timeout=30) response.raise_for_status() history = response.json() if prompt_id in history: item = history[prompt_id] status = item.get("status", {}) if status.get("completed"): return item messages = status.get("messages") or [] for message in messages: if isinstance(message, list) and message and message[0] == "execution_error": raise RuntimeError(json.dumps(message[1], indent=2)[:2000]) time.sleep(1) raise RuntimeError("Timed out waiting for ComfyUI generation.") def _load_output_image(history_item): outputs = history_item.get("outputs", {}) for output in outputs.values(): for image in output.get("images", []): params = { "filename": image["filename"], "subfolder": image.get("subfolder", ""), "type": image.get("type", "output"), } response = requests.get(f"{COMFY_URL}/view", params=params, timeout=120) response.raise_for_status() temp_path = Path("/tmp") / f"{uuid.uuid4().hex}.png" temp_path.write_bytes(response.content) return Image.open(temp_path).convert("RGB") raise RuntimeError("ComfyUI completed without returning an image.") def _duration(prompt, negative_prompt, width, height, steps, cfg, seed, randomize_seed, sampler, scheduler, selected_loras=None, lora_strength=0.8): megapixels = max(1.0, (int(width) * int(height)) / (1024 * 1024)) return int(900 + int(steps) * 8 * megapixels) def _edit_duration( input_image, prompt, negative_prompt, width, height, steps, cfg, denoise, seed, randomize_seed, sampler, scheduler, ): megapixels = max(1.0, (int(width) * int(height)) / (1024 * 1024)) return int(900 + int(steps) * 9 * megapixels) def _identity_edit_duration( input_image, prompt, ref_boost, grounding_px, max_megapixels, steps, cfg, seed, randomize_seed, sampler, scheduler, ): return int(900 + int(steps) * 10 * max(1.0, float(max_megapixels))) @spaces.GPU(duration=_duration) def generate( prompt, negative_prompt="", width=1024, height=1024, steps=10, cfg=1.0, seed=0, randomize_seed=True, sampler="er_sde", scheduler="simple", selected_loras=None, lora_strength=0.8, ): if not prompt or not prompt.strip(): raise gr.Error("Enter a prompt.") if randomize_seed: seed = random.randint(0, MAX_SEED) try: _start_comfyui() workflow = _build_workflow(prompt, negative_prompt, width, height, steps, cfg, seed, sampler, scheduler, selected_loras, lora_strength) prompt_id = _queue_prompt(workflow) history_item = _wait_for_history(prompt_id) return _load_output_image(history_item), seed except Exception as exc: raise gr.Error(str(exc)) from exc @spaces.GPU(duration=_edit_duration) def edit_image( input_image, prompt, negative_prompt="", width=1024, height=1024, steps=12, cfg=1.2, denoise=0.35, seed=0, randomize_seed=True, sampler="er_sde", scheduler="simple", ): if input_image is None: raise gr.Error("Upload an image to edit.") if not prompt or not prompt.strip(): raise gr.Error("Enter an edit prompt.") if randomize_seed: seed = random.randint(0, MAX_SEED) try: _start_comfyui() resized = _resize_for_edit(input_image, width, height) input_filename = _upload_image_to_comfy(resized) workflow = _build_edit_workflow( input_filename, prompt, negative_prompt, steps, cfg, seed, sampler, scheduler, denoise, ) prompt_id = _queue_prompt(workflow) history_item = _wait_for_history(prompt_id) return _load_output_image(history_item), seed except Exception as exc: raise gr.Error(str(exc)) from exc @spaces.GPU(duration=_identity_edit_duration) def identity_edit( input_image, prompt, ref_boost=4.0, grounding_px=768, max_megapixels=1.0, steps=10, cfg=1.0, seed=0, randomize_seed=True, sampler="euler", scheduler="simple", ): if input_image is None: raise gr.Error("Upload an image to edit.") if not prompt or not prompt.strip(): raise gr.Error("Enter an edit instruction.") if randomize_seed: seed = random.randint(0, MAX_SEED) try: _start_comfyui() source = input_image.convert("RGB") width, height = _target_size_from_source(source, max_megapixels) input_filename = _upload_image_to_comfy(source) workflow = _build_identity_edit_workflow( input_filename, prompt, width, height, steps, cfg, seed, ref_boost, grounding_px, sampler, scheduler, ) prompt_id = _queue_prompt(workflow) history_item = _wait_for_history(prompt_id) return _load_output_image(history_item), seed except Exception as exc: raise gr.Error(str(exc)) from exc CSS = """ .gradio-container { max-width: 1120px !important; margin: 0 auto !important; } #result-image { min-height: 520px; } """ with gr.Blocks(title="Redcraft Krea2", css=CSS) as demo: gr.Markdown("# Redcraft Krea2") gr.Markdown("ComfyUI-native Redcraft Krea2 generation and image editing.") with gr.Tabs(): with gr.Tab("Generate"): with gr.Row(): with gr.Column(scale=5): prompt = gr.Textbox(label="Prompt", lines=5, placeholder="Describe the image to generate.") negative_prompt = gr.Textbox(label="Negative prompt", lines=2, value="") with gr.Row(): lora_selector = gr.Dropdown( choices=_scan_space_loras(), value=[], multiselect=True, label="LoRAs", info="Select one or more .safetensors files uploaded to the loras/ folder.", ) refresh_loras = gr.Button("Refresh LoRAs", scale=0) lora_strength = gr.Slider( 0.0, 1.5, value=0.8, step=0.05, label="LoRA strength", info="Shared model strength for all selected LoRAs.", ) refresh_loras.click(_refresh_lora_choices, outputs=lora_selector) with gr.Row(): width = gr.Slider(512, 1536, value=1024, step=64, label="Width") height = gr.Slider(512, 1536, value=1024, step=64, label="Height") with gr.Row(): steps = gr.Slider(1, 30, value=10, step=1, label="Steps") cfg = gr.Slider(0.0, 8.0, value=1.0, step=0.1, label="CFG") with gr.Row(): seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed") randomize_seed = gr.Checkbox(value=True, label="Randomize seed") with gr.Accordion("Sampler", open=False): sampler = gr.Dropdown( ["er_sde", "euler", "euler_ancestral", "dpmpp_2m", "dpmpp_sde"], value="er_sde", label="Sampler", ) scheduler = gr.Dropdown(["simple", "normal", "karras", "exponential"], value="simple", label="Scheduler") run = gr.Button("Generate", variant="primary") with gr.Column(scale=6): output = gr.Image(label="Result", format="png", elem_id="result-image") inputs = [prompt, negative_prompt, width, height, steps, cfg, seed, randomize_seed, sampler, scheduler, lora_selector, lora_strength] run.click(generate, inputs, [output, seed]) prompt.submit(generate, inputs, [output, seed]) with gr.Tab("Edit Image"): with gr.Row(): with gr.Column(scale=5): edit_input = gr.Image(type="pil", label="Input image") edit_prompt = gr.Textbox(label="Edit prompt", lines=5, placeholder="Describe the edit while preserving identity.") edit_negative_prompt = gr.Textbox(label="Negative prompt", lines=2, value="") with gr.Row(): edit_width = gr.Slider(512, 1536, value=1024, step=64, label="Width") edit_height = gr.Slider(512, 1536, value=1024, step=64, label="Height") with gr.Row(): edit_steps = gr.Slider(1, 30, value=12, step=1, label="Steps") edit_cfg = gr.Slider(0.0, 8.0, value=1.2, step=0.1, label="CFG") edit_denoise = gr.Slider( 0.05, 0.8, value=0.35, step=0.05, label="Edit strength", info="Lower values preserve identity and composition more strongly.", ) with gr.Row(): edit_seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed") edit_randomize_seed = gr.Checkbox(value=True, label="Randomize seed") with gr.Accordion("Sampler", open=False): edit_sampler = gr.Dropdown( ["er_sde", "euler", "euler_ancestral", "dpmpp_2m", "dpmpp_sde"], value="er_sde", label="Sampler", ) edit_scheduler = gr.Dropdown(["simple", "normal", "karras", "exponential"], value="simple", label="Scheduler") edit_run = gr.Button("Edit Image", variant="primary") with gr.Column(scale=6): edit_output = gr.Image(label="Edited image", format="png", elem_id="result-image") edit_inputs = [ edit_input, edit_prompt, edit_negative_prompt, edit_width, edit_height, edit_steps, edit_cfg, edit_denoise, edit_seed, edit_randomize_seed, edit_sampler, edit_scheduler, ] edit_run.click(edit_image, edit_inputs, [edit_output, edit_seed]) edit_prompt.submit(edit_image, edit_inputs, [edit_output, edit_seed]) with gr.Tab("Identity Edit"): gr.Markdown( "Instruction-based, identity-preserving editing using the community LoRA " "[`conradlocke/krea2-identity-edit`](https://huggingface.co/conradlocke/krea2-identity-edit) " "on the Redcraft Krea2 checkpoint, via the " "[ComfyUI-Krea2Edit](https://github.com/lbouaraba/comfyui-krea2edit) node pack. " "Give it an image and a plain-language instruction; it edits while preserving what you " "didn't ask to change, including the person." ) with gr.Row(): with gr.Column(scale=5): id_input = gr.Image(type="pil", label="Source image") id_prompt = gr.Textbox( label="Edit instruction", lines=3, placeholder="e.g. create a photo of this person at a night market", ) id_ref_boost = gr.Slider( 0.0, 10.0, value=4.0, step=0.5, label="Likeness (ref_boost)", info="How hard the edit holds the reference. 1 = off, 4 = strong likeness (recommended), 8+ over-copies.", ) with gr.Accordion("Advanced settings", open=False): id_grounding_px = gr.Slider( 384, 1536, value=768, step=64, label="Grounding resolution (px)", info="Lower = stronger edit adherence. Higher = stronger identity likeness. Trained range 384-768; 1024+ often still works.", ) id_max_mp = gr.Slider(0.5, 2.0, value=1.0, step=0.1, label="Output size (megapixels)") with gr.Row(): id_steps = gr.Slider(4, 28, value=10, step=1, label="Steps") id_cfg = gr.Slider( 0.0, 5.0, value=1.0, step=0.5, label="CFG", info="Turbo convention: 1.0 = guidance off. Raise (e.g. 3) with more steps for removals/large edits.", ) with gr.Row(): id_seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed") id_randomize_seed = gr.Checkbox(value=True, label="Randomize seed") with gr.Row(): id_sampler = gr.Dropdown( ["euler", "er_sde", "euler_ancestral", "dpmpp_2m", "dpmpp_sde"], value="euler", label="Sampler", ) id_scheduler = gr.Dropdown(["simple", "normal", "karras", "exponential"], value="simple", label="Scheduler") id_run = gr.Button("Edit Identity", variant="primary") with gr.Column(scale=6): id_output = gr.Image(label="Edited image", format="png", elem_id="result-image") id_inputs = [ id_input, id_prompt, id_ref_boost, id_grounding_px, id_max_mp, id_steps, id_cfg, id_seed, id_randomize_seed, id_sampler, id_scheduler, ] id_run.click(identity_edit, id_inputs, [id_output, id_seed]) id_prompt.submit(identity_edit, id_inputs, [id_output, id_seed]) gr.Examples( examples=[ ["examples/woman.jpg", "create a photo of this person at a busy night market at night"], ["examples/businessman_suit.jpg", "change the suit jacket to a red leather jacket"], ["examples/man_beach.jpg", "make it a vintage film photo with warm golden-hour light"], ], inputs=[id_input, id_prompt], label="Examples", ) _ensure_comfyui() if __name__ == "__main__": demo.queue().launch()