prithivMLmods commited on
Commit
f184efc
·
1 Parent(s): 86ee1c7

add app [ref --2e8856a] (#15)

Browse files

- add app [ref --2e8856a] (d950cdf419f540594f540840aac60d88705df4ed)

Files changed (1) hide show
  1. app.py +1636 -0
app.py ADDED
@@ -0,0 +1,1636 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gc
3
+ import gradio as gr
4
+ import numpy as np
5
+ import spaces
6
+ import torch
7
+ import random
8
+ import base64
9
+ import json
10
+ import html as html_lib
11
+ from io import BytesIO
12
+ from PIL import Image
13
+
14
+ MAX_SEED = np.iinfo(np.int32).max
15
+ LANCZOS = getattr(Image, "Resampling", Image).LANCZOS
16
+
17
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
18
+
19
+ print("CUDA_VISIBLE_DEVICES=", os.environ.get("CUDA_VISIBLE_DEVICES"))
20
+ print("torch.__version__ =", torch.__version__)
21
+ print("torch.version.cuda =", torch.version.cuda)
22
+ print("cuda available:", torch.cuda.is_available())
23
+ print("cuda device count:", torch.cuda.device_count())
24
+ if torch.cuda.is_available():
25
+ print("current device:", torch.cuda.current_device())
26
+ print("device name:", torch.cuda.get_device_name(torch.cuda.current_device()))
27
+
28
+ print("Using device:", device)
29
+
30
+ from diffusers import FlowMatchEulerDiscreteScheduler
31
+ from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
32
+ from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
33
+ from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
34
+
35
+ dtype = torch.bfloat16
36
+
37
+ pipe = QwenImageEditPlusPipeline.from_pretrained(
38
+ "Qwen/Qwen-Image-Edit-2511",
39
+ transformer=QwenImageTransformer2DModel.from_pretrained(
40
+ "prithivMLmods/Qwen-Image-Edit-Rapid-AIO-V19",
41
+ torch_dtype=dtype,
42
+ device_map="cuda",
43
+ ),
44
+ torch_dtype=dtype,
45
+ ).to(device)
46
+
47
+ try:
48
+ pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
49
+ print("Flash Attention 3 Processor set successfully.")
50
+ except Exception as e:
51
+ print(f"Warning: Could not set FA3 processor: {e}")
52
+
53
+ ADAPTER_SPECS = {
54
+ "Multiple-Angles": {
55
+ "repo": "dx8152/Qwen-Edit-2509-Multiple-angles",
56
+ "weights": "镜头转换.safetensors",
57
+ "adapter_name": "multiple-angles",
58
+ },
59
+ "Photo-to-Anime": {
60
+ "repo": "autoweeb/Qwen-Image-Edit-2509-Photo-to-Anime",
61
+ "weights": "Qwen-Image-Edit-2509-Photo-to-Anime_000001000.safetensors",
62
+ "adapter_name": "photo-to-anime",
63
+ },
64
+ "Anime-V2": {
65
+ "repo": "prithivMLmods/Qwen-Image-Edit-2511-Anime",
66
+ "weights": "Qwen-Image-Edit-2511-Anime-2000.safetensors",
67
+ "adapter_name": "anime-v2",
68
+ },
69
+ "Light-Migration": {
70
+ "repo": "dx8152/Qwen-Edit-2509-Light-Migration",
71
+ "weights": "参考色调.safetensors",
72
+ "adapter_name": "light-migration",
73
+ },
74
+ "Upscaler": {
75
+ "repo": "starsfriday/Qwen-Image-Edit-2511-Upscale2K",
76
+ "weights": "qwen_image_edit_2511_upscale.safetensors",
77
+ "adapter_name": "upscale-2k",
78
+ },
79
+ "Style-Transfer": {
80
+ "repo": "zooeyy/Style-Transfer",
81
+ "weights": "Style Transfer-Alpha-V0.1.safetensors",
82
+ "adapter_name": "style-transfer",
83
+ },
84
+ "Manga-Tone": {
85
+ "repo": "nappa114514/Qwen-Image-Edit-2509-Manga-Tone",
86
+ "weights": "tone001.safetensors",
87
+ "adapter_name": "manga-tone",
88
+ },
89
+ "Anything2Real": {
90
+ "repo": "lrzjason/Anything2Real_2601",
91
+ "weights": "anything2real_2601.safetensors",
92
+ "adapter_name": "anything2real",
93
+ },
94
+ "Fal-Multiple-Angles": {
95
+ "repo": "fal/Qwen-Image-Edit-2511-Multiple-Angles-LoRA",
96
+ "weights": "qwen-image-edit-2511-multiple-angles-lora.safetensors",
97
+ "adapter_name": "fal-multiple-angles",
98
+ },
99
+ "Polaroid-Photo": {
100
+ "repo": "prithivMLmods/Qwen-Image-Edit-2511-Polaroid-Photo",
101
+ "weights": "Qwen-Image-Edit-2511-Polaroid-Photo.safetensors",
102
+ "adapter_name": "polaroid-photo",
103
+ },
104
+ "Unblur-Anything": {
105
+ "repo": "prithivMLmods/Qwen-Image-Edit-2511-Unblur-Upscale",
106
+ "weights": "Qwen-Image-Edit-Unblur-Upscale_15.safetensors",
107
+ "adapter_name": "unblur-anything",
108
+ },
109
+ "Midnight-Noir-Eyes-Spotlight": {
110
+ "repo": "prithivMLmods/Qwen-Image-Edit-2511-Midnight-Noir-Eyes-Spotlight",
111
+ "weights": "Qwen-Image-Edit-2511-Midnight-Noir-Eyes-Spotlight.safetensors",
112
+ "adapter_name": "midnight-noir-eyes-spotlight",
113
+ },
114
+ "Hyper-Realistic-Portrait": {
115
+ "repo": "prithivMLmods/Qwen-Image-Edit-2511-Hyper-Realistic-Portrait",
116
+ "weights": "HRP_20.safetensors",
117
+ "adapter_name": "hyper-realistic-portrait",
118
+ },
119
+ "Ultra-Realistic-Portrait": {
120
+ "repo": "prithivMLmods/Qwen-Image-Edit-2511-Ultra-Realistic-Portrait",
121
+ "weights": "URP_20.safetensors",
122
+ "adapter_name": "ultra-realistic-portrait",
123
+ },
124
+ "Pixar-Inspired-3D": {
125
+ "repo": "prithivMLmods/Qwen-Image-Edit-2511-Pixar-Inspired-3D",
126
+ "weights": "PI3_20.safetensors",
127
+ "adapter_name": "pi3",
128
+ },
129
+ "Noir-Comic-Book": {
130
+ "repo": "prithivMLmods/Qwen-Image-Edit-2511-Noir-Comic-Book-Panel",
131
+ "weights": "Noir-Comic-Book-Panel_20.safetensors",
132
+ "adapter_name": "ncb",
133
+ },
134
+ "Any-light": {
135
+ "repo": "lilylilith/QIE-2511-MP-AnyLight",
136
+ "weights": "QIE-2511-AnyLight_.safetensors",
137
+ "adapter_name": "any-light",
138
+ },
139
+ "Studio-DeLight": {
140
+ "repo": "prithivMLmods/QIE-2511-Studio-DeLight",
141
+ "weights": "QIE-2511-Studio-DeLight-5000.safetensors",
142
+ "adapter_name": "studio-delight",
143
+ },
144
+ "Cinematic-FlatLog": {
145
+ "repo": "prithivMLmods/QIE-2511-Cinematic-FlatLog-Control",
146
+ "weights": "QIE-2511-Cinematic-FlatLog-Control-3200.safetensors",
147
+ "adapter_name": "flat-log",
148
+ },
149
+ }
150
+
151
+ LOADED_ADAPTERS: set = set()
152
+ ADAPTER_NAMES = list(ADAPTER_SPECS.keys())
153
+
154
+ EXAMPLES_CONFIG = [
155
+ {"images": ["examples/B.jpg"], "prompt": "Transform into anime.", "lora": "Photo-to-Anime"},
156
+ {"images": ["examples/HRP.jpg"], "prompt": "Transform into a hyper-realistic face portrait.", "lora": "Hyper-Realistic-Portrait"},
157
+ {"images": ["examples/A.jpeg"], "prompt": "Rotate the camera 45 degrees to the right.", "lora": "Multiple-Angles"},
158
+ {"images": ["examples/U.jpg"], "prompt": "Upscale this picture to 4K resolution.", "lora": "Upscaler"},
159
+ {"images": ["examples/L1.jpg", "examples/L2.jpg"], "prompt": "Apply the lighting from image 2 to image 1.", "lora": "Any-light"},
160
+ {"images": ["examples/PP1.jpg"], "prompt": "cinematic polaroid with soft grain subtle vignette gentle lighting white frame handwritten photographed preserving realistic texture and details.", "lora": "Polaroid-Photo"},
161
+ {"images": ["examples/Z1.jpg"], "prompt": "Front-right quarter view.", "lora": "Fal-Multiple-Angles"},
162
+ {"images": ["examples/URP.jpg"], "prompt": "Transform into a cinematic flat log.", "lora": "Cinematic-FlatLog"},
163
+ {"images": ["examples/SL.jpg"], "prompt": "Neutral uniform lighting. Preserve identity and composition.", "lora": "Studio-DeLight"},
164
+ {"images": ["examples/PI.jpg"], "prompt": "Transform it into Pixar-inspired 3D.", "lora": "Pixar-Inspired-3D"},
165
+ {"images": ["examples/MT.jpg"], "prompt": "Paint with manga tone.", "lora": "Manga-Tone"},
166
+ {"images": ["examples/NCB.jpg"], "prompt": "Transform into a noir comic book style.", "lora": "Noir-Comic-Book"},
167
+ {"images": ["examples/URP.jpg"], "prompt": "Ultra-realistic portrait.", "lora": "Ultra-Realistic-Portrait"},
168
+ {"images": ["examples/MN.jpg"], "prompt": "Transform into Midnight Noir Eyes Spotlight.", "lora": "Midnight-Noir-Eyes-Spotlight"},
169
+ {"images": ["examples/ST1.jpg", "examples/ST2.jpg"], "prompt": "Convert Image 1 to the style of Image 2.", "lora": "Style-Transfer"},
170
+ {"images": ["examples/R1.jpg"], "prompt": "Change the picture to realistic photograph.", "lora": "Anything2Real"},
171
+ {"images": ["examples/UA.jpeg"], "prompt": "Unblur and upscale.", "lora": "Unblur-Anything"},
172
+ {"images": ["examples/L1.jpg", "examples/L2.jpg"], "prompt": "Refer to the color tone, remove the original lighting from Image 1, and relight Image 1 based on the lighting and color tone of Image 2.", "lora": "Light-Migration"},
173
+ {"images": ["examples/P1.jpg"], "prompt": "Transform into anime (while preserving the background and remaining elements maintaining realism and original details.)", "lora": "Anime-V2"},
174
+ ]
175
+
176
+
177
+ def make_thumb_b64(path, max_dim=220):
178
+ if not os.path.exists(path):
179
+ return ""
180
+ try:
181
+ img = Image.open(path).convert("RGB")
182
+ img.thumbnail((max_dim, max_dim), LANCZOS)
183
+ buf = BytesIO()
184
+ img.save(buf, format="JPEG", quality=65)
185
+ return f"data:image/jpeg;base64,{base64.b64encode(buf.getvalue()).decode()}"
186
+ except Exception as e:
187
+ print(f"Thumbnail error for {path}: {e}")
188
+ return ""
189
+
190
+
191
+ def encode_full_image(path):
192
+ if not os.path.exists(path):
193
+ return ""
194
+ try:
195
+ with open(path, "rb") as f:
196
+ data = f.read()
197
+ ext = path.rsplit(".", 1)[-1].lower()
198
+ mime = {"jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png", "webp": "image/webp"}.get(ext, "image/jpeg")
199
+ return f"data:{mime};base64,{base64.b64encode(data).decode()}"
200
+ except Exception as e:
201
+ print(f"Encode error for {path}: {e}")
202
+ return ""
203
+
204
+
205
+ def build_example_cards_html():
206
+ cards = ""
207
+ for i, ex in enumerate(EXAMPLES_CONFIG):
208
+ thumbs_html = ""
209
+ for path in ex["images"]:
210
+ thumb = make_thumb_b64(path)
211
+ if thumb:
212
+ thumbs_html += f'<img src="{thumb}" alt="">'
213
+ else:
214
+ thumbs_html += '<div class="example-thumb-placeholder">Preview</div>'
215
+ n = len(ex["images"])
216
+ img_badge = f'{n} image{"s" if n > 1 else ""}'
217
+ lora_badge = html_lib.escape(ex["lora"])
218
+ prompt_short = html_lib.escape(ex["prompt"][:85])
219
+ if len(ex["prompt"]) > 85:
220
+ prompt_short += "…"
221
+ cards += f'''<div class="example-card" data-idx="{i}">
222
+ <div class="example-thumbs">{thumbs_html}</div>
223
+ <div class="example-meta">
224
+ <span class="example-badge">{img_badge}</span>
225
+ <span class="example-lora-badge">{lora_badge}</span>
226
+ </div>
227
+ <div class="example-prompt-text">{prompt_short}</div>
228
+ </div>'''
229
+ return cards
230
+
231
+
232
+ def load_example_data(idx_str):
233
+ try:
234
+ idx = int(float(idx_str)) if idx_str and idx_str.strip() else -1
235
+ except (ValueError, TypeError):
236
+ idx = -1
237
+ if idx < 0 or idx >= len(EXAMPLES_CONFIG):
238
+ return json.dumps({"images": [], "prompt": "", "lora": "", "names": [], "status": "error"})
239
+ ex = EXAMPLES_CONFIG[idx]
240
+ b64_list, names = [], []
241
+ for path in ex["images"]:
242
+ b64 = encode_full_image(path)
243
+ if b64:
244
+ b64_list.append(b64)
245
+ names.append(os.path.basename(path))
246
+ return json.dumps({"images": b64_list, "prompt": ex["prompt"], "lora": ex["lora"], "names": names, "status": "ok"})
247
+
248
+
249
+ print("Building example thumbnails…")
250
+ EXAMPLE_CARDS_HTML = build_example_cards_html()
251
+ print(f"Built {len(EXAMPLES_CONFIG)} example cards.")
252
+
253
+
254
+ def b64_to_pil_list(b64_json_str):
255
+ if not b64_json_str or b64_json_str.strip() in ("", "[]"):
256
+ return []
257
+ try:
258
+ b64_list = json.loads(b64_json_str)
259
+ except Exception:
260
+ return []
261
+ pil_images = []
262
+ for b64_str in b64_list:
263
+ if not b64_str or not isinstance(b64_str, str):
264
+ continue
265
+ try:
266
+ if b64_str.startswith("data:image"):
267
+ _, data = b64_str.split(",", 1)
268
+ else:
269
+ data = b64_str
270
+ image_data = base64.b64decode(data)
271
+ pil_images.append(Image.open(BytesIO(image_data)).convert("RGB"))
272
+ except Exception as e:
273
+ print(f"Error decoding image: {e}")
274
+ return pil_images
275
+
276
+
277
+ def update_dimensions_on_upload(image):
278
+ if image is None:
279
+ return 1024, 1024
280
+ w, h = image.size
281
+ if w > h:
282
+ nw = 1024
283
+ nh = int(nw * h / w)
284
+ else:
285
+ nh = 1024
286
+ nw = int(nh * w / h)
287
+ return (nw // 8) * 8, (nh // 8) * 8
288
+
289
+
290
+ @spaces.GPU
291
+ def infer(
292
+ images_b64_json,
293
+ prompt,
294
+ lora_adapter,
295
+ seed,
296
+ randomize_seed,
297
+ guidance_scale,
298
+ steps,
299
+ progress=gr.Progress(track_tqdm=True),
300
+ ):
301
+ gc.collect()
302
+ torch.cuda.empty_cache()
303
+
304
+ pil_images = b64_to_pil_list(images_b64_json)
305
+ if not pil_images:
306
+ raise gr.Error("Please upload at least one image to edit.")
307
+ if not prompt or prompt.strip() == "":
308
+ raise gr.Error("Please enter an edit prompt.")
309
+
310
+ spec = ADAPTER_SPECS.get(lora_adapter)
311
+ if not spec:
312
+ raise gr.Error(f"Configuration not found for: {lora_adapter}")
313
+
314
+ adapter_name = spec["adapter_name"]
315
+ if adapter_name not in LOADED_ADAPTERS:
316
+ print(f"--- Downloading and Loading Adapter: {lora_adapter} ---")
317
+ try:
318
+ pipe.load_lora_weights(spec["repo"], weight_name=spec["weights"], adapter_name=adapter_name)
319
+ LOADED_ADAPTERS.add(adapter_name)
320
+ except Exception as e:
321
+ raise gr.Error(f"Failed to load adapter {lora_adapter}: {e}")
322
+ else:
323
+ print(f"--- Adapter {lora_adapter} already loaded. ---")
324
+
325
+ pipe.set_adapters([adapter_name], adapter_weights=[1.0])
326
+
327
+ if randomize_seed:
328
+ seed = random.randint(0, MAX_SEED)
329
+
330
+ generator = torch.Generator(device=device).manual_seed(seed)
331
+ negative_prompt = (
332
+ "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, "
333
+ "extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
334
+ )
335
+ width, height = update_dimensions_on_upload(pil_images[0])
336
+
337
+ try:
338
+ result_image = pipe(
339
+ image=pil_images,
340
+ prompt=prompt,
341
+ negative_prompt=negative_prompt,
342
+ height=height,
343
+ width=width,
344
+ num_inference_steps=steps,
345
+ generator=generator,
346
+ true_cfg_scale=guidance_scale,
347
+ ).images[0]
348
+ return result_image, seed
349
+ except Exception as e:
350
+ raise e
351
+ finally:
352
+ gc.collect()
353
+ torch.cuda.empty_cache()
354
+
355
+
356
+ css = r"""
357
+ @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');
358
+
359
+ *{box-sizing:border-box;margin:0;padding:0}
360
+
361
+ :root{
362
+ --or: #FF4500;
363
+ --or-dim: #CC3700;
364
+ --or-bright:#FF6A33;
365
+ --or-glow: rgba(255,69,0,.28);
366
+ --or-soft: rgba(255,69,0,.12);
367
+ --or-xsoft: rgba(255,69,0,.06);
368
+ --bg: #0d0d0f;
369
+ --bg1: #141416;
370
+ --bg2: #1a1a1d;
371
+ --bg3: #222226;
372
+ --border: #2a2a2e;
373
+ --border2: #333338;
374
+ --text: #e8e8ec;
375
+ --text2: #9898a8;
376
+ --text3: #555560;
377
+ --mono: 'JetBrains Mono', monospace;
378
+ }
379
+
380
+ html, body, .gradio-container {
381
+ color-scheme: dark !important;
382
+ background: #0d0d0f !important;
383
+ }
384
+
385
+ body, .gradio-container {
386
+ font-family: 'Inter', system-ui, -apple-system, sans-serif !important;
387
+ font-size: 14px !important;
388
+ color: #e8e8ec !important;
389
+ min-height: 100vh;
390
+ background: #0d0d0f !important;
391
+ }
392
+
393
+ footer { display: none !important; }
394
+ .hidden-input { display:none!important;height:0!important;overflow:hidden!important;margin:0!important;padding:0!important; }
395
+
396
+ #example-load-btn, #gradio-run-btn {
397
+ position: absolute !important;
398
+ left: -9999px !important;
399
+ top: -9999px !important;
400
+ width: 1px !important;
401
+ height: 1px !important;
402
+ opacity: .01 !important;
403
+ pointer-events: none !important;
404
+ overflow: hidden !important;
405
+ }
406
+
407
+ /* ── App shell ─────────────────────────────────────────────────────────────── */
408
+ .app-shell {
409
+ background: #141416 !important;
410
+ border: 1px solid #2a2a2e !important;
411
+ border-radius: 18px;
412
+ margin: 12px auto;
413
+ max-width: 1440px;
414
+ overflow: hidden;
415
+ box-shadow:
416
+ 0 32px 64px -16px rgba(0,0,0,.8),
417
+ 0 0 0 1px rgba(255,255,255,.03),
418
+ 0 0 60px -20px rgba(255,69,0,.2);
419
+ }
420
+
421
+ /* ── Header ────────────────────────────────────────────────────────────────── */
422
+ .app-header {
423
+ background: #1a1a1d !important;
424
+ border-bottom: 1px solid #2a2a2e !important;
425
+ padding: 14px 24px;
426
+ display: flex;
427
+ align-items: center;
428
+ justify-content: space-between;
429
+ flex-wrap: wrap;
430
+ gap: 12px;
431
+ }
432
+ .app-header-left { display:flex; align-items:center; gap:12px; }
433
+ .app-logo {
434
+ width: 38px; height: 38px;
435
+ background: linear-gradient(135deg, #FF4500, #FF6A33);
436
+ border-radius: 11px;
437
+ display: flex; align-items: center; justify-content: center;
438
+ box-shadow: 0 4px 14px rgba(255,69,0,.3);
439
+ flex-shrink: 0;
440
+ }
441
+ .app-logo svg { width:22px; height:22px; fill:#fff; }
442
+ .app-title {
443
+ font-size: 18px; font-weight: 800; letter-spacing: -.4px;
444
+ background: linear-gradient(135deg, #fff, #aaa);
445
+ -webkit-background-clip: text;
446
+ -webkit-text-fill-color: transparent;
447
+ font-family: 'Inter', sans-serif;
448
+ }
449
+ .app-badge {
450
+ font-size: 11px; font-weight: 700; padding: 3px 10px;
451
+ border-radius: 20px; letter-spacing: .3px;
452
+ background: rgba(255,69,0,.12);
453
+ color: #FF6A33 !important;
454
+ -webkit-text-fill-color: #FF6A33 !important;
455
+ border: 1px solid rgba(255,69,0,.3);
456
+ font-family: 'Inter', sans-serif;
457
+ }
458
+ .app-badge.fast {
459
+ background: rgba(34,197,94,.1);
460
+ color: #4ade80 !important;
461
+ -webkit-text-fill-color: #4ade80 !important;
462
+ border: 1px solid rgba(34,197,94,.25);
463
+ }
464
+
465
+ /* ── GitHub button - highlighted, always same in light & dark ────────────── */
466
+ .gh-btn {
467
+ display: inline-flex !important;
468
+ align-items: center !important;
469
+ gap: 7px !important;
470
+ padding: 7px 14px !important;
471
+ border-radius: 8px !important;
472
+ text-decoration: none !important;
473
+ font-family: 'Inter', sans-serif !important;
474
+ font-size: 13px !important;
475
+ font-weight: 700 !important;
476
+ letter-spacing: .1px !important;
477
+ /* Fixed colors - never change with theme */
478
+ background: #FF4500 !important;
479
+ color: #ffffff !important;
480
+ -webkit-text-fill-color: #ffffff !important;
481
+ border: 1px solid rgba(255,255,255,.15) !important;
482
+ box-shadow:
483
+ 0 2px 8px rgba(255,69,0,.4),
484
+ 0 1px 0 rgba(255,255,255,.1) inset !important;
485
+ transition: transform .15s ease, box-shadow .15s ease, background .15s ease !important;
486
+ cursor: pointer !important;
487
+ flex-shrink: 0 !important;
488
+ }
489
+ .gh-btn:hover {
490
+ background: #FF6A33 !important;
491
+ color: #ffffff !important;
492
+ -webkit-text-fill-color: #ffffff !important;
493
+ transform: translateY(-1px) !important;
494
+ box-shadow:
495
+ 0 4px 16px rgba(255,69,0,.55),
496
+ 0 1px 0 rgba(255,255,255,.12) inset !important;
497
+ }
498
+ .gh-btn:active {
499
+ background: #CC3700 !important;
500
+ transform: translateY(0) !important;
501
+ box-shadow: 0 1px 4px rgba(255,69,0,.3) !important;
502
+ }
503
+ .gh-btn svg {
504
+ fill: #ffffff !important;
505
+ flex-shrink: 0;
506
+ width: 15px !important;
507
+ height: 15px !important;
508
+ }
509
+ .gh-btn span {
510
+ color: #ffffff !important;
511
+ -webkit-text-fill-color: #ffffff !important;
512
+ }
513
+
514
+ /* Lock GitHub button against ANY browser/OS theme override */
515
+ @media (prefers-color-scheme: light) {
516
+ .gh-btn {
517
+ background: #FF4500 !important;
518
+ color: #ffffff !important;
519
+ -webkit-text-fill-color: #ffffff !important;
520
+ border-color: rgba(255,255,255,.15) !important;
521
+ }
522
+ .gh-btn:hover {
523
+ background: #FF6A33 !important;
524
+ color: #ffffff !important;
525
+ -webkit-text-fill-color: #ffffff !important;
526
+ }
527
+ .gh-btn svg { fill: #ffffff !important; }
528
+ .gh-btn span { color: #ffffff !important; -webkit-text-fill-color: #ffffff !important; }
529
+ }
530
+ @media (prefers-color-scheme: dark) {
531
+ .gh-btn {
532
+ background: #FF4500 !important;
533
+ color: #ffffff !important;
534
+ -webkit-text-fill-color: #ffffff !important;
535
+ }
536
+ .gh-btn:hover { background: #FF6A33 !important; }
537
+ .gh-btn svg { fill: #ffffff !important; }
538
+ .gh-btn span { color: #ffffff !important; -webkit-text-fill-color: #ffffff !important; }
539
+ }
540
+
541
+ /* ── Toolbar ───────────────────────────────────────────────────────────────── */
542
+ .app-toolbar {
543
+ background: #141416 !important;
544
+ border-bottom: 1px solid #2a2a2e !important;
545
+ padding: 7px 16px;
546
+ display: flex; gap: 4px; align-items: center; flex-wrap: wrap;
547
+ }
548
+ .tb-sep { width:1px; height:28px; background:#2a2a2e; margin:0 8px; }
549
+ .modern-tb-btn {
550
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px;
551
+ min-width: 32px; height: 34px;
552
+ background: transparent; border: 1px solid transparent;
553
+ border-radius: 8px; cursor: pointer;
554
+ font-size: 13px; font-weight: 700; padding: 0 12px;
555
+ font-family: 'Inter', sans-serif;
556
+ color: #ffffff !important;
557
+ -webkit-text-fill-color: #ffffff !important;
558
+ transition: all .15s ease;
559
+ }
560
+ .modern-tb-btn:hover { background: rgba(255,69,0,.12); border-color: rgba(255,69,0,.4); }
561
+ .modern-tb-btn:active { background: rgba(255,69,0,.22); border-color: #FF4500; }
562
+ .modern-tb-btn .tb-label { font-size:13px; color:#fff!important; -webkit-text-fill-color:#fff!important; font-weight:700; }
563
+ .modern-tb-btn .tb-svg { width:15px; height:15px; flex-shrink:0; }
564
+ .modern-tb-btn .tb-svg, .modern-tb-btn .tb-svg * { stroke:#fff!important; fill:none!important; }
565
+ .tb-info { font-family:var(--mono); font-size:12px; color:#555560; padding:0 8px; display:flex; align-items:center; }
566
+
567
+ @media (prefers-color-scheme: light) {
568
+ .modern-tb-btn { color:#fff!important; -webkit-text-fill-color:#fff!important; }
569
+ .modern-tb-btn .tb-label { color:#fff!important; -webkit-text-fill-color:#fff!important; }
570
+ .modern-tb-btn .tb-svg, .modern-tb-btn .tb-svg * { stroke:#fff!important; }
571
+ }
572
+
573
+ /* ── Main layout ───────────────────────────────────────────────────────────── */
574
+ .app-main-row { display:flex; gap:0; flex:1; overflow:hidden; }
575
+ .app-main-left { flex:1; display:flex; flex-direction:column; min-width:0; border-right:1px solid #2a2a2e; }
576
+ .app-main-right { width:440px; display:flex; flex-direction:column; flex-shrink:0; background:#141416!important; }
577
+
578
+ /* ── Drop zone ─────────────────────────────────────────────────────────────── */
579
+ #gallery-drop-zone { position:relative; background:#09090b!important; min-height:440px; overflow:auto; }
580
+ #gallery-drop-zone.drag-over { outline:2px solid #FF4500; outline-offset:-2px; background:rgba(255,69,0,.05)!important; }
581
+
582
+ .upload-prompt-modern { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); z-index:20; }
583
+ .upload-click-area {
584
+ display:flex; flex-direction:column; align-items:center; justify-content:center;
585
+ cursor:pointer; padding:36px 52px; border:2px dashed #333338; border-radius:16px;
586
+ background:rgba(255,69,0,.05); transition:all .2s ease; gap:8px;
587
+ }
588
+ .upload-click-area:hover { background:rgba(255,69,0,.1); border-color:#FF4500; transform:scale(1.03); }
589
+ .upload-click-area:active { background:rgba(255,69,0,.15); transform:scale(.98); }
590
+ .upload-click-area svg { width:80px; height:80px; }
591
+ .upload-main-text { color:#9898a8; font-size:14px; font-weight:600; margin-top:4px; font-family:'Inter',sans-serif; }
592
+ .upload-sub-text { color:#555560; font-size:12px; font-weight:400; text-align:center; max-width:280px; line-height:1.5; font-family:'Inter',sans-serif; }
593
+
594
+ /* ── Gallery grid ──────────────────────────────────────────────────────────── */
595
+ .image-gallery-grid {
596
+ display:grid; grid-template-columns:repeat(auto-fill,minmax(140px,1fr));
597
+ gap:12px; padding:16px; align-content:start;
598
+ }
599
+ .gallery-thumb {
600
+ position:relative; aspect-ratio:1; border-radius:10px; overflow:hidden;
601
+ cursor:pointer; border:2px solid #2a2a2e; transition:all .2s ease; background:#1a1a1d;
602
+ }
603
+ .gallery-thumb:hover { border-color:#333338; transform:translateY(-2px); box-shadow:0 4px 12px rgba(0,0,0,.4); }
604
+ .gallery-thumb.selected { border-color:#FF4500!important; box-shadow:0 0 0 3px rgba(255,69,0,.25); }
605
+ .gallery-thumb img { width:100%; height:100%; object-fit:cover; }
606
+ .thumb-badge {
607
+ position:absolute; top:6px; left:6px; background:#FF4500; color:#fff;
608
+ padding:2px 8px; border-radius:4px; font-family:var(--mono); font-size:11px; font-weight:600;
609
+ }
610
+ .thumb-remove {
611
+ position:absolute; top:6px; right:6px; width:24px; height:24px;
612
+ background:rgba(0,0,0,.75); color:#fff; border:1px solid rgba(255,255,255,.15);
613
+ border-radius:50%; cursor:pointer; display:none;
614
+ align-items:center; justify-content:center; font-size:12px; transition:all .15s; line-height:1;
615
+ }
616
+ .gallery-thumb:hover .thumb-remove { display:flex; }
617
+ .thumb-remove:hover { background:#FF4500; border-color:#FF4500; }
618
+ .gallery-add-card {
619
+ aspect-ratio:1; border-radius:10px; border:2px dashed #333338;
620
+ display:flex; flex-direction:column; align-items:center; justify-content:center;
621
+ cursor:pointer; transition:all .2s ease; background:rgba(255,69,0,.05); gap:4px;
622
+ }
623
+ .gallery-add-card:hover { border-color:#FF4500; background:rgba(255,69,0,.1); }
624
+ .gallery-add-card .add-icon { font-size:28px; color:#555560; font-weight:300; }
625
+ .gallery-add-card .add-text { font-size:12px; color:#555560; font-weight:600; font-family:'Inter',sans-serif; }
626
+
627
+ /* ── Hint bar ──────────────────────────────────────────────────────────────── */
628
+ .hint-bar {
629
+ background: rgba(255,69,0,.05) !important;
630
+ border-top:1px solid #2a2a2e; border-bottom:1px solid #2a2a2e;
631
+ padding:10px 20px; font-size:13px; color:#9898a8; line-height:1.7;
632
+ font-weight:400; font-family:'Inter',sans-serif;
633
+ }
634
+ .hint-bar b { color:#FF7A4D; font-weight:700; }
635
+ .hint-bar kbd {
636
+ display:inline-block; padding:1px 6px; background:#222226;
637
+ border:1px solid #333338; border-radius:4px;
638
+ font-family:var(--mono); font-size:11px; color:#9898a8;
639
+ }
640
+
641
+ /* ── Suggestions ───────────────────────────────────────────────────────────── */
642
+ .suggestions-section { border-top:1px solid #2a2a2e; padding:12px 16px; background:#141416!important; }
643
+ .suggestions-title, .examples-title {
644
+ font-size:11px; font-weight:700; color:#555560;
645
+ text-transform:uppercase; letter-spacing:1px; margin-bottom:10px;
646
+ font-family:'Inter',sans-serif;
647
+ }
648
+ .suggestions-wrap { display:flex; flex-wrap:wrap; gap:6px; }
649
+ .suggestion-chip {
650
+ display:inline-flex; align-items:center; gap:4px; padding:5px 13px;
651
+ background:rgba(255,69,0,.1); border:1px solid rgba(255,69,0,.25); border-radius:20px;
652
+ color:#FF7A4D; font-size:12px; font-weight:600; font-family:'Inter',sans-serif;
653
+ cursor:pointer; transition:all .15s; white-space:nowrap;
654
+ }
655
+ .suggestion-chip:hover { background:rgba(255,69,0,.2); border-color:#FF4500; color:#fff; transform:translateY(-1px); }
656
+
657
+ /* ── Examples ──────────────────────────────────────────────────────────────── */
658
+ .examples-section { border-top:1px solid #2a2a2e; padding:14px 16px 18px; background:#141416!important; }
659
+ .examples-scroll { display:flex; gap:10px; overflow-x:auto; padding-bottom:10px; padding-top:2px; }
660
+ .examples-scroll::-webkit-scrollbar { height:5px; }
661
+ .examples-scroll::-webkit-scrollbar-track { background:#0d0d0f; border-radius:3px; }
662
+ .examples-scroll::-webkit-scrollbar-thumb { background:#333338; border-radius:3px; }
663
+ .examples-scroll::-webkit-scrollbar-thumb:hover { background:#CC3700; }
664
+ .example-card {
665
+ flex-shrink:0; width:220px; background:#1a1a1d!important; border:1px solid #2a2a2e;
666
+ border-radius:12px; overflow:hidden; cursor:pointer; transition:all .2s ease;
667
+ }
668
+ .example-card:hover { border-color:#FF4500; transform:translateY(-3px); box-shadow:0 6px 20px rgba(255,69,0,.22); }
669
+ .example-card.loading { opacity:.5; pointer-events:none; }
670
+ .example-thumbs { display:flex; height:115px; overflow:hidden; background:#222226!important; }
671
+ .example-thumbs img { flex:1; object-fit:cover; min-width:0; }
672
+ .example-thumb-placeholder {
673
+ flex:1; display:flex; align-items:center; justify-content:center;
674
+ background:#222226!important; color:#555560; font-size:11px; min-width:0;
675
+ }
676
+ .example-meta { padding:7px 10px 3px; display:flex; align-items:center; gap:5px; flex-wrap:wrap; }
677
+ .example-badge {
678
+ display:inline-flex; padding:2px 7px; background:rgba(255,69,0,.1); border-radius:4px;
679
+ font-size:10px; font-weight:700; color:#FF6A33; font-family:var(--mono);
680
+ white-space:nowrap; border:1px solid rgba(255,69,0,.2);
681
+ }
682
+ .example-lora-badge {
683
+ display:inline-flex; padding:2px 7px; background:rgba(255,255,255,.06); border-radius:4px;
684
+ font-size:10px; font-weight:600; color:#9898a8; font-family:var(--mono);
685
+ white-space:nowrap; border:1px solid #2a2a2e;
686
+ max-width:120px; overflow:hidden; text-overflow:ellipsis;
687
+ }
688
+ .example-prompt-text {
689
+ padding:2px 10px 10px; font-size:11.5px; color:#9898a8; line-height:1.45;
690
+ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
691
+ overflow:hidden; font-weight:400; font-family:'Inter',sans-serif;
692
+ }
693
+
694
+ /* ── Right panel ───────────────────────────────────────────────────────────── */
695
+ .panel-card { border-bottom:1px solid #2a2a2e; background:#141416!important; }
696
+ .panel-card-title {
697
+ padding:11px 20px; font-size:11px; font-weight:700; color:#555560;
698
+ text-transform:uppercase; letter-spacing:1px;
699
+ border-bottom:1px solid rgba(42,42,46,.6);
700
+ background:#141416!important; font-family:'Inter',sans-serif;
701
+ }
702
+ .panel-card-body { padding:14px 18px; display:flex; flex-direction:column; gap:8px; background:#141416!important; }
703
+ .modern-label { font-size:13px; font-weight:600; color:#9898a8; margin-bottom:4px; display:block; font-family:'Inter',sans-serif; }
704
+ .modern-textarea {
705
+ width:100%; background:#09090b!important; border:1px solid #2a2a2e; border-radius:8px;
706
+ padding:10px 14px; font-family:'Inter',sans-serif; font-size:14px;
707
+ color:#e8e8ec!important; -webkit-text-fill-color:#e8e8ec!important;
708
+ resize:vertical; outline:none; min-height:44px; transition:border-color .2s; font-weight:400;
709
+ }
710
+ .modern-textarea:focus { border-color:#FF4500; box-shadow:0 0 0 3px rgba(255,69,0,.22); }
711
+ .modern-textarea::placeholder { color:#555560!important; -webkit-text-fill-color:#555560!important; }
712
+ .modern-textarea.error-flash {
713
+ border-color:#ef4444!important;
714
+ box-shadow:0 0 0 3px rgba(239,68,68,.2)!important;
715
+ animation:shake .4s ease;
716
+ }
717
+ @keyframes shake {
718
+ 0%,100%{transform:translateX(0)}
719
+ 20%,60%{transform:translateX(-4px)}
720
+ 40%,80%{transform:translateX(4px)}
721
+ }
722
+
723
+ /* ── LoRA selector - always dark ───────────────────────────────────────────── */
724
+ .lora-selector-card { border-bottom:1px solid #2a2a2e!important; background:#0d0d0f!important; }
725
+ .lora-selector-body { padding:12px 18px!important; background:#0d0d0f!important; }
726
+ .lora-select-label {
727
+ font-size:11px!important; font-weight:700!important;
728
+ color:#555560!important; -webkit-text-fill-color:#555560!important;
729
+ text-transform:uppercase!important; letter-spacing:1px!important;
730
+ margin-bottom:8px!important; display:flex!important; align-items:center!important;
731
+ gap:6px!important; font-family:'Inter',sans-serif!important;
732
+ }
733
+ .lora-select-label::before {
734
+ content:''; display:inline-block; width:8px; height:8px;
735
+ background:#FF4500; border-radius:50%; flex-shrink:0;
736
+ }
737
+ .lora-native-select {
738
+ width:100%!important; background:#09090b!important;
739
+ border:1px solid #333338!important; border-radius:8px!important;
740
+ padding:9px 36px 9px 14px!important;
741
+ font-family:'Inter',sans-serif!important;
742
+ font-size:13px!important; font-weight:600!important;
743
+ color:#e8e8ec!important; -webkit-text-fill-color:#e8e8ec!important;
744
+ outline:none!important; appearance:none!important; -webkit-appearance:none!important;
745
+ background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23FF4500' d='M6 8L1 3h10z'/%3E%3C/svg%3E")!important;
746
+ background-repeat:no-repeat!important; background-position:right 12px center!important;
747
+ cursor:pointer!important; transition:border-color .2s!important; color-scheme:dark!important;
748
+ }
749
+ .lora-native-select:focus { border-color:#FF4500!important; box-shadow:0 0 0 3px rgba(255,69,0,.22)!important; }
750
+ .lora-native-select option { background:#1a1a1d!important; color:#e8e8ec!important; }
751
+
752
+ @media (prefers-color-scheme: light) {
753
+ .lora-selector-card { background:#0d0d0f!important; }
754
+ .lora-selector-body { background:#0d0d0f!important; }
755
+ .lora-select-label { color:#555560!important; -webkit-text-fill-color:#555560!important; }
756
+ .lora-native-select {
757
+ background:#09090b!important; color:#e8e8ec!important;
758
+ -webkit-text-fill-color:#e8e8ec!important;
759
+ border-color:#333338!important; color-scheme:dark!important;
760
+ }
761
+ .lora-native-select option { background:#1a1a1d!important; color:#e8e8ec!important; }
762
+ }
763
+
764
+ /* ── Toast ─────────────────────────────────────────────────────────────────── */
765
+ .toast-notification {
766
+ position:fixed; top:24px; left:50%;
767
+ transform:translateX(-50%) translateY(-120%);
768
+ z-index:9999; padding:10px 24px; border-radius:10px;
769
+ font-family:'Inter',sans-serif; font-size:14px; font-weight:700;
770
+ display:flex; align-items:center; gap:8px;
771
+ box-shadow:0 8px 24px rgba(0,0,0,.5);
772
+ transition:transform .35s cubic-bezier(.34,1.56,.64,1),opacity .35s ease;
773
+ opacity:0; pointer-events:none;
774
+ }
775
+ .toast-notification.visible { transform:translateX(-50%) translateY(0); opacity:1; pointer-events:auto; }
776
+ .toast-notification.error { background:linear-gradient(135deg,#dc2626,#b91c1c); color:#fff; border:1px solid rgba(255,255,255,.15); }
777
+ .toast-notification.warning { background:linear-gradient(135deg,#FF4500,#CC3700); color:#fff; border:1px solid rgba(255,255,255,.15); }
778
+ .toast-notification.info { background:linear-gradient(135deg,#2563eb,#1d4ed8); color:#fff; border:1px solid rgba(255,255,255,.15); }
779
+
780
+ /* ── Run button ────────────────────────────────────────────────────────────── */
781
+ .btn-run {
782
+ display:flex; align-items:center; justify-content:center; gap:8px; width:100%;
783
+ background:linear-gradient(135deg,#FF4500,#CC3700); border:none; border-radius:10px;
784
+ padding:13px 24px; cursor:pointer; font-size:15px; font-weight:800;
785
+ font-family:'Inter',sans-serif;
786
+ color:#fff!important; -webkit-text-fill-color:#fff!important;
787
+ transition:all .2s ease; letter-spacing:.2px;
788
+ box-shadow:0 4px 20px rgba(255,69,0,.28),inset 0 1px 0 rgba(255,255,255,.12);
789
+ }
790
+ .btn-run:hover {
791
+ background:linear-gradient(135deg,#FF6A33,#FF4500); transform:translateY(-1px);
792
+ box-shadow:0 8px 28px rgba(255,69,0,.45),inset 0 1px 0 rgba(255,255,255,.15);
793
+ }
794
+ .btn-run:active { transform:translateY(0); box-shadow:0 2px 10px rgba(255,69,0,.28); }
795
+ .btn-run svg { width:18px; height:18px; fill:#fff!important; }
796
+ #custom-run-btn, #custom-run-btn *, #run-btn-label, .btn-run, .btn-run * {
797
+ color:#fff!important; -webkit-text-fill-color:#fff!important; fill:#fff!important;
798
+ }
799
+
800
+ /* ── Output ────────────────────────────────────────────────────────────────── */
801
+ .output-frame { border-bottom:1px solid #2a2a2e; display:flex; flex-direction:column; position:relative; }
802
+ .out-title {
803
+ padding:10px 20px; font-size:11px; font-weight:700;
804
+ color:#fff!important; -webkit-text-fill-color:#fff!important;
805
+ text-transform:uppercase; letter-spacing:1px;
806
+ border-bottom:1px solid rgba(42,42,46,.6);
807
+ display:flex; align-items:center; justify-content:space-between;
808
+ background:#141416!important; font-family:'Inter',sans-serif;
809
+ }
810
+ .out-body {
811
+ flex:1; background:#09090b!important;
812
+ display:flex; align-items:center; justify-content:center;
813
+ overflow:hidden; min-height:240px; position:relative;
814
+ }
815
+ .out-body img { max-width:100%; max-height:460px; image-rendering:auto; }
816
+ .out-placeholder { color:#555560; font-size:13px; text-align:center; padding:20px; font-weight:500; font-family:'Inter',sans-serif; }
817
+ .out-download-btn {
818
+ display:none; align-items:center; justify-content:center;
819
+ background:rgba(255,69,0,.12); border:1px solid rgba(255,69,0,.28); border-radius:6px;
820
+ cursor:pointer; padding:3px 10px; font-size:11px; font-weight:700;
821
+ color:#FF6A33!important; -webkit-text-fill-color:#FF6A33!important;
822
+ gap:4px; height:24px; transition:all .15s; font-family:'Inter',sans-serif;
823
+ }
824
+ .out-download-btn:hover {
825
+ background:#FF4500; border-color:#FF4500;
826
+ color:#fff!important; -webkit-text-fill-color:#fff!important;
827
+ }
828
+ .out-download-btn.visible { display:inline-flex; }
829
+ .out-download-btn svg { width:12px; height:12px; fill:#FF6A33; }
830
+ .out-download-btn:hover svg { fill:#fff; }
831
+
832
+ /* ── Loader ────────────────────────────────────────────────────────────────── */
833
+ .modern-loader {
834
+ display:none; position:absolute; top:0; left:0; right:0; bottom:0;
835
+ background:rgba(9,9,11,.93); z-index:15;
836
+ flex-direction:column; align-items:center; justify-content:center;
837
+ gap:16px; backdrop-filter:blur(4px);
838
+ }
839
+ .modern-loader.active { display:flex; }
840
+ .modern-loader .loader-spinner {
841
+ width:36px; height:36px; border:3px solid #2a2a2e;
842
+ border-top-color:#FF4500; border-radius:50%; animation:spin .8s linear infinite;
843
+ }
844
+ @keyframes spin { to{transform:rotate(360deg)} }
845
+ .modern-loader .loader-text { font-size:13px; color:#9898a8; font-weight:600; font-family:'Inter',sans-serif; }
846
+ .loader-bar-track { width:200px; height:4px; background:#2a2a2e; border-radius:2px; overflow:hidden; }
847
+ .loader-bar-fill {
848
+ height:100%; background:linear-gradient(90deg,#FF4500,#FF6A33,#FF4500);
849
+ background-size:200% 100%; animation:shimmer 1.5s ease-in-out infinite; border-radius:2px;
850
+ }
851
+ @keyframes shimmer { 0%{background-position:200% 0} 100%{background-position:-200% 0} }
852
+
853
+ /* ── Settings ──────────────────────────────────────────────────────────────── */
854
+ .settings-group { border:1px solid #2a2a2e; border-radius:10px; margin:12px 16px; padding:0; overflow:hidden; background:#141416!important; }
855
+ .settings-group-title {
856
+ font-size:11px; font-weight:700; color:#555560; text-transform:uppercase; letter-spacing:1px;
857
+ padding:9px 16px; border-bottom:1px solid #2a2a2e;
858
+ background:rgba(26,26,29,.5)!important; font-family:'Inter',sans-serif;
859
+ }
860
+ .settings-group-body { padding:14px 16px; display:flex; flex-direction:column; gap:12px; background:#141416!important; }
861
+ .slider-row { display:flex; align-items:center; gap:10px; min-height:28px; }
862
+ .slider-row label { font-size:13px; font-weight:600; color:#9898a8; min-width:72px; flex-shrink:0; font-family:'Inter',sans-serif; }
863
+ .slider-row input[type="range"] {
864
+ flex:1; -webkit-appearance:none; appearance:none;
865
+ height:5px; background:#333338; border-radius:3px; outline:none; min-width:0;
866
+ }
867
+ .slider-row input[type="range"]::-webkit-slider-thumb {
868
+ -webkit-appearance:none; width:16px; height:16px;
869
+ background:linear-gradient(135deg,#FF4500,#CC3700);
870
+ border-radius:50%; cursor:pointer;
871
+ box-shadow:0 2px 6px rgba(255,69,0,.28); transition:transform .15s;
872
+ }
873
+ .slider-row input[type="range"]::-webkit-slider-thumb:hover { transform:scale(1.2); }
874
+ .slider-row input[type="range"]::-moz-range-thumb {
875
+ width:16px; height:16px; background:linear-gradient(135deg,#FF4500,#CC3700);
876
+ border-radius:50%; cursor:pointer; border:none; box-shadow:0 2px 6px rgba(255,69,0,.28);
877
+ }
878
+ .slider-row .slider-val {
879
+ min-width:52px; text-align:right; font-family:var(--mono); font-size:12px;
880
+ font-weight:500; padding:3px 8px; background:#09090b; border:1px solid #2a2a2e;
881
+ border-radius:6px; color:#9898a8; flex-shrink:0;
882
+ }
883
+ .checkbox-row { display:flex; align-items:center; gap:8px; font-size:13px; color:#9898a8; }
884
+ .checkbox-row input[type="checkbox"] { accent-color:#FF4500; width:16px; height:16px; cursor:pointer; }
885
+ .checkbox-row label { color:#9898a8; font-size:13px; cursor:pointer; font-weight:500; font-family:'Inter',sans-serif; }
886
+
887
+ /* ── Status bar ────────────────────────────────────────────────────────────── */
888
+ .app-statusbar {
889
+ background:#141416!important; border-top:1px solid #2a2a2e;
890
+ padding:6px 20px; display:flex; gap:12px; height:34px; align-items:center; font-size:12px;
891
+ }
892
+ .app-statusbar .sb-section {
893
+ padding:0 12px; flex:1; display:flex; align-items:center;
894
+ font-family:var(--mono); font-size:12px; color:#555560;
895
+ overflow:hidden; white-space:nowrap;
896
+ }
897
+ .app-statusbar .sb-section.sb-fixed {
898
+ flex:0 0 auto; min-width:90px; text-align:center; justify-content:center;
899
+ padding:3px 12px; background:rgba(255,69,0,.1); border-radius:6px;
900
+ color:#FF6A33; font-weight:700; border:1px solid rgba(255,69,0,.2);
901
+ }
902
+
903
+ /* ── Footer ────────────────────────────────────────────────────────────────── */
904
+ .exp-note {
905
+ padding:10px 20px; font-size:12px; color:#555560;
906
+ border-top:1px solid #2a2a2e; text-align:center; font-weight:500;
907
+ background:#141416!important; font-family:'Inter',sans-serif;
908
+ }
909
+ .exp-note a { color:#FF6A33!important; -webkit-text-fill-color:#FF6A33!important; text-decoration:none; }
910
+ .exp-note a:hover { text-decoration:underline; color:#fff!important; -webkit-text-fill-color:#fff!important; }
911
+
912
+ /* ── Scrollbars ────────────────────────────────────────────────────────────── */
913
+ ::-webkit-scrollbar { width:7px; height:7px; }
914
+ ::-webkit-scrollbar-track { background:#09090b; }
915
+ ::-webkit-scrollbar-thumb { background:#333338; border-radius:4px; }
916
+ ::-webkit-scrollbar-thumb:hover { background:#CC3700; }
917
+
918
+ /* ── Responsive ────────────────────────────────────────────────────────────── */
919
+ @media(max-width:860px){
920
+ .app-main-row { flex-direction:column; }
921
+ .app-main-right { width:100%; }
922
+ .app-main-left { border-right:none; border-bottom:1px solid #2a2a2e; }
923
+ }
924
+ """
925
+
926
+ gallery_js = r"""
927
+ () => {
928
+ function init() {
929
+ if (window.__qwenInitDone) return;
930
+
931
+ const galleryGrid = document.getElementById('image-gallery-grid');
932
+ const dropZone = document.getElementById('gallery-drop-zone');
933
+ const uploadPrompt = document.getElementById('upload-prompt');
934
+ const uploadClick = document.getElementById('upload-click-area');
935
+ const fileInput = document.getElementById('custom-file-input');
936
+ const btnUpload = document.getElementById('tb-upload');
937
+ const btnRemove = document.getElementById('tb-remove');
938
+ const btnClear = document.getElementById('tb-clear');
939
+ const promptInput = document.getElementById('custom-prompt-input');
940
+ const loraSelect = document.getElementById('custom-lora-select');
941
+ const runBtnEl = document.getElementById('custom-run-btn');
942
+ const imgCountTb = document.getElementById('tb-image-count');
943
+ const imgCountSb = document.getElementById('sb-image-count');
944
+
945
+ if (!galleryGrid || !fileInput || !dropZone) { setTimeout(init, 250); return; }
946
+ window.__qwenInitDone = true;
947
+
948
+ let images = [];
949
+ window.__uploadedImages = images;
950
+ let selectedIdx = -1;
951
+ let toastTimer = null;
952
+
953
+ /* ── Force dark styles on elements that browsers may override ── */
954
+ function enforceDarkStyles() {
955
+ /* LoRA card */
956
+ const loraCard = document.querySelector('.lora-selector-card');
957
+ const loraBody = document.querySelector('.lora-selector-body');
958
+ const loraLabel = document.querySelector('.lora-select-label');
959
+ const loraEl = document.getElementById('custom-lora-select');
960
+ if (loraCard) { loraCard.style.setProperty('background','#0d0d0f','important'); }
961
+ if (loraBody) { loraBody.style.setProperty('background','#0d0d0f','important'); }
962
+ if (loraLabel) {
963
+ loraLabel.style.setProperty('color','#555560','important');
964
+ loraLabel.style.setProperty('-webkit-text-fill-color','#555560','important');
965
+ }
966
+ if (loraEl) {
967
+ loraEl.style.setProperty('background-color','#09090b','important');
968
+ loraEl.style.setProperty('color','#e8e8ec','important');
969
+ loraEl.style.setProperty('-webkit-text-fill-color','#e8e8ec','important');
970
+ loraEl.style.setProperty('border-color','#333338','important');
971
+ }
972
+
973
+ /* GitHub button */
974
+ const ghBtn = document.querySelector('.gh-btn');
975
+ if (ghBtn) {
976
+ ghBtn.style.setProperty('background','#FF4500','important');
977
+ ghBtn.style.setProperty('color','#ffffff','important');
978
+ ghBtn.style.setProperty('-webkit-text-fill-color','#ffffff','important');
979
+ ghBtn.style.setProperty('border-color','rgba(255,255,255,.15)','important');
980
+ ghBtn.style.setProperty('box-shadow','0 2px 8px rgba(255,69,0,.4)','important');
981
+ const svg = ghBtn.querySelector('svg');
982
+ if (svg) svg.style.setProperty('fill','#ffffff','important');
983
+ const span = ghBtn.querySelector('span');
984
+ if (span) {
985
+ span.style.setProperty('color','#ffffff','important');
986
+ span.style.setProperty('-webkit-text-fill-color','#ffffff','important');
987
+ }
988
+ }
989
+
990
+ /* Shell sections */
991
+ const shell = document.querySelector('.app-shell');
992
+ const header = document.querySelector('.app-header');
993
+ const toolbar = document.querySelector('.app-toolbar');
994
+ if (shell) shell.style.setProperty('background','#141416','important');
995
+ if (header) header.style.setProperty('background','#1a1a1d','important');
996
+ if (toolbar) toolbar.style.setProperty('background','#141416','important');
997
+ }
998
+
999
+ enforceDarkStyles();
1000
+ setInterval(enforceDarkStyles, 800);
1001
+
1002
+ /* GitHub button hover interaction */
1003
+ const ghBtn = document.querySelector('.gh-btn');
1004
+ if (ghBtn) {
1005
+ ghBtn.addEventListener('mouseenter', () => {
1006
+ ghBtn.style.setProperty('background','#FF6A33','important');
1007
+ ghBtn.style.setProperty('color','#ffffff','important');
1008
+ ghBtn.style.setProperty('-webkit-text-fill-color','#ffffff','important');
1009
+ ghBtn.style.setProperty('transform','translateY(-1px)','important');
1010
+ ghBtn.style.setProperty('box-shadow','0 4px 16px rgba(255,69,0,.55)','important');
1011
+ });
1012
+ ghBtn.addEventListener('mouseleave', () => {
1013
+ ghBtn.style.setProperty('background','#FF4500','important');
1014
+ ghBtn.style.setProperty('color','#ffffff','important');
1015
+ ghBtn.style.setProperty('-webkit-text-fill-color','#ffffff','important');
1016
+ ghBtn.style.setProperty('transform','translateY(0)','important');
1017
+ ghBtn.style.setProperty('box-shadow','0 2px 8px rgba(255,69,0,.4)','important');
1018
+ });
1019
+ ghBtn.addEventListener('mousedown', () => {
1020
+ ghBtn.style.setProperty('background','#CC3700','important');
1021
+ ghBtn.style.setProperty('transform','translateY(0)','important');
1022
+ });
1023
+ ghBtn.addEventListener('mouseup', () => {
1024
+ ghBtn.style.setProperty('background','#FF6A33','important');
1025
+ });
1026
+ }
1027
+
1028
+ function showToast(message, type) {
1029
+ let toast = document.getElementById('app-toast');
1030
+ if (!toast) {
1031
+ toast = document.createElement('div');
1032
+ toast.id = 'app-toast';
1033
+ toast.className = 'toast-notification';
1034
+ toast.innerHTML = '<span class="toast-icon"></span><span class="toast-text"></span>';
1035
+ document.body.appendChild(toast);
1036
+ }
1037
+ const icon = toast.querySelector('.toast-icon');
1038
+ const text = toast.querySelector('.toast-text');
1039
+ toast.className = 'toast-notification ' + (type || 'error');
1040
+ icon.textContent = type === 'warning' ? '\u26A0' : type === 'info' ? '\u2139' : '\u2717';
1041
+ text.textContent = message;
1042
+ if (toastTimer) clearTimeout(toastTimer);
1043
+ void toast.offsetWidth;
1044
+ toast.classList.add('visible');
1045
+ toastTimer = setTimeout(() => toast.classList.remove('visible'), 3500);
1046
+ }
1047
+ window.__showToast = showToast;
1048
+
1049
+ function flashPromptError() {
1050
+ if (!promptInput) return;
1051
+ promptInput.classList.add('error-flash');
1052
+ promptInput.focus();
1053
+ setTimeout(() => promptInput.classList.remove('error-flash'), 800);
1054
+ }
1055
+
1056
+ function setGradioValue(containerId, value) {
1057
+ const container = document.getElementById(containerId);
1058
+ if (!container) return;
1059
+ container.querySelectorAll('input, textarea').forEach(el => {
1060
+ if (el.type === 'file' || el.type === 'range' || el.type === 'checkbox') return;
1061
+ const proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
1062
+ const ns = Object.getOwnPropertyDescriptor(proto, 'value');
1063
+ if (ns && ns.set) {
1064
+ ns.set.call(el, value);
1065
+ el.dispatchEvent(new Event('input', {bubbles:true, composed:true}));
1066
+ el.dispatchEvent(new Event('change', {bubbles:true, composed:true}));
1067
+ }
1068
+ });
1069
+ }
1070
+ window.__setGradioValue = setGradioValue;
1071
+
1072
+ function syncImagesToGradio() {
1073
+ window.__uploadedImages = images;
1074
+ const b64Array = images.map(img => img.b64);
1075
+ setGradioValue('hidden-images-b64', JSON.stringify(b64Array));
1076
+ updateCounts();
1077
+ }
1078
+ function syncPromptToGradio() {
1079
+ if (promptInput) setGradioValue('prompt-gradio-input', promptInput.value);
1080
+ }
1081
+ function syncLoraToGradio() {
1082
+ if (!loraSelect) return;
1083
+ const container = document.getElementById('gradio-lora');
1084
+ if (!container) return;
1085
+ container.querySelectorAll('input').forEach(el => {
1086
+ const ns = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
1087
+ if (ns && ns.set) {
1088
+ ns.set.call(el, loraSelect.value);
1089
+ el.dispatchEvent(new Event('input', {bubbles:true, composed:true}));
1090
+ el.dispatchEvent(new Event('change', {bubbles:true, composed:true}));
1091
+ }
1092
+ });
1093
+ }
1094
+
1095
+ function updateCounts() {
1096
+ const n = images.length;
1097
+ const txt = n > 0 ? n + ' image' + (n > 1 ? 's' : '') : 'No images';
1098
+ if (imgCountTb) imgCountTb.textContent = txt;
1099
+ if (imgCountSb) imgCountSb.textContent = n > 0 ? txt + ' uploaded' : 'No images uploaded';
1100
+ }
1101
+
1102
+ function addImage(b64, name) {
1103
+ images.push({id: Date.now() + Math.random(), b64, name});
1104
+ renderGallery(); syncImagesToGradio();
1105
+ }
1106
+ window.__addImage = addImage;
1107
+
1108
+ function removeImage(idx) {
1109
+ images.splice(idx, 1);
1110
+ if (selectedIdx === idx) selectedIdx = -1;
1111
+ else if (selectedIdx > idx) selectedIdx--;
1112
+ renderGallery(); syncImagesToGradio();
1113
+ }
1114
+
1115
+ function clearAll() {
1116
+ images = []; window.__uploadedImages = images; selectedIdx = -1;
1117
+ renderGallery(); syncImagesToGradio();
1118
+ }
1119
+ window.__clearAll = clearAll;
1120
+
1121
+ function renderGallery() {
1122
+ if (images.length === 0) {
1123
+ galleryGrid.innerHTML = ''; galleryGrid.style.display = 'none';
1124
+ if (uploadPrompt) uploadPrompt.style.display = '';
1125
+ return;
1126
+ }
1127
+ if (uploadPrompt) uploadPrompt.style.display = 'none';
1128
+ galleryGrid.style.display = 'grid';
1129
+ let html = '';
1130
+ images.forEach((img, i) => {
1131
+ const sel = i === selectedIdx ? ' selected' : '';
1132
+ html += '<div class="gallery-thumb' + sel + '" data-idx="' + i + '">'
1133
+ + '<img src="' + img.b64 + '" alt="' + (img.name||'image') + '">'
1134
+ + '<span class="thumb-badge">#' + (i+1) + '</span>'
1135
+ + '<button class="thumb-remove" data-remove="' + i + '">\u2715</button>'
1136
+ + '</div>';
1137
+ });
1138
+ html += '<div class="gallery-add-card" id="gallery-add-card"><span class="add-icon">+</span><span class="add-text">Add</span></div>';
1139
+ galleryGrid.innerHTML = html;
1140
+ galleryGrid.querySelectorAll('.gallery-thumb').forEach(thumb => {
1141
+ thumb.addEventListener('click', (e) => {
1142
+ if (e.target.closest('.thumb-remove')) return;
1143
+ selectedIdx = (selectedIdx === parseInt(thumb.dataset.idx)) ? -1 : parseInt(thumb.dataset.idx);
1144
+ renderGallery();
1145
+ });
1146
+ });
1147
+ galleryGrid.querySelectorAll('.thumb-remove').forEach(btn => {
1148
+ btn.addEventListener('click', (e) => { e.stopPropagation(); removeImage(parseInt(btn.dataset.remove)); });
1149
+ });
1150
+ const addCard = document.getElementById('gallery-add-card');
1151
+ if (addCard) addCard.addEventListener('click', () => fileInput.click());
1152
+ }
1153
+
1154
+ function processFiles(files) {
1155
+ Array.from(files).forEach(file => {
1156
+ if (!file.type.startsWith('image/')) return;
1157
+ const reader = new FileReader();
1158
+ reader.onload = (e) => addImage(e.target.result, file.name);
1159
+ reader.readAsDataURL(file);
1160
+ });
1161
+ }
1162
+
1163
+ fileInput.addEventListener('change', (e) => { processFiles(e.target.files); e.target.value = ''; });
1164
+ if (uploadClick) uploadClick.addEventListener('click', () => fileInput.click());
1165
+ if (btnUpload) btnUpload.addEventListener('click', () => fileInput.click());
1166
+ if (btnRemove) btnRemove.addEventListener('click', () => { if (selectedIdx >= 0) removeImage(selectedIdx); });
1167
+ if (btnClear) btnClear.addEventListener('click', clearAll);
1168
+
1169
+ dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('drag-over'); });
1170
+ dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); });
1171
+ dropZone.addEventListener('drop', (e) => {
1172
+ e.preventDefault(); dropZone.classList.remove('drag-over');
1173
+ if (e.dataTransfer.files.length) processFiles(e.dataTransfer.files);
1174
+ });
1175
+
1176
+ if (promptInput) promptInput.addEventListener('input', syncPromptToGradio);
1177
+ if (loraSelect) loraSelect.addEventListener('change', syncLoraToGradio);
1178
+
1179
+ window.__setPrompt = function(text) { if (promptInput) { promptInput.value = text; syncPromptToGradio(); } };
1180
+ window.__setLora = function(lora) {
1181
+ if (loraSelect) {
1182
+ loraSelect.value = lora;
1183
+ loraSelect.dispatchEvent(new Event('change', {bubbles:true}));
1184
+ syncLoraToGradio();
1185
+ }
1186
+ };
1187
+
1188
+ document.querySelectorAll('.example-card[data-idx]').forEach(card => {
1189
+ card.addEventListener('click', () => {
1190
+ const idx = card.getAttribute('data-idx');
1191
+ document.querySelectorAll('.example-card.loading').forEach(c => c.classList.remove('loading'));
1192
+ card.classList.add('loading');
1193
+ showToast('Loading example\u2026', 'info');
1194
+ setGradioValue('example-result-data', '');
1195
+ setGradioValue('example-idx-input', idx);
1196
+ setTimeout(() => {
1197
+ const btn = document.getElementById('example-load-btn');
1198
+ if (btn) { const b = btn.querySelector('button'); if (b) b.click(); else btn.click(); }
1199
+ }, 150);
1200
+ setTimeout(() => card.classList.remove('loading'), 12000);
1201
+ });
1202
+ });
1203
+
1204
+ function syncSlider(customId, gradioId) {
1205
+ const slider = document.getElementById(customId);
1206
+ const valSpan = document.getElementById(customId + '-val');
1207
+ if (!slider) return;
1208
+ slider.addEventListener('input', () => {
1209
+ if (valSpan) valSpan.textContent = slider.value;
1210
+ const container = document.getElementById(gradioId);
1211
+ if (!container) return;
1212
+ container.querySelectorAll('input[type="range"],input[type="number"]').forEach(el => {
1213
+ const ns = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
1214
+ if (ns && ns.set) {
1215
+ ns.set.call(el, slider.value);
1216
+ el.dispatchEvent(new Event('input', {bubbles:true, composed:true}));
1217
+ el.dispatchEvent(new Event('change', {bubbles:true, composed:true}));
1218
+ }
1219
+ });
1220
+ });
1221
+ }
1222
+ syncSlider('custom-seed', 'gradio-seed');
1223
+ syncSlider('custom-guidance', 'gradio-guidance');
1224
+ syncSlider('custom-steps', 'gradio-steps');
1225
+
1226
+ const randCheck = document.getElementById('custom-randomize');
1227
+ if (randCheck) {
1228
+ randCheck.addEventListener('change', () => {
1229
+ const container = document.getElementById('gradio-randomize');
1230
+ if (!container) return;
1231
+ const cb = container.querySelector('input[type="checkbox"]');
1232
+ if (cb && cb.checked !== randCheck.checked) cb.click();
1233
+ });
1234
+ }
1235
+
1236
+ function showLoader() {
1237
+ const l = document.getElementById('output-loader');
1238
+ if (l) l.classList.add('active');
1239
+ const sb = document.querySelector('.sb-fixed');
1240
+ if (sb) sb.textContent = 'Processing\u2026';
1241
+ }
1242
+ function hideLoader() {
1243
+ const l = document.getElementById('output-loader');
1244
+ if (l) l.classList.remove('active');
1245
+ const sb = document.querySelector('.sb-fixed');
1246
+ if (sb) sb.textContent = 'Done';
1247
+ }
1248
+ window.__showLoader = showLoader;
1249
+ window.__hideLoader = hideLoader;
1250
+
1251
+ function validateBeforeRun() {
1252
+ const promptVal = promptInput ? promptInput.value.trim() : '';
1253
+ const hasImages = images.length > 0;
1254
+ if (!hasImages && !promptVal) { showToast('Please upload an image and enter a prompt', 'error'); flashPromptError(); return false; }
1255
+ if (!hasImages) { showToast('Please upload at least one image', 'error'); return false; }
1256
+ if (!promptVal) { showToast('Please enter an edit prompt', 'warning'); flashPromptError(); return false; }
1257
+ return true;
1258
+ }
1259
+
1260
+ window.__clickGradioRunBtn = function() {
1261
+ if (!validateBeforeRun()) return;
1262
+ syncPromptToGradio(); syncImagesToGradio(); syncLoraToGradio(); showLoader();
1263
+ setTimeout(() => {
1264
+ const gradioBtn = document.getElementById('gradio-run-btn');
1265
+ if (!gradioBtn) return;
1266
+ const btn = gradioBtn.querySelector('button');
1267
+ if (btn) btn.click(); else gradioBtn.click();
1268
+ }, 200);
1269
+ };
1270
+
1271
+ if (runBtnEl) runBtnEl.addEventListener('click', () => window.__clickGradioRunBtn());
1272
+
1273
+ renderGallery();
1274
+ updateCounts();
1275
+ }
1276
+ init();
1277
+ }
1278
+ """
1279
+
1280
+ wire_outputs_js = r"""
1281
+ () => {
1282
+ function watchOutputs() {
1283
+ const resultContainer = document.getElementById('gradio-result');
1284
+ const outBody = document.getElementById('output-image-container');
1285
+ const outPh = document.getElementById('output-placeholder');
1286
+ const dlBtn = document.getElementById('dl-btn-output');
1287
+
1288
+ if (!resultContainer || !outBody) { setTimeout(watchOutputs, 500); return; }
1289
+
1290
+ if (dlBtn) {
1291
+ dlBtn.addEventListener('click', (e) => {
1292
+ e.stopPropagation();
1293
+ const img = outBody.querySelector('img.modern-out-img');
1294
+ if (img && img.src) {
1295
+ const a = document.createElement('a');
1296
+ a.href = img.src; a.download = 'qwen_edit_output.png';
1297
+ document.body.appendChild(a); a.click(); document.body.removeChild(a);
1298
+ }
1299
+ });
1300
+ }
1301
+
1302
+ function syncImage() {
1303
+ const resultImg = resultContainer.querySelector('img');
1304
+ if (resultImg && resultImg.src) {
1305
+ if (outPh) outPh.style.display = 'none';
1306
+ let existing = outBody.querySelector('img.modern-out-img');
1307
+ if (!existing) {
1308
+ existing = document.createElement('img');
1309
+ existing.className = 'modern-out-img';
1310
+ outBody.appendChild(existing);
1311
+ }
1312
+ if (existing.src !== resultImg.src) {
1313
+ existing.src = resultImg.src;
1314
+ if (dlBtn) dlBtn.classList.add('visible');
1315
+ if (window.__hideLoader) window.__hideLoader();
1316
+ }
1317
+ }
1318
+ }
1319
+ const observer = new MutationObserver(syncImage);
1320
+ observer.observe(resultContainer, {childList:true, subtree:true, attributes:true, attributeFilter:['src']});
1321
+ setInterval(syncImage, 800);
1322
+ }
1323
+ watchOutputs();
1324
+
1325
+ function watchSeed() {
1326
+ const seedContainer = document.getElementById('gradio-seed');
1327
+ const seedSlider = document.getElementById('custom-seed');
1328
+ const seedVal = document.getElementById('custom-seed-val');
1329
+ if (!seedContainer || !seedSlider) { setTimeout(watchSeed, 500); return; }
1330
+ function sync() {
1331
+ const el = seedContainer.querySelector('input[type="range"],input[type="number"]');
1332
+ if (el && el.value) { seedSlider.value = el.value; if (seedVal) seedVal.textContent = el.value; }
1333
+ }
1334
+ const obs = new MutationObserver(sync);
1335
+ obs.observe(seedContainer, {childList:true, subtree:true, attributes:true, attributeFilter:['value']});
1336
+ setInterval(sync, 1000);
1337
+ }
1338
+ watchSeed();
1339
+
1340
+ function watchExampleResults() {
1341
+ const container = document.getElementById('example-result-data');
1342
+ if (!container) { setTimeout(watchExampleResults, 500); return; }
1343
+ let lastProcessed = '';
1344
+
1345
+ function checkResult() {
1346
+ const el = container.querySelector('textarea') || container.querySelector('input');
1347
+ if (!el) return;
1348
+ const val = el.value;
1349
+ if (!val || val === lastProcessed || val.length < 20) return;
1350
+ try {
1351
+ const data = JSON.parse(val);
1352
+ if (data.status === 'ok' && data.images && data.images.length > 0) {
1353
+ lastProcessed = val;
1354
+ if (window.__clearAll) window.__clearAll();
1355
+ if (window.__setPrompt && data.prompt) window.__setPrompt(data.prompt);
1356
+ if (window.__setLora && data.lora) window.__setLora(data.lora);
1357
+ data.images.forEach((b64, i) => {
1358
+ if (b64 && window.__addImage) {
1359
+ const name = (data.names && data.names[i]) ? data.names[i] : ('example_'+(i+1)+'.jpg');
1360
+ window.__addImage(b64, name);
1361
+ }
1362
+ });
1363
+ document.querySelectorAll('.example-card.loading').forEach(c => c.classList.remove('loading'));
1364
+ if (window.__showToast) window.__showToast('Example loaded \u2014 ' + data.images.length + ' image(s)', 'info');
1365
+ } else if (data.status === 'error') {
1366
+ document.querySelectorAll('.example-card.loading').forEach(c => c.classList.remove('loading'));
1367
+ if (window.__showToast) window.__showToast('Could not load example images', 'error');
1368
+ }
1369
+ } catch(e) { console.error('Example parse error:', e); }
1370
+ }
1371
+
1372
+ const obs = new MutationObserver(checkResult);
1373
+ obs.observe(container, {childList:true, subtree:true, characterData:true, attributes:true});
1374
+ setInterval(checkResult, 500);
1375
+ }
1376
+ watchExampleResults();
1377
+ }
1378
+ """
1379
+
1380
+ ROCKET_LOGO_SVG = '''<svg viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg">
1381
+ <path d="M12 2C12 2 7 6.5 7 13c0 1.4.3 2.7.8 3.9L5 19.7l1.4 1.4 2.8-2.8c1.2.5 2.5.7 3.8.7s2.6-.2 3.8-.7l2.8 2.8 1.4-1.4-2.8-2.8c.5-1.2.8-2.5.8-3.9 0-6.5-5-11-5-11z"/>
1382
+ <circle cx="12" cy="13" r="2"/>
1383
+ <path d="M9 21c0 1.1.9 2 2 2h2c1.1 0 2-.9 2-2v-1H9v1z"/>
1384
+ </svg>'''
1385
+
1386
+ 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>'
1387
+ 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>'
1388
+ 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>'
1389
+ DOWNLOAD_SVG = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 16l-5-5h3V4h4v7h3l-5 5z" fill="currentColor"/><path d="M20 18H4v2h16v-2z" fill="currentColor"/></svg>'
1390
+ GITHUB_SVG = '<svg width="15" height="15" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>'
1391
+
1392
+ LORA_OPTIONS_HTML = "\n".join(
1393
+ f'<option value="{html_lib.escape(name)}">{html_lib.escape(name)}</option>'
1394
+ for name in ADAPTER_NAMES
1395
+ )
1396
+
1397
+ with gr.Blocks() as demo:
1398
+
1399
+ hidden_images_b64 = gr.Textbox(value="[]", elem_id="hidden-images-b64", elem_classes="hidden-input", container=False)
1400
+ prompt = gr.Textbox(value="", elem_id="prompt-gradio-input", elem_classes="hidden-input", container=False)
1401
+ lora_adapter = gr.Dropdown(choices=ADAPTER_NAMES, value="Photo-to-Anime", elem_id="gradio-lora", elem_classes="hidden-input", container=False)
1402
+ seed = gr.Slider(minimum=0, maximum=MAX_SEED, step=1, value=0, elem_id="gradio-seed", elem_classes="hidden-input", container=False)
1403
+ randomize_seed = gr.Checkbox(value=True, elem_id="gradio-randomize", elem_classes="hidden-input", container=False)
1404
+ guidance_scale = gr.Slider(minimum=1.0, maximum=10.0, step=0.1, value=1.0, elem_id="gradio-guidance", elem_classes="hidden-input", container=False)
1405
+ steps = gr.Slider(minimum=1, maximum=50, step=1, value=4, elem_id="gradio-steps", elem_classes="hidden-input", container=False)
1406
+ result = gr.Image(elem_id="gradio-result", elem_classes="hidden-input", container=False, format="png")
1407
+
1408
+ example_idx = gr.Textbox(value="", elem_id="example-idx-input", elem_classes="hidden-input", container=False)
1409
+ example_result = gr.Textbox(value="", elem_id="example-result-data", elem_classes="hidden-input", container=False)
1410
+ example_load_btn = gr.Button("Load Example", elem_id="example-load-btn")
1411
+
1412
+ gr.HTML(f"""
1413
+ <div class="app-shell">
1414
+
1415
+ <!-- Header -->
1416
+ <div class="app-header">
1417
+ <div class="app-header-left">
1418
+ <div class="app-logo">{ROCKET_LOGO_SVG}</div>
1419
+ <span class="app-title">Qwen-Image-Edit</span>
1420
+ <span class="app-badge">2511</span>
1421
+ <span class="app-badge fast">4-Step Fast</span>
1422
+ </div>
1423
+
1424
+ <!-- GitHub button: highlighted orange, same in light & dark -->
1425
+ <a href="https://github.com/PRITHIVSAKTHIUR/Qwen-Image-Edit-2511-LoRAs-Fast-Lazy-Load"
1426
+ target="_blank"
1427
+ class="gh-btn">
1428
+ {GITHUB_SVG}
1429
+ <span>GitHub</span>
1430
+ </a>
1431
+ </div>
1432
+
1433
+ <!-- Toolbar -->
1434
+ <div class="app-toolbar">
1435
+ <button id="tb-upload" class="modern-tb-btn" title="Upload images">
1436
+ {UPLOAD_SVG}<span class="tb-label">Upload</span>
1437
+ </button>
1438
+ <button id="tb-remove" class="modern-tb-btn" title="Remove selected image">
1439
+ {REMOVE_SVG}<span class="tb-label">Remove</span>
1440
+ </button>
1441
+ <button id="tb-clear" class="modern-tb-btn" title="Clear all images">
1442
+ {CLEAR_SVG}<span class="tb-label">Clear All</span>
1443
+ </button>
1444
+ <div class="tb-sep"></div>
1445
+ <span id="tb-image-count" class="tb-info">No images</span>
1446
+ </div>
1447
+
1448
+ <!-- Main row -->
1449
+ <div class="app-main-row">
1450
+
1451
+ <!-- Left -->
1452
+ <div class="app-main-left">
1453
+
1454
+ <div id="gallery-drop-zone">
1455
+ <div id="upload-prompt" class="upload-prompt-modern">
1456
+ <div id="upload-click-area" class="upload-click-area">
1457
+ <svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
1458
+ <rect x="8" y="14" width="64" height="52" rx="6" fill="none"
1459
+ stroke="#FF4500" stroke-width="2" stroke-dasharray="4 3"/>
1460
+ <polygon points="12,62 30,40 42,50 54,34 68,62"
1461
+ fill="rgba(255,69,0,0.12)" stroke="#FF4500" stroke-width="1.5"/>
1462
+ <circle cx="28" cy="30" r="6"
1463
+ fill="rgba(255,69,0,0.18)" stroke="#FF4500" stroke-width="1.5"/>
1464
+ </svg>
1465
+ <span class="upload-main-text">Click or drag images here</span>
1466
+ <span class="upload-sub-text">Supports multiple images for reference-based editing and guided manipulation</span>
1467
+ </div>
1468
+ </div>
1469
+ <input id="custom-file-input" type="file" accept="image/*" multiple style="display:none;" />
1470
+ <div id="image-gallery-grid" class="image-gallery-grid" style="display:none;"></div>
1471
+ </div>
1472
+
1473
+ <div class="hint-bar">
1474
+ <b>Upload:</b> Click or drag images &nbsp;&middot;&nbsp;
1475
+ <b>Multi-image:</b> Upload multiple for reference editing &nbsp;&middot;&nbsp;
1476
+ <kbd>Remove</kbd> deletes selected &nbsp;&middot;&nbsp;
1477
+ <kbd>Clear All</kbd> removes everything
1478
+ </div>
1479
+
1480
+ <div class="suggestions-section">
1481
+ <div class="suggestions-title">Quick Prompts</div>
1482
+ <div class="suggestions-wrap">
1483
+ <button class="suggestion-chip" onclick="window.__setPrompt('Transform into anime.')">Anime</button>
1484
+ <button class="suggestion-chip" onclick="window.__setPrompt('Convert it to black and white.')">B&amp;W</button>
1485
+ <button class="suggestion-chip" onclick="window.__setPrompt('Add cinematic lighting with warm orange tones and film grain.')">Cinematic</button>
1486
+ <button class="suggestion-chip" onclick="window.__setPrompt('Apply oil painting effect with visible brush strokes.')">Oil Paint</button>
1487
+ <button class="suggestion-chip" onclick="window.__setPrompt('Upscale this picture to 4K resolution.')">Upscale 4K</button>
1488
+ <button class="suggestion-chip" onclick="window.__setPrompt('Make it look like a watercolor painting with soft edges.')">Watercolor</button>
1489
+ <button class="suggestion-chip" onclick="window.__setPrompt('Convert to detailed pencil sketch with cross-hatching and shading.')">Pencil Sketch</button>
1490
+ <button class="suggestion-chip" onclick="window.__setPrompt('Apply pop art style with bold colors and halftone patterns.')">Pop Art</button>
1491
+ <button class="suggestion-chip" onclick="window.__setPrompt('Apply a vintage retro film look with faded colors and light leaks.')">Vintage Retro</button>
1492
+ <button class="suggestion-chip" onclick="window.__setPrompt('Add neon glow effects with vibrant colors against a dark background.')">Neon Glow</button>
1493
+ <button class="suggestion-chip" onclick="window.__setPrompt('Convert to pixel art style with a retro 16-bit aesthetic.')">Pixel Art</button>
1494
+ <button class="suggestion-chip" onclick="window.__setPrompt('Transform into a noir comic book style.')">Noir Comic</button>
1495
+ <button class="suggestion-chip" onclick="window.__setPrompt('Transform into a hyper-realistic face portrait.')">HyperReal</button>
1496
+ <button class="suggestion-chip" onclick="window.__setPrompt('Unblur and upscale.')">Unblur</button>
1497
+ <button class="suggestion-chip" onclick="window.__setPrompt('Transform into Pixar-inspired 3D.')">Pixar 3D</button>
1498
+ <button class="suggestion-chip" onclick="window.__setPrompt('Paint with manga tone.')">Manga Tone</button>
1499
+ </div>
1500
+ </div>
1501
+
1502
+ <div class="examples-section">
1503
+ <div class="examples-title">Quick Examples &mdash; click to load</div>
1504
+ <div class="examples-scroll">
1505
+ {EXAMPLE_CARDS_HTML}
1506
+ </div>
1507
+ </div>
1508
+
1509
+ </div><!-- /left -->
1510
+
1511
+ <!-- Right -->
1512
+ <div class="app-main-right">
1513
+
1514
+ <div class="panel-card">
1515
+ <div class="panel-card-title">Edit Instruction</div>
1516
+ <div class="panel-card-body">
1517
+ <label class="modern-label" for="custom-prompt-input">Prompt</label>
1518
+ <textarea id="custom-prompt-input" class="modern-textarea" rows="3"
1519
+ placeholder="e.g., transform into anime, upscale, change lighting&hellip;"></textarea>
1520
+ </div>
1521
+ </div>
1522
+
1523
+ <div class="lora-selector-card">
1524
+ <div class="lora-selector-body">
1525
+ <div class="lora-select-label">Editing Style / LoRA</div>
1526
+ <select id="custom-lora-select" class="lora-native-select">
1527
+ {LORA_OPTIONS_HTML}
1528
+ </select>
1529
+ </div>
1530
+ </div>
1531
+
1532
+ <div style="padding:14px 18px 6px;">
1533
+ <button id="custom-run-btn" class="btn-run">
1534
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
1535
+ <path d="M12 2C12 2 7 6.5 7 13c0 1.4.3 2.7.8 3.9L5 19.7l1.4 1.4 2.8-2.8c1.2.5 2.5.7 3.8.7s2.6-.2 3.8-.7l2.8 2.8 1.4-1.4-2.8-2.8c.5-1.2.8-2.5.8-3.9 0-6.5-5-11-5-11z"/>
1536
+ <circle cx="12" cy="13" r="2"/>
1537
+ </svg>
1538
+ <span id="run-btn-label">Edit Image</span>
1539
+ </button>
1540
+ </div>
1541
+
1542
+ <div class="output-frame" style="flex:1">
1543
+ <div class="out-title">
1544
+ <span>Output</span>
1545
+ <span id="dl-btn-output" class="out-download-btn" title="Download result">
1546
+ {DOWNLOAD_SVG} Save
1547
+ </span>
1548
+ </div>
1549
+ <div class="out-body" id="output-image-container">
1550
+ <div class="modern-loader" id="output-loader">
1551
+ <div class="loader-spinner"></div>
1552
+ <div class="loader-text">Processing image&hellip;</div>
1553
+ <div class="loader-bar-track"><div class="loader-bar-fill"></div></div>
1554
+ </div>
1555
+ <div class="out-placeholder" id="output-placeholder">Result will appear here</div>
1556
+ </div>
1557
+ </div>
1558
+
1559
+ <div class="settings-group">
1560
+ <div class="settings-group-title">Advanced Settings</div>
1561
+ <div class="settings-group-body">
1562
+ <div class="slider-row">
1563
+ <label>Seed</label>
1564
+ <input type="range" id="custom-seed" min="0" max="2147483647" step="1" value="0">
1565
+ <span class="slider-val" id="custom-seed-val">0</span>
1566
+ </div>
1567
+ <div class="checkbox-row">
1568
+ <input type="checkbox" id="custom-randomize" checked>
1569
+ <label for="custom-randomize">Randomize seed</label>
1570
+ </div>
1571
+ <div class="slider-row">
1572
+ <label>Guidance</label>
1573
+ <input type="range" id="custom-guidance" min="1" max="10" step="0.1" value="1.0">
1574
+ <span class="slider-val" id="custom-guidance-val">1.0</span>
1575
+ </div>
1576
+ <div class="slider-row">
1577
+ <label>Steps</label>
1578
+ <input type="range" id="custom-steps" min="1" max="50" step="1" value="4">
1579
+ <span class="slider-val" id="custom-steps-val">4</span>
1580
+ </div>
1581
+ </div>
1582
+ </div>
1583
+
1584
+ </div><!-- /right -->
1585
+ </div><!-- /main-row -->
1586
+
1587
+ <div class="exp-note">
1588
+ Experimental Space for
1589
+ <a href="https://huggingface.co/Qwen/Qwen-Image-Edit-2511" target="_blank">Qwen-Image-Edit-2511</a>
1590
+ &middot; LoRAs loaded lazily on first use
1591
+ </div>
1592
+
1593
+ <div class="app-statusbar">
1594
+ <div class="sb-section" id="sb-image-count">No images uploaded</div>
1595
+ <div class="sb-section sb-fixed">Ready</div>
1596
+ </div>
1597
+
1598
+ </div><!-- /app-shell -->
1599
+ """)
1600
+
1601
+ run_btn = gr.Button("Run", elem_id="gradio-run-btn")
1602
+
1603
+ demo.load(fn=None, js=gallery_js)
1604
+ demo.load(fn=None, js=wire_outputs_js)
1605
+
1606
+ run_btn.click(
1607
+ fn=infer,
1608
+ inputs=[hidden_images_b64, prompt, lora_adapter, seed, randomize_seed, guidance_scale, steps],
1609
+ outputs=[result, seed],
1610
+ js=r"""(imgs, p, la, s, rs, gs, st) => {
1611
+ const images = window.__uploadedImages || [];
1612
+ const b64Array = images.map(img => img.b64);
1613
+ const imgsJson = JSON.stringify(b64Array);
1614
+ const promptEl = document.getElementById('custom-prompt-input');
1615
+ const loraEl = document.getElementById('custom-lora-select');
1616
+ const promptVal = promptEl ? promptEl.value : p;
1617
+ const loraVal = loraEl ? loraEl.value : la;
1618
+ return [imgsJson, promptVal, loraVal, s, rs, gs, st];
1619
+ }""",
1620
+ )
1621
+
1622
+ example_load_btn.click(
1623
+ fn=load_example_data,
1624
+ inputs=[example_idx],
1625
+ outputs=[example_result],
1626
+ queue=False,
1627
+ )
1628
+
1629
+ if __name__ == "__main__":
1630
+ demo.queue(max_size=50).launch(
1631
+ css=css,
1632
+ mcp_server=True,
1633
+ ssr_mode=False,
1634
+ show_error=True,
1635
+ allowed_paths=["examples"],
1636
+ )