import { Flex, IconButton, Progress, Text, Tooltip } from '@invoke-ai/ui-library'; import { toast } from 'features/toast/toast'; import { t } from 'i18next'; import { isNil } from 'lodash-es'; import { memo, useCallback, useMemo } from 'react'; import { PiXBold } from 'react-icons/pi'; import { useCancelModelInstallMutation } from 'services/api/endpoints/models'; import type { ModelInstallJob } from 'services/api/types'; import ModelInstallQueueBadge from './ModelInstallQueueBadge'; type ModelListItemProps = { installJob: ModelInstallJob; }; const formatBytes = (bytes: number) => { const units = ['B', 'KB', 'MB', 'GB', 'TB']; let i = 0; for (i; bytes >= 1024 && i < 4; i++) { bytes /= 1024; } return `${bytes.toFixed(2)} ${units[i]}`; }; export const ModelInstallQueueItem = memo((props: ModelListItemProps) => { const { installJob } = props; const [deleteImportModel] = useCancelModelInstallMutation(); const handleDeleteModelImport = useCallback(() => { deleteImportModel(installJob.id) .unwrap() .then((_) => { toast({ id: 'MODEL_INSTALL_CANCELED', title: t('toast.modelImportCanceled'), status: 'success', }); }) .catch((error) => { if (error) { toast({ id: 'MODEL_INSTALL_CANCEL_FAILED', title: `${error.data.detail} `, status: 'error', }); } }); }, [deleteImportModel, installJob]); const sourceLocation = useMemo(() => { switch (installJob.source.type) { case 'hf': return installJob.source.repo_id; case 'url': return installJob.source.url; case 'local': return installJob.source.path; default: return t('common.unknown'); } }, [installJob.source]); const modelName = useMemo(() => { switch (installJob.source.type) { case 'hf': return installJob.source.repo_id; case 'url': return installJob.source.url.split('/').slice(-1)[0] ?? t('common.unknown'); case 'local': return installJob.source.path.split('\\').slice(-1)[0] ?? t('common.unknown'); default: return t('common.unknown'); } }, [installJob.source]); const progressValue = useMemo(() => { if (installJob.status === 'completed' || installJob.status === 'error' || installJob.status === 'cancelled') { return 100; } if (isNil(installJob.bytes) || isNil(installJob.total_bytes)) { return null; } if (installJob.total_bytes === 0) { return 0; } return (installJob.bytes / installJob.total_bytes) * 100; }, [installJob.bytes, installJob.status, installJob.total_bytes]); return ( }> {modelName} } onClick={handleDeleteModelImport} variant="ghost" /> ); }); ModelInstallQueueItem.displayName = 'ModelInstallQueueItem'; type TooltipLabelProps = { installJob: ModelInstallJob; name: string; source: string; }; const TooltipLabel = memo(({ name, source, installJob }: TooltipLabelProps) => { const progressString = useMemo(() => { if (installJob.status !== 'downloading' || installJob.bytes === undefined || installJob.total_bytes === undefined) { return ''; } return `${formatBytes(installJob.bytes)} / ${formatBytes(installJob.total_bytes)}`; }, [installJob.bytes, installJob.total_bytes, installJob.status]); return ( <> {name} {progressString && {progressString}} {source} {installJob.error_reason && ( {t('queue.failed')}: {installJob.error} )} ); }); TooltipLabel.displayName = 'TooltipLabel';