From 0782d90ec531778157ce38a455439feffd97dd9e Mon Sep 17 00:00:00 2001 From: Miguel Cruz Date: Thu, 12 Mar 2026 06:39:34 -0400 Subject: [PATCH] fix: show partial download progress on initial dashboard load (#1706) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Dashboard now shows partial download progress for models that were partially downloaded in a previous session, instead of showing 0% - Both `getModelDownloadStatus()` and `getInstanceDownloadStatus()` now handle `DownloadPending` entries that carry non-zero `downloaded`/`total` bytes Fixes #1042 ## Root cause When exo restarts with partially downloaded models, the `DownloadCoordinator` emits `DownloadPending` events (because `downloaded_this_session` is 0, even though real bytes exist on disk). The main page dashboard only checked for `DownloadOngoing` entries, so these partially downloaded models showed as 0%. The dedicated `/downloads` page already handled this correctly — it renders `DownloadPending` entries with a progress bar when `downloaded > 0`. This fix brings the same behavior to the main page. ## Changes For both `getModelDownloadStatus()` and `getInstanceDownloadStatus()` in `+page.svelte`: - Accept `DownloadPending` in addition to `DownloadOngoing` - For `DownloadPending` entries with `downloaded > 0` or `total > 0`, synthesize a `DownloadProgress` object from the top-level fields (with `speed: 0` and `etaMs: 0` since no active download is in progress) - Skip `DownloadPending` entries where both `downloaded` and `total` are 0 (truly pending, not yet started) ## Test plan - [ ] Partially download a model, quit exo, relaunch — dashboard should show partial progress instead of 0% - [ ] Fully downloaded models still show as complete - [ ] Active downloads still show real-time progress with speed/ETA - [ ] Models never downloaded show as not started (not falsely showing progress) - [ ] Dashboard builds without errors (`cd dashboard && npm run build`) --------- Co-authored-by: Evan --- dashboard/src/routes/+page.svelte | 77 ++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/dashboard/src/routes/+page.svelte b/dashboard/src/routes/+page.svelte index ad106b61..7f8a0382 100644 --- a/dashboard/src/routes/+page.svelte +++ b/dashboard/src/routes/+page.svelte @@ -1527,7 +1527,11 @@ downloadKind ] as Record; - if (downloadKind !== "DownloadOngoing") continue; + if ( + downloadKind !== "DownloadOngoing" && + downloadKind !== "DownloadPending" + ) + continue; if (!downloadPayload) continue; const downloadModelId = extractModelIdFromDownload(downloadPayload); @@ -1542,9 +1546,38 @@ if (downloadModelId !== modelId) continue; } - isDownloading = true; + // For DownloadPending with partial bytes (paused/resumed downloads), + // synthesize a progress object from the top-level downloaded/total fields + let progress: DownloadProgress | null; + if (downloadKind === "DownloadPending") { + const pendingDownloaded = getBytes( + downloadPayload.downloaded ?? + downloadPayload.downloaded_bytes ?? + downloadPayload.downloadedBytes, + ); + const pendingTotal = getBytes( + downloadPayload.total ?? + downloadPayload.total_bytes ?? + downloadPayload.totalBytes, + ); + if (pendingDownloaded <= 0 && pendingTotal <= 0) continue; + isDownloading = true; + progress = { + totalBytes: pendingTotal, + downloadedBytes: pendingDownloaded, + speed: 0, + etaMs: 0, + percentage: + pendingTotal > 0 ? (pendingDownloaded / pendingTotal) * 100 : 0, + completedFiles: 0, + totalFiles: 0, + files: [], + }; + } else { + isDownloading = true; + progress = parseDownloadProgress(downloadPayload); + } - const progress = parseDownloadProgress(downloadPayload); if (progress) { // Sum all values across nodes - each node downloads independently totalBytes += progress.totalBytes; @@ -1696,7 +1729,11 @@ } } - if (downloadKind !== "DownloadOngoing") continue; + if ( + downloadKind !== "DownloadOngoing" && + downloadKind !== "DownloadPending" + ) + continue; if (!downloadPayload) continue; // Check if this download is for this instance's model @@ -1706,9 +1743,37 @@ downloadModelId && downloadModelId === instanceModelId ) { - isDownloading = true; + // For DownloadPending with partial bytes, synthesize progress + let progress: DownloadProgress | null; + if (downloadKind === "DownloadPending") { + const pendingDownloaded = getBytes( + downloadPayload.downloaded ?? + downloadPayload.downloaded_bytes ?? + downloadPayload.downloadedBytes, + ); + const pendingTotal = getBytes( + downloadPayload.total ?? + downloadPayload.total_bytes ?? + downloadPayload.totalBytes, + ); + if (pendingDownloaded <= 0 && pendingTotal <= 0) continue; + isDownloading = true; + progress = { + totalBytes: pendingTotal, + downloadedBytes: pendingDownloaded, + speed: 0, + etaMs: 0, + percentage: + pendingTotal > 0 ? (pendingDownloaded / pendingTotal) * 100 : 0, + completedFiles: 0, + totalFiles: 0, + files: [], + }; + } else { + isDownloading = true; + progress = parseDownloadProgress(downloadPayload); + } - const progress = parseDownloadProgress(downloadPayload); if (progress) { // Sum all values across nodes - each node downloads independently totalBytes += progress.totalBytes;