diff --git a/app/src/components/ServerSettings/GpuAcceleration.tsx b/app/src/components/ServerSettings/GpuAcceleration.tsx index 17da7690..69824d63 100644 --- a/app/src/components/ServerSettings/GpuAcceleration.tsx +++ b/app/src/components/ServerSettings/GpuAcceleration.tsx @@ -32,7 +32,7 @@ export function GpuAcceleration() { } = useQuery({ queryKey: ['cuda-status', serverUrl], queryFn: () => apiClient.getCudaStatus(), - refetchInterval: cudaStatusLoading ? false : 10000, + refetchInterval: (query) => (query.state.status === 'pending' ? false : 10000), retry: 1, enabled: !!health, // Only fetch when backend is reachable }); diff --git a/app/src/components/ServerSettings/ModelManagement.tsx b/app/src/components/ServerSettings/ModelManagement.tsx index 4783a880..ec94cb12 100644 --- a/app/src/components/ServerSettings/ModelManagement.tsx +++ b/app/src/components/ServerSettings/ModelManagement.tsx @@ -16,7 +16,7 @@ import { X, Zap, } from 'lucide-react'; -import { useCallback, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { AlertDialog, AlertDialogAction, @@ -36,6 +36,7 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; +import { Progress } from '@/components/ui/progress'; import { useToast } from '@/components/ui/use-toast'; import { apiClient } from '@/lib/api/client'; import type { ActiveDownloadTask, HuggingFaceModelInfo, ModelStatus } from '@/lib/api/types'; @@ -73,6 +74,14 @@ function formatPipelineTag(tag: string): string { .join(' '); } +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${(bytes / k ** i).toFixed(1)} ${sizes[i]}`; +} + export function ModelManagement() { const { toast } = useToast(); const queryClient = useQueryClient(); @@ -98,7 +107,11 @@ export function ModelManagement() { const { data: activeTasks } = useQuery({ queryKey: ['activeTasks'], queryFn: () => apiClient.getActiveTasks(), - refetchInterval: 5000, + refetchInterval: (query) => { + const data = query.state.data; + const hasActive = data?.downloads.some((d) => d.status === 'downloading'); + return hasActive ? 1000 : 5000; + }, }); // HuggingFace model card query - only fetches when modal is open and model has a repo ID @@ -133,6 +146,19 @@ export function ModelManagement() { const errorCount = erroredDownloads.size; + // Build progress map from active tasks for inline display + const downloadProgressMap = useMemo(() => { + const map = new Map(); + if (activeTasks?.downloads) { + for (const dl of activeTasks.downloads) { + if (dl.status === 'downloading') { + map.set(dl.model_name, dl); + } + } + } + return map; + }, [activeTasks]); + const handleDownloadComplete = useCallback(() => { setDownloadingModel(null); setDownloadingDisplayName(null); @@ -371,16 +397,29 @@ export function ModelManagement() { )} - {/* Name + meta */} + {/* Name + inline progress */}
{model.display_name} + {isDownloading && + (() => { + const dl = downloadProgressMap.get(model.model_name); + const pct = dl?.progress ?? 0; + const hasProgress = dl && dl.total && dl.total > 0; + return ( +
+ +
+ {hasProgress + ? `${formatBytes(dl.current ?? 0)} / ${formatBytes(dl.total!)} (${pct.toFixed(0)}%)` + : dl?.filename || 'Connecting...'} +
+
+ ); + })()}
{/* Right side info */}
- {isDownloading && ( - Downloading... - )} {hasError && ( Error @@ -510,12 +549,6 @@ export function ModelManagement() { Downloaded )} - {selectedState?.isDownloading && ( - - - Downloading - - )} {selectedState?.hasError && ( @@ -633,10 +666,25 @@ export function ModelManagement() { ) : selectedState?.isDownloading ? ( <> - +
+ {(() => { + const dl = freshSelectedModel + ? downloadProgressMap.get(freshSelectedModel.model_name) + : undefined; + const pct = dl?.progress ?? 0; + const hasProgress = dl && dl.total && dl.total > 0; + return ( + <> + +
+ {hasProgress + ? `${formatBytes(dl.current ?? 0)} / ${formatBytes(dl.total!)} (${pct.toFixed(1)}%)` + : dl?.filename || 'Connecting to HuggingFace...'} +
+ + ); + })()} +