Avanish11 commited on
Commit
6a2ea73
·
verified ·
1 Parent(s): d40e23c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +731 -129
app.py CHANGED
@@ -231,190 +231,792 @@ def process_job(job_id, image):
231
 
232
  HTML_PAGE = """
233
  <!DOCTYPE html>
234
- <html>
235
  <head>
 
 
236
  <title>CodeFormer Face Enhancement</title>
237
-
238
  <style>
239
- body{
240
- font-family:Arial,sans-serif;
241
- max-width:1000px;
242
- margin:auto;
243
- padding:30px;
244
  }
245
 
246
- .container{
247
- display:flex;
248
- gap:20px;
 
 
 
 
 
249
  }
250
 
251
- .card{
252
- flex:1;
 
 
253
  }
254
 
255
- img{
256
- width:100%;
257
- border:1px solid #ddd;
258
- border-radius:8px;
 
 
 
259
  }
260
 
261
- button{
262
- padding:12px 20px;
263
- cursor:pointer;
264
- margin-top:10px;
 
 
 
 
 
265
  }
266
 
267
- h1{
268
- text-align:center;
 
 
 
 
269
  }
270
-
271
- .progress-container {
272
- width:100%;
273
- border:1px solid #ccc;
274
- height:30px;
275
- margin:10px 0;
276
- border-radius:4px;
277
- overflow:hidden;
 
 
278
  }
279
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  .progress-bar {
281
- height:100%;
282
- width:0%;
283
- background:#4CAF50;
284
- transition: width 0.3s ease;
 
 
285
  }
286
- </style>
287
 
288
- </head>
289
- <body>
 
 
 
 
 
 
 
 
290
 
291
- <h1>CodeFormer Face Enhancement</h1>
 
 
 
 
 
 
 
292
 
293
- <input type="file" id="file">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
- <br>
 
 
 
 
 
296
 
297
- <button onclick="enhance()">
298
- Enhance Face
299
- </button>
 
300
 
301
- <p id="status"></p>
 
 
 
 
 
 
 
 
302
 
303
- <div class="progress-container">
304
- <div id="bar" class="progress-bar"></div>
305
- </div>
 
 
 
 
 
306
 
307
- <div class="container">
 
 
 
 
 
 
 
 
 
 
308
 
309
- <div class="card">
310
- <h3>Original</h3>
311
- <img id="inputPreview">
312
- </div>
 
 
313
 
314
- <div class="card">
315
- <h3>Enhanced</h3>
316
- <img id="outputPreview">
317
- </div>
 
 
318
 
319
- </div>
 
 
 
 
320
 
321
- <script>
 
 
 
 
 
 
 
 
 
 
322
 
323
- const fileInput =
324
- document.getElementById("file");
 
 
 
 
 
 
325
 
326
- fileInput.addEventListener("change", function(){
 
 
 
 
 
 
 
 
 
 
327
 
328
- const file = this.files[0];
 
 
 
 
 
 
329
 
330
- if(file){
331
- document.getElementById("inputPreview")
332
- .src = URL.createObjectURL(file);
333
- }
334
- });
335
 
336
- async function enhance(){
 
 
 
 
 
 
 
337
 
338
- const file =
339
- document.getElementById("file")
340
- .files[0];
341
 
342
- if(!file){
343
- alert("Select an image");
344
- return;
345
- }
 
 
 
 
 
 
 
 
 
 
346
 
347
- document.getElementById("status")
348
- .innerHTML = "Starting...";
349
-
350
- document.getElementById("bar")
351
- .style.width = "0%";
 
 
352
 
353
- const formData = new FormData();
 
 
354
 
355
- formData.append(
356
- "file",
357
- file
358
- );
359
 
360
- const start =
361
- await fetch("/convert", {
362
- method: "POST",
363
- body: formData
364
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
 
366
- const data =
367
- await start.json();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
369
- const jobId =
370
- data.job_id;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
 
372
- const timer = setInterval(
373
- async () => {
 
 
 
 
374
 
375
- const res =
376
- await fetch(
377
- "/progress/" + jobId
378
- );
379
 
380
- const progress =
381
- await res.json();
 
 
 
382
 
383
- document.getElementById(
384
- "status"
385
- ).innerHTML =
386
- progress.message;
 
387
 
388
- document.getElementById(
389
- "bar"
390
- ).style.width =
391
- progress.progress + "%";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
 
393
- if(progress.progress >= 100){
 
 
 
 
 
 
 
394
 
395
- clearInterval(timer);
 
 
 
 
 
 
 
 
 
 
 
396
 
397
- document
398
- .getElementById("outputPreview")
399
- .src =
400
- "/result/" +
401
- jobId +
402
- "?t=" +
403
- Date.now();
404
  }
 
 
 
 
 
405
 
406
- if(progress.progress < 0){
407
- clearInterval(timer);
408
- document.getElementById("status")
409
- .innerHTML = "Error: " + progress.message;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  }
 
 
 
 
 
411
 
412
- },
413
- 500
414
- );
415
- }
 
 
 
 
 
 
 
416
 
417
- </script>
 
 
418
 
419
  </body>
420
  </html>
 
231
 
232
  HTML_PAGE = """
233
  <!DOCTYPE html>
234
+ <html lang="en">
235
  <head>
236
+ <meta charset="UTF-8">
237
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
238
  <title>CodeFormer Face Enhancement</title>
239
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
240
  <style>
241
+ * {
242
+ margin: 0;
243
+ padding: 0;
244
+ box-sizing: border-box;
 
245
  }
246
 
247
+ body {
248
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
249
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
250
+ min-height: 100vh;
251
+ padding: 20px;
252
+ display: flex;
253
+ justify-content: center;
254
+ align-items: center;
255
  }
256
 
257
+ .main-container {
258
+ max-width: 1200px;
259
+ width: 100%;
260
+ margin: 0 auto;
261
  }
262
 
263
+ .card {
264
+ background: rgba(255, 255, 255, 0.95);
265
+ backdrop-filter: blur(20px);
266
+ border-radius: 24px;
267
+ padding: 40px;
268
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
269
+ border: 1px solid rgba(255, 255, 255, 0.2);
270
  }
271
 
272
+ h1 {
273
+ text-align: center;
274
+ font-size: 2.5rem;
275
+ font-weight: 700;
276
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
277
+ -webkit-background-clip: text;
278
+ -webkit-text-fill-color: transparent;
279
+ background-clip: text;
280
+ margin-bottom: 8px;
281
  }
282
 
283
+ .subtitle {
284
+ text-align: center;
285
+ color: #6b7280;
286
+ font-size: 1rem;
287
+ margin-bottom: 30px;
288
+ font-weight: 400;
289
  }
290
+
291
+ .upload-area {
292
+ border: 2px dashed #d1d5db;
293
+ border-radius: 16px;
294
+ padding: 40px;
295
+ text-align: center;
296
+ cursor: pointer;
297
+ transition: all 0.3s ease;
298
+ background: #f9fafb;
299
+ position: relative;
300
  }
301
+
302
+ .upload-area:hover {
303
+ border-color: #667eea;
304
+ background: #f3f4f6;
305
+ transform: translateY(-2px);
306
+ }
307
+
308
+ .upload-area.dragover {
309
+ border-color: #667eea;
310
+ background: #eef2ff;
311
+ transform: scale(1.02);
312
+ }
313
+
314
+ .upload-icon {
315
+ font-size: 48px;
316
+ margin-bottom: 12px;
317
+ }
318
+
319
+ .upload-text {
320
+ color: #374151;
321
+ font-size: 1.1rem;
322
+ font-weight: 500;
323
+ }
324
+
325
+ .upload-subtext {
326
+ color: #6b7280;
327
+ font-size: 0.9rem;
328
+ margin-top: 4px;
329
+ }
330
+
331
+ #file {
332
+ display: none;
333
+ }
334
+
335
+ .controls {
336
+ display: flex;
337
+ gap: 12px;
338
+ margin-top: 20px;
339
+ flex-wrap: wrap;
340
+ }
341
+
342
+ .btn {
343
+ padding: 12px 32px;
344
+ border: none;
345
+ border-radius: 12px;
346
+ font-size: 1rem;
347
+ font-weight: 600;
348
+ cursor: pointer;
349
+ transition: all 0.3s ease;
350
+ display: inline-flex;
351
+ align-items: center;
352
+ gap: 8px;
353
+ font-family: 'Inter', sans-serif;
354
+ }
355
+
356
+ .btn-primary {
357
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
358
+ color: white;
359
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
360
+ }
361
+
362
+ .btn-primary:hover:not(:disabled) {
363
+ transform: translateY(-2px);
364
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
365
+ }
366
+
367
+ .btn-primary:disabled {
368
+ opacity: 0.5;
369
+ cursor: not-allowed;
370
+ transform: none;
371
+ }
372
+
373
+ .btn-secondary {
374
+ background: #f3f4f6;
375
+ color: #374151;
376
+ }
377
+
378
+ .btn-secondary:hover {
379
+ background: #e5e7eb;
380
+ transform: translateY(-2px);
381
+ }
382
+
383
+ .btn-danger {
384
+ background: #ef4444;
385
+ color: white;
386
+ }
387
+
388
+ .btn-danger:hover {
389
+ background: #dc2626;
390
+ transform: translateY(-2px);
391
+ }
392
+
393
+ .status-container {
394
+ margin-top: 20px;
395
+ padding: 16px;
396
+ border-radius: 12px;
397
+ background: #f9fafb;
398
+ display: none;
399
+ }
400
+
401
+ .status-container.active {
402
+ display: block;
403
+ animation: slideDown 0.3s ease;
404
+ }
405
+
406
+ @keyframes slideDown {
407
+ from {
408
+ opacity: 0;
409
+ transform: translateY(-10px);
410
+ }
411
+ to {
412
+ opacity: 1;
413
+ transform: translateY(0);
414
+ }
415
+ }
416
+
417
+ .status-header {
418
+ display: flex;
419
+ justify-content: space-between;
420
+ align-items: center;
421
+ margin-bottom: 8px;
422
+ }
423
+
424
+ .status-message {
425
+ color: #374151;
426
+ font-weight: 500;
427
+ font-size: 0.95rem;
428
+ }
429
+
430
+ .status-percentage {
431
+ color: #667eea;
432
+ font-weight: 700;
433
+ font-size: 1.1rem;
434
+ }
435
+
436
+ .progress-wrapper {
437
+ width: 100%;
438
+ height: 8px;
439
+ background: #e5e7eb;
440
+ border-radius: 100px;
441
+ overflow: hidden;
442
+ position: relative;
443
+ }
444
+
445
  .progress-bar {
446
+ height: 100%;
447
+ width: 0%;
448
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
449
+ border-radius: 100px;
450
+ transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
451
+ position: relative;
452
  }
 
453
 
454
+ .progress-bar::after {
455
+ content: '';
456
+ position: absolute;
457
+ top: 0;
458
+ left: 0;
459
+ right: 0;
460
+ bottom: 0;
461
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
462
+ animation: shimmer 2s infinite;
463
+ }
464
 
465
+ @keyframes shimmer {
466
+ 0% {
467
+ transform: translateX(-100%);
468
+ }
469
+ 100% {
470
+ transform: translateX(100%);
471
+ }
472
+ }
473
 
474
+ .progress-bar.complete::after {
475
+ animation: none;
476
+ }
477
+
478
+ .image-grid {
479
+ display: grid;
480
+ grid-template-columns: 1fr 1fr;
481
+ gap: 24px;
482
+ margin-top: 24px;
483
+ }
484
+
485
+ @media (max-width: 768px) {
486
+ .image-grid {
487
+ grid-template-columns: 1fr;
488
+ gap: 16px;
489
+ }
490
+ }
491
 
492
+ .image-card {
493
+ background: #f9fafb;
494
+ border-radius: 16px;
495
+ padding: 16px;
496
+ transition: all 0.3s ease;
497
+ }
498
 
499
+ .image-card:hover {
500
+ transform: translateY(-4px);
501
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
502
+ }
503
 
504
+ .image-label {
505
+ font-weight: 600;
506
+ color: #374151;
507
+ margin-bottom: 12px;
508
+ display: flex;
509
+ align-items: center;
510
+ gap: 8px;
511
+ font-size: 0.95rem;
512
+ }
513
 
514
+ .image-label .badge {
515
+ background: #667eea;
516
+ color: white;
517
+ padding: 2px 10px;
518
+ border-radius: 100px;
519
+ font-size: 0.75rem;
520
+ font-weight: 600;
521
+ }
522
 
523
+ .image-container {
524
+ width: 100%;
525
+ aspect-ratio: 1;
526
+ background: #e5e7eb;
527
+ border-radius: 12px;
528
+ overflow: hidden;
529
+ position: relative;
530
+ display: flex;
531
+ align-items: center;
532
+ justify-content: center;
533
+ }
534
 
535
+ .image-container img {
536
+ width: 100%;
537
+ height: 100%;
538
+ object-fit: cover;
539
+ transition: opacity 0.5s ease;
540
+ }
541
 
542
+ .image-container .placeholder {
543
+ color: #9ca3af;
544
+ font-size: 0.9rem;
545
+ text-align: center;
546
+ padding: 20px;
547
+ }
548
 
549
+ .image-container .placeholder .icon {
550
+ font-size: 32px;
551
+ display: block;
552
+ margin-bottom: 8px;
553
+ }
554
 
555
+ .image-container.loading::after {
556
+ content: '';
557
+ position: absolute;
558
+ top: 0;
559
+ left: 0;
560
+ right: 0;
561
+ bottom: 0;
562
+ background: linear-gradient(90deg, #e5e7eb, #f3f4f6, #e5e7eb);
563
+ background-size: 200% 100%;
564
+ animation: loading 1.5s infinite;
565
+ }
566
 
567
+ @keyframes loading {
568
+ 0% {
569
+ background-position: -200% 0;
570
+ }
571
+ 100% {
572
+ background-position: 200% 0;
573
+ }
574
+ }
575
 
576
+ .file-info {
577
+ display: flex;
578
+ align-items: center;
579
+ gap: 12px;
580
+ padding: 12px 16px;
581
+ background: #f3f4f6;
582
+ border-radius: 8px;
583
+ margin-top: 12px;
584
+ font-size: 0.9rem;
585
+ color: #374151;
586
+ }
587
 
588
+ .file-info .file-name {
589
+ font-weight: 500;
590
+ flex: 1;
591
+ overflow: hidden;
592
+ text-overflow: ellipsis;
593
+ white-space: nowrap;
594
+ }
595
 
596
+ .file-info .file-size {
597
+ color: #6b7280;
598
+ font-size: 0.85rem;
599
+ }
 
600
 
601
+ .file-info .remove-file {
602
+ cursor: pointer;
603
+ color: #ef4444;
604
+ font-weight: 600;
605
+ padding: 4px 8px;
606
+ border-radius: 4px;
607
+ transition: background 0.2s;
608
+ }
609
 
610
+ .file-info .remove-file:hover {
611
+ background: #fee2e2;
612
+ }
613
 
614
+ .toast {
615
+ position: fixed;
616
+ top: 20px;
617
+ right: 20px;
618
+ padding: 16px 24px;
619
+ border-radius: 12px;
620
+ color: white;
621
+ font-weight: 500;
622
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
623
+ transform: translateX(400px);
624
+ transition: transform 0.3s ease;
625
+ z-index: 1000;
626
+ max-width: 400px;
627
+ }
628
 
629
+ .toast.show {
630
+ transform: translateX(0);
631
+ }
632
+
633
+ .toast.success {
634
+ background: #10b981;
635
+ }
636
 
637
+ .toast.error {
638
+ background: #ef4444;
639
+ }
640
 
641
+ .toast.info {
642
+ background: #3b82f6;
643
+ }
 
644
 
645
+ @media (max-width: 640px) {
646
+ .card {
647
+ padding: 20px;
648
+ }
649
+
650
+ h1 {
651
+ font-size: 1.8rem;
652
+ }
653
+
654
+ .controls {
655
+ flex-direction: column;
656
+ }
657
+
658
+ .btn {
659
+ width: 100%;
660
+ justify-content: center;
661
+ }
662
+
663
+ .upload-area {
664
+ padding: 24px;
665
+ }
666
+
667
+ .toast {
668
+ left: 20px;
669
+ right: 20px;
670
+ max-width: none;
671
+ }
672
+ }
673
+ </style>
674
+ </head>
675
+ <body>
676
 
677
+ <div class="main-container">
678
+ <div class="card">
679
+ <h1>✨ Face Enhancer</h1>
680
+ <p class="subtitle">AI-powered face restoration using CodeFormer</p>
681
+
682
+ <!-- Upload Area -->
683
+ <div class="upload-area" id="uploadArea">
684
+ <div class="upload-icon">📸</div>
685
+ <div class="upload-text">Drop your image here or click to browse</div>
686
+ <div class="upload-subtext">Supports JPG, PNG, WEBP (Max 10MB)</div>
687
+ <input type="file" id="file" accept="image/*">
688
+ </div>
689
+
690
+ <!-- File Info -->
691
+ <div id="fileInfo" class="file-info" style="display:none;">
692
+ <span>📎</span>
693
+ <span class="file-name" id="fileName">image.jpg</span>
694
+ <span class="file-size" id="fileSize">2.4 MB</span>
695
+ <span class="remove-file" id="removeFile">✕</span>
696
+ </div>
697
+
698
+ <!-- Controls -->
699
+ <div class="controls">
700
+ <button class="btn btn-primary" id="enhanceBtn" disabled>
701
+ 🚀 Enhance Face
702
+ </button>
703
+ <button class="btn btn-secondary" id="resetBtn">
704
+ 🔄 Reset
705
+ </button>
706
+ </div>
707
+
708
+ <!-- Status -->
709
+ <div class="status-container" id="statusContainer">
710
+ <div class="status-header">
711
+ <span class="status-message" id="statusMessage">Processing...</span>
712
+ <span class="status-percentage" id="statusPercentage">0%</span>
713
+ </div>
714
+ <div class="progress-wrapper">
715
+ <div class="progress-bar" id="progressBar"></div>
716
+ </div>
717
+ </div>
718
+
719
+ <!-- Image Grid -->
720
+ <div class="image-grid">
721
+ <div class="image-card">
722
+ <div class="image-label">
723
+ <span>📷 Original</span>
724
+ <span class="badge">Input</span>
725
+ </div>
726
+ <div class="image-container" id="inputContainer">
727
+ <div class="placeholder">
728
+ <span class="icon">🖼️</span>
729
+ No image selected
730
+ </div>
731
+ <img id="inputPreview" style="display:none;" alt="Original">
732
+ </div>
733
+ </div>
734
+
735
+ <div class="image-card">
736
+ <div class="image-label">
737
+ <span>✨ Enhanced</span>
738
+ <span class="badge">Output</span>
739
+ </div>
740
+ <div class="image-container" id="outputContainer">
741
+ <div class="placeholder">
742
+ <span class="icon">🎯</span>
743
+ Waiting for enhancement
744
+ </div>
745
+ <img id="outputPreview" style="display:none;" alt="Enhanced">
746
+ </div>
747
+ </div>
748
+ </div>
749
+ </div>
750
+ </div>
751
 
752
+ <!-- Toast Notification -->
753
+ <div class="toast" id="toast"></div>
754
+
755
+ <script>
756
+ // DOM Elements
757
+ const fileInput = document.getElementById('file');
758
+ const uploadArea = document.getElementById('uploadArea');
759
+ const fileInfo = document.getElementById('fileInfo');
760
+ const fileName = document.getElementById('fileName');
761
+ const fileSize = document.getElementById('fileSize');
762
+ const removeFile = document.getElementById('removeFile');
763
+ const enhanceBtn = document.getElementById('enhanceBtn');
764
+ const resetBtn = document.getElementById('resetBtn');
765
+ const inputPreview = document.getElementById('inputPreview');
766
+ const outputPreview = document.getElementById('outputPreview');
767
+ const inputContainer = document.getElementById('inputContainer');
768
+ const outputContainer = document.getElementById('outputContainer');
769
+ const statusContainer = document.getElementById('statusContainer');
770
+ const statusMessage = document.getElementById('statusMessage');
771
+ const statusPercentage = document.getElementById('statusPercentage');
772
+ const progressBar = document.getElementById('progressBar');
773
+ const toast = document.getElementById('toast');
774
+
775
+ let selectedFile = null;
776
+ let pollingInterval = null;
777
+ let currentJobId = null;
778
+
779
+ // Toast function
780
+ function showToast(message, type = 'info') {
781
+ toast.textContent = message;
782
+ toast.className = `toast ${type}`;
783
+ setTimeout(() => {
784
+ toast.classList.add('show');
785
+ }, 10);
786
+ setTimeout(() => {
787
+ toast.classList.remove('show');
788
+ }, 3000);
789
+ }
790
 
791
+ // Format file size
792
+ function formatFileSize(bytes) {
793
+ if (bytes < 1024) return bytes + ' B';
794
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
795
+ return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
796
+ }
797
 
798
+ // Update UI with selected file
799
+ function handleFileSelect(file) {
800
+ if (!file) return;
 
801
 
802
+ // Validate file type
803
+ if (!file.type.startsWith('image/')) {
804
+ showToast('Please select an image file', 'error');
805
+ return;
806
+ }
807
 
808
+ // Validate file size (10MB)
809
+ if (file.size > 10 * 1024 * 1024) {
810
+ showToast('File size must be less than 10MB', 'error');
811
+ return;
812
+ }
813
 
814
+ selectedFile = file;
815
+ fileName.textContent = file.name;
816
+ fileSize.textContent = formatFileSize(file.size);
817
+ fileInfo.style.display = 'flex';
818
+ enhanceBtn.disabled = false;
819
+
820
+ // Show preview
821
+ const reader = new FileReader();
822
+ reader.onload = function(e) {
823
+ inputPreview.src = e.target.result;
824
+ inputPreview.style.display = 'block';
825
+ inputContainer.querySelector('.placeholder').style.display = 'none';
826
+ };
827
+ reader.readAsDataURL(file);
828
+
829
+ showToast(`Loaded: ${file.name}`, 'success');
830
+ }
831
+
832
+ // Remove file
833
+ function removeSelectedFile() {
834
+ selectedFile = null;
835
+ fileInput.value = '';
836
+ fileInfo.style.display = 'none';
837
+ enhanceBtn.disabled = true;
838
+ inputPreview.style.display = 'none';
839
+ inputContainer.querySelector('.placeholder').style.display = 'block';
840
+ outputPreview.style.display = 'none';
841
+ outputContainer.querySelector('.placeholder').style.display = 'block';
842
+ resetProgress();
843
+ if (pollingInterval) {
844
+ clearInterval(pollingInterval);
845
+ pollingInterval = null;
846
+ }
847
+ }
848
 
849
+ // Reset progress UI
850
+ function resetProgress() {
851
+ statusContainer.classList.remove('active');
852
+ statusMessage.textContent = 'Processing...';
853
+ statusPercentage.textContent = '0%';
854
+ progressBar.style.width = '0%';
855
+ progressBar.classList.remove('complete');
856
+ }
857
 
858
+ // Update progress
859
+ function updateProgress(progress) {
860
+ statusContainer.classList.add('active');
861
+ statusMessage.textContent = progress.message || 'Processing...';
862
+ const value = Math.min(Math.max(progress.progress, 0), 100);
863
+ statusPercentage.textContent = Math.round(value) + '%';
864
+ progressBar.style.width = value + '%';
865
+
866
+ if (value >= 100) {
867
+ progressBar.classList.add('complete');
868
+ }
869
+ }
870
 
871
+ // Enhance function
872
+ async function enhance() {
873
+ if (!selectedFile) {
874
+ showToast('Please select an image first', 'error');
875
+ return;
 
 
876
  }
877
+
878
+ // Reset previous results
879
+ outputPreview.style.display = 'none';
880
+ outputContainer.querySelector('.placeholder').style.display = 'block';
881
+ resetProgress();
882
 
883
+ // Disable button during processing
884
+ enhanceBtn.disabled = true;
885
+ enhanceBtn.textContent = '⏳ Processing...';
886
+
887
+ try {
888
+ const formData = new FormData();
889
+ formData.append('file', selectedFile);
890
+
891
+ const response = await fetch('/convert', {
892
+ method: 'POST',
893
+ body: formData
894
+ });
895
+
896
+ if (!response.ok) {
897
+ throw new Error('Failed to start processing');
898
+ }
899
+
900
+ const data = await response.json();
901
+ currentJobId = data.job_id;
902
+
903
+ // Start polling for progress
904
+ if (pollingInterval) {
905
+ clearInterval(pollingInterval);
906
+ }
907
+
908
+ pollingInterval = setInterval(async () => {
909
+ try {
910
+ const res = await fetch(`/progress/${currentJobId}`);
911
+ const progress = await res.json();
912
+
913
+ updateProgress(progress);
914
+
915
+ if (progress.progress >= 100) {
916
+ clearInterval(pollingInterval);
917
+ pollingInterval = null;
918
+
919
+ // Fetch the result
920
+ const resultResponse = await fetch(`/result/${currentJobId}?t=${Date.now()}`);
921
+ if (resultResponse.ok) {
922
+ const blob = await resultResponse.blob();
923
+ const url = URL.createObjectURL(blob);
924
+ outputPreview.src = url;
925
+ outputPreview.style.display = 'block';
926
+ outputContainer.querySelector('.placeholder').style.display = 'none';
927
+ showToast('✨ Enhancement completed!', 'success');
928
+ }
929
+
930
+ enhanceBtn.disabled = false;
931
+ enhanceBtn.textContent = '🚀 Enhance Face';
932
+ } else if (progress.progress < 0) {
933
+ // Error occurred
934
+ clearInterval(pollingInterval);
935
+ pollingInterval = null;
936
+ showToast(`Error: ${progress.message}`, 'error');
937
+ enhanceBtn.disabled = false;
938
+ enhanceBtn.textContent = '🚀 Enhance Face';
939
+ }
940
+ } catch (error) {
941
+ console.error('Polling error:', error);
942
+ }
943
+ }, 500);
944
+
945
+ } catch (error) {
946
+ console.error('Enhancement error:', error);
947
+ showToast(`Error: ${error.message}`, 'error');
948
+ enhanceBtn.disabled = false;
949
+ enhanceBtn.textContent = '🚀 Enhance Face';
950
+ }
951
+ }
952
+
953
+ // Reset everything
954
+ function resetAll() {
955
+ removeSelectedFile();
956
+ if (pollingInterval) {
957
+ clearInterval(pollingInterval);
958
+ pollingInterval = null;
959
+ }
960
+ resetProgress();
961
+ outputPreview.style.display = 'none';
962
+ outputContainer.querySelector('.placeholder').style.display = 'block';
963
+ enhanceBtn.disabled = true;
964
+ enhanceBtn.textContent = '🚀 Enhance Face';
965
+ showToast('Reset complete', 'info');
966
+ }
967
+
968
+ // Event Listeners
969
+ fileInput.addEventListener('change', function(e) {
970
+ if (this.files && this.files[0]) {
971
+ handleFileSelect(this.files[0]);
972
+ }
973
+ });
974
+
975
+ uploadArea.addEventListener('click', function() {
976
+ fileInput.click();
977
+ });
978
+
979
+ uploadArea.addEventListener('dragover', function(e) {
980
+ e.preventDefault();
981
+ this.classList.add('dragover');
982
+ });
983
+
984
+ uploadArea.addEventListener('dragleave', function(e) {
985
+ e.preventDefault();
986
+ this.classList.remove('dragover');
987
+ });
988
+
989
+ uploadArea.addEventListener('drop', function(e) {
990
+ e.preventDefault();
991
+ this.classList.remove('dragover');
992
+ if (e.dataTransfer.files && e.dataTransfer.files[0]) {
993
+ handleFileSelect(e.dataTransfer.files[0]);
994
+ // Update file input
995
+ const dt = new DataTransfer();
996
+ dt.items.add(e.dataTransfer.files[0]);
997
+ fileInput.files = dt.files;
998
  }
999
+ });
1000
+
1001
+ removeFile.addEventListener('click', removeSelectedFile);
1002
+
1003
+ enhanceBtn.addEventListener('click', enhance);
1004
 
1005
+ resetBtn.addEventListener('click', resetAll);
1006
+
1007
+ // Keyboard shortcuts
1008
+ document.addEventListener('keydown', function(e) {
1009
+ if (e.key === 'Enter' && !enhanceBtn.disabled) {
1010
+ enhance();
1011
+ }
1012
+ if (e.key === 'Escape') {
1013
+ resetAll();
1014
+ }
1015
+ });
1016
 
1017
+ // Initial state
1018
+ console.log('✨ Face Enhancer ready!');
1019
+ </script>
1020
 
1021
  </body>
1022
  </html>