prithivMLmods commited on
Commit
a4e0b7e
·
verified ·
1 Parent(s): 3f5a8a6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -61
app.py CHANGED
@@ -5,9 +5,9 @@ import uuid
5
  import json
6
  import base64
7
  import random
 
8
  import threading
9
  import concurrent.futures
10
- import subprocess
11
  from pathlib import Path
12
  from typing import List, Optional
13
 
@@ -18,7 +18,7 @@ from PIL import Image
18
 
19
  from gradio import Server
20
  from fastapi import Request, UploadFile, File, Form
21
- from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
22
  from diffusers import Flux2KleinPipeline, AutoencoderKLFlux2
23
 
24
  # --- App Configuration & Directories ---
@@ -214,6 +214,32 @@ async def download_file(filename: str):
214
  return JSONResponse({"error": "File not found"}, status_code=404)
215
  return FileResponse(path, filename=filename, media_type="image/png")
216
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  @app.post("/api/compare")
218
  async def compare_images(
219
  prompt: str = Form(...),
@@ -258,6 +284,8 @@ async def compare_images(
258
  "seed": used_seed,
259
  "std_url": f"/download/{std_filename}",
260
  "small_url": f"/download/{small_filename}",
 
 
261
  "device": DEVICE_LABEL,
262
  })
263
 
@@ -365,6 +393,9 @@ async def homepage(request: Request):
365
  font-weight: 500;
366
  font-size: 1.1rem;
367
  flex-shrink: 0;
 
 
 
368
  }}
369
 
370
  .panel-body-scroll {{
@@ -437,7 +468,6 @@ async def homepage(request: Request):
437
  cursor: pointer; font-size: 12px;
438
  }}
439
 
440
- /* The (+) Button */
441
  .add-more-btn {{
442
  display: flex; align-items: center; justify-content: center;
443
  border: 2px dashed var(--ub-muted); border-radius: 4px;
@@ -461,40 +491,23 @@ async def homepage(request: Request):
461
  .advanced-body.open {{ display: block; }}
462
  .grid-2 {{ display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }}
463
 
464
- /* Status Container (Execution Log) */
465
  .status-container {{
466
- margin-top: 20px;
467
- margin-bottom: 20px;
468
- border: 1px solid var(--ub-border);
469
- border-radius: 4px;
470
- background: #200014; /* deep ubuntu terminal background */
471
- display: flex;
472
- flex-direction: column;
473
- flex: 1;
474
- min-height: 100px;
475
- max-height: 200px; /* limits expansion if advanced is closed */
476
  }}
477
  .status-header {{
478
- padding: 8px 12px;
479
- font-size: 11px;
480
- font-weight: 700;
481
- color: var(--ub-muted);
482
- background: rgba(0,0,0,0.4);
483
- border-bottom: 1px solid var(--ub-border);
484
- text-transform: uppercase;
485
- letter-spacing: 0.5px;
486
- flex-shrink: 0;
487
  }}
488
  .status-log {{
489
- padding: 10px;
490
- font-family: 'Courier New', Courier, monospace;
491
- font-size: 12px;
492
- color: #eeeeee;
493
- overflow-y: auto;
494
- flex: 1;
495
- display: flex;
496
- flex-direction: column;
497
- gap: 4px;
498
  }}
499
  .log-time {{ color: #777; margin-right: 8px; }}
500
  .log-info {{ color: #5bc0eb; }}
@@ -505,8 +518,7 @@ async def homepage(request: Request):
505
  .btn {{
506
  width: 100%; padding: 14px; border: none; border-radius: 4px;
507
  font-size: 16px; font-weight: 700; cursor: pointer;
508
- transition: opacity 0.2s, background 0.2s;
509
- flex-shrink: 0;
510
  }}
511
  .btn-primary {{
512
  background: var(--ub-orange); color: white;
@@ -515,42 +527,39 @@ async def homepage(request: Request):
515
  .btn-primary:hover {{ background: var(--ub-orange-hover); }}
516
  .btn:disabled {{ opacity: 0.6; cursor: not-allowed; }}
517
 
 
 
 
 
 
 
 
518
  /* SLIDER CONTAINER */
519
  .panel-body-slider {{
520
- flex: 1;
521
- display: flex;
522
- flex-direction: column;
523
- padding: 0;
524
- position: relative;
525
  }}
526
  .slider-stage {{
527
- position: absolute;
528
- top: 0; left: 0; right: 0; bottom: 0;
529
- background: #111;
530
- overflow: hidden;
531
- display: flex;
532
- align-items: center;
533
- justify-content: center;
534
  }}
535
  .slider-empty {{ color: var(--ub-muted); text-align: center; z-index: 1; }}
536
 
537
  .slider-img {{
538
- position: absolute; top: 0; left: 0;
539
- width: 100%; height: 100%; object-fit: contain;
540
- display: none; user-select: none; -webkit-user-drag: none;
541
  }}
542
  #imgSmall {{ clip-path: inset(0 50% 0 0); }}
543
 
544
  .slider-handle {{
545
  position: absolute; left: 50%; top: 0; bottom: 0;
546
- width: 4px; background: var(--ub-orange);
547
- cursor: ew-resize; display: none; z-index: 10;
548
  }}
549
  .slider-handle::after {{
550
  content: '◀ ▶'; position: absolute; top: 50%; left: 50%;
551
- transform: translate(-50%, -50%);
552
- width: 40px; height: 30px; background: var(--ub-orange);
553
- color: white; border-radius: 15px;
554
  display: flex; align-items: center; justify-content: center;
555
  font-size: 10px; font-weight: bold; box-shadow: 0 2px 6px rgba(0,0,0,0.5);
556
  }}
@@ -567,8 +576,7 @@ async def homepage(request: Request):
567
 
568
  .loader {{
569
  position: absolute; inset: 0; background: rgba(0,0,0,0.7);
570
- display: none; flex-direction: column;
571
- align-items: center; justify-content: center; z-index: 20;
572
  }}
573
  .spinner {{
574
  width: 40px; height: 40px; border: 4px solid rgba(255,255,255,0.2);
@@ -587,7 +595,6 @@ async def homepage(request: Request):
587
  cursor: pointer; transition: transform 0.2s, box-shadow 0.2s;
588
  }}
589
  .ex-card:hover {{ transform: translateY(-3px); box-shadow: 0 6px 16px rgba(0,0,0,0.3); }}
590
-
591
  .ex-card-img-wrap {{ width: 100%; aspect-ratio: 1; display: flex; background: #000; }}
592
  .ex-card-img-wrap img {{ height: 100%; object-fit: cover; }}
593
  .ex-card p {{ padding: 12px; margin: 0; font-size: 13px; color: var(--ub-muted); line-height: 1.4; }}
@@ -675,7 +682,16 @@ async def homepage(request: Request):
675
  </div>
676
 
677
  <div class="panel">
678
- <div class="panel-header">Comparison View</div>
 
 
 
 
 
 
 
 
 
679
  <div class="panel-body-slider">
680
  <div class="slider-stage" id="sliderStage">
681
  <div class="slider-empty" id="sliderEmpty">
@@ -713,6 +729,8 @@ async def homepage(request: Request):
713
  <script>
714
  const examples = {examples_json};
715
  let filesState = [];
 
 
716
 
717
  // UI Elements
718
  const dropZone = document.getElementById('dropZone');
@@ -721,6 +739,7 @@ async def homepage(request: Request):
721
  const uploadText = document.getElementById('uploadText');
722
  const promptInput = document.getElementById('promptInput');
723
  const runBtn = document.getElementById('runBtn');
 
724
 
725
  // Status Log
726
  const statusLog = document.getElementById('statusLog');
@@ -807,10 +826,8 @@ async def homepage(request: Request):
807
  }}
808
  renderPreviews();
809
 
810
- // Scroll to top
811
  window.scrollTo({{top: 0, behavior: 'smooth'}});
812
 
813
- // Wait a tiny bit for UI update, then trigger generation
814
  setTimeout(() => {{
815
  logMsg("Example loaded. Starting comparison...", "log-info");
816
  runBtn.click();
@@ -829,7 +846,6 @@ async def homepage(request: Request):
829
 
830
  let imgHTML = '';
831
  if(ex.urls.length > 1) {{
832
- // Split view for multiple image examples
833
  imgHTML = `
834
  <div class="ex-card-img-wrap">
835
  <img src="${{ex.urls[0]}}" style="width:50%; border-right:1px solid #000;">
@@ -871,6 +887,13 @@ async def homepage(request: Request):
871
  updateSlider(e.touches[0].clientX);
872
  }});
873
 
 
 
 
 
 
 
 
874
  // --- Form Submission ---
875
  runBtn.onclick = async () => {{
876
  const prompt = promptInput.value.trim();
@@ -894,6 +917,7 @@ async def homepage(request: Request):
894
 
895
  loader.style.display = 'flex';
896
  runBtn.disabled = true;
 
897
 
898
  logMsg("Sending request to backend. Running both VAE models...", "log-info");
899
 
@@ -904,6 +928,9 @@ async def homepage(request: Request):
904
  if(data.success) {{
905
  logMsg(`Success! Inference completed. Used seed: ${{data.seed}}`, "log-success");
906
 
 
 
 
907
  imgStd.src = data.std_url;
908
  imgSmall.src = data.small_url;
909
 
@@ -913,6 +940,7 @@ async def homepage(request: Request):
913
  imgSmall.style.display = 'block';
914
  sliderHandle.style.display = 'block';
915
  sliderLabels.style.display = 'flex';
 
916
 
917
  // Reset slider to center
918
  const rect = sliderStage.getBoundingClientRect();
 
5
  import json
6
  import base64
7
  import random
8
+ import zipfile
9
  import threading
10
  import concurrent.futures
 
11
  from pathlib import Path
12
  from typing import List, Optional
13
 
 
18
 
19
  from gradio import Server
20
  from fastapi import Request, UploadFile, File, Form
21
+ from fastapi.responses import HTMLResponse, JSONResponse, FileResponse, StreamingResponse
22
  from diffusers import Flux2KleinPipeline, AutoencoderKLFlux2
23
 
24
  # --- App Configuration & Directories ---
 
214
  return JSONResponse({"error": "File not found"}, status_code=404)
215
  return FileResponse(path, filename=filename, media_type="image/png")
216
 
217
+ @app.get("/api/download-zip")
218
+ async def download_zip(std: str, small: str):
219
+ """Packages both generated images into a single ZIP file and streams it."""
220
+ # Prevent path traversal by taking only the filename
221
+ std_name = Path(std).name
222
+ small_name = Path(small).name
223
+
224
+ std_path = OUTPUT_DIR / std_name
225
+ small_path = OUTPUT_DIR / small_name
226
+
227
+ if not std_path.exists() or not small_path.exists():
228
+ return JSONResponse({"error": "Generated files not found"}, status_code=404)
229
+
230
+ memory_file = io.BytesIO()
231
+ with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf:
232
+ zf.write(std_path, arcname=f"Standard_Decoder_{std_name}")
233
+ zf.write(small_path, arcname=f"Small_Decoder_{small_name}")
234
+
235
+ memory_file.seek(0)
236
+
237
+ return StreamingResponse(
238
+ memory_file,
239
+ media_type="application/zip",
240
+ headers={"Content-Disposition": f"attachment; filename=Flux2_Comparison_{uuid.uuid4().hex[:6]}.zip"}
241
+ )
242
+
243
  @app.post("/api/compare")
244
  async def compare_images(
245
  prompt: str = Form(...),
 
284
  "seed": used_seed,
285
  "std_url": f"/download/{std_filename}",
286
  "small_url": f"/download/{small_filename}",
287
+ "std_filename": std_filename,
288
+ "small_filename": small_filename,
289
  "device": DEVICE_LABEL,
290
  })
291
 
 
393
  font-weight: 500;
394
  font-size: 1.1rem;
395
  flex-shrink: 0;
396
+ display: flex;
397
+ justify-content: space-between;
398
+ align-items: center;
399
  }}
400
 
401
  .panel-body-scroll {{
 
468
  cursor: pointer; font-size: 12px;
469
  }}
470
 
 
471
  .add-more-btn {{
472
  display: flex; align-items: center; justify-content: center;
473
  border: 2px dashed var(--ub-muted); border-radius: 4px;
 
491
  .advanced-body.open {{ display: block; }}
492
  .grid-2 {{ display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }}
493
 
494
+ /* Status Container */
495
  .status-container {{
496
+ margin-top: 20px; margin-bottom: 20px;
497
+ border: 1px solid var(--ub-border); border-radius: 4px;
498
+ background: #200014; display: flex; flex-direction: column;
499
+ flex: 1; min-height: 100px; max-height: 200px;
 
 
 
 
 
 
500
  }}
501
  .status-header {{
502
+ padding: 8px 12px; font-size: 11px; font-weight: 700;
503
+ color: var(--ub-muted); background: rgba(0,0,0,0.4);
504
+ border-bottom: 1px solid var(--ub-border); text-transform: uppercase;
505
+ letter-spacing: 0.5px; flex-shrink: 0;
 
 
 
 
 
506
  }}
507
  .status-log {{
508
+ padding: 10px; font-family: 'Courier New', Courier, monospace;
509
+ font-size: 12px; color: #eeeeee; overflow-y: auto;
510
+ flex: 1; display: flex; flex-direction: column; gap: 4px;
 
 
 
 
 
 
511
  }}
512
  .log-time {{ color: #777; margin-right: 8px; }}
513
  .log-info {{ color: #5bc0eb; }}
 
518
  .btn {{
519
  width: 100%; padding: 14px; border: none; border-radius: 4px;
520
  font-size: 16px; font-weight: 700; cursor: pointer;
521
+ transition: opacity 0.2s, background 0.2s; flex-shrink: 0;
 
522
  }}
523
  .btn-primary {{
524
  background: var(--ub-orange); color: white;
 
527
  .btn-primary:hover {{ background: var(--ub-orange-hover); }}
528
  .btn:disabled {{ opacity: 0.6; cursor: not-allowed; }}
529
 
530
+ /* Top-Right Download Icon */
531
+ .action-icon {{
532
+ display: none; background: none; border: none; color: var(--ub-muted);
533
+ cursor: pointer; padding: 4px; transition: color 0.2s;
534
+ }}
535
+ .action-icon:hover {{ color: var(--ub-orange); }}
536
+
537
  /* SLIDER CONTAINER */
538
  .panel-body-slider {{
539
+ flex: 1; display: flex; flex-direction: column;
540
+ padding: 0; position: relative;
 
 
 
541
  }}
542
  .slider-stage {{
543
+ position: absolute; top: 0; left: 0; right: 0; bottom: 0;
544
+ background: #111; overflow: hidden; display: flex;
545
+ align-items: center; justify-content: center;
 
 
 
 
546
  }}
547
  .slider-empty {{ color: var(--ub-muted); text-align: center; z-index: 1; }}
548
 
549
  .slider-img {{
550
+ position: absolute; top: 0; left: 0; width: 100%; height: 100%;
551
+ object-fit: contain; display: none; user-select: none; -webkit-user-drag: none;
 
552
  }}
553
  #imgSmall {{ clip-path: inset(0 50% 0 0); }}
554
 
555
  .slider-handle {{
556
  position: absolute; left: 50%; top: 0; bottom: 0;
557
+ width: 4px; background: var(--ub-orange); cursor: ew-resize; display: none; z-index: 10;
 
558
  }}
559
  .slider-handle::after {{
560
  content: '◀ ▶'; position: absolute; top: 50%; left: 50%;
561
+ transform: translate(-50%, -50%); width: 40px; height: 30px;
562
+ background: var(--ub-orange); color: white; border-radius: 15px;
 
563
  display: flex; align-items: center; justify-content: center;
564
  font-size: 10px; font-weight: bold; box-shadow: 0 2px 6px rgba(0,0,0,0.5);
565
  }}
 
576
 
577
  .loader {{
578
  position: absolute; inset: 0; background: rgba(0,0,0,0.7);
579
+ display: none; flex-direction: column; align-items: center; justify-content: center; z-index: 20;
 
580
  }}
581
  .spinner {{
582
  width: 40px; height: 40px; border: 4px solid rgba(255,255,255,0.2);
 
595
  cursor: pointer; transition: transform 0.2s, box-shadow 0.2s;
596
  }}
597
  .ex-card:hover {{ transform: translateY(-3px); box-shadow: 0 6px 16px rgba(0,0,0,0.3); }}
 
598
  .ex-card-img-wrap {{ width: 100%; aspect-ratio: 1; display: flex; background: #000; }}
599
  .ex-card-img-wrap img {{ height: 100%; object-fit: cover; }}
600
  .ex-card p {{ padding: 12px; margin: 0; font-size: 13px; color: var(--ub-muted); line-height: 1.4; }}
 
682
  </div>
683
 
684
  <div class="panel">
685
+ <div class="panel-header">
686
+ <span>Comparison View</span>
687
+ <button id="downloadZipBtn" class="action-icon" title="Download Both Images (ZIP)">
688
+ <svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
689
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
690
+ <polyline points="7 10 12 15 17 10"></polyline>
691
+ <line x1="12" y1="15" x2="12" y2="3"></line>
692
+ </svg>
693
+ </button>
694
+ </div>
695
  <div class="panel-body-slider">
696
  <div class="slider-stage" id="sliderStage">
697
  <div class="slider-empty" id="sliderEmpty">
 
729
  <script>
730
  const examples = {examples_json};
731
  let filesState = [];
732
+ let currentStdFilename = "";
733
+ let currentSmallFilename = "";
734
 
735
  // UI Elements
736
  const dropZone = document.getElementById('dropZone');
 
739
  const uploadText = document.getElementById('uploadText');
740
  const promptInput = document.getElementById('promptInput');
741
  const runBtn = document.getElementById('runBtn');
742
+ const downloadZipBtn = document.getElementById('downloadZipBtn');
743
 
744
  // Status Log
745
  const statusLog = document.getElementById('statusLog');
 
826
  }}
827
  renderPreviews();
828
 
 
829
  window.scrollTo({{top: 0, behavior: 'smooth'}});
830
 
 
831
  setTimeout(() => {{
832
  logMsg("Example loaded. Starting comparison...", "log-info");
833
  runBtn.click();
 
846
 
847
  let imgHTML = '';
848
  if(ex.urls.length > 1) {{
 
849
  imgHTML = `
850
  <div class="ex-card-img-wrap">
851
  <img src="${{ex.urls[0]}}" style="width:50%; border-right:1px solid #000;">
 
887
  updateSlider(e.touches[0].clientX);
888
  }});
889
 
890
+ // --- Download Zip Logic ---
891
+ downloadZipBtn.onclick = () => {{
892
+ if(!currentStdFilename || !currentSmallFilename) return;
893
+ logMsg("Initiating ZIP download...", "log-info");
894
+ window.location.href = `/api/download-zip?std=${{currentStdFilename}}&small=${{currentSmallFilename}}`;
895
+ }};
896
+
897
  // --- Form Submission ---
898
  runBtn.onclick = async () => {{
899
  const prompt = promptInput.value.trim();
 
917
 
918
  loader.style.display = 'flex';
919
  runBtn.disabled = true;
920
+ downloadZipBtn.style.display = 'none';
921
 
922
  logMsg("Sending request to backend. Running both VAE models...", "log-info");
923
 
 
928
  if(data.success) {{
929
  logMsg(`Success! Inference completed. Used seed: ${{data.seed}}`, "log-success");
930
 
931
+ currentStdFilename = data.std_filename;
932
+ currentSmallFilename = data.small_filename;
933
+
934
  imgStd.src = data.std_url;
935
  imgSmall.src = data.small_url;
936
 
 
940
  imgSmall.style.display = 'block';
941
  sliderHandle.style.display = 'block';
942
  sliderLabels.style.display = 'flex';
943
+ downloadZipBtn.style.display = 'block'; // Reveal download button
944
 
945
  // Reset slider to center
946
  const rect = sliderStage.getBoundingClientRect();