Sanjana Vekhande commited on
Commit
b051edc
·
1 Parent(s): 98edf11

fix: wire up real backend, /api/ prefix, blob download

Browse files
Files changed (2) hide show
  1. backend/main.py +3 -3
  2. frontend/index.html +83 -24
backend/main.py CHANGED
@@ -19,7 +19,7 @@ app = FastAPI()
19
  jobs: dict = {}
20
 
21
 
22
- @app.post("/process")
23
  async def process(
24
  model_files: list[UploadFile] = File(...),
25
  mapping_files: list[UploadFile] = File(...),
@@ -140,7 +140,7 @@ async def run_bootstrap(job_id, model_paths, mapping_paths, expr_path, species_p
140
  print(traceback.format_exc(), flush=True)
141
 
142
 
143
- @app.get("/progress/{job_id}")
144
  async def progress_stream(job_id: str):
145
  async def event_stream():
146
  last_idx = 0
@@ -181,7 +181,7 @@ async def progress_stream(job_id: str):
181
  )
182
 
183
 
184
- @app.get("/download/{job_id}")
185
  async def download(job_id: str):
186
  job = jobs.get(job_id, {})
187
  zip_path = job.get("output_zip")
 
19
  jobs: dict = {}
20
 
21
 
22
+ @app.post("/api/process")
23
  async def process(
24
  model_files: list[UploadFile] = File(...),
25
  mapping_files: list[UploadFile] = File(...),
 
140
  print(traceback.format_exc(), flush=True)
141
 
142
 
143
+ @app.get("/api/progress/{job_id}")
144
  async def progress_stream(job_id: str):
145
  async def event_stream():
146
  last_idx = 0
 
181
  )
182
 
183
 
184
+ @app.get("/api/download/{job_id}")
185
  async def download(job_id: str):
186
  job = jobs.get(job_id, {})
187
  zip_path = job.get("output_zip")
frontend/index.html CHANGED
@@ -649,9 +649,9 @@ footer a:hover { color: var(--ink); }
649
  <div class="stat-label">Species processed</div>
650
  </div>
651
  </div>
652
- <a class="btn-download" id="downloadBtn" href="#" download="geneExpr_bootstrapped.zip">
653
  ⬇ Download Bootstrapped ZIP
654
- </a>
655
  </div>
656
  </div>
657
 
@@ -716,6 +716,8 @@ function handleFiles(type, files) {
716
  el.addEventListener('drop', () => el.style.borderColor = '');
717
  });
718
 
 
 
719
  async function startJob() {
720
  if (!state.model.length) { alert('Please upload at least one model file.'); return; }
721
  if (!state.mapping.length) { alert('Please upload at least one mapping file.'); return; }
@@ -726,30 +728,87 @@ async function startJob() {
726
  document.getElementById('progressSection').classList.remove('hidden');
727
  document.getElementById('resultsSection').classList.add('hidden');
728
  document.getElementById('logBox').innerHTML = '';
729
-
730
- const steps = [
731
- [10, 'info', 'Files received. Starting bootstrap…'],
732
- [30, 'info', `Bootstrapping genes for ${state.model.map(f => f.name.split('_')[0]).join(', ')}…`],
733
- [55, '', 'Sampling from metabolic subsystems…'],
734
- [80, '', 'Writing output files…'],
735
- [95, '', 'Compressing ZIP archive…'],
736
- [100, 'ok', `Done! ${Number(document.getElementById('batchSlider').value).toLocaleString()} files generated.`],
737
- ];
738
-
739
- for (const [pct, cls, msg] of steps) {
740
- await new Promise(r => setTimeout(r, 700));
741
- document.getElementById('progressBar').style.width = pct + '%';
742
- document.getElementById('progressPct').textContent = pct + '%';
743
- document.getElementById('progressLabel').textContent = msg;
744
- appendLog(msg, cls);
 
 
 
 
 
 
 
745
  }
746
 
747
- await new Promise(r => setTimeout(r, 400));
748
- document.getElementById('statFiles').textContent =
749
- Number(document.getElementById('batchSlider').value).toLocaleString();
750
- document.getElementById('statSpecies').textContent = state.model.length;
751
- document.getElementById('resultsSection').classList.remove('hidden');
752
- document.getElementById('runBtn').disabled = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
753
  }
754
 
755
  function appendLog(text, cls = '') {
 
649
  <div class="stat-label">Species processed</div>
650
  </div>
651
  </div>
652
+ <button class="btn-download" id="downloadBtn" onclick="triggerDownload()">
653
  ⬇ Download Bootstrapped ZIP
654
+ </button>
655
  </div>
656
  </div>
657
 
 
716
  el.addEventListener('drop', () => el.style.borderColor = '');
717
  });
718
 
719
+ let activeJobId = null;
720
+
721
  async function startJob() {
722
  if (!state.model.length) { alert('Please upload at least one model file.'); return; }
723
  if (!state.mapping.length) { alert('Please upload at least one mapping file.'); return; }
 
728
  document.getElementById('progressSection').classList.remove('hidden');
729
  document.getElementById('resultsSection').classList.add('hidden');
730
  document.getElementById('logBox').innerHTML = '';
731
+ document.getElementById('progressBar').style.width = '0%';
732
+ document.getElementById('progressPct').textContent = '0%';
733
+ document.getElementById('progressLabel').textContent = 'Starting…';
734
+
735
+ // Build form data
736
+ const fd = new FormData();
737
+ state.model.forEach(f => fd.append('model_files', f));
738
+ state.mapping.forEach(f => fd.append('mapping_files', f));
739
+ fd.append('expr_file', state.expr[0]);
740
+ fd.append('batch_count', document.getElementById('batchSlider').value);
741
+
742
+ // Submit job
743
+ let jobId;
744
+ try {
745
+ const res = await fetch('/api/process', { method: 'POST', body: fd });
746
+ if (!res.ok) throw new Error((await res.json()).detail || res.statusText);
747
+ jobId = (await res.json()).job_id;
748
+ activeJobId = jobId;
749
+ appendLog('Files received. Starting bootstrap…', 'info');
750
+ } catch (e) {
751
+ appendLog('Error: ' + e.message, 'err');
752
+ document.getElementById('runBtn').disabled = false;
753
+ return;
754
  }
755
 
756
+ // Stream progress via SSE
757
+ const sse = new EventSource(`/api/progress/${jobId}`);
758
+
759
+ sse.onmessage = e => {
760
+ const d = JSON.parse(e.data);
761
+
762
+ if (d.message) appendLog(d.message, d.type || '');
763
+
764
+ if (d.progress !== undefined) {
765
+ document.getElementById('progressBar').style.width = d.progress + '%';
766
+ document.getElementById('progressPct').textContent = d.progress + '%';
767
+ if (d.label) document.getElementById('progressLabel').textContent = d.label;
768
+ }
769
+
770
+ if (d.status === 'done') {
771
+ sse.close();
772
+ document.getElementById('statFiles').textContent = (d.file_count || '—').toLocaleString();
773
+ document.getElementById('statSpecies').textContent = d.species_count || '—';
774
+ document.getElementById('resultsSection').classList.remove('hidden');
775
+ document.getElementById('runBtn').disabled = false;
776
+ // Store job id for download
777
+ document.getElementById('downloadBtn').dataset.jobId = jobId;
778
+ }
779
+
780
+ if (d.status === 'error') {
781
+ sse.close();
782
+ appendLog('Fatal error: ' + d.error, 'err');
783
+ document.getElementById('runBtn').disabled = false;
784
+ }
785
+ };
786
+
787
+ sse.onerror = () => {
788
+ sse.close();
789
+ appendLog('Connection lost. Check logs.', 'err');
790
+ document.getElementById('runBtn').disabled = false;
791
+ };
792
+ }
793
+
794
+ async function triggerDownload() {
795
+ const jobId = document.getElementById('downloadBtn').dataset.jobId;
796
+ if (!jobId) return;
797
+ try {
798
+ const res = await fetch(`/api/download/${jobId}`);
799
+ if (!res.ok) throw new Error('Download failed');
800
+ const blob = await res.blob();
801
+ const url = URL.createObjectURL(blob);
802
+ const a = document.createElement('a');
803
+ a.href = url;
804
+ a.download = 'geneExpr_bootstrapped.zip';
805
+ document.body.appendChild(a);
806
+ a.click();
807
+ document.body.removeChild(a);
808
+ URL.revokeObjectURL(url);
809
+ } catch (e) {
810
+ alert('Download failed: ' + e.message);
811
+ }
812
  }
813
 
814
  function appendLog(text, cls = '') {