diff --git a/tools/ui/src/lib/components/app/dialogs/DialogModelsDownloadManager.svelte b/tools/ui/src/lib/components/app/dialogs/DialogModelsDownloadManager.svelte
new file mode 100644
index 0000000000..986f50d69e
--- /dev/null
+++ b/tools/ui/src/lib/components/app/dialogs/DialogModelsDownloadManager.svelte
@@ -0,0 +1,28 @@
+
+
+
+
+
+ Download manager
+
+
+
+
+
diff --git a/tools/ui/src/lib/components/app/dialogs/index.ts b/tools/ui/src/lib/components/app/dialogs/index.ts
index e3963ba499..895a13199e 100644
--- a/tools/ui/src/lib/components/app/dialogs/index.ts
+++ b/tools/ui/src/lib/components/app/dialogs/index.ts
@@ -536,3 +536,13 @@ export { default as DialogMermaidPreview } from './DialogMermaidPreview.svelte';
* @see ModelsDiscover in $lib/components/app/models/discover
*/
export { default as DialogModelsDiscover } from './DialogModelsDiscover.svelte';
+
+/**
+ * **DialogModelsDownloadManager** - download manager dialog.
+ *
+ * Lists all tracked model downloads with per-file progress, cancel and
+ * delete actions.
+ *
+ * @see ModelsDownloadManager in $lib/components/app/models/download-manager
+ */
+export { default as DialogModelsDownloadManager } from './DialogModelsDownloadManager.svelte';
diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscover.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscover.svelte
index b75b02d585..16db298d77 100644
--- a/tools/ui/src/lib/components/app/models/discover/ModelsDiscover.svelte
+++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscover.svelte
@@ -1,7 +1,7 @@
+
+
+ handleInput(v)} {placeholder} />
+
diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetails.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetails.svelte
index ea23111d28..6e3d8370d4 100644
--- a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetails.svelte
+++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetails.svelte
@@ -1,6 +1,6 @@
- {#each boxes as box (box.title)}
+
+ Quant
+
+
+
+ {#if sidecars.length}
+ Draft
+
+
+ {/if}
+
+
+ {#each boxes as box, i (box.title)}
{box.title}
-
- {#each box.commands as cmd, i (cmd)}
-
-
{cmd}
+
+ {box.command}
-
-
- {/each}
+
{/each}
diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsHeader.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsHeader.svelte
index 62c00e5b22..da21d873bd 100644
--- a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsHeader.svelte
+++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsHeader.svelte
@@ -1,21 +1,11 @@
@@ -148,70 +131,5 @@
{/if}
- {#if description}
- {description}
- {/if}
-
-
-
- {#if gguf?.total}
-
- Model size
-
- {formatParameters(gguf.total)} params
-
- {/if}
-
- {#if gguf?.context_length}
-
- Context
-
- {gguf.context_length.toLocaleString()}
-
- {/if}
-
- {#if gguf?.architecture}
-
- Architecture
-
- {gguf.architecture}
-
- {/if}
-
- {#if gguf?.chat_template}
-
- {/if}
-
- {#if licenseTag}
-
- License
-
- {licenseTag}
-
-
-
- {/if}
-
- {#if details.gated === true}
-
- gated
-
- {/if}
-
+
-
-{#if gguf?.chat_template}
-
-{/if}
diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsMetadata.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsMetadata.svelte
new file mode 100644
index 0000000000..78eeba6517
--- /dev/null
+++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsMetadata.svelte
@@ -0,0 +1,89 @@
+
+
+{#if description}
+ {description}
+{/if}
+
+
+
+ {#if gguf?.total}
+
+ Model size
+
+ {formatParameters(gguf.total)} params
+
+ {/if}
+
+ {#if gguf?.context_length}
+
+ Context
+
+ {gguf.context_length.toLocaleString()}
+
+ {/if}
+
+ {#if gguf?.architecture}
+
+ Architecture
+
+ {gguf.architecture}
+
+ {/if}
+
+ {#if gguf?.chat_template}
+
+ {/if}
+
+ {#if licenseTag}
+
+ License
+
+ {licenseTag}
+
+ {/if}
+
+ {#if details.gated === true}
+
+ gated
+
+ {/if}
+
+
+{#if gguf?.chat_template}
+
+{/if}
diff --git a/tools/ui/src/lib/components/app/models/discover/index.ts b/tools/ui/src/lib/components/app/models/discover/index.ts
index dd2376ed81..a37c5b0eda 100644
--- a/tools/ui/src/lib/components/app/models/discover/index.ts
+++ b/tools/ui/src/lib/components/app/models/discover/index.ts
@@ -24,6 +24,13 @@ export { default as ModelsDiscover } from './ModelsDiscover.svelte';
*/
export { default as ModelsDiscoverList } from './ModelsDiscoverList.svelte';
+/**
+ * **ModelsDiscoverListSearch** - Sidebar search input
+ *
+ * Debounced search field for the model list.
+ */
+export { default as ModelsDiscoverListSearch } from './ModelsDiscoverListSearch.svelte';
+
/**
* **ModelsDiscoverItem** - Single sidebar row
*
diff --git a/tools/ui/src/lib/components/app/models/download-manager/ModelsDownloadManager.svelte b/tools/ui/src/lib/components/app/models/download-manager/ModelsDownloadManager.svelte
new file mode 100644
index 0000000000..84762f60c9
--- /dev/null
+++ b/tools/ui/src/lib/components/app/models/download-manager/ModelsDownloadManager.svelte
@@ -0,0 +1,51 @@
+
+
+{#if open}
+
+ {#each modelsStore.status.downloadEntries() as entry (entry.repoWithTag)}
+
+
+ {entry.repoWithTag}
+
+
+
+
+ {#each Object.entries(entry.progress.files) as [file, fileProgress] (file)}
+
+
+ {file}
+
+
+ {fileProgress.total > 0
+ ? Math.round((fileProgress.done / fileProgress.total) * 100)
+ : 0}%
+
+
+
+
+
+ {/each}
+
+ {/each}
+
+{/if}
diff --git a/tools/ui/src/lib/components/app/models/download-manager/ModelsDownloadManagerDownloadStatusToast.svelte b/tools/ui/src/lib/components/app/models/download-manager/ModelsDownloadManagerDownloadStatusToast.svelte
new file mode 100644
index 0000000000..9f36824748
--- /dev/null
+++ b/tools/ui/src/lib/components/app/models/download-manager/ModelsDownloadManagerDownloadStatusToast.svelte
@@ -0,0 +1,65 @@
+
+
+
+
+ {repoId}
+
+
+
+
+
+ {#each files as [file, fileProgress] (file)}
+
+
+ {file}
+
+
+ {percent(fileProgress.done, fileProgress.total)}%
+
+
+
+
+
+ {/each}
+
+
+ {#if onOpenManager}
+
+ {/if}
+
diff --git a/tools/ui/src/lib/components/app/models/index.ts b/tools/ui/src/lib/components/app/models/index.ts
index 3ac6ecb678..279e10fc3d 100644
--- a/tools/ui/src/lib/components/app/models/index.ts
+++ b/tools/ui/src/lib/components/app/models/index.ts
@@ -109,3 +109,18 @@ export { default as ModelBadge } from './ModelBadge.svelte';
* Respects the user's `showRawModelNames` setting.
*/
export { default as ModelId } from './ModelId.svelte';
+
+/**
+ * **ModelsDownloadManager** - tracked downloads list
+ *
+ * Lists every in-flight download with per-file progress and a delete action.
+ */
+export { default as ModelsDownloadManager } from './download-manager/ModelsDownloadManager.svelte';
+
+/**
+ * **ModelsDownloadManagerDownloadStatusToast** - per-entry download toast
+ *
+ * One toast per download with a progress bar per file (main + sidecars) and
+ * a CTA to open the download manager.
+ */
+export { default as ModelsDownloadManagerDownloadStatusToast } from './download-manager/ModelsDownloadManagerDownloadStatusToast.svelte';
diff --git a/tools/ui/src/lib/stores/models/status.svelte.ts b/tools/ui/src/lib/stores/models/status.svelte.ts
index 15a0c3b87e..fe78f761d5 100644
--- a/tools/ui/src/lib/stores/models/status.svelte.ts
+++ b/tools/ui/src/lib/stores/models/status.svelte.ts
@@ -124,6 +124,16 @@ export class ModelStatusManager {
constructor(private host: ModelStatusHost) {}
+ /**
+ * All tracked downloads (in flight), as a list for the download manager.
+ */
+ downloadEntries(): { progress: ModelDownloadProgress; repoWithTag: string }[] {
+ return Array.from(this.downloadProgress, ([repoWithTag, progress]) => ({
+ progress,
+ repoWithTag
+ }));
+ }
+
/**
* Trigger a model download from HuggingFace via POST /models
* (ggml-org/llama.cpp#23976). The download runs in the background on the
@@ -332,7 +342,11 @@ export class ModelStatusManager {
total += file?.total ?? 0;
}
- this.downloadProgress.set(event.model, { downloadedBytes: downloaded, totalBytes: total });
+ this.downloadProgress.set(event.model, {
+ downloadedBytes: downloaded,
+ files: progress,
+ totalBytes: total
+ });
}
/**
diff --git a/tools/ui/src/lib/types/index.ts b/tools/ui/src/lib/types/index.ts
index 208d10fd30..cd9b94047e 100644
--- a/tools/ui/src/lib/types/index.ts
+++ b/tools/ui/src/lib/types/index.ts
@@ -109,6 +109,7 @@ export type {
ModelCapabilities,
ModelModalities,
ModelOption,
+ ModelDownloadFileProgress,
ModelDownloadProgress,
ModelLoadProgress,
ModalityCapabilities
diff --git a/tools/ui/src/lib/types/models.d.ts b/tools/ui/src/lib/types/models.d.ts
index 962285f4b0..485c5b0aee 100644
--- a/tools/ui/src/lib/types/models.d.ts
+++ b/tools/ui/src/lib/types/models.d.ts
@@ -40,19 +40,20 @@ export interface ModelLoadProgress {
* Per-byte download progress for one in-flight model download, driven by the
* /models/sse feed. Lives only while a download runs.
*/
-export interface ModelDownloadProgress {
- downloadedBytes: number;
- totalBytes: number;
+export interface ModelDownloadFileProgress {
+ /** Bytes downloaded for this file so far. */
+ done: number;
+ /** Total bytes of the file. */
+ total: number;
}
-
-/**
- * Per-byte download progress for one in-flight model download, driven by the
- * /models/sse feed. Lives only while a download runs.
- */
export interface ModelDownloadProgress {
+ /** Summed progress across all files of the download plan. */
downloadedBytes: number;
+ /** Summed plan size across all files. */
totalBytes: number;
+ /** Per-file progress keyed by file URL, as reported by the feed. */
+ files: Record;
}
// LLAMA-APP-REUSE: parsed model id shape
diff --git a/tools/ui/tests/stories/ModelsDiscoverModelDetails.stories.svelte b/tools/ui/tests/stories/ModelsDiscoverModelDetails.stories.svelte
index 1ef5f8cbdd..c228349c47 100644
--- a/tools/ui/tests/stories/ModelsDiscoverModelDetails.stories.svelte
+++ b/tools/ui/tests/stories/ModelsDiscoverModelDetails.stories.svelte
@@ -74,7 +74,10 @@
-
+
@@ -82,6 +85,7 @@