← 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
M dashboard/src/routes/+page.svelte
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 →