[object Object]

← back to Exo

fix: show partial download progress on initial dashboard load (#1706)

0782d90ec531778157ce38a455439feffd97dd9e · 2026-03-12 06:39:34 -0400 · Miguel Cruz

## 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 <evanev7@gmail.com>

Files touched

Diff

commit 0782d90ec531778157ce38a455439feffd97dd9e
Author: Miguel Cruz <miguelc4600@gmail.com>
Date:   Thu Mar 12 06:39:34 2026 -0400

    fix: show partial download progress on initial dashboard load (#1706)
    
    ## 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 <evanev7@gmail.com>
---
 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<string, unknown>;
 
-        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;

← f221a6c8 Normalise Responses API tool call format (#1704)  ·  back to Exo  ·  fix: guard against ZeroDivisionError in mlx_lm stats (#1707) ea18a625 →