import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover' import { Progress } from '@/components/ui/progress' import { useDownloadStore } from '@/hooks/useDownloadStore' import { useModelProvider } from '@/hooks/useModelProvider' import { abortDownload } from '@/services/models' import { getProviders } from '@/services/providers' import { DownloadEvent, DownloadState, events } from '@janhq/core' import { IconX } from '@tabler/icons-react' import { useCallback, useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' export function DownloadManagement() { const { setProviders } = useModelProvider() const [isPopoverOpen, setIsPopoverOpen] = useState(false) const { downloads, updateProgress, removeDownload } = useDownloadStore() const downloadCount = useMemo( () => Object.keys(downloads).length, [downloads] ) const downloadProcesses = useMemo( () => Object.values(downloads).map((download) => ({ id: download.name, name: download.name, progress: download.progress, current: download.current, total: download.total, })), [downloads] ) const overallProgress = useMemo(() => { const total = downloadProcesses.reduce((acc, download) => { return acc + download.total }, 0) const current = downloadProcesses.reduce((acc, download) => { return acc + download.current }, 0) return total > 0 ? current / total : 0 }, [downloadProcesses]) const onFileDownloadUpdate = useCallback( async (state: DownloadState) => { console.debug('onFileDownloadUpdate', state) updateProgress( state.modelId, state.percent, state.modelId, state.size?.transferred, state.size?.total ) }, [updateProgress] ) const onFileDownloadError = useCallback( (state: DownloadState) => { console.debug('onFileDownloadError', state) removeDownload(state.modelId) }, [removeDownload] ) const onFileDownloadStopped = useCallback( (state: DownloadState) => { console.debug('onFileDownloadError', state) removeDownload(state.modelId) }, [removeDownload] ) const onFileDownloadSuccess = useCallback( async (state: DownloadState) => { console.debug('onFileDownloadSuccess', state) removeDownload(state.modelId) getProviders().then(setProviders) toast.success('Download Complete', { id: 'download-complete', description: `The model ${state.modelId} has been downloaded`, }) }, [removeDownload, setProviders] ) useEffect(() => { console.debug('DownloadListener: registering event listeners...') events.on(DownloadEvent.onFileDownloadUpdate, onFileDownloadUpdate) events.on(DownloadEvent.onFileDownloadError, onFileDownloadError) events.on(DownloadEvent.onFileDownloadSuccess, onFileDownloadSuccess) events.on(DownloadEvent.onFileDownloadStopped, onFileDownloadStopped) return () => { console.debug('DownloadListener: unregistering event listeners...') events.off(DownloadEvent.onFileDownloadUpdate, onFileDownloadUpdate) events.off(DownloadEvent.onFileDownloadError, onFileDownloadError) events.off(DownloadEvent.onFileDownloadSuccess, onFileDownloadSuccess) events.off(DownloadEvent.onFileDownloadStopped, onFileDownloadStopped) } }, [ onFileDownloadUpdate, onFileDownloadError, onFileDownloadSuccess, onFileDownloadStopped, ]) function renderGB(bytes: number): string { const gb = bytes / 1024 ** 3 return ((gb * 100) / 100).toFixed(2) } return ( <> {downloadCount > 0 && (
{downloadCount}

Downloads

{overallProgress.toFixed(2)}%
e.preventDefault} >

Downloading

{downloadProcesses.map((download) => (

{download.name}

{ abortDownload(download.name).then(() => { toast.info('Download Cancelled', { id: 'cancel-download', description: 'The download process was cancelled', }) if (downloadProcesses.length === 0) { setIsPopoverOpen(false) } }) }} />

{`${renderGB(download.current)} / ${renderGB(download.total)}`}{' '} GB ({download.progress.toFixed(2)}%)

))}
)} ) }