prithivMLmods commited on
Commit
5cf87ba
Β·
verified Β·
1 Parent(s): d0c65d0

update app

Browse files
Files changed (1) hide show
  1. app.py +355 -510
app.py CHANGED
@@ -7,10 +7,12 @@ import torch
7
  import random
8
  import base64
9
  import json
 
10
  from io import BytesIO
11
  from PIL import Image
12
 
13
  MAX_SEED = np.iinfo(np.int32).max
 
14
 
15
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
16
 
@@ -41,6 +43,114 @@ try:
41
  except Exception as e:
42
  print(f"Warning: Could not set FA3 processor: {e}")
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  def b64_to_pil_list(b64_json_str):
46
  if not b64_json_str or b64_json_str.strip() in ("", "[]"):
@@ -68,60 +178,35 @@ def b64_to_pil_list(b64_json_str):
68
  def update_dimensions_on_upload(image):
69
  if image is None:
70
  return 1024, 1024
71
- original_width, original_height = image.size
72
- if original_width > original_height:
73
- new_width = 1024
74
- aspect_ratio = original_height / original_width
75
- new_height = int(new_width * aspect_ratio)
76
  else:
77
- new_height = 1024
78
- aspect_ratio = original_width / original_height
79
- new_width = int(new_height * aspect_ratio)
80
- new_width = (new_width // 8) * 8
81
- new_height = (new_height // 8) * 8
82
- return new_width, new_height
83
 
84
 
85
  @spaces.GPU
86
- def infer(
87
- images_b64_json,
88
- prompt,
89
- seed,
90
- randomize_seed,
91
- guidance_scale,
92
- steps,
93
- progress=gr.Progress(track_tqdm=True),
94
- ):
95
  gc.collect()
96
  torch.cuda.empty_cache()
97
-
98
  pil_images = b64_to_pil_list(images_b64_json)
99
  if not pil_images:
100
  raise gr.Error("Please upload at least one image to edit.")
101
  if not prompt or prompt.strip() == "":
102
  raise gr.Error("Please enter an edit prompt.")
103
-
104
  if randomize_seed:
105
  seed = random.randint(0, MAX_SEED)
106
-
107
  generator = torch.Generator(device=device).manual_seed(seed)
108
- negative_prompt = (
109
- "worst quality, low quality, bad anatomy, bad hands, text, error, "
110
- "missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, "
111
- "signature, watermark, username, blurry"
112
- )
113
  width, height = update_dimensions_on_upload(pil_images[0])
114
-
115
  try:
116
  result_image = pipe(
117
- image=pil_images,
118
- prompt=prompt,
119
- negative_prompt=negative_prompt,
120
- height=height,
121
- width=width,
122
- num_inference_steps=steps,
123
- generator=generator,
124
- true_cfg_scale=guidance_scale,
125
  ).images[0]
126
  return result_image, seed
127
  except Exception as e:
@@ -131,145 +216,103 @@ def infer(
131
  torch.cuda.empty_cache()
132
 
133
 
 
 
 
 
134
  css = r"""
135
  @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');
136
-
137
  *{box-sizing:border-box;margin:0;padding:0}
138
-
139
  body,.gradio-container{
140
- background:#0f0f13!important;
141
- font-family:'Inter',system-ui,-apple-system,sans-serif!important;
142
- font-size:14px!important;
143
- color:#e4e4e7!important;
144
- min-height:100vh;
145
  }
146
  .dark body,.dark .gradio-container{background:#0f0f13!important;color:#e4e4e7!important}
147
  footer{display:none!important}
 
148
 
149
- .hidden-input{
150
- display:none!important;height:0!important;overflow:hidden!important;
151
- margin:0!important;padding:0!important;
 
 
 
 
 
152
  }
153
 
154
- /* ── App Shell ── */
155
  .app-shell{
156
  background:#18181b;border:1px solid #27272a;border-radius:16px;
157
  margin:12px auto;max-width:1400px;overflow:hidden;
158
  box-shadow:0 25px 50px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.03);
159
  }
160
-
161
- /* ── Header ── */
162
  .app-header{
163
- background:linear-gradient(135deg,#18181b,#1e1e24);
164
- border-bottom:1px solid #27272a;padding:14px 24px;
165
- display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;
166
  }
167
  .app-header-left{display:flex;align-items:center;gap:12px}
168
  .app-logo{
169
- width:36px;height:36px;
170
- background:linear-gradient(135deg,#FF4500,#FF6633,#FF8C66);
171
  border-radius:10px;display:flex;align-items:center;justify-content:center;
172
  box-shadow:0 4px 12px rgba(255,69,0,.35);
173
  }
174
  .app-logo svg{width:20px;height:20px;fill:#fff;flex-shrink:0}
175
  .app-title{
176
- font-size:18px;font-weight:700;
177
- background:linear-gradient(135deg,#e4e4e7,#a1a1aa);
178
  -webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-.3px;
179
  }
180
  .app-badge{
181
  font-size:11px;font-weight:600;padding:3px 10px;border-radius:20px;
182
- background:rgba(255,69,0,.15);color:#FF6633;
183
- border:1px solid rgba(255,69,0,.25);letter-spacing:.3px;
184
- }
185
- .app-badge.fast{
186
- background:rgba(34,197,94,.12);color:#4ade80;
187
- border:1px solid rgba(34,197,94,.25);
188
  }
 
189
 
190
- /* ── Toolbar ── */
191
  .app-toolbar{
192
- background:#18181b;border-bottom:1px solid #27272a;
193
- padding:8px 16px;display:flex;gap:4px;align-items:center;flex-wrap:wrap;
194
  }
195
  .tb-sep{width:1px;height:28px;background:#27272a;margin:0 8px}
196
  .modern-tb-btn{
197
  display:inline-flex;align-items:center;justify-content:center;gap:6px;
198
- min-width:32px;height:34px;background:transparent;
199
- border:1px solid transparent;border-radius:8px;cursor:pointer;
200
- font-size:13px;font-weight:600;padding:0 12px;
201
- font-family:'Inter',sans-serif;color:#ffffff!important;
202
- -webkit-text-fill-color:#ffffff!important;transition:all .15s ease;
203
- }
204
- .modern-tb-btn:hover{
205
- background:rgba(255,69,0,.15);color:#ffffff!important;
206
- -webkit-text-fill-color:#ffffff!important;border-color:rgba(255,69,0,.3);
207
- }
208
- .modern-tb-btn:active,.modern-tb-btn.active{
209
- background:rgba(255,69,0,.25);color:#ffffff!important;
210
- -webkit-text-fill-color:#ffffff!important;border-color:rgba(255,69,0,.45);
211
  }
 
 
212
  .modern-tb-btn .tb-label{font-size:13px;color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;font-weight:600}
213
- .modern-tb-btn .tb-svg{
214
- width:15px;height:15px;flex-shrink:0;
215
- stroke:#ffffff!important;color:#ffffff!important;
216
- }
217
- .tb-info{
218
- font-family:'JetBrains Mono',monospace;font-size:12px;
219
- color:#71717a;padding:0 8px;display:flex;align-items:center;
220
- }
221
-
222
- /* Force toolbar white in all themes */
223
- body:not(.dark) .modern-tb-btn,body:not(.dark) .modern-tb-btn *{
224
- color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;
225
- }
226
- body:not(.dark) .modern-tb-btn .tb-svg{stroke:#ffffff!important}
227
- .dark .modern-tb-btn,.dark .modern-tb-btn *{
228
- color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;
229
- }
230
- .dark .modern-tb-btn .tb-svg{stroke:#ffffff!important}
231
- .gradio-container .modern-tb-btn,.gradio-container .modern-tb-btn *{
232
- color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;
233
- }
234
- .gradio-container .modern-tb-btn .tb-svg{stroke:#ffffff!important}
235
 
236
- /* ── Main Layout ── */
237
  .app-main-row{display:flex;gap:0;flex:1;overflow:hidden}
238
- .app-main-left{
239
- flex:1;display:flex;flex-direction:column;min-width:0;
240
- border-right:1px solid #27272a;
241
- }
242
- .app-main-right{
243
- width:420px;display:flex;flex-direction:column;flex-shrink:0;background:#18181b;
244
- }
245
 
246
- /* ── Gallery Drop Zone ── */
247
- #gallery-drop-zone{
248
- position:relative;background:#09090b;min-height:440px;overflow:auto;
249
- }
250
- #gallery-drop-zone.drag-over{
251
- outline:2px solid #FF4500;outline-offset:-2px;
252
- background:rgba(255,69,0,.04);
253
- }
254
 
255
- /* ── Upload Prompt ── */
256
- .upload-prompt-modern{
257
- position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:20;
258
- }
259
  .upload-click-area{
260
  display:flex;flex-direction:column;align-items:center;justify-content:center;
261
- cursor:pointer;padding:36px 52px;border:2px dashed #3f3f46;
262
- border-radius:16px;background:rgba(255,69,0,.03);transition:all .2s ease;gap:8px;
263
- }
264
- .upload-click-area:hover{
265
- background:rgba(255,69,0,.08);border-color:#FF4500;transform:scale(1.03);
266
  }
 
267
  .upload-click-area:active{background:rgba(255,69,0,.12);transform:scale(.98)}
268
  .upload-click-area svg{width:80px;height:80px}
269
  .upload-main-text{color:#71717a;font-size:14px;font-weight:500;margin-top:4px}
270
  .upload-sub-text{color:#52525b;font-size:12px}
271
 
272
- /* ── Gallery Grid ── */
273
  .image-gallery-grid{
274
  display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));
275
  gap:12px;padding:16px;align-content:start;
@@ -278,24 +321,17 @@ body:not(.dark) .modern-tb-btn .tb-svg{stroke:#ffffff!important}
278
  position:relative;aspect-ratio:1;border-radius:10px;overflow:hidden;
279
  cursor:pointer;border:2px solid #27272a;transition:all .2s ease;background:#18181b;
280
  }
281
- .gallery-thumb:hover{
282
- border-color:#3f3f46;transform:translateY(-2px);
283
- box-shadow:0 4px 12px rgba(0,0,0,.4);
284
- }
285
- .gallery-thumb.selected{
286
- border-color:#FF4500!important;box-shadow:0 0 0 3px rgba(255,69,0,.2);
287
- }
288
  .gallery-thumb img{width:100%;height:100%;object-fit:cover}
289
  .thumb-badge{
290
  position:absolute;top:6px;left:6px;background:#FF4500;color:#fff;
291
- padding:2px 8px;border-radius:4px;font-family:'JetBrains Mono',monospace;
292
- font-size:11px;font-weight:600;
293
  }
294
  .thumb-remove{
295
- position:absolute;top:6px;right:6px;width:24px;height:24px;
296
- background:rgba(0,0,0,.75);color:#fff;border:1px solid rgba(255,255,255,.15);
297
- border-radius:50%;cursor:pointer;display:none;align-items:center;
298
- justify-content:center;font-size:12px;transition:all .15s;line-height:1;
299
  }
300
  .gallery-thumb:hover .thumb-remove{display:flex}
301
  .thumb-remove:hover{background:#FF4500;border-color:#FF4500}
@@ -308,46 +344,32 @@ body:not(.dark) .modern-tb-btn .tb-svg{stroke:#ffffff!important}
308
  .gallery-add-card .add-icon{font-size:28px;color:#71717a;font-weight:300}
309
  .gallery-add-card .add-text{font-size:12px;color:#71717a;font-weight:500}
310
 
311
- /* ── Hint Bar ── */
312
  .hint-bar{
313
- background:rgba(255,69,0,.06);border-top:1px solid #27272a;
314
- border-bottom:1px solid #27272a;padding:10px 20px;
315
- font-size:13px;color:#a1a1aa;line-height:1.7;
316
  }
317
  .hint-bar b{color:#FF8C66;font-weight:600}
318
  .hint-bar kbd{
319
- display:inline-block;padding:1px 6px;background:#27272a;
320
- border:1px solid #3f3f46;border-radius:4px;
321
- font-family:'JetBrains Mono',monospace;font-size:11px;color:#a1a1aa;
322
  }
323
 
324
- /* ── Suggestions ── */
325
  .suggestions-section{border-top:1px solid #27272a;padding:12px 16px}
326
- .suggestions-title{
327
  font-size:12px;font-weight:600;color:#71717a;text-transform:uppercase;
328
  letter-spacing:.8px;margin-bottom:10px;
329
  }
330
  .suggestions-wrap{display:flex;flex-wrap:wrap;gap:6px}
331
  .suggestion-chip{
332
  display:inline-flex;align-items:center;gap:4px;padding:5px 12px;
333
- background:rgba(255,69,0,.08);border:1px solid rgba(255,69,0,.2);
334
- border-radius:20px;color:#FF8C66;font-size:12px;font-weight:500;
335
- font-family:'Inter',sans-serif;cursor:pointer;transition:all .15s;white-space:nowrap;
336
- }
337
- .suggestion-chip:hover{
338
- background:rgba(255,69,0,.15);border-color:rgba(255,69,0,.35);
339
- color:#FF6633;transform:translateY(-1px);
340
  }
 
341
 
342
- /* ── Quick Examples ── */
343
  .examples-section{border-top:1px solid #27272a;padding:12px 16px}
344
- .examples-title{
345
- font-size:12px;font-weight:600;color:#71717a;text-transform:uppercase;
346
- letter-spacing:.8px;margin-bottom:10px;
347
- }
348
- .examples-scroll{
349
- display:flex;gap:10px;overflow-x:auto;padding-bottom:8px;
350
- }
351
  .examples-scroll::-webkit-scrollbar{height:6px}
352
  .examples-scroll::-webkit-scrollbar-track{background:#09090b;border-radius:3px}
353
  .examples-scroll::-webkit-scrollbar-thumb{background:#27272a;border-radius:3px}
@@ -356,136 +378,88 @@ body:not(.dark) .modern-tb-btn .tb-svg{stroke:#ffffff!important}
356
  flex-shrink:0;width:210px;background:#09090b;border:1px solid #27272a;
357
  border-radius:10px;overflow:hidden;cursor:pointer;transition:all .2s ease;
358
  }
359
- .example-card:hover{
360
- border-color:#FF4500;transform:translateY(-2px);
361
- box-shadow:0 4px 12px rgba(255,69,0,.15);
362
- }
363
- .example-card.loading{opacity:.6;pointer-events:none}
364
  .example-thumbs{display:flex;height:110px;overflow:hidden;background:#18181b}
365
- .example-thumbs img{
366
- flex:1;object-fit:cover;min-width:0;
367
- border-bottom:1px solid #27272a;
368
- }
369
  .example-thumb-placeholder{
370
  flex:1;display:flex;align-items:center;justify-content:center;
371
  background:#18181b;color:#3f3f46;font-size:11px;min-width:0;
372
  }
373
  .example-meta{padding:6px 10px;display:flex;align-items:center;gap:6px}
374
  .example-badge{
375
- display:inline-flex;padding:2px 7px;
376
- background:rgba(255,69,0,.1);border-radius:4px;
377
- font-size:10px;font-weight:600;color:#FF6633;
378
- font-family:'JetBrains Mono',monospace;white-space:nowrap;
379
  }
380
  .example-prompt-text{
381
  padding:0 10px 8px;font-size:11px;color:#a1a1aa;line-height:1.4;
382
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
383
  }
384
 
385
- /* ── Right Panel Cards ── */
386
  .panel-card{border-bottom:1px solid #27272a}
387
  .panel-card-title{
388
  padding:12px 20px;font-size:12px;font-weight:600;color:#71717a;
389
- text-transform:uppercase;letter-spacing:.8px;
390
- border-bottom:1px solid rgba(39,39,42,.6);
391
  }
392
  .panel-card-body{padding:16px 20px;display:flex;flex-direction:column;gap:8px}
393
  .modern-label{font-size:13px;font-weight:500;color:#a1a1aa;margin-bottom:4px;display:block}
394
  .modern-textarea{
395
  width:100%;background:#09090b;border:1px solid #27272a;border-radius:8px;
396
- padding:10px 14px;font-family:'Inter',sans-serif;font-size:14px;
397
- color:#e4e4e7;resize:vertical;outline:none;min-height:42px;transition:border-color .2s;
398
  }
399
  .modern-textarea:focus{border-color:#FF4500;box-shadow:0 0 0 3px rgba(255,69,0,.15)}
400
  .modern-textarea::placeholder{color:#3f3f46}
401
  .modern-textarea.error-flash{
402
- border-color:#ef4444!important;
403
- box-shadow:0 0 0 3px rgba(239,68,68,.2)!important;
404
- animation:shake .4s ease;
405
- }
406
- @keyframes shake{
407
- 0%,100%{transform:translateX(0)}
408
- 20%,60%{transform:translateX(-4px)}
409
- 40%,80%{transform:translateX(4px)}
410
  }
 
411
 
412
- /* ── Toast ── */
413
  .toast-notification{
414
  position:fixed;top:24px;left:50%;transform:translateX(-50%) translateY(-120%);
415
- z-index:9999;padding:10px 24px;border-radius:10px;
416
- font-family:'Inter',sans-serif;font-size:14px;font-weight:600;
417
- display:flex;align-items:center;gap:8px;
418
  box-shadow:0 8px 24px rgba(0,0,0,.5);
419
- transition:transform .35s cubic-bezier(.34,1.56,.64,1),opacity .35s ease;
420
- opacity:0;pointer-events:none;
421
- }
422
- .toast-notification.visible{
423
- transform:translateX(-50%) translateY(0);opacity:1;pointer-events:auto;
424
- }
425
- .toast-notification.error{
426
- background:linear-gradient(135deg,#dc2626,#b91c1c);
427
- color:#fff;border:1px solid rgba(255,255,255,.15);
428
- }
429
- .toast-notification.warning{
430
- background:linear-gradient(135deg,#d97706,#b45309);
431
- color:#fff;border:1px solid rgba(255,255,255,.15);
432
- }
433
- .toast-notification.info{
434
- background:linear-gradient(135deg,#2563eb,#1d4ed8);
435
- color:#fff;border:1px solid rgba(255,255,255,.15);
436
  }
 
 
 
 
437
  .toast-notification .toast-icon{font-size:16px;line-height:1}
438
  .toast-notification .toast-text{line-height:1.3}
439
 
440
- /* ── Primary Button ── */
441
  .btn-run{
442
- display:flex;align-items:center;justify-content:center;gap:8px;
443
- width:100%;background:linear-gradient(135deg,#FF4500,#E63E00);
444
- border:none;border-radius:10px;padding:12px 24px;cursor:pointer;
445
- font-size:15px;font-weight:600;font-family:'Inter',sans-serif;
446
- color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;
447
- transition:all .2s ease;letter-spacing:-.2px;
448
  box-shadow:0 4px 16px rgba(255,69,0,.3),inset 0 1px 0 rgba(255,255,255,.1);
449
  }
450
  .btn-run:hover{
451
- background:linear-gradient(135deg,#FF6633,#FF4500);
452
  box-shadow:0 6px 24px rgba(255,69,0,.45),inset 0 1px 0 rgba(255,255,255,.15);
453
- transform:translateY(-1px);color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;
454
- }
455
- .btn-run:active{
456
- transform:translateY(0);box-shadow:0 2px 8px rgba(255,69,0,.3);
457
- color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;
458
  }
459
- .btn-run:focus{color:#ffffff!important;-webkit-text-fill-color:#ffffff!important}
460
- .btn-run svg{width:18px;height:18px;fill:#ffffff!important;color:#ffffff!important}
461
- .btn-run svg path{fill:#ffffff!important;color:#ffffff!important}
462
- .btn-run span,#run-btn-label{color:#ffffff!important;-webkit-text-fill-color:#ffffff!important}
463
- #custom-run-btn,#custom-run-btn *,.btn-run *{
464
  color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;fill:#ffffff!important;
465
  }
466
  body:not(.dark) .btn-run,body:not(.dark) .btn-run *,body:not(.dark) #custom-run-btn,
467
- body:not(.dark) #custom-run-btn *,body:not(.dark) #run-btn-label{
468
- color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;fill:#ffffff!important;
469
- }
470
- .dark .btn-run,.dark .btn-run *,.dark #custom-run-btn,.dark #custom-run-btn *,
471
- .dark #run-btn-label{
472
  color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;fill:#ffffff!important;
473
  }
474
  .gradio-container .btn-run,.gradio-container .btn-run *,.gradio-container #custom-run-btn,
475
- .gradio-container #custom-run-btn *,.gradio-container #run-btn-label{
476
- color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;fill:#ffffff!important;
477
- }
478
 
479
- /* ── Output Frames ── */
480
- .output-frame{
481
- border-bottom:1px solid #27272a;display:flex;flex-direction:column;position:relative;
482
- }
483
  .output-frame .out-title{
484
- padding:10px 20px;font-size:13px;font-weight:700;
485
- color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;
486
- text-transform:uppercase;letter-spacing:.8px;
487
- border-bottom:1px solid rgba(39,39,42,.6);
488
- display:flex;align-items:center;justify-content:space-between;
489
  }
490
  .output-frame .out-title span{color:#ffffff!important;-webkit-text-fill-color:#ffffff!important}
491
  .output-frame .out-body{
@@ -495,27 +469,22 @@ body:not(.dark) #custom-run-btn *,body:not(.dark) #run-btn-label{
495
  .output-frame .out-body img{max-width:100%;max-height:460px;image-rendering:auto}
496
  .output-frame .out-placeholder{color:#3f3f46;font-size:13px;text-align:center;padding:20px}
497
  .out-download-btn{
498
- display:none;align-items:center;justify-content:center;
499
- background:rgba(255,69,0,.1);border:1px solid rgba(255,69,0,.2);
500
- border-radius:6px;cursor:pointer;padding:3px 10px;font-size:11px;
501
- font-weight:500;color:#FF8C66!important;gap:4px;height:24px;transition:all .15s;
502
- }
503
- .out-download-btn:hover{
504
- background:rgba(255,69,0,.2);border-color:rgba(255,69,0,.35);color:#ffffff!important;
505
  }
 
506
  .out-download-btn.visible{display:inline-flex}
507
  .out-download-btn svg{width:12px;height:12px;fill:#FF8C66}
508
 
509
- /* ── Loader ── */
510
  .modern-loader{
511
- display:none;position:absolute;top:0;left:0;right:0;bottom:0;
512
- background:rgba(9,9,11,.92);z-index:15;flex-direction:column;
513
- align-items:center;justify-content:center;gap:16px;backdrop-filter:blur(4px);
514
  }
515
  .modern-loader.active{display:flex}
516
  .modern-loader .loader-spinner{
517
- width:36px;height:36px;border:3px solid #27272a;
518
- border-top-color:#FF4500;border-radius:50%;animation:spin .8s linear infinite;
519
  }
520
  @keyframes spin{to{transform:rotate(360deg)}}
521
  .modern-loader .loader-text{font-size:13px;color:#a1a1aa;font-weight:500}
@@ -526,26 +495,21 @@ body:not(.dark) #custom-run-btn *,body:not(.dark) #run-btn-label{
526
  }
527
  @keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
528
 
529
- /* ── Settings ── */
530
- .settings-group{
531
- border:1px solid #27272a;border-radius:10px;margin:12px 16px;padding:0;overflow:hidden;
532
- }
533
  .settings-group-title{
534
- font-size:12px;font-weight:600;color:#71717a;text-transform:uppercase;
535
- letter-spacing:.8px;padding:10px 16px;border-bottom:1px solid #27272a;
536
- background:rgba(24,24,27,.5);
537
  }
538
  .settings-group-body{padding:14px 16px;display:flex;flex-direction:column;gap:12px}
539
  .slider-row{display:flex;align-items:center;gap:10px;min-height:28px}
540
  .slider-row label{font-size:13px;font-weight:500;color:#a1a1aa;min-width:72px;flex-shrink:0}
541
  .slider-row input[type="range"]{
542
- flex:1;-webkit-appearance:none;appearance:none;height:6px;
543
- background:#27272a;border-radius:3px;outline:none;min-width:0;
544
  }
545
  .slider-row input[type="range"]::-webkit-slider-thumb{
546
- -webkit-appearance:none;appearance:none;width:16px;height:16px;
547
- background:linear-gradient(135deg,#FF4500,#E63E00);border-radius:50%;
548
- cursor:pointer;box-shadow:0 2px 6px rgba(255,69,0,.4);transition:transform .15s;
549
  }
550
  .slider-row input[type="range"]::-webkit-slider-thumb:hover{transform:scale(1.2)}
551
  .slider-row input[type="range"]::-moz-range-thumb{
@@ -553,39 +517,28 @@ body:not(.dark) #custom-run-btn *,body:not(.dark) #run-btn-label{
553
  border-radius:50%;cursor:pointer;border:none;box-shadow:0 2px 6px rgba(255,69,0,.4);
554
  }
555
  .slider-row .slider-val{
556
- min-width:52px;text-align:right;font-family:'JetBrains Mono',monospace;
557
- font-size:12px;font-weight:500;padding:3px 8px;background:#09090b;
558
- border:1px solid #27272a;border-radius:6px;color:#a1a1aa;flex-shrink:0;
559
  }
560
  .checkbox-row{display:flex;align-items:center;gap:8px;font-size:13px;color:#a1a1aa}
561
  .checkbox-row input[type="checkbox"]{accent-color:#FF4500;width:16px;height:16px;cursor:pointer}
562
  .checkbox-row label{color:#a1a1aa;font-size:13px;cursor:pointer}
563
 
564
- /* ── Status Bar ── */
565
  .app-statusbar{
566
  background:#18181b;border-top:1px solid #27272a;padding:6px 20px;
567
  display:flex;gap:12px;height:34px;align-items:center;font-size:12px;
568
  }
569
  .app-statusbar .sb-section{
570
- padding:0 12px;flex:1;display:flex;align-items:center;
571
- font-family:'JetBrains Mono',monospace;font-size:12px;color:#52525b;
572
- overflow:hidden;white-space:nowrap;
573
  }
574
  .app-statusbar .sb-section.sb-fixed{
575
  flex:0 0 auto;min-width:90px;text-align:center;justify-content:center;
576
- padding:3px 12px;background:rgba(255,69,0,.08);border-radius:6px;
577
- color:#FF6633;font-weight:500;
578
- }
579
-
580
- #gradio-run-btn{
581
- position:absolute;left:-9999px;top:-9999px;width:1px;height:1px;
582
- opacity:0.01;pointer-events:none;overflow:hidden;
583
  }
584
 
585
- .exp-note{
586
- padding:10px 20px;font-size:12px;color:#52525b;border-top:1px solid #27272a;
587
- text-align:center;
588
- }
589
  .exp-note a{color:#FF6633;text-decoration:none}
590
  .exp-note a:hover{text-decoration:underline}
591
 
@@ -610,6 +563,10 @@ body:not(.dark) #custom-run-btn *,body:not(.dark) #run-btn-label{
610
  }
611
  """
612
 
 
 
 
 
613
  gallery_js = r"""
614
  () => {
615
  function init() {
@@ -662,6 +619,7 @@ function init() {
662
  toast.classList.add('visible');
663
  toastTimer = setTimeout(() => toast.classList.remove('visible'), 3500);
664
  }
 
665
 
666
  function flashPromptError() {
667
  if (!promptInput) return;
@@ -684,6 +642,7 @@ function init() {
684
  }
685
  });
686
  }
 
687
 
688
  function syncImagesToGradio() {
689
  window.__uploadedImages = images;
@@ -755,7 +714,6 @@ function init() {
755
  + '<span class="add-icon">+</span>'
756
  + '<span class="add-text">Add</span>'
757
  + '</div>';
758
-
759
  galleryGrid.innerHTML = html;
760
 
761
  galleryGrid.querySelectorAll('.gallery-thumb').forEach(thumb => {
@@ -783,11 +741,7 @@ function init() {
783
  });
784
  }
785
 
786
- fileInput.addEventListener('change', (e) => {
787
- processFiles(e.target.files);
788
- e.target.value = '';
789
- });
790
-
791
  if (uploadClick) uploadClick.addEventListener('click', () => fileInput.click());
792
  if (btnUpload) btnUpload.addEventListener('click', () => fileInput.click());
793
  if (btnRemove) btnRemove.addEventListener('click', () => {
@@ -808,91 +762,30 @@ function init() {
808
  if (promptInput) { promptInput.value = text; syncPromptToGradio(); }
809
  };
810
 
811
- /* ── Example Loading ── */
812
- const EXAMPLES = [
813
- {
814
- images: ["examples/1.jpg"],
815
- prompt: "cinematic polaroid with soft grain subtle vignette gentle lighting white frame handwritten photographed 'Fire-Edit' preserving realistic texture and details."
816
- },
817
- {
818
- images: ["examples/2.jpg"],
819
- prompt: "Transform the image into a dotted cartoon style."
820
- },
821
- {
822
- images: ["examples/3.jpeg"],
823
- prompt: "Convert it to black and white."
824
- },
825
- {
826
- images: ["examples/4.jpg", "examples/5.jpg"],
827
- prompt: "Replace her glasses with the new glasses from image 1."
828
- },
829
- {
830
- images: ["examples/8.jpg", "examples/9.png"],
831
- prompt: "Replace the current clothing with the clothing from the reference image 2. Keep the person's face, hairstyle, body pose, background, lighting, and camera angle unchanged. Ensure the new outfit fits naturally with realistic fabric texture, proper shadows, folds, and accurate proportions. Match the lighting, color tone, and overall style for a seamless and high-quality result."
832
- },
833
- {
834
- images: ["examples/10.jpg", "examples/11.png"],
835
- prompt: "Replace the current clothing with the clothing from the reference image 2. Keep the person's face, hairstyle, body pose, background, lighting, and camera angle unchanged. Ensure the new outfit fits naturally with realistic fabric texture, proper shadows, folds, and accurate proportions. Match the lighting, color tone, and overall style for a seamless and high-quality result."
836
- }
837
- ];
838
-
839
- async function fetchImageAsBase64(path) {
840
- const urls = ['/file=' + path, 'file/' + path, path, './' + path];
841
- for (const url of urls) {
842
- try {
843
- const resp = await fetch(url);
844
- if (resp.ok && resp.headers.get('content-type') &&
845
- resp.headers.get('content-type').startsWith('image')) {
846
- const blob = await resp.blob();
847
- return await new Promise((resolve) => {
848
- const reader = new FileReader();
849
- reader.onload = () => resolve(reader.result);
850
- reader.readAsDataURL(blob);
851
- });
852
- }
853
- } catch (e) {}
854
- }
855
- return null;
856
- }
857
-
858
- async function loadExample(idx) {
859
- if (idx < 0 || idx >= EXAMPLES.length) return;
860
- const ex = EXAMPLES[idx];
861
-
862
- const card = document.querySelector('.example-card[data-idx="' + idx + '"]');
863
- if (card) card.classList.add('loading');
864
-
865
- showToast('Loading example...', 'info');
866
-
867
- clearAll();
868
-
869
- if (promptInput) { promptInput.value = ex.prompt; syncPromptToGradio(); }
870
-
871
- for (const path of ex.images) {
872
- const b64 = await fetchImageAsBase64(path);
873
- if (b64) {
874
- addImage(b64, path.split('/').pop());
875
- } else {
876
- console.warn('Could not load example image:', path);
877
- }
878
- }
879
-
880
- if (card) card.classList.remove('loading');
881
-
882
- if (images.length > 0) {
883
- showToast('Example loaded β€” ' + images.length + ' image(s)', 'info');
884
- } else {
885
- showToast('Could not load example images', 'warning');
886
- }
887
- }
888
- window.__loadExample = loadExample;
889
-
890
  document.querySelectorAll('.example-card[data-idx]').forEach(card => {
891
  card.addEventListener('click', () => {
892
- loadExample(parseInt(card.getAttribute('data-idx')));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
893
  });
894
  });
895
 
 
896
  function syncSlider(customId, gradioId) {
897
  const slider = document.getElementById(customId);
898
  const valSpan = document.getElementById(customId + '-val');
@@ -943,28 +836,15 @@ function init() {
943
  function validateBeforeRun() {
944
  const promptVal = promptInput ? promptInput.value.trim() : '';
945
  const hasImages = images.length > 0;
946
- if (!hasImages && !promptVal) {
947
- showToast('Please upload an image and enter a prompt', 'error');
948
- flashPromptError();
949
- return false;
950
- }
951
- if (!hasImages) {
952
- showToast('Please upload at least one image', 'error');
953
- return false;
954
- }
955
- if (!promptVal) {
956
- showToast('Please enter an edit prompt', 'warning');
957
- flashPromptError();
958
- return false;
959
- }
960
  return true;
961
  }
962
 
963
  window.__clickGradioRunBtn = function() {
964
  if (!validateBeforeRun()) return;
965
- syncPromptToGradio();
966
- syncImagesToGradio();
967
- showLoader();
968
  setTimeout(() => {
969
  const gradioBtn = document.getElementById('gradio-run-btn');
970
  if (!gradioBtn) return;
@@ -1009,11 +889,7 @@ function watchOutputs() {
1009
  if (resultImg && resultImg.src) {
1010
  if (outPh) outPh.style.display = 'none';
1011
  let existing = outBody.querySelector('img.modern-out-img');
1012
- if (!existing) {
1013
- existing = document.createElement('img');
1014
- existing.className = 'modern-out-img';
1015
- outBody.appendChild(existing);
1016
- }
1017
  if (existing.src !== resultImg.src) {
1018
  existing.src = resultImg.src;
1019
  if (dlBtn) dlBtn.classList.add('visible');
@@ -1021,7 +897,6 @@ function watchOutputs() {
1021
  }
1022
  }
1023
  }
1024
-
1025
  const observer = new MutationObserver(syncImage);
1026
  observer.observe(resultContainer, {childList:true, subtree:true, attributes:true, attributeFilter:['src']});
1027
  setInterval(syncImage, 800);
@@ -1035,19 +910,65 @@ function watchSeed() {
1035
  if (!seedContainer || !seedSlider) { setTimeout(watchSeed, 500); return; }
1036
  function sync() {
1037
  const el = seedContainer.querySelector('input[type="range"],input[type="number"]');
1038
- if (el && el.value) {
1039
- seedSlider.value = el.value;
1040
- if (seedVal) seedVal.textContent = el.value;
1041
- }
1042
  }
1043
  const obs = new MutationObserver(sync);
1044
  obs.observe(seedContainer, {childList:true, subtree:true, attributes:true, attributeFilter:['value']});
1045
  setInterval(sync, 1000);
1046
  }
1047
  watchSeed();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1048
  }
1049
  """
1050
 
 
 
 
 
1051
  DOWNLOAD_SVG = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 16l-5-5h3V4h4v7h3l-5 5z"/><path d="M20 18H4v2h16v-2z"/></svg>'
1052
 
1053
  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>'
@@ -1058,41 +979,30 @@ CLEAR_SVG = '<svg class="tb-svg" viewBox="0 0 24 24" fill="none" stroke="current
1058
 
1059
  FIRE_LOGO_SVG = '<svg viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg"><path d="M12 23c-3.6 0-8-2.69-8-7.5 0-3.5 3-6.5 4.5-8 .27-.27.75-.08.75.28v2.44c0 .42.5.63.72.28C12.28 7.5 13 3 13 1c0-.42.48-.64.8-.35C18 4.5 20 9 20 12c0 5.5-3.5 11-8 11z"/></svg>'
1060
 
1061
- with gr.Blocks() as demo:
 
 
1062
 
1063
- hidden_images_b64 = gr.Textbox(
1064
- value="[]", elem_id="hidden-images-b64",
1065
- elem_classes="hidden-input", container=False,
1066
- )
1067
- prompt = gr.Textbox(
1068
- value="", elem_id="prompt-gradio-input",
1069
- elem_classes="hidden-input", container=False,
1070
- )
1071
- seed = gr.Slider(
1072
- minimum=0, maximum=MAX_SEED, step=1, value=0,
1073
- elem_id="gradio-seed", elem_classes="hidden-input", container=False,
1074
- )
1075
- randomize_seed = gr.Checkbox(
1076
- value=True, elem_id="gradio-randomize",
1077
- elem_classes="hidden-input", container=False,
1078
- )
1079
- guidance_scale = gr.Slider(
1080
- minimum=1.0, maximum=10.0, step=0.1, value=1.0,
1081
- elem_id="gradio-guidance", elem_classes="hidden-input", container=False,
1082
- )
1083
- steps = gr.Slider(
1084
- minimum=1, maximum=50, step=1, value=4,
1085
- elem_id="gradio-steps", elem_classes="hidden-input", container=False,
1086
- )
1087
- result = gr.Image(
1088
- elem_id="gradio-result", elem_classes="hidden-input",
1089
- container=False, format="png",
1090
- )
1091
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1092
  gr.HTML(f"""
1093
  <div class="app-shell">
1094
 
1095
- <!-- Header -->
1096
  <div class="app-header">
1097
  <div class="app-header-left">
1098
  <div class="app-logo">{FIRE_LOGO_SVG}</div>
@@ -1102,7 +1012,6 @@ with gr.Blocks() as demo:
1102
  </div>
1103
  </div>
1104
 
1105
- <!-- Toolbar -->
1106
  <div class="app-toolbar">
1107
  <button id="tb-upload" class="modern-tb-btn" title="Upload images">
1108
  {UPLOAD_SVG}<span class="tb-label">Upload</span>
@@ -1117,10 +1026,7 @@ with gr.Blocks() as demo:
1117
  <span id="tb-image-count" class="tb-info">No images</span>
1118
  </div>
1119
 
1120
- <!-- Main -->
1121
  <div class="app-main-row">
1122
-
1123
- <!-- Left: Gallery -->
1124
  <div class="app-main-left">
1125
  <div id="gallery-drop-zone">
1126
  <div id="upload-prompt" class="upload-prompt-modern">
@@ -1139,9 +1045,9 @@ with gr.Blocks() as demo:
1139
  </div>
1140
 
1141
  <div class="hint-bar">
1142
- <b>Upload:</b> Click or drag to add images &nbsp;Β·&nbsp;
1143
- <b>Multi-image:</b> Upload multiple images for reference-based editing &nbsp;Β·&nbsp;
1144
- <kbd>Remove</kbd> deletes selected &nbsp;Β·&nbsp;
1145
  <kbd>Clear All</kbd> removes everything
1146
  </div>
1147
 
@@ -1170,78 +1076,12 @@ with gr.Blocks() as demo:
1170
  <div class="examples-section">
1171
  <div class="examples-title">Quick Examples</div>
1172
  <div class="examples-scroll">
1173
-
1174
- <div class="example-card" data-idx="0">
1175
- <div class="example-thumbs">
1176
- <img src="/file=examples/1.jpg" onerror="this.parentElement.innerHTML='<div class=example-thumb-placeholder>Preview</div>'" alt="">
1177
- </div>
1178
- <div class="example-meta">
1179
- <span class="example-badge">1 image</span>
1180
- </div>
1181
- <div class="example-prompt-text">Cinematic polaroid with soft grain, subtle vignette, gentle lighting...</div>
1182
- </div>
1183
-
1184
- <div class="example-card" data-idx="1">
1185
- <div class="example-thumbs">
1186
- <img src="/file=examples/2.jpg" onerror="this.parentElement.innerHTML='<div class=example-thumb-placeholder>Preview</div>'" alt="">
1187
- </div>
1188
- <div class="example-meta">
1189
- <span class="example-badge">1 image</span>
1190
- </div>
1191
- <div class="example-prompt-text">Transform the image into a dotted cartoon style.</div>
1192
- </div>
1193
-
1194
- <div class="example-card" data-idx="2">
1195
- <div class="example-thumbs">
1196
- <img src="/file=examples/3.jpeg" onerror="this.parentElement.innerHTML='<div class=example-thumb-placeholder>Preview</div>'" alt="">
1197
- </div>
1198
- <div class="example-meta">
1199
- <span class="example-badge">1 image</span>
1200
- </div>
1201
- <div class="example-prompt-text">Convert it to black and white.</div>
1202
- </div>
1203
-
1204
- <div class="example-card" data-idx="3">
1205
- <div class="example-thumbs">
1206
- <img src="/file=examples/4.jpg" onerror="this.parentElement.innerHTML='<div class=example-thumb-placeholder>Preview</div>'" alt="">
1207
- <img src="/file=examples/5.jpg" onerror="this.parentElement.innerHTML='<div class=example-thumb-placeholder>Preview</div>'" alt="">
1208
- </div>
1209
- <div class="example-meta">
1210
- <span class="example-badge">2 images</span>
1211
- </div>
1212
- <div class="example-prompt-text">Replace her glasses with the new glasses from image 1.</div>
1213
- </div>
1214
-
1215
- <div class="example-card" data-idx="4">
1216
- <div class="example-thumbs">
1217
- <img src="/file=examples/8.jpg" onerror="this.parentElement.innerHTML='<div class=example-thumb-placeholder>Preview</div>'" alt="">
1218
- <img src="/file=examples/9.png" onerror="this.parentElement.innerHTML='<div class=example-thumb-placeholder>Preview</div>'" alt="">
1219
- </div>
1220
- <div class="example-meta">
1221
- <span class="example-badge">2 images</span>
1222
- </div>
1223
- <div class="example-prompt-text">Replace clothing with reference. Keep face, pose, and background unchanged...</div>
1224
- </div>
1225
-
1226
- <div class="example-card" data-idx="5">
1227
- <div class="example-thumbs">
1228
- <img src="/file=examples/10.jpg" onerror="this.parentElement.innerHTML='<div class=example-thumb-placeholder>Preview</div>'" alt="">
1229
- <img src="/file=examples/11.png" onerror="this.parentElement.innerHTML='<div class=example-thumb-placeholder>Preview</div>'" alt="">
1230
- </div>
1231
- <div class="example-meta">
1232
- <span class="example-badge">2 images</span>
1233
- </div>
1234
- <div class="example-prompt-text">Replace clothing with reference. Keep face, pose, and background unchanged...</div>
1235
- </div>
1236
-
1237
  </div>
1238
  </div>
1239
-
1240
  </div>
1241
 
1242
- <!-- Right: Controls & Output -->
1243
  <div class="app-main-right">
1244
-
1245
  <div class="panel-card">
1246
  <div class="panel-card-title">Edit Instruction</div>
1247
  <div class="panel-card-body">
@@ -1298,22 +1138,18 @@ with gr.Blocks() as demo:
1298
  </div>
1299
  </div>
1300
  </div>
1301
-
1302
  </div>
1303
  </div>
1304
 
1305
- <!-- Note -->
1306
  <div class="exp-note">
1307
  Experimental Space for <a href="https://huggingface.co/FireRedTeam/FireRed-Image-Edit-1.1" target="_blank">FireRed-Image-Edit-1.1</a>
1308
  &middot; Open on <a href="https://github.com/PRITHIVSAKTHIUR/FireRed-Image-Edit-1.0-Fast" target="_blank">GitHub</a>
1309
  </div>
1310
 
1311
- <!-- Status Bar -->
1312
  <div class="app-statusbar">
1313
  <div class="sb-section" id="sb-image-count">No images uploaded</div>
1314
  <div class="sb-section sb-fixed">Ready</div>
1315
  </div>
1316
-
1317
  </div>
1318
  """)
1319
 
@@ -1322,6 +1158,7 @@ with gr.Blocks() as demo:
1322
  demo.load(fn=None, js=gallery_js)
1323
  demo.load(fn=None, js=wire_outputs_js)
1324
 
 
1325
  run_btn.click(
1326
  fn=infer,
1327
  inputs=[hidden_images_b64, prompt, seed, randomize_seed, guidance_scale, steps],
@@ -1336,6 +1173,14 @@ with gr.Blocks() as demo:
1336
  }""",
1337
  )
1338
 
 
 
 
 
 
 
 
 
1339
  if __name__ == "__main__":
1340
  demo.queue(max_size=30).launch(
1341
  css=css,
 
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
 
 
43
  except Exception as e:
44
  print(f"Warning: Could not set FA3 processor: {e}")
45
 
46
+ # ═══════════════════════════════════════════
47
+ # Example Configuration & Helpers
48
+ # ═══════════════════════════════════════════
49
+
50
+ EXAMPLES_CONFIG = [
51
+ {
52
+ "images": ["examples/1.jpg"],
53
+ "prompt": "cinematic polaroid with soft grain subtle vignette gentle lighting white frame handwritten photographed 'Fire-Edit' preserving realistic texture and details.",
54
+ },
55
+ {
56
+ "images": ["examples/2.jpg"],
57
+ "prompt": "Transform the image into a dotted cartoon style.",
58
+ },
59
+ {
60
+ "images": ["examples/3.jpeg"],
61
+ "prompt": "Convert it to black and white.",
62
+ },
63
+ {
64
+ "images": ["examples/4.jpg", "examples/5.jpg"],
65
+ "prompt": "Replace her glasses with the new glasses from image 1.",
66
+ },
67
+ {
68
+ "images": ["examples/8.jpg", "examples/9.png"],
69
+ "prompt": "Replace the current clothing with the clothing from the reference image 2. Keep the person's face, hairstyle, body pose, background, lighting, and camera angle unchanged. Ensure the new outfit fits naturally with realistic fabric texture, proper shadows, folds, and accurate proportions. Match the lighting, color tone, and overall style for a seamless and high-quality result.",
70
+ },
71
+ {
72
+ "images": ["examples/10.jpg", "examples/11.png"],
73
+ "prompt": "Replace the current clothing with the clothing from the reference image 2. Keep the person's face, hairstyle, body pose, background, lighting, and camera angle unchanged. Ensure the new outfit fits naturally with realistic fabric texture, proper shadows, folds, and accurate proportions. Match the lighting, color tone, and overall style for a seamless and high-quality result.",
74
+ },
75
+ ]
76
+
77
+
78
+ def make_thumb_b64(path, max_dim=220):
79
+ if not os.path.exists(path):
80
+ return ""
81
+ try:
82
+ img = Image.open(path).convert("RGB")
83
+ img.thumbnail((max_dim, max_dim), LANCZOS)
84
+ buf = BytesIO()
85
+ img.save(buf, format="JPEG", quality=65)
86
+ return f"data:image/jpeg;base64,{base64.b64encode(buf.getvalue()).decode()}"
87
+ except Exception as e:
88
+ print(f"Thumbnail error for {path}: {e}")
89
+ return ""
90
+
91
+
92
+ def encode_full_image(path):
93
+ if not os.path.exists(path):
94
+ return ""
95
+ try:
96
+ with open(path, "rb") as f:
97
+ data = f.read()
98
+ ext = path.rsplit(".", 1)[-1].lower()
99
+ mime = {"jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png", "webp": "image/webp"}.get(ext, "image/jpeg")
100
+ return f"data:{mime};base64,{base64.b64encode(data).decode()}"
101
+ except Exception as e:
102
+ print(f"Encode error for {path}: {e}")
103
+ return ""
104
+
105
+
106
+ def build_example_cards_html():
107
+ cards = ""
108
+ for i, ex in enumerate(EXAMPLES_CONFIG):
109
+ thumbs_html = ""
110
+ for path in ex["images"]:
111
+ thumb = make_thumb_b64(path)
112
+ if thumb:
113
+ thumbs_html += f'<img src="{thumb}" alt="">'
114
+ else:
115
+ thumbs_html += '<div class="example-thumb-placeholder">Preview</div>'
116
+ n = len(ex["images"])
117
+ badge = f'{n} image{"s" if n > 1 else ""}'
118
+ prompt_short = html_lib.escape(ex["prompt"][:90])
119
+ if len(ex["prompt"]) > 90:
120
+ prompt_short += "..."
121
+ cards += f'''<div class="example-card" data-idx="{i}">
122
+ <div class="example-thumbs">{thumbs_html}</div>
123
+ <div class="example-meta"><span class="example-badge">{badge}</span></div>
124
+ <div class="example-prompt-text">{prompt_short}</div>
125
+ </div>'''
126
+ return cards
127
+
128
+
129
+ def load_example_data(idx_str):
130
+ try:
131
+ idx = int(float(idx_str)) if idx_str and idx_str.strip() else -1
132
+ except (ValueError, TypeError):
133
+ idx = -1
134
+ if idx < 0 or idx >= len(EXAMPLES_CONFIG):
135
+ return json.dumps({"images": [], "prompt": "", "names": [], "status": "error"})
136
+ ex = EXAMPLES_CONFIG[idx]
137
+ b64_list, names = [], []
138
+ for path in ex["images"]:
139
+ b64 = encode_full_image(path)
140
+ if b64:
141
+ b64_list.append(b64)
142
+ names.append(os.path.basename(path))
143
+ return json.dumps({"images": b64_list, "prompt": ex["prompt"], "names": names, "status": "ok"})
144
+
145
+
146
+ print("Building example thumbnails...")
147
+ EXAMPLE_CARDS_HTML = build_example_cards_html()
148
+ print(f"Built {len(EXAMPLES_CONFIG)} example cards.")
149
+
150
+ # ═══════════════════════════════════════��═══
151
+ # Inference Helpers
152
+ # ═══════════════════════════════════════════
153
+
154
 
155
  def b64_to_pil_list(b64_json_str):
156
  if not b64_json_str or b64_json_str.strip() in ("", "[]"):
 
178
  def update_dimensions_on_upload(image):
179
  if image is None:
180
  return 1024, 1024
181
+ w, h = image.size
182
+ if w > h:
183
+ nw = 1024
184
+ nh = int(nw * h / w)
 
185
  else:
186
+ nh = 1024
187
+ nw = int(nh * w / h)
188
+ return (nw // 8) * 8, (nh // 8) * 8
 
 
 
189
 
190
 
191
  @spaces.GPU
192
+ def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps, progress=gr.Progress(track_tqdm=True)):
 
 
 
 
 
 
 
 
193
  gc.collect()
194
  torch.cuda.empty_cache()
 
195
  pil_images = b64_to_pil_list(images_b64_json)
196
  if not pil_images:
197
  raise gr.Error("Please upload at least one image to edit.")
198
  if not prompt or prompt.strip() == "":
199
  raise gr.Error("Please enter an edit prompt.")
 
200
  if randomize_seed:
201
  seed = random.randint(0, MAX_SEED)
 
202
  generator = torch.Generator(device=device).manual_seed(seed)
203
+ negative_prompt = "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
 
 
 
 
204
  width, height = update_dimensions_on_upload(pil_images[0])
 
205
  try:
206
  result_image = pipe(
207
+ image=pil_images, prompt=prompt, negative_prompt=negative_prompt,
208
+ height=height, width=width, num_inference_steps=steps,
209
+ generator=generator, true_cfg_scale=guidance_scale,
 
 
 
 
 
210
  ).images[0]
211
  return result_image, seed
212
  except Exception as e:
 
216
  torch.cuda.empty_cache()
217
 
218
 
219
+ # ═══════════════════════════════════════════
220
+ # CSS
221
+ # ═══════════════════════════════════════════
222
+
223
  css = r"""
224
  @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');
 
225
  *{box-sizing:border-box;margin:0;padding:0}
 
226
  body,.gradio-container{
227
+ background:#0f0f13!important;font-family:'Inter',system-ui,-apple-system,sans-serif!important;
228
+ font-size:14px!important;color:#e4e4e7!important;min-height:100vh;
 
 
 
229
  }
230
  .dark body,.dark .gradio-container{background:#0f0f13!important;color:#e4e4e7!important}
231
  footer{display:none!important}
232
+ .hidden-input{display:none!important;height:0!important;overflow:hidden!important;margin:0!important;padding:0!important}
233
 
234
+ #example-load-btn{
235
+ position:absolute!important;left:-9999px!important;top:-9999px!important;
236
+ width:1px!important;height:1px!important;opacity:0.01!important;
237
+ pointer-events:none!important;overflow:hidden!important;
238
+ }
239
+ #gradio-run-btn{
240
+ position:absolute;left:-9999px;top:-9999px;width:1px;height:1px;
241
+ opacity:0.01;pointer-events:none;overflow:hidden;
242
  }
243
 
 
244
  .app-shell{
245
  background:#18181b;border:1px solid #27272a;border-radius:16px;
246
  margin:12px auto;max-width:1400px;overflow:hidden;
247
  box-shadow:0 25px 50px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.03);
248
  }
 
 
249
  .app-header{
250
+ background:linear-gradient(135deg,#18181b,#1e1e24);border-bottom:1px solid #27272a;
251
+ padding:14px 24px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;
 
252
  }
253
  .app-header-left{display:flex;align-items:center;gap:12px}
254
  .app-logo{
255
+ width:36px;height:36px;background:linear-gradient(135deg,#FF4500,#FF6633,#FF8C66);
 
256
  border-radius:10px;display:flex;align-items:center;justify-content:center;
257
  box-shadow:0 4px 12px rgba(255,69,0,.35);
258
  }
259
  .app-logo svg{width:20px;height:20px;fill:#fff;flex-shrink:0}
260
  .app-title{
261
+ font-size:18px;font-weight:700;background:linear-gradient(135deg,#e4e4e7,#a1a1aa);
 
262
  -webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-.3px;
263
  }
264
  .app-badge{
265
  font-size:11px;font-weight:600;padding:3px 10px;border-radius:20px;
266
+ background:rgba(255,69,0,.15);color:#FF6633;border:1px solid rgba(255,69,0,.25);letter-spacing:.3px;
 
 
 
 
 
267
  }
268
+ .app-badge.fast{background:rgba(34,197,94,.12);color:#4ade80;border:1px solid rgba(34,197,94,.25)}
269
 
 
270
  .app-toolbar{
271
+ background:#18181b;border-bottom:1px solid #27272a;padding:8px 16px;
272
+ display:flex;gap:4px;align-items:center;flex-wrap:wrap;
273
  }
274
  .tb-sep{width:1px;height:28px;background:#27272a;margin:0 8px}
275
  .modern-tb-btn{
276
  display:inline-flex;align-items:center;justify-content:center;gap:6px;
277
+ min-width:32px;height:34px;background:transparent;border:1px solid transparent;
278
+ border-radius:8px;cursor:pointer;font-size:13px;font-weight:600;padding:0 12px;
279
+ font-family:'Inter',sans-serif;color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;
280
+ transition:all .15s ease;
 
 
 
 
 
 
 
 
 
281
  }
282
+ .modern-tb-btn:hover{background:rgba(255,69,0,.15);border-color:rgba(255,69,0,.3)}
283
+ .modern-tb-btn:active,.modern-tb-btn.active{background:rgba(255,69,0,.25);border-color:rgba(255,69,0,.45)}
284
  .modern-tb-btn .tb-label{font-size:13px;color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;font-weight:600}
285
+ .modern-tb-btn .tb-svg{width:15px;height:15px;flex-shrink:0;color:#ffffff!important}
286
+ .modern-tb-btn .tb-svg,
287
+ .modern-tb-btn .tb-svg *{stroke:#ffffff!important;fill:none!important}
288
+ .tb-info{font-family:'JetBrains Mono',monospace;font-size:12px;color:#71717a;padding:0 8px;display:flex;align-items:center}
289
+
290
+ body:not(.dark) .modern-tb-btn,body:not(.dark) .modern-tb-btn *{color:#ffffff!important;-webkit-text-fill-color:#ffffff!important}
291
+ body:not(.dark) .modern-tb-btn .tb-svg,body:not(.dark) .modern-tb-btn .tb-svg *{stroke:#ffffff!important}
292
+ .dark .modern-tb-btn,.dark .modern-tb-btn *{color:#ffffff!important;-webkit-text-fill-color:#ffffff!important}
293
+ .dark .modern-tb-btn .tb-svg,.dark .modern-tb-btn .tb-svg *{stroke:#ffffff!important}
294
+ .gradio-container .modern-tb-btn,.gradio-container .modern-tb-btn *{color:#ffffff!important;-webkit-text-fill-color:#ffffff!important}
295
+ .gradio-container .modern-tb-btn .tb-svg,.gradio-container .modern-tb-btn .tb-svg *{stroke:#ffffff!important}
 
 
 
 
 
 
 
 
 
 
 
296
 
 
297
  .app-main-row{display:flex;gap:0;flex:1;overflow:hidden}
298
+ .app-main-left{flex:1;display:flex;flex-direction:column;min-width:0;border-right:1px solid #27272a}
299
+ .app-main-right{width:420px;display:flex;flex-direction:column;flex-shrink:0;background:#18181b}
 
 
 
 
 
300
 
301
+ #gallery-drop-zone{position:relative;background:#09090b;min-height:440px;overflow:auto}
302
+ #gallery-drop-zone.drag-over{outline:2px solid #FF4500;outline-offset:-2px;background:rgba(255,69,0,.04)}
 
 
 
 
 
 
303
 
304
+ .upload-prompt-modern{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:20}
 
 
 
305
  .upload-click-area{
306
  display:flex;flex-direction:column;align-items:center;justify-content:center;
307
+ cursor:pointer;padding:36px 52px;border:2px dashed #3f3f46;border-radius:16px;
308
+ background:rgba(255,69,0,.03);transition:all .2s ease;gap:8px;
 
 
 
309
  }
310
+ .upload-click-area:hover{background:rgba(255,69,0,.08);border-color:#FF4500;transform:scale(1.03)}
311
  .upload-click-area:active{background:rgba(255,69,0,.12);transform:scale(.98)}
312
  .upload-click-area svg{width:80px;height:80px}
313
  .upload-main-text{color:#71717a;font-size:14px;font-weight:500;margin-top:4px}
314
  .upload-sub-text{color:#52525b;font-size:12px}
315
 
 
316
  .image-gallery-grid{
317
  display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));
318
  gap:12px;padding:16px;align-content:start;
 
321
  position:relative;aspect-ratio:1;border-radius:10px;overflow:hidden;
322
  cursor:pointer;border:2px solid #27272a;transition:all .2s ease;background:#18181b;
323
  }
324
+ .gallery-thumb:hover{border-color:#3f3f46;transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,0,0,.4)}
325
+ .gallery-thumb.selected{border-color:#FF4500!important;box-shadow:0 0 0 3px rgba(255,69,0,.2)}
 
 
 
 
 
326
  .gallery-thumb img{width:100%;height:100%;object-fit:cover}
327
  .thumb-badge{
328
  position:absolute;top:6px;left:6px;background:#FF4500;color:#fff;
329
+ padding:2px 8px;border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:600;
 
330
  }
331
  .thumb-remove{
332
+ position:absolute;top:6px;right:6px;width:24px;height:24px;background:rgba(0,0,0,.75);
333
+ color:#fff;border:1px solid rgba(255,255,255,.15);border-radius:50%;cursor:pointer;
334
+ display:none;align-items:center;justify-content:center;font-size:12px;transition:all .15s;line-height:1;
 
335
  }
336
  .gallery-thumb:hover .thumb-remove{display:flex}
337
  .thumb-remove:hover{background:#FF4500;border-color:#FF4500}
 
344
  .gallery-add-card .add-icon{font-size:28px;color:#71717a;font-weight:300}
345
  .gallery-add-card .add-text{font-size:12px;color:#71717a;font-weight:500}
346
 
 
347
  .hint-bar{
348
+ background:rgba(255,69,0,.06);border-top:1px solid #27272a;border-bottom:1px solid #27272a;
349
+ padding:10px 20px;font-size:13px;color:#a1a1aa;line-height:1.7;
 
350
  }
351
  .hint-bar b{color:#FF8C66;font-weight:600}
352
  .hint-bar kbd{
353
+ display:inline-block;padding:1px 6px;background:#27272a;border:1px solid #3f3f46;
354
+ border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:11px;color:#a1a1aa;
 
355
  }
356
 
 
357
  .suggestions-section{border-top:1px solid #27272a;padding:12px 16px}
358
+ .suggestions-title,.examples-title{
359
  font-size:12px;font-weight:600;color:#71717a;text-transform:uppercase;
360
  letter-spacing:.8px;margin-bottom:10px;
361
  }
362
  .suggestions-wrap{display:flex;flex-wrap:wrap;gap:6px}
363
  .suggestion-chip{
364
  display:inline-flex;align-items:center;gap:4px;padding:5px 12px;
365
+ background:rgba(255,69,0,.08);border:1px solid rgba(255,69,0,.2);border-radius:20px;
366
+ color:#FF8C66;font-size:12px;font-weight:500;font-family:'Inter',sans-serif;
367
+ cursor:pointer;transition:all .15s;white-space:nowrap;
 
 
 
 
368
  }
369
+ .suggestion-chip:hover{background:rgba(255,69,0,.15);border-color:rgba(255,69,0,.35);color:#FF6633;transform:translateY(-1px)}
370
 
 
371
  .examples-section{border-top:1px solid #27272a;padding:12px 16px}
372
+ .examples-scroll{display:flex;gap:10px;overflow-x:auto;padding-bottom:8px}
 
 
 
 
 
 
373
  .examples-scroll::-webkit-scrollbar{height:6px}
374
  .examples-scroll::-webkit-scrollbar-track{background:#09090b;border-radius:3px}
375
  .examples-scroll::-webkit-scrollbar-thumb{background:#27272a;border-radius:3px}
 
378
  flex-shrink:0;width:210px;background:#09090b;border:1px solid #27272a;
379
  border-radius:10px;overflow:hidden;cursor:pointer;transition:all .2s ease;
380
  }
381
+ .example-card:hover{border-color:#FF4500;transform:translateY(-2px);box-shadow:0 4px 12px rgba(255,69,0,.15)}
382
+ .example-card.loading{opacity:.5;pointer-events:none}
 
 
 
383
  .example-thumbs{display:flex;height:110px;overflow:hidden;background:#18181b}
384
+ .example-thumbs img{flex:1;object-fit:cover;min-width:0;border-bottom:1px solid #27272a}
 
 
 
385
  .example-thumb-placeholder{
386
  flex:1;display:flex;align-items:center;justify-content:center;
387
  background:#18181b;color:#3f3f46;font-size:11px;min-width:0;
388
  }
389
  .example-meta{padding:6px 10px;display:flex;align-items:center;gap:6px}
390
  .example-badge{
391
+ display:inline-flex;padding:2px 7px;background:rgba(255,69,0,.1);border-radius:4px;
392
+ font-size:10px;font-weight:600;color:#FF6633;font-family:'JetBrains Mono',monospace;white-space:nowrap;
 
 
393
  }
394
  .example-prompt-text{
395
  padding:0 10px 8px;font-size:11px;color:#a1a1aa;line-height:1.4;
396
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
397
  }
398
 
 
399
  .panel-card{border-bottom:1px solid #27272a}
400
  .panel-card-title{
401
  padding:12px 20px;font-size:12px;font-weight:600;color:#71717a;
402
+ text-transform:uppercase;letter-spacing:.8px;border-bottom:1px solid rgba(39,39,42,.6);
 
403
  }
404
  .panel-card-body{padding:16px 20px;display:flex;flex-direction:column;gap:8px}
405
  .modern-label{font-size:13px;font-weight:500;color:#a1a1aa;margin-bottom:4px;display:block}
406
  .modern-textarea{
407
  width:100%;background:#09090b;border:1px solid #27272a;border-radius:8px;
408
+ padding:10px 14px;font-family:'Inter',sans-serif;font-size:14px;color:#e4e4e7;
409
+ resize:vertical;outline:none;min-height:42px;transition:border-color .2s;
410
  }
411
  .modern-textarea:focus{border-color:#FF4500;box-shadow:0 0 0 3px rgba(255,69,0,.15)}
412
  .modern-textarea::placeholder{color:#3f3f46}
413
  .modern-textarea.error-flash{
414
+ border-color:#ef4444!important;box-shadow:0 0 0 3px rgba(239,68,68,.2)!important;animation:shake .4s ease;
 
 
 
 
 
 
 
415
  }
416
+ @keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-4px)}40%,80%{transform:translateX(4px)}}
417
 
 
418
  .toast-notification{
419
  position:fixed;top:24px;left:50%;transform:translateX(-50%) translateY(-120%);
420
+ z-index:9999;padding:10px 24px;border-radius:10px;font-family:'Inter',sans-serif;
421
+ font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px;
 
422
  box-shadow:0 8px 24px rgba(0,0,0,.5);
423
+ transition:transform .35s cubic-bezier(.34,1.56,.64,1),opacity .35s ease;opacity:0;pointer-events:none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  }
425
+ .toast-notification.visible{transform:translateX(-50%) translateY(0);opacity:1;pointer-events:auto}
426
+ .toast-notification.error{background:linear-gradient(135deg,#dc2626,#b91c1c);color:#fff;border:1px solid rgba(255,255,255,.15)}
427
+ .toast-notification.warning{background:linear-gradient(135deg,#d97706,#b45309);color:#fff;border:1px solid rgba(255,255,255,.15)}
428
+ .toast-notification.info{background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;border:1px solid rgba(255,255,255,.15)}
429
  .toast-notification .toast-icon{font-size:16px;line-height:1}
430
  .toast-notification .toast-text{line-height:1.3}
431
 
 
432
  .btn-run{
433
+ display:flex;align-items:center;justify-content:center;gap:8px;width:100%;
434
+ background:linear-gradient(135deg,#FF4500,#E63E00);border:none;border-radius:10px;
435
+ padding:12px 24px;cursor:pointer;font-size:15px;font-weight:600;font-family:'Inter',sans-serif;
436
+ color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;transition:all .2s ease;letter-spacing:-.2px;
 
 
437
  box-shadow:0 4px 16px rgba(255,69,0,.3),inset 0 1px 0 rgba(255,255,255,.1);
438
  }
439
  .btn-run:hover{
440
+ background:linear-gradient(135deg,#FF6633,#FF4500);transform:translateY(-1px);
441
  box-shadow:0 6px 24px rgba(255,69,0,.45),inset 0 1px 0 rgba(255,255,255,.15);
 
 
 
 
 
442
  }
443
+ .btn-run:active{transform:translateY(0);box-shadow:0 2px 8px rgba(255,69,0,.3)}
444
+ .btn-run svg{width:18px;height:18px;fill:#ffffff!important}
445
+ .btn-run svg path{fill:#ffffff!important}
446
+ #custom-run-btn,#custom-run-btn *,#custom-run-btn span,#custom-run-btn svg,
447
+ #custom-run-btn svg path,#run-btn-label,.btn-run,.btn-run *{
448
  color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;fill:#ffffff!important;
449
  }
450
  body:not(.dark) .btn-run,body:not(.dark) .btn-run *,body:not(.dark) #custom-run-btn,
451
+ body:not(.dark) #custom-run-btn *{color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;fill:#ffffff!important}
452
+ .dark .btn-run,.dark .btn-run *,.dark #custom-run-btn,.dark #custom-run-btn *{
 
 
 
453
  color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;fill:#ffffff!important;
454
  }
455
  .gradio-container .btn-run,.gradio-container .btn-run *,.gradio-container #custom-run-btn,
456
+ .gradio-container #custom-run-btn *{color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;fill:#ffffff!important}
 
 
457
 
458
+ .output-frame{border-bottom:1px solid #27272a;display:flex;flex-direction:column;position:relative}
 
 
 
459
  .output-frame .out-title{
460
+ padding:10px 20px;font-size:13px;font-weight:700;color:#ffffff!important;
461
+ -webkit-text-fill-color:#ffffff!important;text-transform:uppercase;letter-spacing:.8px;
462
+ border-bottom:1px solid rgba(39,39,42,.6);display:flex;align-items:center;justify-content:space-between;
 
 
463
  }
464
  .output-frame .out-title span{color:#ffffff!important;-webkit-text-fill-color:#ffffff!important}
465
  .output-frame .out-body{
 
469
  .output-frame .out-body img{max-width:100%;max-height:460px;image-rendering:auto}
470
  .output-frame .out-placeholder{color:#3f3f46;font-size:13px;text-align:center;padding:20px}
471
  .out-download-btn{
472
+ display:none;align-items:center;justify-content:center;background:rgba(255,69,0,.1);
473
+ border:1px solid rgba(255,69,0,.2);border-radius:6px;cursor:pointer;padding:3px 10px;
474
+ font-size:11px;font-weight:500;color:#FF8C66!important;gap:4px;height:24px;transition:all .15s;
 
 
 
 
475
  }
476
+ .out-download-btn:hover{background:rgba(255,69,0,.2);border-color:rgba(255,69,0,.35);color:#ffffff!important}
477
  .out-download-btn.visible{display:inline-flex}
478
  .out-download-btn svg{width:12px;height:12px;fill:#FF8C66}
479
 
 
480
  .modern-loader{
481
+ display:none;position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(9,9,11,.92);
482
+ z-index:15;flex-direction:column;align-items:center;justify-content:center;gap:16px;backdrop-filter:blur(4px);
 
483
  }
484
  .modern-loader.active{display:flex}
485
  .modern-loader .loader-spinner{
486
+ width:36px;height:36px;border:3px solid #27272a;border-top-color:#FF4500;
487
+ border-radius:50%;animation:spin .8s linear infinite;
488
  }
489
  @keyframes spin{to{transform:rotate(360deg)}}
490
  .modern-loader .loader-text{font-size:13px;color:#a1a1aa;font-weight:500}
 
495
  }
496
  @keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
497
 
498
+ .settings-group{border:1px solid #27272a;border-radius:10px;margin:12px 16px;padding:0;overflow:hidden}
 
 
 
499
  .settings-group-title{
500
+ font-size:12px;font-weight:600;color:#71717a;text-transform:uppercase;letter-spacing:.8px;
501
+ padding:10px 16px;border-bottom:1px solid #27272a;background:rgba(24,24,27,.5);
 
502
  }
503
  .settings-group-body{padding:14px 16px;display:flex;flex-direction:column;gap:12px}
504
  .slider-row{display:flex;align-items:center;gap:10px;min-height:28px}
505
  .slider-row label{font-size:13px;font-weight:500;color:#a1a1aa;min-width:72px;flex-shrink:0}
506
  .slider-row input[type="range"]{
507
+ flex:1;-webkit-appearance:none;appearance:none;height:6px;background:#27272a;
508
+ border-radius:3px;outline:none;min-width:0;
509
  }
510
  .slider-row input[type="range"]::-webkit-slider-thumb{
511
+ -webkit-appearance:none;width:16px;height:16px;background:linear-gradient(135deg,#FF4500,#E63E00);
512
+ border-radius:50%;cursor:pointer;box-shadow:0 2px 6px rgba(255,69,0,.4);transition:transform .15s;
 
513
  }
514
  .slider-row input[type="range"]::-webkit-slider-thumb:hover{transform:scale(1.2)}
515
  .slider-row input[type="range"]::-moz-range-thumb{
 
517
  border-radius:50%;cursor:pointer;border:none;box-shadow:0 2px 6px rgba(255,69,0,.4);
518
  }
519
  .slider-row .slider-val{
520
+ min-width:52px;text-align:right;font-family:'JetBrains Mono',monospace;font-size:12px;
521
+ font-weight:500;padding:3px 8px;background:#09090b;border:1px solid #27272a;
522
+ border-radius:6px;color:#a1a1aa;flex-shrink:0;
523
  }
524
  .checkbox-row{display:flex;align-items:center;gap:8px;font-size:13px;color:#a1a1aa}
525
  .checkbox-row input[type="checkbox"]{accent-color:#FF4500;width:16px;height:16px;cursor:pointer}
526
  .checkbox-row label{color:#a1a1aa;font-size:13px;cursor:pointer}
527
 
 
528
  .app-statusbar{
529
  background:#18181b;border-top:1px solid #27272a;padding:6px 20px;
530
  display:flex;gap:12px;height:34px;align-items:center;font-size:12px;
531
  }
532
  .app-statusbar .sb-section{
533
+ padding:0 12px;flex:1;display:flex;align-items:center;font-family:'JetBrains Mono',monospace;
534
+ font-size:12px;color:#52525b;overflow:hidden;white-space:nowrap;
 
535
  }
536
  .app-statusbar .sb-section.sb-fixed{
537
  flex:0 0 auto;min-width:90px;text-align:center;justify-content:center;
538
+ padding:3px 12px;background:rgba(255,69,0,.08);border-radius:6px;color:#FF6633;font-weight:500;
 
 
 
 
 
 
539
  }
540
 
541
+ .exp-note{padding:10px 20px;font-size:12px;color:#52525b;border-top:1px solid #27272a;text-align:center}
 
 
 
542
  .exp-note a{color:#FF6633;text-decoration:none}
543
  .exp-note a:hover{text-decoration:underline}
544
 
 
563
  }
564
  """
565
 
566
+ # ═══════════════════════════════════════════
567
+ # JavaScript
568
+ # ═══════════════════════════════════════════
569
+
570
  gallery_js = r"""
571
  () => {
572
  function init() {
 
619
  toast.classList.add('visible');
620
  toastTimer = setTimeout(() => toast.classList.remove('visible'), 3500);
621
  }
622
+ window.__showToast = showToast;
623
 
624
  function flashPromptError() {
625
  if (!promptInput) return;
 
642
  }
643
  });
644
  }
645
+ window.__setGradioValue = setGradioValue;
646
 
647
  function syncImagesToGradio() {
648
  window.__uploadedImages = images;
 
714
  + '<span class="add-icon">+</span>'
715
  + '<span class="add-text">Add</span>'
716
  + '</div>';
 
717
  galleryGrid.innerHTML = html;
718
 
719
  galleryGrid.querySelectorAll('.gallery-thumb').forEach(thumb => {
 
741
  });
742
  }
743
 
744
+ fileInput.addEventListener('change', (e) => { processFiles(e.target.files); e.target.value = ''; });
 
 
 
 
745
  if (uploadClick) uploadClick.addEventListener('click', () => fileInput.click());
746
  if (btnUpload) btnUpload.addEventListener('click', () => fileInput.click());
747
  if (btnRemove) btnRemove.addEventListener('click', () => {
 
762
  if (promptInput) { promptInput.value = text; syncPromptToGradio(); }
763
  };
764
 
765
+ /* ── Example card click β†’ trigger Python callback ── */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
766
  document.querySelectorAll('.example-card[data-idx]').forEach(card => {
767
  card.addEventListener('click', () => {
768
+ const idx = card.getAttribute('data-idx');
769
+ document.querySelectorAll('.example-card.loading').forEach(c => c.classList.remove('loading'));
770
+ card.classList.add('loading');
771
+ showToast('Loading example...', 'info');
772
+
773
+ setGradioValue('example-result-data', '');
774
+ setGradioValue('example-idx-input', idx);
775
+
776
+ setTimeout(() => {
777
+ const btn = document.getElementById('example-load-btn');
778
+ if (btn) {
779
+ const b = btn.querySelector('button');
780
+ if (b) b.click(); else btn.click();
781
+ }
782
+ }, 150);
783
+
784
+ setTimeout(() => card.classList.remove('loading'), 12000);
785
  });
786
  });
787
 
788
+ /* ── Slider sync ── */
789
  function syncSlider(customId, gradioId) {
790
  const slider = document.getElementById(customId);
791
  const valSpan = document.getElementById(customId + '-val');
 
836
  function validateBeforeRun() {
837
  const promptVal = promptInput ? promptInput.value.trim() : '';
838
  const hasImages = images.length > 0;
839
+ if (!hasImages && !promptVal) { showToast('Please upload an image and enter a prompt', 'error'); flashPromptError(); return false; }
840
+ if (!hasImages) { showToast('Please upload at least one image', 'error'); return false; }
841
+ if (!promptVal) { showToast('Please enter an edit prompt', 'warning'); flashPromptError(); return false; }
 
 
 
 
 
 
 
 
 
 
 
842
  return true;
843
  }
844
 
845
  window.__clickGradioRunBtn = function() {
846
  if (!validateBeforeRun()) return;
847
+ syncPromptToGradio(); syncImagesToGradio(); showLoader();
 
 
848
  setTimeout(() => {
849
  const gradioBtn = document.getElementById('gradio-run-btn');
850
  if (!gradioBtn) return;
 
889
  if (resultImg && resultImg.src) {
890
  if (outPh) outPh.style.display = 'none';
891
  let existing = outBody.querySelector('img.modern-out-img');
892
+ if (!existing) { existing = document.createElement('img'); existing.className = 'modern-out-img'; outBody.appendChild(existing); }
 
 
 
 
893
  if (existing.src !== resultImg.src) {
894
  existing.src = resultImg.src;
895
  if (dlBtn) dlBtn.classList.add('visible');
 
897
  }
898
  }
899
  }
 
900
  const observer = new MutationObserver(syncImage);
901
  observer.observe(resultContainer, {childList:true, subtree:true, attributes:true, attributeFilter:['src']});
902
  setInterval(syncImage, 800);
 
910
  if (!seedContainer || !seedSlider) { setTimeout(watchSeed, 500); return; }
911
  function sync() {
912
  const el = seedContainer.querySelector('input[type="range"],input[type="number"]');
913
+ if (el && el.value) { seedSlider.value = el.value; if (seedVal) seedVal.textContent = el.value; }
 
 
 
914
  }
915
  const obs = new MutationObserver(sync);
916
  obs.observe(seedContainer, {childList:true, subtree:true, attributes:true, attributeFilter:['value']});
917
  setInterval(sync, 1000);
918
  }
919
  watchSeed();
920
+
921
+ /* ── Watch for example load results from Python ── */
922
+ function watchExampleResults() {
923
+ const container = document.getElementById('example-result-data');
924
+ if (!container) { setTimeout(watchExampleResults, 500); return; }
925
+
926
+ let lastProcessed = '';
927
+
928
+ function checkResult() {
929
+ const el = container.querySelector('textarea') || container.querySelector('input');
930
+ if (!el) return;
931
+ const val = el.value;
932
+ if (!val || val === lastProcessed || val.length < 20) return;
933
+
934
+ try {
935
+ const data = JSON.parse(val);
936
+ if (data.status === 'ok' && data.images && data.images.length > 0) {
937
+ lastProcessed = val;
938
+
939
+ if (window.__clearAll) window.__clearAll();
940
+ if (window.__setPrompt && data.prompt) window.__setPrompt(data.prompt);
941
+
942
+ data.images.forEach((b64, i) => {
943
+ if (b64 && window.__addImage) {
944
+ const name = (data.names && data.names[i]) ? data.names[i] : ('example_' + (i+1) + '.jpg');
945
+ window.__addImage(b64, name);
946
+ }
947
+ });
948
+
949
+ document.querySelectorAll('.example-card.loading').forEach(c => c.classList.remove('loading'));
950
+ if (window.__showToast) window.__showToast('Example loaded β€” ' + data.images.length + ' image(s)', 'info');
951
+ } else if (data.status === 'error') {
952
+ document.querySelectorAll('.example-card.loading').forEach(c => c.classList.remove('loading'));
953
+ if (window.__showToast) window.__showToast('Could not load example images', 'error');
954
+ }
955
+ } catch(e) {
956
+ console.error('Example parse error:', e);
957
+ }
958
+ }
959
+
960
+ const obs = new MutationObserver(checkResult);
961
+ obs.observe(container, {childList:true, subtree:true, characterData:true, attributes:true});
962
+ setInterval(checkResult, 500);
963
+ }
964
+ watchExampleResults();
965
  }
966
  """
967
 
968
+ # ═══════════════════════════════════════════
969
+ # SVG Definitions (no emojis)
970
+ # ═══════════════════════════════════════════
971
+
972
  DOWNLOAD_SVG = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 16l-5-5h3V4h4v7h3l-5 5z"/><path d="M20 18H4v2h16v-2z"/></svg>'
973
 
974
  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>'
 
979
 
980
  FIRE_LOGO_SVG = '<svg viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg"><path d="M12 23c-3.6 0-8-2.69-8-7.5 0-3.5 3-6.5 4.5-8 .27-.27.75-.08.75.28v2.44c0 .42.5.63.72.28C12.28 7.5 13 3 13 1c0-.42.48-.64.8-.35C18 4.5 20 9 20 12c0 5.5-3.5 11-8 11z"/></svg>'
981
 
982
+ # ═══════════���═══════════════════════════════
983
+ # Gradio App
984
+ # ═══════════════════════════════════════════
985
 
986
+ with gr.Blocks() as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
987
 
988
+ # ── Hidden Gradio components ──
989
+ hidden_images_b64 = gr.Textbox(value="[]", elem_id="hidden-images-b64", elem_classes="hidden-input", container=False)
990
+ prompt = gr.Textbox(value="", elem_id="prompt-gradio-input", elem_classes="hidden-input", container=False)
991
+ seed = gr.Slider(minimum=0, maximum=MAX_SEED, step=1, value=0, elem_id="gradio-seed", elem_classes="hidden-input", container=False)
992
+ randomize_seed = gr.Checkbox(value=True, elem_id="gradio-randomize", elem_classes="hidden-input", container=False)
993
+ 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)
994
+ steps = gr.Slider(minimum=1, maximum=50, step=1, value=4, elem_id="gradio-steps", elem_classes="hidden-input", container=False)
995
+ result = gr.Image(elem_id="gradio-result", elem_classes="hidden-input", container=False, format="png")
996
+
997
+ # ── Hidden example loading components ──
998
+ example_idx = gr.Textbox(value="", elem_id="example-idx-input", elem_classes="hidden-input", container=False)
999
+ example_result = gr.Textbox(value="", elem_id="example-result-data", elem_classes="hidden-input", container=False)
1000
+ example_load_btn = gr.Button("Load Example", elem_id="example-load-btn")
1001
+
1002
+ # ── Main HTML UI ──
1003
  gr.HTML(f"""
1004
  <div class="app-shell">
1005
 
 
1006
  <div class="app-header">
1007
  <div class="app-header-left">
1008
  <div class="app-logo">{FIRE_LOGO_SVG}</div>
 
1012
  </div>
1013
  </div>
1014
 
 
1015
  <div class="app-toolbar">
1016
  <button id="tb-upload" class="modern-tb-btn" title="Upload images">
1017
  {UPLOAD_SVG}<span class="tb-label">Upload</span>
 
1026
  <span id="tb-image-count" class="tb-info">No images</span>
1027
  </div>
1028
 
 
1029
  <div class="app-main-row">
 
 
1030
  <div class="app-main-left">
1031
  <div id="gallery-drop-zone">
1032
  <div id="upload-prompt" class="upload-prompt-modern">
 
1045
  </div>
1046
 
1047
  <div class="hint-bar">
1048
+ <b>Upload:</b> Click or drag to add images &nbsp;&middot;&nbsp;
1049
+ <b>Multi-image:</b> Upload multiple images for reference-based editing &nbsp;&middot;&nbsp;
1050
+ <kbd>Remove</kbd> deletes selected &nbsp;&middot;&nbsp;
1051
  <kbd>Clear All</kbd> removes everything
1052
  </div>
1053
 
 
1076
  <div class="examples-section">
1077
  <div class="examples-title">Quick Examples</div>
1078
  <div class="examples-scroll">
1079
+ {EXAMPLE_CARDS_HTML}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1080
  </div>
1081
  </div>
 
1082
  </div>
1083
 
 
1084
  <div class="app-main-right">
 
1085
  <div class="panel-card">
1086
  <div class="panel-card-title">Edit Instruction</div>
1087
  <div class="panel-card-body">
 
1138
  </div>
1139
  </div>
1140
  </div>
 
1141
  </div>
1142
  </div>
1143
 
 
1144
  <div class="exp-note">
1145
  Experimental Space for <a href="https://huggingface.co/FireRedTeam/FireRed-Image-Edit-1.1" target="_blank">FireRed-Image-Edit-1.1</a>
1146
  &middot; Open on <a href="https://github.com/PRITHIVSAKTHIUR/FireRed-Image-Edit-1.0-Fast" target="_blank">GitHub</a>
1147
  </div>
1148
 
 
1149
  <div class="app-statusbar">
1150
  <div class="sb-section" id="sb-image-count">No images uploaded</div>
1151
  <div class="sb-section sb-fixed">Ready</div>
1152
  </div>
 
1153
  </div>
1154
  """)
1155
 
 
1158
  demo.load(fn=None, js=gallery_js)
1159
  demo.load(fn=None, js=wire_outputs_js)
1160
 
1161
+ # ── Run inference ──
1162
  run_btn.click(
1163
  fn=infer,
1164
  inputs=[hidden_images_b64, prompt, seed, randomize_seed, guidance_scale, steps],
 
1173
  }""",
1174
  )
1175
 
1176
+ # ── Load example (bypasses queue for instant response) ──
1177
+ example_load_btn.click(
1178
+ fn=load_example_data,
1179
+ inputs=[example_idx],
1180
+ outputs=[example_result],
1181
+ queue=False,
1182
+ )
1183
+
1184
  if __name__ == "__main__":
1185
  demo.queue(max_size=30).launch(
1186
  css=css,