From f0ec3e03d134077cb2e495d991d26d64a3597f03 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Tue, 17 Jun 2025 12:46:23 +0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9Bfix:=20immediately=20show=20downloa?= =?UTF-8?q?d=20progress=20(#5308)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web-app/src/containers/DownloadManegement.tsx | 69 +++++++++++++------ web-app/src/hooks/useDownloadStore.ts | 18 +++++ web-app/src/routes/hub.tsx | 30 ++++++-- 3 files changed, 90 insertions(+), 27 deletions(-) diff --git a/web-app/src/containers/DownloadManegement.tsx b/web-app/src/containers/DownloadManegement.tsx index 47b448485..db78181c6 100644 --- a/web-app/src/containers/DownloadManegement.tsx +++ b/web-app/src/containers/DownloadManegement.tsx @@ -19,7 +19,13 @@ export function DownloadManagement() { const { setProviders } = useModelProvider() const { open: isLeftPanelOpen } = useLeftPanel() const [isPopoverOpen, setIsPopoverOpen] = useState(false) - const { downloads, updateProgress, removeDownload } = useDownloadStore() + const { + downloads, + updateProgress, + localDownloadingModels, + removeDownload, + removeLocalDownloadingModel, + } = useDownloadStore() const { updateState } = useAppUpdater() const [appUpdateState, setAppUpdateState] = useState({ @@ -76,23 +82,36 @@ export function DownloadManagement() { }) }, []) + const downloadProcesses = useMemo(() => { + // Get downloads with progress data + const downloadsWithProgress = Object.values(downloads).map((download) => ({ + id: download.name, + name: download.name, + progress: download.progress, + current: download.current, + total: download.total, + })) + + // Add local downloading models that don't have progress data yet + const localDownloadsWithoutProgress = Array.from(localDownloadingModels) + .filter((modelId) => !downloads[modelId]) // Only include models not in downloads + .map((modelId) => ({ + id: modelId, + name: modelId, + progress: 0, + current: 0, + total: 0, + })) + + return [...downloadsWithProgress, ...localDownloadsWithoutProgress] + }, [downloads, localDownloadingModels]) + const downloadCount = useMemo(() => { - const modelDownloads = Object.keys(downloads).length + const modelDownloads = downloadProcesses.length const appUpdateDownload = appUpdateState.isDownloading ? 1 : 0 const total = modelDownloads + appUpdateDownload return total - }, [downloads, appUpdateState.isDownloading]) - const downloadProcesses = useMemo( - () => - Object.values(downloads).map((download) => ({ - id: download.name, - name: download.name, - progress: download.progress, - current: download.current, - total: download.total, - })), - [downloads] - ) + }, [downloadProcesses, appUpdateState.isDownloading]) const overallProgress = useMemo(() => { const modelTotal = downloadProcesses.reduce((acc, download) => { @@ -139,29 +158,32 @@ export function DownloadManagement() { (state: DownloadState) => { console.debug('onFileDownloadError', state) removeDownload(state.modelId) + removeLocalDownloadingModel(state.modelId) }, - [removeDownload] + [removeDownload, removeLocalDownloadingModel] ) const onFileDownloadStopped = useCallback( (state: DownloadState) => { console.debug('onFileDownloadError', state) removeDownload(state.modelId) + removeLocalDownloadingModel(state.modelId) }, - [removeDownload] + [removeDownload, removeLocalDownloadingModel] ) const onFileDownloadSuccess = useCallback( async (state: DownloadState) => { console.debug('onFileDownloadSuccess', state) removeDownload(state.modelId) + removeLocalDownloadingModel(state.modelId) getProviders().then(setProviders) toast.success('Download Complete', { id: 'download-complete', description: `The model ${state.modelId} has been downloaded`, }) }, - [removeDownload, setProviders] + [removeDownload, removeLocalDownloadingModel, setProviders] ) useEffect(() => { @@ -264,12 +286,16 @@ export function DownloadManagement() { />

{`${renderGB(appUpdateState.downloadedBytes)} / ${renderGB(appUpdateState.totalBytes)}`}{' '} - GB ({Math.round(appUpdateState.downloadProgress * 100)}%) + GB ({Math.round(appUpdateState.downloadProgress * 100)} + %)

)} {downloadProcesses.map((download) => ( -
+

{download.name} @@ -299,8 +325,9 @@ export function DownloadManagement() { className="my-2" />

- {`${renderGB(download.current)} / ${renderGB(download.total)}`}{' '} - GB ({Math.round(download.progress * 100)}%) + {download.total > 0 + ? `${renderGB(download.current)} / ${renderGB(download.total)} GB (${Math.round(download.progress * 100)}%)` + : 'Initializing download...'}

))} diff --git a/web-app/src/hooks/useDownloadStore.ts b/web-app/src/hooks/useDownloadStore.ts index 8a0e6ac19..48a5a347c 100644 --- a/web-app/src/hooks/useDownloadStore.ts +++ b/web-app/src/hooks/useDownloadStore.ts @@ -11,6 +11,7 @@ export interface DownloadProgressProps { // Zustand store for thinking block state export type DownloadState = { downloads: { [id: string]: DownloadProgressProps } + localDownloadingModels: Set removeDownload: (id: string) => void updateProgress: ( id: string, @@ -19,6 +20,8 @@ export type DownloadState = { current?: number, total?: number ) => void + addLocalDownloadingModel: (modelId: string) => void + removeLocalDownloadingModel: (modelId: string) => void } /** @@ -26,6 +29,7 @@ export type DownloadState = { */ export const useDownloadStore = create((set) => ({ downloads: {}, + localDownloadingModels: new Set(), removeDownload: (id: string) => set((state) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -46,4 +50,18 @@ export const useDownloadStore = create((set) => ({ }, }, })), + + addLocalDownloadingModel: (modelId: string) => + set((state) => ({ + localDownloadingModels: new Set(state.localDownloadingModels).add( + modelId + ), + })), + + removeLocalDownloadingModel: (modelId: string) => + set((state) => { + const newSet = new Set(state.localDownloadingModels) + newSet.delete(modelId) + return { localDownloadingModels: newSet } + }), })) diff --git a/web-app/src/routes/hub.tsx b/web-app/src/routes/hub.tsx index c4327fb04..42147d0f8 100644 --- a/web-app/src/routes/hub.tsx +++ b/web-app/src/routes/hub.tsx @@ -182,7 +182,8 @@ function Hub() { } } - const { downloads } = useDownloadStore() + const { downloads, localDownloadingModels, addLocalDownloadingModel } = + useDownloadStore() const downloadProcesses = useMemo( () => @@ -225,7 +226,9 @@ function Hub() { model.models.find((e) => defaultModelQuantizations.some((m) => e.id.toLowerCase().includes(m)) )?.id ?? model.models[0]?.id - const isDownloading = downloadProcesses.some((e) => e.id === modelId) + const isDownloading = + localDownloadingModels.has(modelId) || + downloadProcesses.some((e) => e.id === modelId) const downloadProgress = downloadProcesses.find((e) => e.id === modelId)?.progress || 0 const isDownloaded = llamaProvider?.models.some( @@ -233,6 +236,12 @@ function Hub() { ) const isRecommended = isRecommendedModel(model.metadata?.id) + const handleDownload = () => { + // Immediately set local downloading state + addLocalDownloadingModel(modelId) + downloadModel(modelId) + } + return (
downloadModel(modelId)} + onClick={handleDownload} className={cn(isDownloading && 'hidden')} ref={isRecommended ? downloadButtonRef : undefined} > @@ -271,6 +280,8 @@ function Hub() { handleUseModel, isRecommendedModel, downloadButtonRef, + localDownloadingModels, + addLocalDownloadingModel, ]) const { step } = useSearch({ from: Route.id }) @@ -320,7 +331,8 @@ function Hub() { } // Check if any model is currently downloading - const isDownloading = downloadProcesses.length > 0 + const isDownloading = + localDownloadingModels.size > 0 || downloadProcesses.length > 0 const steps = [ { @@ -553,6 +565,9 @@ function Hub() {

{(() => { const isDownloading = + localDownloadingModels.has( + variant.id + ) || downloadProcesses.some( (e) => e.id === variant.id ) @@ -607,9 +622,12 @@ function Hub() {
+ onClick={() => { + addLocalDownloadingModel( + variant.id + ) downloadModel(variant.id) - } + }} >