mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): wip mm tab design
This commit is contained in:
parent
09d1e190e7
commit
375b8bc67d
@ -40,7 +40,7 @@ export const ModelInstallQueue = memo(() => {
|
|||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" p={3} h="full" gap={3}>
|
<Flex flexDir="column" h="full" gap={2}>
|
||||||
<Flex justifyContent="space-between" alignItems="center">
|
<Flex justifyContent="space-between" alignItems="center">
|
||||||
<Heading size="sm">{t('modelManager.installQueue')}</Heading>
|
<Heading size="sm">{t('modelManager.installQueue')}</Heading>
|
||||||
<Button
|
<Button
|
||||||
@ -52,7 +52,7 @@ export const ModelInstallQueue = memo(() => {
|
|||||||
{t('modelManager.prune')}
|
{t('modelManager.prune')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box layerStyle="first" p={3} borderRadius="base" w="full" h="full">
|
<Box layerStyle="first" borderRadius="base" w="full" h="full">
|
||||||
<ScrollableContent>
|
<ScrollableContent>
|
||||||
<Flex flexDir="column-reverse" gap="2" w="full">
|
<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} />)}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { Box, Flex, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
|
import { Flex, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { StarterModelsForm } from 'features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterModelsForm';
|
import { StarterModelsForm } from 'features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterModelsForm';
|
||||||
|
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||||
|
|
||||||
import { HuggingFaceForm } from './AddModelPanel/HuggingFaceFolder/HuggingFaceForm';
|
import { HuggingFaceForm } from './AddModelPanel/HuggingFaceFolder/HuggingFaceForm';
|
||||||
import { InstallModelForm } from './AddModelPanel/InstallModelForm';
|
import { InstallModelForm } from './AddModelPanel/InstallModelForm';
|
||||||
@ -20,9 +22,11 @@ export const InstallModels = memo(() => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex layerStyle="first" borderRadius="base" w="full" h="full" flexDir="column" gap={4}>
|
<PanelGroup direction="vertical">
|
||||||
|
<Panel>
|
||||||
|
<Flex w="full" h="full" flexDir="column" gap={2}>
|
||||||
<Heading fontSize="xl">{t('modelManager.addModel')}</Heading>
|
<Heading fontSize="xl">{t('modelManager.addModel')}</Heading>
|
||||||
<Tabs variant="collapse" height="50%" display="flex" flexDir="column" index={index} onChange={onChange}>
|
<Tabs variant="collapse" height="full" display="flex" flexDir="column" index={index} onChange={onChange}>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab>{t('modelManager.urlOrLocalPath')}</Tab>
|
<Tab>{t('modelManager.urlOrLocalPath')}</Tab>
|
||||||
<Tab>{t('modelManager.huggingFace')}</Tab>
|
<Tab>{t('modelManager.huggingFace')}</Tab>
|
||||||
@ -44,10 +48,15 @@ export const InstallModels = memo(() => {
|
|||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<Box layerStyle="second" borderRadius="base" h="50%">
|
|
||||||
<ModelInstallQueue />
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</Panel>
|
||||||
|
<ResizeHandle orientation="horizontal" />
|
||||||
|
<Panel>
|
||||||
|
{/* <Box layerStyle="second" borderRadius="base" h="full"> */}
|
||||||
|
<ModelInstallQueue />
|
||||||
|
{/* </Box> */}
|
||||||
|
</Panel>
|
||||||
|
</PanelGroup>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import { PiPlusBold } from 'react-icons/pi';
|
|||||||
import ModelList from './ModelManagerPanel/ModelList';
|
import ModelList from './ModelManagerPanel/ModelList';
|
||||||
import { ModelListNavigation } from './ModelManagerPanel/ModelListNavigation';
|
import { ModelListNavigation } from './ModelManagerPanel/ModelListNavigation';
|
||||||
|
|
||||||
export const ModelManager = memo(() => {
|
export const ModelManagerLeftPanel = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const handleClickAddModel = useCallback(() => {
|
const handleClickAddModel = useCallback(() => {
|
||||||
@ -16,19 +16,17 @@ export const ModelManager = memo(() => {
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" layerStyle="first" p={4} gap={4} borderRadius="base" w="50%" h="full">
|
<Flex flexDir="column" gap={2} w="full" h="full">
|
||||||
<Flex w="full" gap={4} justifyContent="space-between" alignItems="center">
|
<Flex w="full" gap={4} justifyContent="space-between" alignItems="center">
|
||||||
<Heading fontSize="xl">{t('common.modelManager')}</Heading>
|
<Heading fontSize="xl">{t('common.modelManager')}</Heading>
|
||||||
<Button size="sm" colorScheme="invokeYellow" leftIcon={<PiPlusBold />} onClick={handleClickAddModel}>
|
<Button size="sm" colorScheme="invokeYellow" leftIcon={<PiPlusBold />} onClick={handleClickAddModel}>
|
||||||
{t('modelManager.addModels')}
|
{t('modelManager.addModels')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex flexDir="column" layerStyle="second" p={4} gap={4} borderRadius="base" w="full" h="full">
|
|
||||||
<ModelListNavigation />
|
<ModelListNavigation />
|
||||||
<ModelList />
|
<ModelList />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ModelManager.displayName = 'ModelManager';
|
ModelManagerLeftPanel.displayName = 'ModelManagerLeftPanel';
|
@ -16,6 +16,10 @@ const BASE_COLOR_MAP: Record<BaseModelType, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ModelBaseBadge = ({ base }: Props) => {
|
const ModelBaseBadge = ({ base }: Props) => {
|
||||||
|
if (base === 'any') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge flexGrow={0} colorScheme={BASE_COLOR_MAP[base]} variant="subtle" h="min-content">
|
<Badge flexGrow={0} colorScheme={BASE_COLOR_MAP[base]} variant="subtle" h="min-content">
|
||||||
{MODEL_TYPE_SHORT_MAP[base]}
|
{MODEL_TYPE_SHORT_MAP[base]}
|
||||||
|
@ -15,12 +15,12 @@ const ModelImage = ({ image_url }: Props) => {
|
|||||||
<Flex
|
<Flex
|
||||||
height={MODEL_IMAGE_THUMBNAIL_SIZE}
|
height={MODEL_IMAGE_THUMBNAIL_SIZE}
|
||||||
minWidth={MODEL_IMAGE_THUMBNAIL_SIZE}
|
minWidth={MODEL_IMAGE_THUMBNAIL_SIZE}
|
||||||
bg="base.650"
|
|
||||||
borderRadius="base"
|
borderRadius="base"
|
||||||
|
bg="baseAlpha.200"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
>
|
>
|
||||||
<Icon color="base.500" as={PiImage} boxSize={FALLBACK_ICON_SIZE} />
|
<Icon color="baseAlpha.700" as={PiImage} boxSize={FALLBACK_ICON_SIZE} />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ const ModelList = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollableContent>
|
<ScrollableContent>
|
||||||
<Flex flexDirection="column" w="full" h="full" gap={4}>
|
<Flex flexDirection="column" w="full" h="full" gap={2}>
|
||||||
{/* Main Model List */}
|
{/* Main Model List */}
|
||||||
{isLoadingMainModels && <FetchingModelsLoader loadingMessage="Loading Main Models..." />}
|
{isLoadingMainModels && <FetchingModelsLoader loadingMessage="Loading Main Models..." />}
|
||||||
{!isLoadingMainModels && filteredMainModels.length > 0 && (
|
{!isLoadingMainModels && filteredMainModels.length > 0 && (
|
||||||
|
@ -20,8 +20,8 @@ type ModelListItemProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sx: SystemStyleObject = {
|
const sx: SystemStyleObject = {
|
||||||
_hover: { bg: 'base.700' },
|
_hover: { bg: 'baseAlpha.100' },
|
||||||
"&[aria-selected='true']": { bg: 'base.700' },
|
"&[aria-selected='true']": { bg: 'baseAlpha.100' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const ModelListItem = ({ model }: ModelListItemProps) => {
|
const ModelListItem = ({ model }: ModelListItemProps) => {
|
||||||
@ -82,8 +82,8 @@ const ModelListItem = ({ model }: ModelListItemProps) => {
|
|||||||
w="full"
|
w="full"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
gap={2}
|
gap={2}
|
||||||
cursor="pointer"
|
|
||||||
onClick={handleSelectModel}
|
onClick={handleSelectModel}
|
||||||
|
role="button"
|
||||||
>
|
>
|
||||||
<Flex gap={2} w="full" h="full" minW={0}>
|
<Flex gap={2} w="full" h="full" minW={0}>
|
||||||
<ModelImage image_url={model.cover_image} />
|
<ModelImage image_url={model.cover_image} />
|
||||||
|
@ -30,12 +30,12 @@ export const ModelListNavigation = memo(() => {
|
|||||||
<InputGroup maxW="400px">
|
<InputGroup maxW="400px">
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('modelManager.search')}
|
placeholder={t('modelManager.search')}
|
||||||
value={searchTerm || ''}
|
value={searchTerm}
|
||||||
data-testid="board-search-input"
|
data-testid="board-search-input"
|
||||||
onChange={handleSearch}
|
onChange={handleSearch}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!!searchTerm?.length && (
|
{Boolean(searchTerm) && (
|
||||||
<InputRightElement h="full" pe={2}>
|
<InputRightElement h="full" pe={2}>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { StickyScrollable } from 'features/system/components/StickyScrollable';
|
import { Box, Flex, Heading } from '@invoke-ai/ui-library';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import type { AnyModelConfig } from 'services/api/types';
|
import type { AnyModelConfig } from 'services/api/types';
|
||||||
|
|
||||||
@ -12,11 +12,16 @@ type ModelListWrapperProps = {
|
|||||||
export const ModelListWrapper = memo((props: ModelListWrapperProps) => {
|
export const ModelListWrapper = memo((props: ModelListWrapperProps) => {
|
||||||
const { title, modelList } = props;
|
const { title, modelList } = props;
|
||||||
return (
|
return (
|
||||||
<StickyScrollable title={title} contentSx={{ gap: 1, p: 2 }}>
|
<Box>
|
||||||
|
<Box pb={2} position="sticky" zIndex={1} top={0} bg="base.900">
|
||||||
|
<Heading size="sm">{title}</Heading>
|
||||||
|
</Box>
|
||||||
|
<Flex flexDir="column" gap={1}>
|
||||||
{modelList.map((model) => (
|
{modelList.map((model) => (
|
||||||
<ModelListItem key={model.key} model={model} />
|
<ModelListItem key={model.key} model={model} />
|
||||||
))}
|
))}
|
||||||
</StickyScrollable>
|
</Flex>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Box } from '@invoke-ai/ui-library';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
@ -7,11 +6,11 @@ import { Model } from './ModelPanel/Model';
|
|||||||
|
|
||||||
export const ModelPane = memo(() => {
|
export const ModelPane = memo(() => {
|
||||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||||
return (
|
if (selectedModelKey) {
|
||||||
<Box layerStyle="first" p={4} borderRadius="base" w="50%" h="full">
|
return <Model key={selectedModelKey} />;
|
||||||
{selectedModelKey ? <Model key={selectedModelKey} /> : <InstallModels />}
|
}
|
||||||
</Box>
|
|
||||||
);
|
return <InstallModels />;
|
||||||
});
|
});
|
||||||
|
|
||||||
ModelPane.displayName = 'ModelPane';
|
ModelPane.displayName = 'ModelPane';
|
||||||
|
@ -6,14 +6,15 @@ import { useDropzone } from 'react-dropzone';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiArrowCounterClockwiseBold, PiUploadSimpleBold } from 'react-icons/pi';
|
import { PiArrowCounterClockwiseBold, PiUploadSimpleBold } from 'react-icons/pi';
|
||||||
import { useDeleteModelImageMutation, useUpdateModelImageMutation } from 'services/api/endpoints/models';
|
import { useDeleteModelImageMutation, useUpdateModelImageMutation } from 'services/api/endpoints/models';
|
||||||
|
import type { AnyModelConfig } from 'services/api/types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model_key: string | null;
|
modelConfig: AnyModelConfig;
|
||||||
model_image?: string | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ModelImageUpload = ({ model_key, model_image }: Props) => {
|
const ModelImageUpload = ({ modelConfig }: Props) => {
|
||||||
const [image, setImage] = useState<string | null>(model_image || null);
|
const { key, cover_image } = modelConfig;
|
||||||
|
const [image, setImage] = useState<string | null>(cover_image || null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [updateModelImage] = useUpdateModelImageMutation();
|
const [updateModelImage] = useUpdateModelImageMutation();
|
||||||
@ -23,11 +24,11 @@ const ModelImageUpload = ({ model_key, model_image }: Props) => {
|
|||||||
(files: File[]) => {
|
(files: File[]) => {
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
|
|
||||||
if (!file || !model_key) {
|
if (!file) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateModelImage({ key: model_key, image: file })
|
updateModelImage({ key, image: file })
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setImage(URL.createObjectURL(file));
|
setImage(URL.createObjectURL(file));
|
||||||
@ -45,15 +46,12 @@ const ModelImageUpload = ({ model_key, model_image }: Props) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[model_key, t, updateModelImage]
|
[key, t, updateModelImage]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleResetImage = useCallback(() => {
|
const handleResetImage = useCallback(() => {
|
||||||
if (!model_key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setImage(null);
|
setImage(null);
|
||||||
deleteModelImage(model_key)
|
deleteModelImage(key)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast({
|
toast({
|
||||||
@ -69,7 +67,7 @@ const ModelImageUpload = ({ model_key, model_image }: Props) => {
|
|||||||
status: 'error',
|
status: 'error',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [model_key, t, deleteModelImage]);
|
}, [deleteModelImage, key, t]);
|
||||||
|
|
||||||
const { getInputProps, getRootProps } = useDropzone({
|
const { getInputProps, getRootProps } = useDropzone({
|
||||||
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
|
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
|
||||||
|
@ -13,10 +13,10 @@ export const ModelHeader = memo(({ modelConfig, children }: Props) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Flex alignItems="flex-start" gap={4}>
|
<Flex alignItems="flex-start" gap={4}>
|
||||||
<ModelImageUpload model_key={modelConfig.key} model_image={modelConfig.cover_image} />
|
<ModelImageUpload modelConfig={modelConfig} />
|
||||||
<Flex flexDir="column" gap={1} flexGrow={1} minW={0}>
|
<Flex flexDir="column" gap={1} flexGrow={1} minW={0}>
|
||||||
<Flex gap={2}>
|
<Flex gap={2}>
|
||||||
<Heading as="h2" fontSize="lg" noOfLines={1} wordBreak="break-all">
|
<Heading size="md" noOfLines={1} wordBreak="break-all">
|
||||||
{modelConfig.name}
|
{modelConfig.name}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
@ -50,15 +50,17 @@ export const ModelView = memo(({ modelConfig }: Props) => {
|
|||||||
)}
|
)}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Box>
|
</Box>
|
||||||
<Box layerStyle="second" borderRadius="base" p={4}>
|
<Flex flexDir="column" layerStyle="second" borderRadius="base" p={4} gap={4}>
|
||||||
{modelConfig.type === 'main' && modelConfig.base !== 'sdxl-refiner' && (
|
{modelConfig.type === 'main' && modelConfig.base !== 'sdxl-refiner' && (
|
||||||
<MainModelDefaultSettings modelConfig={modelConfig} />
|
<MainModelDefaultSettings modelConfig={modelConfig} />
|
||||||
)}
|
)}
|
||||||
{(modelConfig.type === 'controlnet' || modelConfig.type === 't2i_adapter') && (
|
{(modelConfig.type === 'controlnet' || modelConfig.type === 't2i_adapter') && (
|
||||||
<ControlNetOrT2IAdapterDefaultSettings modelConfig={modelConfig} />
|
<ControlNetOrT2IAdapterDefaultSettings modelConfig={modelConfig} />
|
||||||
)}
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Flex flexDir="column" layerStyle="second" borderRadius="base" p={4} gap={4}>
|
||||||
{(modelConfig.type === 'main' || modelConfig.type === 'lora') && <TriggerPhrases modelConfig={modelConfig} />}
|
{(modelConfig.type === 'main' || modelConfig.type === 'lora') && <TriggerPhrases modelConfig={modelConfig} />}
|
||||||
</Box>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
import type { AnyModelConfig } from 'services/api/types';
|
||||||
|
|
||||||
|
export const SelectedModelContext = createContext<AnyModelConfig | null>(null);
|
@ -99,16 +99,39 @@ export const TriggerPhrases = memo(({ modelConfig }: Props) => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{triggerPhrases.length > 0 && (
|
||||||
<Flex gap="4" flexWrap="wrap">
|
<Flex gap="4" flexWrap="wrap">
|
||||||
{triggerPhrases.map((phrase, index) => (
|
{triggerPhrases.map((phrase, index) => (
|
||||||
<Tag size="md" key={index} py={2} px={4} bg="base.700">
|
<TriggerPhrasesTag
|
||||||
<TagLabel>{phrase}</TagLabel>
|
key={`${phrase}_${index}`}
|
||||||
<TagCloseButton onClick={removeTriggerPhrase.bind(null, phrase)} isDisabled={isLoading} />
|
phrase={phrase}
|
||||||
</Tag>
|
onRemoveTriggerPhrase={removeTriggerPhrase}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
TriggerPhrases.displayName = 'TriggerPhrases';
|
TriggerPhrases.displayName = 'TriggerPhrases';
|
||||||
|
|
||||||
|
type TriggerPhrasesTagProps = {
|
||||||
|
phrase: string;
|
||||||
|
onRemoveTriggerPhrase: (phrase: string) => void;
|
||||||
|
isLoading: boolean;
|
||||||
|
};
|
||||||
|
const TriggerPhrasesTag = memo(({ phrase, onRemoveTriggerPhrase, isLoading }: TriggerPhrasesTagProps) => {
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
onRemoveTriggerPhrase(phrase);
|
||||||
|
}, [onRemoveTriggerPhrase, phrase]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tag size="md" py={2} px={4} bg="base.700">
|
||||||
|
<TagLabel>{phrase}</TagLabel>
|
||||||
|
<TagCloseButton onClick={onClick} isDisabled={isLoading} />
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
TriggerPhrasesTag.displayName = 'TriggerPhrasesTag';
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { ModelManagerLeftPanel } from 'features/modelManagerV2/subpanels/ModelManagerLeftPanel';
|
||||||
import { ModelManager } from 'features/modelManagerV2/subpanels/ModelManager';
|
|
||||||
import { ModelPane } from 'features/modelManagerV2/subpanels/ModelPane';
|
import { ModelPane } from 'features/modelManagerV2/subpanels/ModelPane';
|
||||||
|
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||||
|
|
||||||
const ModelManagerTab = () => {
|
const ModelManagerTab = () => {
|
||||||
return (
|
return (
|
||||||
<Flex w="full" h="full" gap="2">
|
<PanelGroup direction="horizontal">
|
||||||
<ModelManager />
|
<Panel>
|
||||||
|
<ModelManagerLeftPanel />
|
||||||
|
</Panel>
|
||||||
|
<ResizeHandle orientation="vertical" />
|
||||||
|
<Panel>
|
||||||
<ModelPane />
|
<ModelPane />
|
||||||
</Flex>
|
</Panel>
|
||||||
|
</PanelGroup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ const ResizeHandle = (props: ResizeHandleProps) => {
|
|||||||
<ChakraPanelResizeHandle {...rest}>
|
<ChakraPanelResizeHandle {...rest}>
|
||||||
<Flex sx={sx} data-orientation={orientation}>
|
<Flex sx={sx} data-orientation={orientation}>
|
||||||
<Box className="resize-handle-inner" data-orientation={orientation} />
|
<Box className="resize-handle-inner" data-orientation={orientation} />
|
||||||
<Box className="resize-handle-drag-handle" data-orientation={orientation} />
|
{/* <Box className="resize-handle-drag-handle" data-orientation={orientation} /> */}
|
||||||
</Flex>
|
</Flex>
|
||||||
</ChakraPanelResizeHandle>
|
</ChakraPanelResizeHandle>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user