mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): model manager UI tweaks
- Move image display to left - Move description into model header - Move model edit & convert buttons to top right of model header - Tweak styles for model display component
This commit is contained in:
parent
ad70cdfe87
commit
ed4e8624dd
@ -7,7 +7,7 @@ import { Model } from './ModelPanel/Model';
|
||||
export const ModelPane = () => {
|
||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||
return (
|
||||
<Box layerStyle="first" p={2} borderRadius="base" w="50%" h="full">
|
||||
<Box layerStyle="first" p={4} borderRadius="base" w="50%" h="full">
|
||||
{selectedModelKey ? <Model key={selectedModelKey} /> : <InstallModels />}
|
||||
</Box>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Box, Button, IconButton, Image } from '@invoke-ai/ui-library';
|
||||
import { Box, Button, Flex, Icon, IconButton, Image, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { typedMemo } from 'common/util/typedMemo';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
@ -95,7 +95,7 @@ const ModelImageUpload = ({ model_key, model_image }: Props) => {
|
||||
|
||||
if (image) {
|
||||
return (
|
||||
<Box position="relative">
|
||||
<Box position="relative" flexShrink={0}>
|
||||
<Image
|
||||
src={image}
|
||||
objectFit="cover"
|
||||
@ -107,15 +107,14 @@ const ModelImageUpload = ({ model_key, model_image }: Props) => {
|
||||
/>
|
||||
<IconButton
|
||||
position="absolute"
|
||||
top="1"
|
||||
right="1"
|
||||
insetInlineEnd={0}
|
||||
insetBlockStart={0}
|
||||
onClick={handleResetImage}
|
||||
aria-label={t('modelManager.deleteModelImage')}
|
||||
tooltip={t('modelManager.deleteModelImage')}
|
||||
icon={<PiArrowCounterClockwiseBold size={16} />}
|
||||
size="sm"
|
||||
variant="link"
|
||||
_hover={{ color: 'base.100' }}
|
||||
icon={<PiArrowCounterClockwiseBold />}
|
||||
size="md"
|
||||
variant="ghost"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
@ -123,9 +122,21 @@ const ModelImageUpload = ({ model_key, model_image }: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button leftIcon={<PiUploadSimpleBold />} {...getRootProps()} pointerEvents="auto">
|
||||
{t('modelManager.uploadImage')}
|
||||
</Button>
|
||||
<Tooltip label={t('modelManager.uploadImage')}>
|
||||
<Flex
|
||||
as={Button}
|
||||
w={100}
|
||||
h={100}
|
||||
opacity={0.3}
|
||||
borderRadius="base"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
flexShrink={0}
|
||||
{...getRootProps()}
|
||||
>
|
||||
<Icon as={PiUploadSimpleBold} w={16} h={16} />
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
<input {...getInputProps()} />
|
||||
</>
|
||||
);
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Box, Flex, Heading, Tab, TabList, TabPanel, TabPanels, Tabs, Text } from '@invoke-ai/ui-library';
|
||||
import { Flex, Heading, Text } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { ModelConvertButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton';
|
||||
import { ModelEditButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelEditButton';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetModelConfigQuery } from 'services/api/endpoints/models';
|
||||
|
||||
import ModelImageUpload from './Fields/ModelImageUpload';
|
||||
import { ModelMetadata } from './Metadata/ModelMetadata';
|
||||
import { ModelAttrView } from './ModelAttrView';
|
||||
import { ModelEdit } from './ModelEdit';
|
||||
import { ModelView } from './ModelView';
|
||||
|
||||
@ -25,38 +25,28 @@ export const Model = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex alignItems="center" justifyContent="space-between" gap="4" paddingRight="5">
|
||||
<Flex flexDir="column" gap={1} p={2}>
|
||||
<Heading as="h2" fontSize="lg">
|
||||
{data.name}
|
||||
</Heading>
|
||||
|
||||
<Flex flexDir="column" gap={4}>
|
||||
<Flex alignItems="flex-start" gap={4}>
|
||||
<ModelImageUpload model_key={selectedModelKey} model_image={data.cover_image} />
|
||||
<Flex flexDir="column" gap={1} flexGrow={1}>
|
||||
<Flex gap={2} position="relative">
|
||||
<Heading as="h2" fontSize="lg" w="full">
|
||||
{data.name}
|
||||
</Heading>
|
||||
<Flex position="absolute" gap={2} right={0} top={0}>
|
||||
<ModelEditButton />
|
||||
<ModelConvertButton modelKey={selectedModelKey} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
{data.source && (
|
||||
<Text variant="subtext">
|
||||
{t('modelManager.source')}: {data?.source}
|
||||
</Text>
|
||||
)}
|
||||
<Box mt="4">
|
||||
<ModelAttrView label="Description" value={data.description} />
|
||||
</Box>
|
||||
<Text noOfLines={3}>{data.description}</Text>
|
||||
</Flex>
|
||||
<ModelImageUpload model_key={selectedModelKey} model_image={data.cover_image} />
|
||||
</Flex>
|
||||
|
||||
<Tabs mt="4" h="100%">
|
||||
<TabList>
|
||||
<Tab>{t('modelManager.settings')}</Tab>
|
||||
<Tab>{t('modelManager.metadata')}</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels h="100%">
|
||||
<TabPanel>{selectedModelMode === 'view' ? <ModelView /> : <ModelEdit />}</TabPanel>
|
||||
<TabPanel h="full">
|
||||
<ModelMetadata />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</>
|
||||
{selectedModelMode === 'view' ? <ModelView /> : <ModelEdit />}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -8,42 +8,47 @@ import {
|
||||
UnorderedList,
|
||||
useDisclosure,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useConvertModelMutation } from 'services/api/endpoints/models';
|
||||
import type { CheckpointModelConfig } from 'services/api/types';
|
||||
import { useConvertModelMutation, useGetModelConfigQuery } from 'services/api/endpoints/models';
|
||||
|
||||
interface ModelConvertProps {
|
||||
model: CheckpointModelConfig;
|
||||
modelKey: string | null;
|
||||
}
|
||||
|
||||
export const ModelConvert = (props: ModelConvertProps) => {
|
||||
const { model } = props;
|
||||
export const ModelConvertButton = (props: ModelConvertProps) => {
|
||||
const { modelKey } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { data } = useGetModelConfigQuery(modelKey ?? skipToken);
|
||||
const [convertModel, { isLoading }] = useConvertModelMutation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const modelConvertHandler = useCallback(() => {
|
||||
if (!data || isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${t('modelManager.convertingModelBegin')}: ${model.name}`,
|
||||
title: `${t('modelManager.convertingModelBegin')}: ${data?.name}`,
|
||||
status: 'info',
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
convertModel(model.key)
|
||||
convertModel(data?.key)
|
||||
.unwrap()
|
||||
.then(() => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${t('modelManager.modelConverted')}: ${model.name}`,
|
||||
title: `${t('modelManager.modelConverted')}: ${data?.name}`,
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
@ -53,13 +58,13 @@ export const ModelConvert = (props: ModelConvertProps) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${t('modelManager.modelConversionFailed')}: ${model.name}`,
|
||||
title: `${t('modelManager.modelConversionFailed')}: ${data?.name}`,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
}, [convertModel, dispatch, model.key, model.name, t]);
|
||||
}, [data, isLoading, dispatch, t, convertModel]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -69,11 +74,12 @@ export const ModelConvert = (props: ModelConvertProps) => {
|
||||
aria-label={t('modelManager.convertToDiffusers')}
|
||||
className=" modal-close-btn"
|
||||
isLoading={isLoading}
|
||||
flexShrink={0}
|
||||
>
|
||||
🧨 {t('modelManager.convertToDiffusers')}
|
||||
</Button>
|
||||
<ConfirmationAlertDialog
|
||||
title={`${t('modelManager.convert')} ${model.name}`}
|
||||
title={`${t('modelManager.convert')} ${data?.name}`}
|
||||
acceptCallback={modelConvertHandler}
|
||||
acceptButtonText={`${t('modelManager.convert')}`}
|
||||
isOpen={isOpen}
|
@ -104,7 +104,7 @@ export const ModelEdit = () => {
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Flex w="full" justifyContent="space-between" gap={4} alignItems="center">
|
||||
<FormControl flexDir="column" alignItems="flex-start" gap={1} isInvalid={Boolean(errors.name)}>
|
||||
<FormLabel hidden={true}>{t('modelManager.modelName')}</FormLabel>
|
||||
<FormLabel>{t('modelManager.modelName')}</FormLabel>
|
||||
<Input
|
||||
{...register('name', {
|
||||
validate: (value) => (value && value.trim().length > 3) || 'Must be at least 3 characters',
|
||||
@ -132,7 +132,7 @@ export const ModelEdit = () => {
|
||||
<Flex gap="4" alignItems="center">
|
||||
<FormControl flexDir="column" alignItems="flex-start" gap={1}>
|
||||
<FormLabel>{t('modelManager.description')}</FormLabel>
|
||||
<Textarea fontSize="md" resize="none" {...register('description')} />
|
||||
<Textarea fontSize="md" {...register('description')} />
|
||||
</FormControl>
|
||||
</Flex>
|
||||
<Heading as="h3" fontSize="md" mt="4">
|
||||
|
@ -0,0 +1,21 @@
|
||||
import { Button } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IoPencil } from 'react-icons/io5';
|
||||
|
||||
export const ModelEditButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleEditModel = useCallback(() => {
|
||||
dispatch(setSelectedModelMode('edit'));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Button size="sm" leftIcon={<IoPencil />} colorScheme="invokeYellow" onClick={handleEditModel} flexShrink={0}>
|
||||
{t('modelManager.edit')}
|
||||
</Button>
|
||||
);
|
||||
};
|
@ -1,127 +1,74 @@
|
||||
import { Box, Button, Flex, Text } from '@invoke-ai/ui-library';
|
||||
import { Box, Flex, Text } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { TriggerPhrases } from 'features/modelManagerV2/subpanels/ModelPanel/Metadata/TriggerPhrases';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IoPencil } from 'react-icons/io5';
|
||||
import { useGetModelConfigQuery } from 'services/api/endpoints/models';
|
||||
import type {
|
||||
CheckpointModelConfig,
|
||||
ControlNetModelConfig,
|
||||
DiffusersModelConfig,
|
||||
IPAdapterModelConfig,
|
||||
LoRAModelConfig,
|
||||
T2IAdapterModelConfig,
|
||||
TextualInversionModelConfig,
|
||||
VAEModelConfig,
|
||||
} from 'services/api/types';
|
||||
|
||||
import { DefaultSettings } from './DefaultSettings';
|
||||
import { ModelAttrView } from './ModelAttrView';
|
||||
import { ModelConvert } from './ModelConvert';
|
||||
|
||||
export const ModelView = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||
const { data, isLoading } = useGetModelConfigQuery(selectedModelKey ?? skipToken);
|
||||
|
||||
const modelData = useMemo(() => {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
const modelFormat = data.format;
|
||||
const modelType = data.type;
|
||||
|
||||
if (modelType === 'main') {
|
||||
if (modelFormat === 'diffusers') {
|
||||
return data as DiffusersModelConfig;
|
||||
} else if (modelFormat === 'checkpoint') {
|
||||
return data as CheckpointModelConfig;
|
||||
}
|
||||
}
|
||||
|
||||
switch (modelType) {
|
||||
case 'lora':
|
||||
return data as LoRAModelConfig;
|
||||
case 'embedding':
|
||||
return data as TextualInversionModelConfig;
|
||||
case 't2i_adapter':
|
||||
return data as T2IAdapterModelConfig;
|
||||
case 'ip_adapter':
|
||||
return data as IPAdapterModelConfig;
|
||||
case 'controlnet':
|
||||
return data as ControlNetModelConfig;
|
||||
case 'vae':
|
||||
return data as VAEModelConfig;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handleEditModel = useCallback(() => {
|
||||
dispatch(setSelectedModelMode('edit'));
|
||||
}, [dispatch]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Text>{t('common.loading')}</Text>;
|
||||
}
|
||||
|
||||
if (!modelData) {
|
||||
if (!data) {
|
||||
return <Text>{t('common.somethingWentWrong')}</Text>;
|
||||
}
|
||||
return (
|
||||
<Flex flexDir="column" h="full" gap="2">
|
||||
<Box layerStyle="second" borderRadius="base" p={3}>
|
||||
<Flex gap="2" justifyContent="flex-end" w="full">
|
||||
<Button size="sm" leftIcon={<IoPencil />} colorScheme="invokeYellow" onClick={handleEditModel}>
|
||||
{t('modelManager.edit')}
|
||||
</Button>
|
||||
|
||||
{modelData.type === 'main' && modelData.format === 'checkpoint' && <ModelConvert model={modelData} />}
|
||||
</Flex>
|
||||
<Flex flexDir="column" gap={3}>
|
||||
<Flex flexDir="column" h="full" gap={4}>
|
||||
<Box layerStyle="second" borderRadius="base" p={4}>
|
||||
<Flex flexDir="column" gap={4}>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.baseModel')} value={modelData.base} />
|
||||
<ModelAttrView label={t('modelManager.modelType')} value={modelData.type} />
|
||||
<ModelAttrView label={t('modelManager.baseModel')} value={data.base} />
|
||||
<ModelAttrView label={t('modelManager.modelType')} value={data.type} />
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('common.format')} value={modelData.format} />
|
||||
<ModelAttrView label={t('modelManager.path')} value={modelData.path} />
|
||||
<ModelAttrView label={t('common.format')} value={data.format} />
|
||||
<ModelAttrView label={t('modelManager.path')} value={data.path} />
|
||||
</Flex>
|
||||
|
||||
{modelData.type === 'main' && modelData.format === 'diffusers' && modelData.repo_variant && (
|
||||
{data.type === 'main' && data.format === 'diffusers' && data.repo_variant && (
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.repoVariant')} value={modelData.repo_variant} />
|
||||
<ModelAttrView label={t('modelManager.repoVariant')} value={data.repo_variant} />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{modelData.type === 'main' && modelData.format === 'checkpoint' && (
|
||||
{data.type === 'main' && data.format === 'checkpoint' && (
|
||||
<>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.pathToConfig')} value={modelData.config_path} />
|
||||
<ModelAttrView label={t('modelManager.variant')} value={modelData.variant} />
|
||||
<ModelAttrView label={t('modelManager.pathToConfig')} value={data.config_path} />
|
||||
<ModelAttrView label={t('modelManager.variant')} value={data.variant} />
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.predictionType')} value={modelData.prediction_type} />
|
||||
<ModelAttrView label={t('modelManager.upcastAttention')} value={`${modelData.upcast_attention}`} />
|
||||
<ModelAttrView label={t('modelManager.predictionType')} value={data.prediction_type} />
|
||||
<ModelAttrView label={t('modelManager.upcastAttention')} value={`${data.upcast_attention}`} />
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
{modelData.type === 'ip_adapter' && (
|
||||
{data.type === 'ip_adapter' && (
|
||||
<Flex gap={2}>
|
||||
<ModelAttrView label={t('modelManager.imageEncoderModelId')} value={modelData.image_encoder_model_id} />
|
||||
<ModelAttrView label={t('modelManager.imageEncoderModelId')} value={data.image_encoder_model_id} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
{modelData.type === 'main' && (
|
||||
<Box layerStyle="second" borderRadius="base" p={3}>
|
||||
{data.type === 'main' && (
|
||||
<Box layerStyle="second" borderRadius="base" p={4}>
|
||||
<DefaultSettings />
|
||||
</Box>
|
||||
)}
|
||||
{data.type === 'lora' && (
|
||||
<Box layerStyle="second" borderRadius="base" p={4}>
|
||||
<TriggerPhrases />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user