mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): style add model panel
This commit is contained in:
parent
d93d4afbb7
commit
8ef8082d65
@ -768,8 +768,7 @@
|
|||||||
"huggingFaceHelper": "If multiple models are found in this repo, you will be prompted to select one to install.",
|
"huggingFaceHelper": "If multiple models are found in this repo, you will be prompted to select one to install.",
|
||||||
"ignoreMismatch": "Ignore Mismatches Between Selected Models",
|
"ignoreMismatch": "Ignore Mismatches Between Selected Models",
|
||||||
"imageEncoderModelId": "Image Encoder Model ID",
|
"imageEncoderModelId": "Image Encoder Model ID",
|
||||||
"importModels": "Import Models",
|
"installQueue": "Install Queue",
|
||||||
"importQueue": "Import Queue",
|
|
||||||
"inpainting": "v1 Inpainting",
|
"inpainting": "v1 Inpainting",
|
||||||
"inplaceInstall": "In-place install",
|
"inplaceInstall": "In-place install",
|
||||||
"inplaceInstallDesc": "Install models without copying the files. When using the model, it will be loaded from its this location. If disabled, the model file(s) will be copied into the Invoke-managed models directory during installation.",
|
"inplaceInstallDesc": "Install models without copying the files. When using the model, it will be loaded from its this location. If disabled, the model file(s) will be copied into the Invoke-managed models directory during installation.",
|
||||||
@ -803,7 +802,6 @@
|
|||||||
"modelImageUpdated": "Model Image Updated",
|
"modelImageUpdated": "Model Image Updated",
|
||||||
"modelImageUpdateFailed": "Model Image Update Failed",
|
"modelImageUpdateFailed": "Model Image Update Failed",
|
||||||
"modelLocation": "Model Location",
|
"modelLocation": "Model Location",
|
||||||
"modelLocationValidationMsg": "Provide the path to a local folder where your Diffusers Model is stored",
|
|
||||||
"modelManager": "Model Manager",
|
"modelManager": "Model Manager",
|
||||||
"modelMergeAlphaHelp": "Alpha controls blend strength for the models. Lower alpha values lead to lower influence of the second model.",
|
"modelMergeAlphaHelp": "Alpha controls blend strength for the models. Lower alpha values lead to lower influence of the second model.",
|
||||||
"modelMergeHeaderHelp1": "You can merge up to three different models to create a blend that suits your needs.",
|
"modelMergeHeaderHelp1": "You can merge up to three different models to create a blend that suits your needs.",
|
||||||
@ -848,7 +846,8 @@
|
|||||||
"safetensorModels": "SafeTensors",
|
"safetensorModels": "SafeTensors",
|
||||||
"sameFolder": "Same folder",
|
"sameFolder": "Same folder",
|
||||||
"scan": "Scan",
|
"scan": "Scan",
|
||||||
"scanFolder": "Scan folder",
|
"scanFolder": "Scan Folder",
|
||||||
|
"scanFolderHelper": "The folder will be recursively scanned for models. This can take a few moments for very large folders.",
|
||||||
"scanAgain": "Scan Again",
|
"scanAgain": "Scan Again",
|
||||||
"scanForModels": "Scan For Models",
|
"scanForModels": "Scan For Models",
|
||||||
"scanPlaceholder": "Path to a local folder",
|
"scanPlaceholder": "Path to a local folder",
|
||||||
@ -875,6 +874,8 @@
|
|||||||
"upcastAttention": "Upcast Attention",
|
"upcastAttention": "Upcast Attention",
|
||||||
"uploadImage": "Upload Image",
|
"uploadImage": "Upload Image",
|
||||||
"updateModel": "Update Model",
|
"updateModel": "Update Model",
|
||||||
|
"urlOrLocalPath": "URL or Local Path",
|
||||||
|
"urlOrLocalPathHelper": "URLs should point to a single file. Local paths can point to a single file or folder for a single diffusers model.",
|
||||||
"useCustomConfig": "Use Custom Config",
|
"useCustomConfig": "Use Custom Config",
|
||||||
"useDefaultSettings": "Use Default Settings",
|
"useDefaultSettings": "Use Default Settings",
|
||||||
"v1": "v1",
|
"v1": "v1",
|
||||||
|
@ -44,7 +44,7 @@ export const HuggingFaceResultItem = ({ result }: Props) => {
|
|||||||
}, [installModel, result, dispatch, t]);
|
}, [installModel, result, dispatch, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex alignItems="center" justifyContent="space-between" w="100%" gap={4}>
|
<Flex alignItems="center" justifyContent="space-between" w="100%" gap={3}>
|
||||||
<Flex fontSize="sm" flexDir="column">
|
<Flex fontSize="sm" flexDir="column">
|
||||||
<Text fontWeight="semibold">{result.split('/').slice(-1)[0]}</Text>
|
<Text fontWeight="semibold">{result.split('/').slice(-1)[0]}</Text>
|
||||||
<Text variant="subtext" noOfLines={1} wordBreak="break-all">
|
<Text variant="subtext" noOfLines={1} wordBreak="break-all">
|
||||||
|
@ -87,7 +87,7 @@ export const HuggingFaceResults = ({ results }: HuggingFaceResultsProps) => {
|
|||||||
<Button size="sm" onClick={handleAddAll} isDisabled={results.length === 0} flexShrink={0}>
|
<Button size="sm" onClick={handleAddAll} isDisabled={results.length === 0} flexShrink={0}>
|
||||||
{t('modelManager.installAll')}
|
{t('modelManager.installAll')}
|
||||||
</Button>
|
</Button>
|
||||||
<InputGroup maxW="300px" size="xs">
|
<InputGroup w={64} size="xs">
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('modelManager.search')}
|
placeholder={t('modelManager.search')}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
@ -110,7 +110,7 @@ export const HuggingFaceResults = ({ results }: HuggingFaceResultsProps) => {
|
|||||||
</InputGroup>
|
</InputGroup>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex height="100%" layerStyle="third" borderRadius="base" p={4}>
|
<Flex height="100%" layerStyle="third" borderRadius="base" p={3}>
|
||||||
<ScrollableContent>
|
<ScrollableContent>
|
||||||
<Flex flexDir="column" gap={3}>
|
<Flex flexDir="column" gap={3}>
|
||||||
{filteredResults.map((result) => (
|
{filteredResults.map((result) => (
|
||||||
|
@ -67,17 +67,21 @@ export const InstallModelForm = () => {
|
|||||||
<Flex flexDir="column" gap={4}>
|
<Flex flexDir="column" gap={4}>
|
||||||
<Flex gap={2} alignItems="flex-end" justifyContent="space-between">
|
<Flex gap={2} alignItems="flex-end" justifyContent="space-between">
|
||||||
<FormControl orientation="vertical">
|
<FormControl orientation="vertical">
|
||||||
<FormLabel>{t('modelManager.modelLocation')}</FormLabel>
|
<FormLabel>{t('modelManager.urlOrLocalPath')}</FormLabel>
|
||||||
<Input placeholder={t('modelManager.simpleModelPlaceholder')} {...register('location')} />
|
<Flex alignItems="center" gap={3} w="full">
|
||||||
|
<Input placeholder={t('modelManager.simpleModelPlaceholder')} {...register('location')} />
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit(onSubmit)}
|
||||||
|
isDisabled={!formState.dirtyFields.location}
|
||||||
|
isLoading={isLoading}
|
||||||
|
type="submit"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{t('modelManager.install')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<FormHelperText>{t('modelManager.urlOrLocalPathHelper')}</FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
|
||||||
onClick={handleSubmit(onSubmit)}
|
|
||||||
isDisabled={!formState.dirtyFields.location}
|
|
||||||
isLoading={isLoading}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
{t('modelManager.addModel')}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Box, Button, Flex, Text } from '@invoke-ai/ui-library';
|
import { Box, Button, Flex, Heading } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
@ -50,9 +50,9 @@ export const ModelInstallQueue = () => {
|
|||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" p={3} h="full">
|
<Flex flexDir="column" p={3} h="full" gap={3}>
|
||||||
<Flex justifyContent="space-between" alignItems="center">
|
<Flex justifyContent="space-between" alignItems="center">
|
||||||
<Text>{t('modelManager.importQueue')}</Text>
|
<Heading size="sm">{t('modelManager.installQueue')}</Heading>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
isDisabled={!pruneAvailable}
|
isDisabled={!pruneAvailable}
|
||||||
@ -62,9 +62,9 @@ export const ModelInstallQueue = () => {
|
|||||||
{t('modelManager.prune')}
|
{t('modelManager.prune')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box mt={3} layerStyle="first" p={3} borderRadius="base" w="full" h="full">
|
<Box layerStyle="first" p={3} borderRadius="base" w="full" h="full">
|
||||||
<ScrollableContent>
|
<ScrollableContent>
|
||||||
<Flex flexDir="column-reverse" gap="2">
|
<Flex flexDir="column-reverse" gap="2" w="full">
|
||||||
{data?.map((model) => <ModelInstallQueueItem key={model.id} installJob={model} />)}
|
{data?.map((model) => <ModelInstallQueueItem key={model.id} installJob={model} />)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</ScrollableContent>
|
</ScrollableContent>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Badge, Tooltip } from '@invoke-ai/ui-library';
|
import { Badge } from '@invoke-ai/ui-library';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { ModelInstallStatus } from 'services/api/types';
|
import type { ModelInstallStatus } from 'services/api/types';
|
||||||
@ -13,13 +13,7 @@ const STATUSES = {
|
|||||||
cancelled: { colorScheme: 'orange', translationKey: 'queue.canceled' },
|
cancelled: { colorScheme: 'orange', translationKey: 'queue.canceled' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const ModelInstallQueueBadge = ({
|
const ModelInstallQueueBadge = ({ status }: { status?: ModelInstallStatus }) => {
|
||||||
status,
|
|
||||||
errorReason,
|
|
||||||
}: {
|
|
||||||
status?: ModelInstallStatus;
|
|
||||||
errorReason?: string | null;
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (!status || !Object.keys(STATUSES).includes(status)) {
|
if (!status || !Object.keys(STATUSES).includes(status)) {
|
||||||
@ -27,9 +21,9 @@ const ModelInstallQueueBadge = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label={errorReason}>
|
<Badge textAlign="center" w="134px" colorScheme={STATUSES[status].colorScheme}>
|
||||||
<Badge colorScheme={STATUSES[status].colorScheme}>{t(STATUSES[status].translationKey)}</Badge>
|
{t(STATUSES[status].translationKey)}
|
||||||
</Tooltip>
|
</Badge>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default memo(ModelInstallQueueBadge);
|
export default memo(ModelInstallQueueBadge);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Box, Flex, IconButton, Progress, Text, Tooltip } from '@invoke-ai/ui-library';
|
import { Flex, IconButton, Progress, Text, Tooltip } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
@ -7,7 +7,7 @@ import { isNil } from 'lodash-es';
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { PiXBold } from 'react-icons/pi';
|
import { PiXBold } from 'react-icons/pi';
|
||||||
import { useCancelModelInstallMutation } from 'services/api/endpoints/models';
|
import { useCancelModelInstallMutation } from 'services/api/endpoints/models';
|
||||||
import type { HFModelSource, LocalModelSource, ModelInstallJob, URLModelSource } from 'services/api/types';
|
import type { ModelInstallJob } from 'services/api/types';
|
||||||
|
|
||||||
import ModelInstallQueueBadge from './ModelInstallQueueBadge';
|
import ModelInstallQueueBadge from './ModelInstallQueueBadge';
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ type ModelListItemProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formatBytes = (bytes: number) => {
|
const formatBytes = (bytes: number) => {
|
||||||
const units = ['b', 'kb', 'mb', 'gb', 'tb'];
|
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
@ -33,18 +33,6 @@ export const ModelInstallQueueItem = (props: ModelListItemProps) => {
|
|||||||
|
|
||||||
const [deleteImportModel] = useCancelModelInstallMutation();
|
const [deleteImportModel] = useCancelModelInstallMutation();
|
||||||
|
|
||||||
const source = useMemo(() => {
|
|
||||||
if (installJob.source.type === 'hf') {
|
|
||||||
return installJob.source as HFModelSource;
|
|
||||||
} else if (installJob.source.type === 'local') {
|
|
||||||
return installJob.source as LocalModelSource;
|
|
||||||
} else if (installJob.source.type === 'url') {
|
|
||||||
return installJob.source as URLModelSource;
|
|
||||||
} else {
|
|
||||||
return installJob.source as LocalModelSource;
|
|
||||||
}
|
|
||||||
}, [installJob.source]);
|
|
||||||
|
|
||||||
const handleDeleteModelImport = useCallback(() => {
|
const handleDeleteModelImport = useCallback(() => {
|
||||||
deleteImportModel(installJob.id)
|
deleteImportModel(installJob.id)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -72,18 +60,31 @@ export const ModelInstallQueueItem = (props: ModelListItemProps) => {
|
|||||||
});
|
});
|
||||||
}, [deleteImportModel, installJob, dispatch]);
|
}, [deleteImportModel, installJob, dispatch]);
|
||||||
|
|
||||||
const modelName = useMemo(() => {
|
const sourceLocation = useMemo(() => {
|
||||||
switch (source.type) {
|
switch (installJob.source.type) {
|
||||||
case 'hf':
|
case 'hf':
|
||||||
return source.repo_id;
|
return installJob.source.repo_id;
|
||||||
case 'url':
|
case 'url':
|
||||||
return source.url;
|
return installJob.source.url;
|
||||||
case 'local':
|
case 'local':
|
||||||
return source.path.split('\\').slice(-1)[0];
|
return installJob.source.path;
|
||||||
default:
|
default:
|
||||||
return '';
|
return t('common.unknown');
|
||||||
}
|
}
|
||||||
}, [source]);
|
}, [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(() => {
|
const progressValue = useMemo(() => {
|
||||||
if (isNil(installJob.bytes) || isNil(installJob.total_bytes)) {
|
if (isNil(installJob.bytes) || isNil(installJob.total_bytes)) {
|
||||||
@ -97,48 +98,67 @@ export const ModelInstallQueueItem = (props: ModelListItemProps) => {
|
|||||||
return (installJob.bytes / installJob.total_bytes) * 100;
|
return (installJob.bytes / installJob.total_bytes) * 100;
|
||||||
}, [installJob.bytes, installJob.total_bytes]);
|
}, [installJob.bytes, installJob.total_bytes]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex gap={3} w="full" alignItems="center">
|
||||||
|
<Tooltip maxW={600} label={<TooltipLabel name={modelName} source={sourceLocation} installJob={installJob} />}>
|
||||||
|
<Flex gap={3} w="full" alignItems="center">
|
||||||
|
<Text w={96} whiteSpace="nowrap" overflow="hidden" textOverflow="ellipsis">
|
||||||
|
{modelName}
|
||||||
|
</Text>
|
||||||
|
<Progress
|
||||||
|
w="full"
|
||||||
|
flexGrow={1}
|
||||||
|
value={progressValue ?? 0}
|
||||||
|
isIndeterminate={progressValue === null}
|
||||||
|
aria-label={t('accessibility.invokeProgressBar')}
|
||||||
|
h={2}
|
||||||
|
/>
|
||||||
|
<ModelInstallQueueBadge status={installJob.status} />
|
||||||
|
</Flex>
|
||||||
|
</Tooltip>
|
||||||
|
<IconButton
|
||||||
|
isDisabled={
|
||||||
|
installJob.status !== 'downloading' && installJob.status !== 'waiting' && installJob.status !== 'running'
|
||||||
|
}
|
||||||
|
size="xs"
|
||||||
|
tooltip={t('modelManager.cancel')}
|
||||||
|
aria-label={t('modelManager.cancel')}
|
||||||
|
icon={<PiXBold />}
|
||||||
|
onClick={handleDeleteModelImport}
|
||||||
|
variant="ghost"
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type TooltipLabelProps = {
|
||||||
|
installJob: ModelInstallJob;
|
||||||
|
name: string;
|
||||||
|
source: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TooltipLabel = ({ name, source, installJob }: TooltipLabelProps) => {
|
||||||
const progressString = useMemo(() => {
|
const progressString = useMemo(() => {
|
||||||
if (installJob.status !== 'downloading' || installJob.bytes === undefined || installJob.total_bytes === undefined) {
|
if (installJob.status === 'downloading' || installJob.bytes === undefined || installJob.total_bytes === undefined) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return `${formatBytes(installJob.bytes)} / ${formatBytes(installJob.total_bytes)}`;
|
return `${formatBytes(installJob.bytes)} / ${formatBytes(installJob.total_bytes)}`;
|
||||||
}, [installJob.bytes, installJob.total_bytes, installJob.status]);
|
}, [installJob.bytes, installJob.total_bytes, installJob.status]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap="2" w="full" alignItems="center">
|
<>
|
||||||
<Tooltip label={modelName}>
|
<Flex gap={3} justifyContent="space-between">
|
||||||
<Text width="30%" whiteSpace="nowrap" overflow="hidden" textOverflow="ellipsis">
|
<Text fontWeight="semibold">{name}</Text>
|
||||||
{modelName}
|
{progressString && <Text>{progressString}</Text>}
|
||||||
</Text>
|
|
||||||
</Tooltip>
|
|
||||||
<Flex flexDir="column" flex={1}>
|
|
||||||
<Tooltip label={progressString}>
|
|
||||||
<Progress
|
|
||||||
value={progressValue ?? 0}
|
|
||||||
isIndeterminate={progressValue === null}
|
|
||||||
aria-label={t('accessibility.invokeProgressBar')}
|
|
||||||
h={2}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box minW="100px" textAlign="center">
|
<Text fontStyle="italic" wordBreak="break-all">
|
||||||
<ModelInstallQueueBadge status={installJob.status} errorReason={installJob.error_reason} />
|
{source}
|
||||||
</Box>
|
</Text>
|
||||||
|
{installJob.error_reason && (
|
||||||
<Box minW="20px">
|
<Text color="error.500">
|
||||||
{(installJob.status === 'downloading' ||
|
{t('queue.failed')}: {installJob.error}
|
||||||
installJob.status === 'waiting' ||
|
</Text>
|
||||||
installJob.status === 'running') && (
|
)}
|
||||||
<IconButton
|
</>
|
||||||
isRound={true}
|
|
||||||
size="xs"
|
|
||||||
tooltip={t('modelManager.cancel')}
|
|
||||||
aria-label={t('modelManager.cancel')}
|
|
||||||
icon={<PiXBold />}
|
|
||||||
onClick={handleDeleteModelImport}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Button, Flex, FormControl, FormErrorMessage, FormLabel, Input } from '@invoke-ai/ui-library';
|
import { Button, Flex, FormControl, FormErrorMessage, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { setScanPath } from 'features/modelManagerV2/store/modelManagerV2Slice';
|
import { setScanPath } from 'features/modelManagerV2/store/modelManagerV2Slice';
|
||||||
import type { ChangeEventHandler } from 'react';
|
import type { ChangeEventHandler } from 'react';
|
||||||
@ -35,8 +35,8 @@ export const ScanModelsForm = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" height="100%">
|
<Flex flexDir="column" height="100%" gap={3}>
|
||||||
<FormControl isInvalid={!!errorMessage.length} w="full" orientation="vertical">
|
<FormControl isInvalid={!!errorMessage.length} w="full" orientation="vertical" flexShrink={0}>
|
||||||
<FormLabel>{t('common.folder')}</FormLabel>
|
<FormLabel>{t('common.folder')}</FormLabel>
|
||||||
<Flex gap={3} alignItems="center" w="full">
|
<Flex gap={3} alignItems="center" w="full">
|
||||||
<Input placeholder={t('modelManager.scanPlaceholder')} value={scanPath} onChange={handleSetScanPath} />
|
<Input placeholder={t('modelManager.scanPlaceholder')} value={scanPath} onChange={handleSetScanPath} />
|
||||||
@ -50,6 +50,7 @@ export const ScanModelsForm = () => {
|
|||||||
{t('modelManager.scanFolder')}
|
{t('modelManager.scanFolder')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<FormHelperText>{t('modelManager.scanFolderHelper')}</FormHelperText>
|
||||||
{!!errorMessage.length && <FormErrorMessage>{errorMessage}</FormErrorMessage>}
|
{!!errorMessage.length && <FormErrorMessage>{errorMessage}</FormErrorMessage>}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{data && <ScanModelsResults results={data} />}
|
{data && <ScanModelsResults results={data} />}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Badge, Box, Flex, IconButton, Text, Tooltip } from '@invoke-ai/ui-library';
|
import { Badge, Box, Flex, IconButton, Text } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
@ -45,7 +45,7 @@ export const ScanModelResultItem = ({ result }: Props) => {
|
|||||||
}, [installModel, result, dispatch, t]);
|
}, [installModel, result, dispatch, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex justifyContent="space-between">
|
<Flex alignItems="center" justifyContent="space-between" w="100%" gap={3}>
|
||||||
<Flex fontSize="sm" flexDir="column">
|
<Flex fontSize="sm" flexDir="column">
|
||||||
<Text fontWeight="semibold">{result.path.split('\\').slice(-1)[0]}</Text>
|
<Text fontWeight="semibold">{result.path.split('\\').slice(-1)[0]}</Text>
|
||||||
<Text variant="subtext">{result.path}</Text>
|
<Text variant="subtext">{result.path}</Text>
|
||||||
@ -54,9 +54,7 @@ export const ScanModelResultItem = ({ result }: Props) => {
|
|||||||
{result.is_installed ? (
|
{result.is_installed ? (
|
||||||
<Badge>{t('common.installed')}</Badge>
|
<Badge>{t('common.installed')}</Badge>
|
||||||
) : (
|
) : (
|
||||||
<Tooltip label={t('modelManager.quickAdd')}>
|
<IconButton aria-label={t('modelManager.install')} icon={<PiPlusBold />} onClick={handleQuickAdd} size="sm" />
|
||||||
<IconButton aria-label={t('modelManager.quickAdd')} icon={<PiPlusBold />} onClick={handleQuickAdd} />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -80,17 +80,15 @@ export const ScanModelsResults = ({ results }: ScanModelResultsProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Divider mt={6} />
|
<Divider />
|
||||||
<Flex flexDir="column" gap={2} mt={4} height="100%">
|
<Flex flexDir="column" gap={3} height="100%">
|
||||||
<Flex justifyContent="space-between" alignItems="center">
|
<Flex justifyContent="space-between" alignItems="center">
|
||||||
<Heading fontSize="md" as="h4">
|
<Heading size="sm">{t('modelManager.scanResults')}</Heading>
|
||||||
{t('modelManager.scanResults')}
|
<Flex alignItems="center" gap={3}>
|
||||||
</Heading>
|
|
||||||
<Flex alignItems="center" gap="4">
|
|
||||||
<Button size="sm" onClick={handleAddAll} isDisabled={filteredResults.length === 0}>
|
<Button size="sm" onClick={handleAddAll} isDisabled={filteredResults.length === 0}>
|
||||||
{t('modelManager.addAll')}
|
{t('modelManager.installAll')}
|
||||||
</Button>
|
</Button>
|
||||||
<InputGroup maxW="300px" size="xs">
|
<InputGroup w={64} size="xs">
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('modelManager.search')}
|
placeholder={t('modelManager.search')}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
@ -107,13 +105,14 @@ export const ScanModelsResults = ({ results }: ScanModelResultsProps) => {
|
|||||||
aria-label={t('boards.clearSearch')}
|
aria-label={t('boards.clearSearch')}
|
||||||
icon={<PiXBold />}
|
icon={<PiXBold />}
|
||||||
onClick={clearSearch}
|
onClick={clearSearch}
|
||||||
|
flexShrink={0}
|
||||||
/>
|
/>
|
||||||
</InputRightElement>
|
</InputRightElement>
|
||||||
)}
|
)}
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex height="100%" layerStyle="third" borderRadius="base" p={4} mt={4} mb={4}>
|
<Flex height="100%" layerStyle="third" borderRadius="base" p={3}>
|
||||||
<ScrollableContent>
|
<ScrollableContent>
|
||||||
<Flex flexDir="column" gap={3}>
|
<Flex flexDir="column" gap={3}>
|
||||||
{filteredResults.map((result) => (
|
{filteredResults.map((result) => (
|
||||||
|
@ -13,9 +13,9 @@ export const InstallModels = () => {
|
|||||||
<Heading fontSize="xl">{t('modelManager.addModel')}</Heading>
|
<Heading fontSize="xl">{t('modelManager.addModel')}</Heading>
|
||||||
<Tabs variant="collapse" height="50%" display="flex" flexDir="column">
|
<Tabs variant="collapse" height="50%" display="flex" flexDir="column">
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab>{t('common.simple')}</Tab>
|
<Tab>{t('modelManager.urlOrLocalPath')}</Tab>
|
||||||
<Tab>{t('modelManager.huggingFace')}</Tab>
|
<Tab>{t('modelManager.huggingFace')}</Tab>
|
||||||
<Tab>{t('modelManager.scan')}</Tab>
|
<Tab>{t('modelManager.scanFolder')}</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels p={3} height="100%">
|
<TabPanels p={3} height="100%">
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user