Spaces:
Running
Running
zip file download, layout centering, upload stability jitter fix
Browse files- backend/server.py +24 -0
- frontend/initial.html +10 -0
- frontend/vehicles.html +12 -19
backend/server.py
CHANGED
|
@@ -28,6 +28,7 @@ REPORT_DIR = Path(tempfile.gettempdir()) / "funky_reports"
|
|
| 28 |
REPORT_DIR.mkdir(exist_ok=True)
|
| 29 |
|
| 30 |
videos = {}
|
|
|
|
| 31 |
run_results = {}
|
| 32 |
model = None
|
| 33 |
|
|
@@ -77,6 +78,7 @@ async def upload(file: UploadFile = File(...)):
|
|
| 77 |
print(f"[BACKEND] Successfully stored: {path} ({file_size} bytes)")
|
| 78 |
|
| 79 |
videos[video_id] = str(path)
|
|
|
|
| 80 |
return {"video_id": video_id}
|
| 81 |
except Exception as e:
|
| 82 |
print(f"[BACKEND] Upload failed: {str(e)}")
|
|
@@ -135,6 +137,28 @@ def get_report(video_id: str, name: str):
|
|
| 135 |
return FileResponse(str(path), media_type=media)
|
| 136 |
|
| 137 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
@app.websocket("/ws/run")
|
| 139 |
async def ws_run(ws: WebSocket):
|
| 140 |
await ws.accept()
|
|
|
|
| 28 |
REPORT_DIR.mkdir(exist_ok=True)
|
| 29 |
|
| 30 |
videos = {}
|
| 31 |
+
video_info = {}
|
| 32 |
run_results = {}
|
| 33 |
model = None
|
| 34 |
|
|
|
|
| 78 |
print(f"[BACKEND] Successfully stored: {path} ({file_size} bytes)")
|
| 79 |
|
| 80 |
videos[video_id] = str(path)
|
| 81 |
+
video_info[video_id] = file.filename
|
| 82 |
return {"video_id": video_id}
|
| 83 |
except Exception as e:
|
| 84 |
print(f"[BACKEND] Upload failed: {str(e)}")
|
|
|
|
| 137 |
return FileResponse(str(path), media_type=media)
|
| 138 |
|
| 139 |
|
| 140 |
+
@app.get("/reports/zip/{video_id}")
|
| 141 |
+
def download_all_reports(video_id: str):
|
| 142 |
+
base_path = REPORT_DIR / video_id
|
| 143 |
+
if not base_path.exists():
|
| 144 |
+
return Response(status_code=404)
|
| 145 |
+
|
| 146 |
+
# Create zip in temp dir
|
| 147 |
+
zip_base = REPORT_DIR / f"bundle_{video_id}"
|
| 148 |
+
shutil.make_archive(str(zip_base), 'zip', str(base_path))
|
| 149 |
+
|
| 150 |
+
final_zip = Path(f"{zip_base}.zip")
|
| 151 |
+
|
| 152 |
+
original_name = video_info.get(video_id, "UrbanFlow_Analysis")
|
| 153 |
+
safe_name = "".join(x for x in original_name if x.isalnum() or x in "._-").rsplit(".", 1)[0]
|
| 154 |
+
|
| 155 |
+
return FileResponse(
|
| 156 |
+
str(final_zip),
|
| 157 |
+
media_type="application/zip",
|
| 158 |
+
filename=f"{safe_name}_UrbanFlow.zip"
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
@app.websocket("/ws/run")
|
| 163 |
async def ws_run(ws: WebSocket):
|
| 164 |
await ws.accept()
|
frontend/initial.html
CHANGED
|
@@ -213,6 +213,11 @@
|
|
| 213 |
if (name === 'upload') {
|
| 214 |
document.getElementById('upload-progress-container').classList.add('hidden');
|
| 215 |
document.getElementById('dropzone').classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
}
|
| 217 |
if (name === 'draw') loadFirstFrame();
|
| 218 |
}
|
|
@@ -236,7 +241,11 @@
|
|
| 236 |
});
|
| 237 |
}
|
| 238 |
|
|
|
|
| 239 |
function uploadFile(file) {
|
|
|
|
|
|
|
|
|
|
| 240 |
const dropzoneEl = document.getElementById('dropzone');
|
| 241 |
const prog = document.getElementById('upload-progress-container');
|
| 242 |
const bar = document.getElementById('upload-bar');
|
|
@@ -250,6 +259,7 @@
|
|
| 250 |
form.append('file', file);
|
| 251 |
|
| 252 |
const xhr = new XMLHttpRequest();
|
|
|
|
| 253 |
xhr.open('POST', '/upload');
|
| 254 |
|
| 255 |
xhr.upload.onprogress = e => {
|
|
|
|
| 213 |
if (name === 'upload') {
|
| 214 |
document.getElementById('upload-progress-container').classList.add('hidden');
|
| 215 |
document.getElementById('dropzone').classList.remove('hidden');
|
| 216 |
+
// Reset Progress Bar state for new uploads
|
| 217 |
+
document.getElementById('upload-bar').style.width = '0%';
|
| 218 |
+
document.getElementById('upload-percentage').innerText = '0%';
|
| 219 |
+
document.getElementById('upload-text').innerText = 'Uploading...';
|
| 220 |
+
document.getElementById('upload-text').classList.remove('text-red-500');
|
| 221 |
}
|
| 222 |
if (name === 'draw') loadFirstFrame();
|
| 223 |
}
|
|
|
|
| 241 |
});
|
| 242 |
}
|
| 243 |
|
| 244 |
+
let currentXHR = null;
|
| 245 |
function uploadFile(file) {
|
| 246 |
+
// Abort previous upload if it exists to prevent jitter/multiple requests
|
| 247 |
+
if (currentXHR) currentXHR.abort();
|
| 248 |
+
|
| 249 |
const dropzoneEl = document.getElementById('dropzone');
|
| 250 |
const prog = document.getElementById('upload-progress-container');
|
| 251 |
const bar = document.getElementById('upload-bar');
|
|
|
|
| 259 |
form.append('file', file);
|
| 260 |
|
| 261 |
const xhr = new XMLHttpRequest();
|
| 262 |
+
currentXHR = xhr;
|
| 263 |
xhr.open('POST', '/upload');
|
| 264 |
|
| 265 |
xhr.upload.onprogress = e => {
|
frontend/vehicles.html
CHANGED
|
@@ -798,7 +798,7 @@
|
|
| 798 |
</div>
|
| 799 |
|
| 800 |
<!-- New Analysis Button (visible only after processing completes) -->
|
| 801 |
-
<div class="col-span-3 pb-4 hidden justify-center" id="new-analysis-wrap">
|
| 802 |
<button onclick="startNewAnalysis()"
|
| 803 |
class="w-fit px-16 py-4 font-bold text-sm rounded-full transition flex items-center justify-center gap-2 shadow-lg hover:scale-105 active:scale-95"
|
| 804 |
style="background:var(--cocoa-l);color:#000">
|
|
@@ -1510,24 +1510,16 @@
|
|
| 1510 |
|
| 1511 |
// Auto-Download Logic (Respects live toggle state)
|
| 1512 |
if (document.getElementById('sv-auto-download').classList.contains('active')) {
|
| 1513 |
-
|
| 1514 |
-
|
| 1515 |
-
|
| 1516 |
-
|
| 1517 |
-
|
| 1518 |
-
|
| 1519 |
-
|
| 1520 |
-
|
| 1521 |
-
|
| 1522 |
-
|
| 1523 |
-
const link = document.createElement('a');
|
| 1524 |
-
link.href = `/reports/${d.video_id}/${targetFile}`;
|
| 1525 |
-
link.download = targetFile;
|
| 1526 |
-
document.body.appendChild(link);
|
| 1527 |
-
link.click();
|
| 1528 |
-
document.body.removeChild(link);
|
| 1529 |
-
}
|
| 1530 |
-
});
|
| 1531 |
}
|
| 1532 |
});
|
| 1533 |
}
|
|
@@ -1729,6 +1721,7 @@
|
|
| 1729 |
</div>
|
| 1730 |
|
| 1731 |
<script>
|
|
|
|
| 1732 |
function openAppModal(id) {
|
| 1733 |
const el = document.getElementById('appModal-' + id);
|
| 1734 |
if (el) { el.style.display = 'flex'; document.body.style.overflow = 'hidden'; }
|
|
|
|
| 798 |
</div>
|
| 799 |
|
| 800 |
<!-- New Analysis Button (visible only after processing completes) -->
|
| 801 |
+
<div class="col-span-3 pb-4 hidden flex justify-center" id="new-analysis-wrap">
|
| 802 |
<button onclick="startNewAnalysis()"
|
| 803 |
class="w-fit px-16 py-4 font-bold text-sm rounded-full transition flex items-center justify-center gap-2 shadow-lg hover:scale-105 active:scale-95"
|
| 804 |
style="background:var(--cocoa-l);color:#000">
|
|
|
|
| 1510 |
|
| 1511 |
// Auto-Download Logic (Respects live toggle state)
|
| 1512 |
if (document.getElementById('sv-auto-download').classList.contains('active')) {
|
| 1513 |
+
// Download the full bundle ZIP
|
| 1514 |
+
setTimeout(() => {
|
| 1515 |
+
console.log('[UrbanFlow] Auto-downloading ZIP bundle...');
|
| 1516 |
+
const link = document.createElement('a');
|
| 1517 |
+
link.href = `/reports/zip/${d.video_id}`;
|
| 1518 |
+
link.download = `UrbanFlow_Analysis.zip`;
|
| 1519 |
+
document.body.appendChild(link);
|
| 1520 |
+
link.click();
|
| 1521 |
+
document.body.removeChild(link);
|
| 1522 |
+
}, 500);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1523 |
}
|
| 1524 |
});
|
| 1525 |
}
|
|
|
|
| 1721 |
</div>
|
| 1722 |
|
| 1723 |
<script>
|
| 1724 |
+
|
| 1725 |
function openAppModal(id) {
|
| 1726 |
const el = document.getElementById('appModal-' + id);
|
| 1727 |
if (el) { el.style.display = 'flex'; document.body.style.overflow = 'hidden'; }
|