← back to Exo
fix: add downloaded_bytes to DownloadPending event (#1564)
6b54a270198a1c554ec67039b796fb96ed77cd45 · 2026-02-20 09:54:18 -0800 · Alex Cheema
## Summary
- Add downloaded_bytes field to existing DownloadPending event for
accurate resume progress
- Minimal change per maintainer directive — no new download states
introduced
## Test plan
- [x] 42 tests passed, 1 skipped
- [x] Verified downloaded_bytes populates correctly for partial
downloads
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: rltakashige <rl.takashige@gmail.com>
Files touched
M dashboard/src/routes/downloads/+page.svelteM src/exo/download/coordinator.pyM src/exo/shared/types/worker/downloads.py
Diff
commit 6b54a270198a1c554ec67039b796fb96ed77cd45
Author: Alex Cheema <41707476+AlexCheema@users.noreply.github.com>
Date: Fri Feb 20 09:54:18 2026 -0800
fix: add downloaded_bytes to DownloadPending event (#1564)
## Summary
- Add downloaded_bytes field to existing DownloadPending event for
accurate resume progress
- Minimal change per maintainer directive — no new download states
introduced
## Test plan
- [x] 42 tests passed, 1 skipped
- [x] Verified downloaded_bytes populates correctly for partial
downloads
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: rltakashige <rl.takashige@gmail.com>
---
dashboard/src/routes/downloads/+page.svelte | 102 +++++++++++++++++++++++++---
src/exo/download/coordinator.py | 2 +
src/exo/shared/types/worker/downloads.py | 3 +-
3 files changed, 95 insertions(+), 12 deletions(-)
diff --git a/dashboard/src/routes/downloads/+page.svelte b/dashboard/src/routes/downloads/+page.svelte
index a57a18d3..c0781c19 100644
--- a/dashboard/src/routes/downloads/+page.svelte
+++ b/dashboard/src/routes/downloads/+page.svelte
@@ -29,7 +29,12 @@
etaMs: number;
modelDirectory?: string;
}
- | { kind: "pending"; modelDirectory?: string }
+ | {
+ kind: "pending";
+ downloaded: number;
+ total: number;
+ modelDirectory?: string;
+ }
| { kind: "failed"; modelDirectory?: string }
| { kind: "not_present" };
@@ -255,7 +260,20 @@
} else if (tag === "DownloadFailed") {
cell = { kind: "failed", modelDirectory };
} else {
- cell = { kind: "pending", modelDirectory };
+ const downloaded = getBytes(
+ payload.downloaded ??
+ payload.downloaded_bytes ??
+ payload.downloadedBytes,
+ );
+ const total = getBytes(
+ payload.total ?? payload.total_bytes ?? payload.totalBytes,
+ );
+ cell = {
+ kind: "pending",
+ downloaded,
+ total,
+ modelDirectory,
+ };
}
const existing = row.cells[nodeId];
@@ -265,14 +283,51 @@
}
}
+ function rowSortKey(row: ModelRow): number {
+ // in progress (4) -> completed (3) -> paused (2) -> not started (1) -> not present (0)
+ let best = 0;
+ for (const cell of Object.values(row.cells)) {
+ let score = 0;
+ if (cell.kind === "downloading") score = 4;
+ else if (cell.kind === "completed") score = 3;
+ else if (cell.kind === "pending" && cell.downloaded > 0)
+ score = 2; // paused
+ else if (cell.kind === "pending" || cell.kind === "failed") score = 1; // not started
+ if (score > best) best = score;
+ }
+ return best;
+ }
+
+ function totalCompletedBytes(row: ModelRow): number {
+ let total = 0;
+ for (const cell of Object.values(row.cells)) {
+ if (cell.kind === "completed") total += cell.totalBytes;
+ }
+ return total;
+ }
+
const rows = Array.from(rowMap.values()).sort((a, b) => {
- const aCompleted = Object.values(a.cells).filter(
- (c) => c.kind === "completed",
- ).length;
- const bCompleted = Object.values(b.cells).filter(
- (c) => c.kind === "completed",
- ).length;
- if (aCompleted !== bCompleted) return bCompleted - aCompleted;
+ const aPriority = rowSortKey(a);
+ const bPriority = rowSortKey(b);
+ if (aPriority !== bPriority) return bPriority - aPriority;
+ // Within completed or paused, sort by biggest size first
+ if (aPriority === 3 && bPriority === 3) {
+ const sizeDiff = totalCompletedBytes(b) - totalCompletedBytes(a);
+ if (sizeDiff !== 0) return sizeDiff;
+ }
+ if (aPriority === 2 && bPriority === 2) {
+ const aSize = Math.max(
+ ...Object.values(a.cells).map((c) =>
+ c.kind === "pending" ? c.total : 0,
+ ),
+ );
+ const bSize = Math.max(
+ ...Object.values(b.cells).map((c) =>
+ c.kind === "pending" ? c.total : 0,
+ ),
+ );
+ if (aSize !== bSize) return bSize - aSize;
+ }
return a.modelId.localeCompare(b.modelId);
});
@@ -482,9 +537,34 @@
{:else if cell.kind === "pending"}
<div
class="flex flex-col items-center gap-0.5"
- title="Download pending"
+ title={cell.downloaded > 0
+ ? `${formatBytes(cell.downloaded)} / ${formatBytes(cell.total)} downloaded`
+ : "Download pending"}
>
- <span class="text-exo-light-gray/50 text-sm">...</span>
+ {#if cell.downloaded > 0 && cell.total > 0}
+ <span class="text-exo-light-gray/70 text-[10px]"
+ >{formatBytes(cell.downloaded)} / {formatBytes(
+ cell.total,
+ )}</span
+ >
+ <div
+ class="w-full h-1 bg-white/10 rounded-full overflow-hidden"
+ >
+ <div
+ class="h-full bg-exo-light-gray/40 rounded-full"
+ style="width: {(
+ (cell.downloaded / cell.total) *
+ 100
+ ).toFixed(1)}%"
+ ></div>
+ </div>
+ <span class="text-exo-light-gray/40 text-[9px]"
+ >paused</span
+ >
+ {:else}
+ <span class="text-exo-light-gray/50 text-sm">...</span
+ >
+ {/if}
</div>
{:else if cell.kind === "failed"}
<div
diff --git a/src/exo/download/coordinator.py b/src/exo/download/coordinator.py
index dc1c71c8..dd6337cc 100644
--- a/src/exo/download/coordinator.py
+++ b/src/exo/download/coordinator.py
@@ -376,6 +376,8 @@ class DownloadCoordinator:
model_directory=self._model_dir(
progress.shard.model_card.model_id
),
+ downloaded=progress.downloaded,
+ total=progress.total,
)
else:
status = DownloadOngoing(
diff --git a/src/exo/shared/types/worker/downloads.py b/src/exo/shared/types/worker/downloads.py
index 45762846..d3203a25 100644
--- a/src/exo/shared/types/worker/downloads.py
+++ b/src/exo/shared/types/worker/downloads.py
@@ -30,7 +30,8 @@ class BaseDownloadProgress(TaggedModel):
class DownloadPending(BaseDownloadProgress):
- pass
+ downloaded: Memory = Memory()
+ total: Memory = Memory()
class DownloadCompleted(BaseDownloadProgress):
← e01f50a5 Update mlx fork (#1565)
·
back to Exo
·
fix: immediate cancel check after prefill completes (#1575) 6b5a7059 →