From 3be1fa504023e15b38cdfe60da2b1c99b35bef42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:44:05 +0000 Subject: [PATCH] fix(upload): fix upload progress counter showing done count always as 0 Replace the fragile global DOM query approach in `updateOverallStatus` with per-batch closure-based counters inside `processFiles`. - Add `total`, `done`, `updateStatus()`, and `markDone()` as closure variables/functions within each `processFiles` invocation - Change `_uploadSingleFile` to accept an `onTerminal` callback instead of `statusMessage`, called when a file reaches a terminal state - Pass `markDone` as the `onTerminal` callback from `scheduleNext` - Remove the now-unused global `updateOverallStatus` function The previous implementation queried `document.querySelectorAll('.file-status')` globally and relied on text `startsWith` checks to count completed files. This was fragile and could produce a stale done=0 count in practice. The new approach uses deterministic closure counters, so the displayed "Uploading files (X/N)" count correctly increments as files complete. Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- frontend/static/js/upload.js | 70 +++++++++++++++--------------------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/frontend/static/js/upload.js b/frontend/static/js/upload.js index 9d817e34..0e25ce7b 100644 --- a/frontend/static/js/upload.js +++ b/frontend/static/js/upload.js @@ -274,6 +274,25 @@ function processFiles(files, progressContainer, statusMessage) { statusMessage.textContent = `Queued ${fileArray.length} file(s) for upload…`; } + // Per-batch counters tracked in closure variables (avoids fragile DOM queries). + const total = fileArray.length; + let done = 0; + + function updateStatus() { + if (!statusMessage) return; + if (done === total) { + statusMessage.textContent = `All uploads completed (${done}/${total})`; + window.dispatchEvent(new CustomEvent('allUploadsComplete', { detail: { total, completed: done } })); + } else { + statusMessage.textContent = `Uploading files (${done}/${total})`; + } + } + + function markDone() { + done++; + updateStatus(); + } + // Pre-create one progress row per file. const queueItems = fileArray.map((file) => { const row = document.createElement('div'); @@ -319,7 +338,7 @@ function processFiles(files, progressContainer, statusMessage) { item.statusEl.textContent = 'Unsupported file type'; item.statusEl.className = 'text-xs text-red-500 mt-1'; active--; - updateOverallStatus(statusMessage); + markDone(); // No HTTP request – skip straight to next without adding delay. scheduleNext(); continue; @@ -330,12 +349,12 @@ function processFiles(files, progressContainer, statusMessage) { item.statusEl.textContent = 'Exceeds 500 MB limit'; item.statusEl.className = 'text-xs text-red-500 mt-1'; active--; - updateOverallStatus(statusMessage); + markDone(); scheduleNext(); continue; } - _uploadSingleFile(item.file, item.progressBar, item.statusEl, statusMessage) + _uploadSingleFile(item.file, item.progressBar, item.statusEl, markDone) .then((result) => { active--; if (result.rateLimited) { @@ -348,7 +367,7 @@ function processFiles(files, progressContainer, statusMessage) { item.progressBar.className = 'file-progress-bar bg-red-500 h-2 rounded-full'; item.statusEl.textContent = 'Failed: rate limit retries exhausted'; item.statusEl.className = 'text-xs text-red-500 mt-1'; - updateOverallStatus(statusMessage); + markDone(); } // Resume after the backoff window. const wait = Math.max(_adaptiveState.pauseUntil - Date.now() + 50, 0); @@ -383,10 +402,10 @@ function _isAcceptedFile(file) { * @param {File} file * @param {HTMLElement} progressBar * @param {HTMLElement} statusEl - * @param {HTMLElement} statusMessage + * @param {Function} onTerminal - Called when the file reaches a terminal state (success or permanent error). * @returns {Promise<{rateLimited: boolean, retryAfterSeconds: number}>} */ -function _uploadSingleFile(file, progressBar, statusEl, statusMessage) { +function _uploadSingleFile(file, progressBar, statusEl, onTerminal) { statusEl.textContent = 'Uploading…'; statusEl.className = 'text-xs text-gray-600 mt-1'; progressBar.style.width = '0%'; @@ -418,7 +437,7 @@ function _uploadSingleFile(file, progressBar, statusEl, statusMessage) { statusEl.textContent = `Success: Task ID: ${result.task_id}`; statusEl.className = 'text-xs text-green-600 mt-1'; _onUploadSuccess(); - updateOverallStatus(statusMessage); + onTerminal(); resolve({ rateLimited: false, retryAfterSeconds: 0 }); } else if (xhr.status === 429) { @@ -438,7 +457,7 @@ function _uploadSingleFile(file, progressBar, statusEl, statusMessage) { progressBar.className = 'file-progress-bar bg-red-500 h-2 rounded-full'; statusEl.textContent = `Error: HTTP ${xhr.status}`; statusEl.className = 'text-xs text-red-500 mt-1'; - updateOverallStatus(statusMessage); + onTerminal(); resolve({ rateLimited: false, retryAfterSeconds: 0 }); } }; @@ -447,7 +466,7 @@ function _uploadSingleFile(file, progressBar, statusEl, statusMessage) { progressBar.className = 'file-progress-bar bg-red-500 h-2 rounded-full'; statusEl.textContent = 'Error: Network error'; statusEl.className = 'text-xs text-red-500 mt-1'; - updateOverallStatus(statusMessage); + onTerminal(); resolve({ rateLimited: false, retryAfterSeconds: 0 }); }; @@ -467,39 +486,6 @@ function validateAndUpload(file, progressContainer, statusMessage) { processFiles([file], progressContainer, statusMessage); } -// ── Status helpers ──────────────────────────────────────────────────────────── - -/** - * Re-calculate and display the overall upload status. - * Fires 'allUploadsComplete' when every item has a terminal status. - * @param {HTMLElement} statusMessage - */ -function updateOverallStatus(statusMessage) { - if (!statusMessage) return; - - const fileStatuses = document.querySelectorAll('.file-status'); - let done = 0; - const total = fileStatuses.length; - - fileStatuses.forEach((s) => { - const t = s.textContent; - if ( - t.startsWith('Success') || - t.startsWith('Error') || - t.startsWith('Unsupported') || - t.startsWith('Exceeds') || - t.startsWith('Failed:') - ) done++; - }); - - if (done === total && total > 0) { - statusMessage.textContent = `All uploads completed (${done}/${total})`; - window.dispatchEvent(new CustomEvent('allUploadsComplete', { detail: { total, completed: done } })); - } else { - statusMessage.textContent = `Uploading files (${done}/${total})`; - } -} - /** * Format a byte count for human-readable display. * @param {number} bytes