update app
Browse files
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 |
-
|
| 72 |
-
if
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
new_height = int(new_width * aspect_ratio)
|
| 76 |
else:
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 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 |
-
|
| 119 |
-
|
| 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-
|
| 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 |
-
|
| 150 |
-
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 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
|
| 200 |
-
font-
|
| 201 |
-
|
| 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 |
-
|
| 215 |
-
|
| 216 |
-
}
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
}
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 297 |
-
|
| 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 |
-
|
| 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:
|
| 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 |
-
|
| 335 |
-
|
| 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-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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-
|
| 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 |
-
|
| 444 |
-
|
| 445 |
-
|
| 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:
|
| 460 |
-
.btn-run svg{width:18px;height:18px;fill:#ffffff!important
|
| 461 |
-
.btn-run svg path{fill:#ffffff!important
|
| 462 |
-
|
| 463 |
-
#custom-run-btn,#
|
| 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 *
|
| 468 |
-
|
| 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 *
|
| 476 |
-
color:#ffffff!important;-webkit-text-fill-color:#ffffff!important;fill:#ffffff!important;
|
| 477 |
-
}
|
| 478 |
|
| 479 |
-
|
| 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 |
-
|
| 486 |
-
|
| 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 |
-
|
| 500 |
-
|
| 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 |
-
|
| 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-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 544 |
}
|
| 545 |
.slider-row input[type="range"]::-webkit-slider-thumb{
|
| 546 |
-
-webkit-appearance:none;
|
| 547 |
-
|
| 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-
|
| 558 |
-
border
|
| 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-
|
| 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
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 948 |
-
|
| 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 |
-
|
|
|
|
|
|
|
| 1062 |
|
| 1063 |
-
|
| 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
|
| 1143 |
-
<b>Multi-image:</b> Upload multiple images for reference-based editing
|
| 1144 |
-
<kbd>Remove</kbd> deletes selected
|
| 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 |
· 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 ·
|
| 1049 |
+
<b>Multi-image:</b> Upload multiple images for reference-based editing ·
|
| 1050 |
+
<kbd>Remove</kbd> deletes selected ·
|
| 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 |
· 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,
|