mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): support disabledFeatures, add nicer loading
- `disabledParametersPanels` -> `disabledFeatures` - handle disabling `faceRestore`, `upscaling`, `lightbox`, `modelManager` and OSS header links/buttons - wait until models are loaded to hide loading screen - also wait until schema is parsed if `nodes` is an enabled tab
This commit is contained in:
parent
82c4dd8b86
commit
c1c881ded5
@ -1,39 +0,0 @@
|
||||
import { Flex, Spinner, Text } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface LoaderProps {
|
||||
showText?: boolean;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
// This component loads before the theme so we cannot use theme tokens here
|
||||
|
||||
const Loading = (props: LoaderProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { showText = false, text = t('common.loadingInvokeAI') } = props;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
width="100vw"
|
||||
height="100vh"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
bg="#121212"
|
||||
flexDirection="column"
|
||||
rowGap={4}
|
||||
>
|
||||
<Spinner color="grey" w="5rem" h="5rem" />
|
||||
{showText && (
|
||||
<Text
|
||||
color="grey"
|
||||
fontWeight="semibold"
|
||||
fontFamily="'Inter', sans-serif"
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loading;
|
@ -15,17 +15,24 @@ import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel';
|
||||
import Lightbox from 'features/lightbox/components/Lightbox';
|
||||
import { useAppDispatch, useAppSelector } from './storeHooks';
|
||||
import { PropsWithChildren, useEffect } from 'react';
|
||||
import { setDisabledPanels, setDisabledTabs } from 'features/ui/store/uiSlice';
|
||||
import { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
import { shouldTransformUrlsChanged } from 'features/system/store/systemSlice';
|
||||
import { setShouldFetchImages } from 'features/gallery/store/resultsSlice';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import Loading from 'common/components/Loading/Loading';
|
||||
import {
|
||||
ApplicationFeature,
|
||||
disabledFeaturesChanged,
|
||||
disabledTabsChanged,
|
||||
} from 'features/system/store/systemSlice';
|
||||
import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady';
|
||||
|
||||
keepGUIAlive();
|
||||
|
||||
interface Props extends PropsWithChildren {
|
||||
options: {
|
||||
disabledPanels: string[];
|
||||
disabledTabs: InvokeTabName[];
|
||||
disabledFeatures: ApplicationFeature[];
|
||||
shouldTransformUrls?: boolean;
|
||||
shouldFetchImages: boolean;
|
||||
};
|
||||
@ -35,15 +42,21 @@ const App = (props: Props) => {
|
||||
useToastWatcher();
|
||||
|
||||
const currentTheme = useAppSelector((state) => state.ui.currentTheme);
|
||||
const disabledFeatures = useAppSelector(
|
||||
(state) => state.system.disabledFeatures
|
||||
);
|
||||
|
||||
const isApplicationReady = useIsApplicationReady();
|
||||
|
||||
const { setColorMode } = useColorMode();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setDisabledPanels(props.options.disabledPanels));
|
||||
}, [dispatch, props.options.disabledPanels]);
|
||||
dispatch(disabledFeaturesChanged(props.options.disabledFeatures));
|
||||
}, [dispatch, props.options.disabledFeatures]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setDisabledTabs(props.options.disabledTabs));
|
||||
dispatch(disabledTabsChanged(props.options.disabledTabs));
|
||||
}, [dispatch, props.options.disabledTabs]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -61,8 +74,8 @@ const App = (props: Props) => {
|
||||
}, [setColorMode, currentTheme]);
|
||||
|
||||
return (
|
||||
<Grid w="100vw" h="100vh">
|
||||
<Lightbox />
|
||||
<Grid w="100vw" h="100vh" position="relative">
|
||||
{!disabledFeatures.includes('lightbox') && <Lightbox />}
|
||||
<ImageUploader>
|
||||
<ProgressBar />
|
||||
<Grid
|
||||
@ -83,16 +96,34 @@ const App = (props: Props) => {
|
||||
<ImageGalleryPanel />
|
||||
</Flex>
|
||||
</Grid>
|
||||
<Box>
|
||||
<Console />
|
||||
</Box>
|
||||
</ImageUploader>
|
||||
|
||||
<AnimatePresence>
|
||||
{!isApplicationReady && (
|
||||
<motion.div
|
||||
key="loading"
|
||||
initial={{ opacity: 1 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
style={{ zIndex: 3 }}
|
||||
>
|
||||
<Box position="absolute" top={0} left={0} w="100vw" h="100vh">
|
||||
<Loading />
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<Portal>
|
||||
<FloatingParametersPanelButtons />
|
||||
</Portal>
|
||||
<Portal>
|
||||
<FloatingGalleryButton />
|
||||
</Portal>
|
||||
<Portal>
|
||||
<Console />
|
||||
</Portal>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,21 @@
|
||||
import { Flex, Image } from '@chakra-ui/react';
|
||||
import InvokeAILogoImage from 'assets/images/logo.png';
|
||||
|
||||
// This component loads before the theme so we cannot use theme tokens here
|
||||
|
||||
const Loading = () => {
|
||||
return (
|
||||
<Flex
|
||||
position="relative"
|
||||
width="100vw"
|
||||
height="100vh"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
bg="#151519"
|
||||
>
|
||||
<Image src={InvokeAILogoImage} w="8rem" h="8rem" />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loading;
|
@ -15,11 +15,12 @@ import '@fontsource/inter/700.css';
|
||||
import '@fontsource/inter/800.css';
|
||||
import '@fontsource/inter/900.css';
|
||||
|
||||
import Loading from './Loading';
|
||||
import Loading from './common/components/Loading/Loading';
|
||||
|
||||
// Localization
|
||||
import './i18n';
|
||||
import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
|
||||
import { ApplicationFeature } from 'features/system/store/systemSlice';
|
||||
|
||||
const App = lazy(() => import('./app/App'));
|
||||
const ThemeLocaleProvider = lazy(() => import('./app/ThemeLocaleProvider'));
|
||||
@ -28,6 +29,7 @@ interface Props extends PropsWithChildren {
|
||||
apiUrl?: string;
|
||||
disabledPanels?: string[];
|
||||
disabledTabs?: InvokeTabName[];
|
||||
disabledFeatures?: ApplicationFeature[];
|
||||
token?: string;
|
||||
shouldTransformUrls?: boolean;
|
||||
shouldFetchImages?: boolean;
|
||||
@ -35,8 +37,15 @@ interface Props extends PropsWithChildren {
|
||||
|
||||
export default function Component({
|
||||
apiUrl,
|
||||
disabledPanels = [],
|
||||
disabledTabs = [],
|
||||
disabledFeatures = [
|
||||
'lightbox',
|
||||
'bugLink',
|
||||
'discordLink',
|
||||
'githubLink',
|
||||
'localization',
|
||||
'modelManager',
|
||||
],
|
||||
token,
|
||||
children,
|
||||
shouldTransformUrls,
|
||||
@ -69,12 +78,12 @@ export default function Component({
|
||||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={<Loading />} persistor={persistor}>
|
||||
<React.Suspense fallback={<Loading showText />}>
|
||||
<React.Suspense fallback={<Loading />}>
|
||||
<ThemeLocaleProvider>
|
||||
<App
|
||||
options={{
|
||||
disabledPanels,
|
||||
disabledTabs,
|
||||
disabledFeatures,
|
||||
shouldTransformUrls,
|
||||
shouldFetchImages,
|
||||
}}
|
||||
|
@ -70,8 +70,8 @@ const currentImageButtonsSelector = createSelector(
|
||||
selectedImageSelector,
|
||||
],
|
||||
(
|
||||
system: SystemState,
|
||||
gallery: GalleryState,
|
||||
system,
|
||||
gallery,
|
||||
postprocessing,
|
||||
ui,
|
||||
lightbox,
|
||||
@ -81,6 +81,8 @@ const currentImageButtonsSelector = createSelector(
|
||||
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
|
||||
system;
|
||||
|
||||
const { disabledFeatures } = system;
|
||||
|
||||
const { upscalingLevel, facetoolStrength } = postprocessing;
|
||||
|
||||
const { isLightboxOpen } = lightbox;
|
||||
@ -90,6 +92,7 @@ const currentImageButtonsSelector = createSelector(
|
||||
const { intermediateImage, currentImage } = gallery;
|
||||
|
||||
return {
|
||||
disabledFeatures,
|
||||
isProcessing,
|
||||
isConnected,
|
||||
isGFPGANAvailable,
|
||||
@ -134,7 +137,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
activeTabName,
|
||||
shouldHidePreview,
|
||||
selectedImage,
|
||||
disabledFeatures,
|
||||
} = useAppSelector(currentImageButtonsSelector);
|
||||
|
||||
const { getUrl, shouldTransformUrls } = useGetUrl();
|
||||
|
||||
const toast = useToast();
|
||||
@ -328,24 +333,19 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
useHotkeys(
|
||||
'Shift+U',
|
||||
() => {
|
||||
if (
|
||||
isESRGANAvailable &&
|
||||
!shouldDisableToolbarButtons &&
|
||||
isConnected &&
|
||||
!isProcessing &&
|
||||
upscalingLevel
|
||||
) {
|
||||
handleClickUpscale();
|
||||
} else {
|
||||
toast({
|
||||
title: t('toast.upscalingFailed'),
|
||||
status: 'error',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
handleClickUpscale();
|
||||
},
|
||||
{
|
||||
enabled: () =>
|
||||
disabledFeatures.includes('upscaling') ||
|
||||
!isESRGANAvailable ||
|
||||
shouldDisableToolbarButtons ||
|
||||
!isConnected ||
|
||||
isProcessing ||
|
||||
!upscalingLevel,
|
||||
},
|
||||
[
|
||||
disabledFeatures,
|
||||
selectedImage,
|
||||
isESRGANAvailable,
|
||||
shouldDisableToolbarButtons,
|
||||
@ -362,24 +362,20 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
useHotkeys(
|
||||
'Shift+R',
|
||||
() => {
|
||||
if (
|
||||
isGFPGANAvailable &&
|
||||
!shouldDisableToolbarButtons &&
|
||||
isConnected &&
|
||||
!isProcessing &&
|
||||
facetoolStrength
|
||||
) {
|
||||
handleClickFixFaces();
|
||||
} else {
|
||||
toast({
|
||||
title: t('toast.faceRestoreFailed'),
|
||||
status: 'error',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
handleClickFixFaces();
|
||||
},
|
||||
{
|
||||
enabled: () =>
|
||||
disabledFeatures.includes('faceRestore') ||
|
||||
!isGFPGANAvailable ||
|
||||
shouldDisableToolbarButtons ||
|
||||
!isConnected ||
|
||||
isProcessing ||
|
||||
!facetoolStrength,
|
||||
},
|
||||
|
||||
[
|
||||
disabledFeatures,
|
||||
selectedImage,
|
||||
isGFPGANAvailable,
|
||||
shouldDisableToolbarButtons,
|
||||
@ -509,21 +505,23 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
isChecked={shouldHidePreview}
|
||||
onClick={handlePreviewVisibility}
|
||||
/>
|
||||
<IAIIconButton
|
||||
icon={<FaExpand />}
|
||||
tooltip={
|
||||
!isLightboxOpen
|
||||
? `${t('parameters.openInViewer')} (Z)`
|
||||
: `${t('parameters.closeViewer')} (Z)`
|
||||
}
|
||||
aria-label={
|
||||
!isLightboxOpen
|
||||
? `${t('parameters.openInViewer')} (Z)`
|
||||
: `${t('parameters.closeViewer')} (Z)`
|
||||
}
|
||||
isChecked={isLightboxOpen}
|
||||
onClick={handleLightBox}
|
||||
/>
|
||||
{!disabledFeatures.includes('lightbox') && (
|
||||
<IAIIconButton
|
||||
icon={<FaExpand />}
|
||||
tooltip={
|
||||
!isLightboxOpen
|
||||
? `${t('parameters.openInViewer')} (Z)`
|
||||
: `${t('parameters.closeViewer')} (Z)`
|
||||
}
|
||||
aria-label={
|
||||
!isLightboxOpen
|
||||
? `${t('parameters.openInViewer')} (Z)`
|
||||
: `${t('parameters.closeViewer')} (Z)`
|
||||
}
|
||||
isChecked={isLightboxOpen}
|
||||
onClick={handleLightBox}
|
||||
/>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup isAttached={true}>
|
||||
@ -556,65 +554,74 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup isAttached={true}>
|
||||
<IAIPopover
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
icon={<FaGrinStars />}
|
||||
aria-label={t('parameters.restoreFaces')}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
rowGap: 4,
|
||||
}}
|
||||
>
|
||||
<FaceRestoreSettings />
|
||||
<IAIButton
|
||||
isDisabled={
|
||||
!isGFPGANAvailable ||
|
||||
!selectedImage ||
|
||||
!(isConnected && !isProcessing) ||
|
||||
!facetoolStrength
|
||||
{!(
|
||||
disabledFeatures.includes('faceRestore') &&
|
||||
disabledFeatures.includes('upscaling')
|
||||
) && (
|
||||
<ButtonGroup isAttached={true}>
|
||||
{!disabledFeatures.includes('faceRestore') && (
|
||||
<IAIPopover
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
icon={<FaGrinStars />}
|
||||
aria-label={t('parameters.restoreFaces')}
|
||||
/>
|
||||
}
|
||||
onClick={handleClickFixFaces}
|
||||
>
|
||||
{t('parameters.restoreFaces')}
|
||||
</IAIButton>
|
||||
</Flex>
|
||||
</IAIPopover>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
rowGap: 4,
|
||||
}}
|
||||
>
|
||||
<FaceRestoreSettings />
|
||||
<IAIButton
|
||||
isDisabled={
|
||||
!isGFPGANAvailable ||
|
||||
!selectedImage ||
|
||||
!(isConnected && !isProcessing) ||
|
||||
!facetoolStrength
|
||||
}
|
||||
onClick={handleClickFixFaces}
|
||||
>
|
||||
{t('parameters.restoreFaces')}
|
||||
</IAIButton>
|
||||
</Flex>
|
||||
</IAIPopover>
|
||||
)}
|
||||
|
||||
<IAIPopover
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
icon={<FaExpandArrowsAlt />}
|
||||
aria-label={t('parameters.upscale')}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<UpscaleSettings />
|
||||
<IAIButton
|
||||
isDisabled={
|
||||
!isESRGANAvailable ||
|
||||
!selectedImage ||
|
||||
!(isConnected && !isProcessing) ||
|
||||
!upscalingLevel
|
||||
{!disabledFeatures.includes('upscaling') && (
|
||||
<IAIPopover
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
icon={<FaExpandArrowsAlt />}
|
||||
aria-label={t('parameters.upscale')}
|
||||
/>
|
||||
}
|
||||
onClick={handleClickUpscale}
|
||||
>
|
||||
{t('parameters.upscaleImage')}
|
||||
</IAIButton>
|
||||
</Flex>
|
||||
</IAIPopover>
|
||||
</ButtonGroup>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<UpscaleSettings />
|
||||
<IAIButton
|
||||
isDisabled={
|
||||
!isESRGANAvailable ||
|
||||
!selectedImage ||
|
||||
!(isConnected && !isProcessing) ||
|
||||
!upscalingLevel
|
||||
}
|
||||
onClick={handleClickUpscale}
|
||||
>
|
||||
{t('parameters.upscaleImage')}
|
||||
</IAIButton>
|
||||
</Flex>
|
||||
</IAIPopover>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
)}
|
||||
|
||||
<ButtonGroup isAttached={true}>
|
||||
<IAIIconButton
|
||||
|
@ -57,6 +57,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
galleryImageMinimumWidth,
|
||||
mayDeleteImage,
|
||||
shouldUseSingleGalleryColumn,
|
||||
disabledFeatures,
|
||||
} = useAppSelector(hoverableImageSelector);
|
||||
const { image, isSelected } = props;
|
||||
const { url, thumbnail, name, metadata } = image;
|
||||
@ -133,7 +134,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
metadata.sd_metadata?.image?.init_image_path
|
||||
);
|
||||
if (response.ok) {
|
||||
dispatch(setActiveTab('img2img'));
|
||||
dispatch(setAllImageToImageParameters(metadata?.sd_metadata));
|
||||
toast({
|
||||
title: t('toast.initialImageSet'),
|
||||
@ -174,9 +174,11 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
menuProps={{ size: 'sm', isLazy: true }}
|
||||
renderMenu={() => (
|
||||
<MenuList>
|
||||
<MenuItem onClickCapture={handleLightBox}>
|
||||
{t('parameters.openInViewer')}
|
||||
</MenuItem>
|
||||
{!disabledFeatures.includes('lightbox') && (
|
||||
<MenuItem onClickCapture={handleLightBox}>
|
||||
{t('parameters.openInViewer')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
onClickCapture={handleUsePrompt}
|
||||
isDisabled={image?.metadata?.sd_metadata?.prompt === undefined}
|
||||
|
@ -77,6 +77,7 @@ export const hoverableImageSelector = createSelector(
|
||||
shouldUseSingleGalleryColumn: gallery.shouldUseSingleGalleryColumn,
|
||||
activeTabName,
|
||||
isLightboxOpen: lightbox.isLightboxOpen,
|
||||
disabledFeatures: system.disabledFeatures,
|
||||
};
|
||||
},
|
||||
{
|
||||
|
@ -85,8 +85,7 @@ const nodesSlice = createSlice({
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => {
|
||||
state.schema = action.payload;
|
||||
state.invocationTemplates = parseSchema(action.payload);
|
||||
state.invocationTemplates = action.payload;
|
||||
});
|
||||
|
||||
builder.addMatcher(isFulfilledAnyGraphBuilt, (state, action) => {
|
||||
|
@ -114,7 +114,5 @@ export const parseSchema = (openAPI: OpenAPIV3.Document) => {
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
console.debug('Generated invocations: ', invocations);
|
||||
|
||||
return invocations;
|
||||
};
|
||||
|
@ -2,36 +2,41 @@ import { Accordion } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { Feature } from 'app/features';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import { tabMap } from 'features/ui/store/tabMap';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { openAccordionItemsChanged } from 'features/ui/store/uiSlice';
|
||||
import { filter } from 'lodash';
|
||||
import { filter, map } from 'lodash';
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import InvokeAccordionItem from './AccordionItems/InvokeAccordionItem';
|
||||
|
||||
const parametersAccordionSelector = createSelector([uiSelector], (uiSlice) => {
|
||||
const {
|
||||
activeTab,
|
||||
openLinearAccordionItems,
|
||||
openUnifiedCanvasAccordionItems,
|
||||
disabledParameterPanels,
|
||||
} = uiSlice;
|
||||
const parametersAccordionSelector = createSelector(
|
||||
[uiSelector, systemSelector],
|
||||
(uiSlice, system) => {
|
||||
const {
|
||||
activeTab,
|
||||
openLinearAccordionItems,
|
||||
openUnifiedCanvasAccordionItems,
|
||||
} = uiSlice;
|
||||
|
||||
let openAccordions: number[] = [];
|
||||
const { disabledFeatures } = system;
|
||||
|
||||
if (tabMap[activeTab] === 'linear') {
|
||||
openAccordions = openLinearAccordionItems;
|
||||
let openAccordions: number[] = [];
|
||||
|
||||
if (tabMap[activeTab] === 'linear') {
|
||||
openAccordions = openLinearAccordionItems;
|
||||
}
|
||||
|
||||
if (tabMap[activeTab] === 'unifiedCanvas') {
|
||||
openAccordions = openUnifiedCanvasAccordionItems;
|
||||
}
|
||||
|
||||
return {
|
||||
openAccordions,
|
||||
disabledFeatures,
|
||||
};
|
||||
}
|
||||
|
||||
if (tabMap[activeTab] === 'unifiedCanvas') {
|
||||
openAccordions = openUnifiedCanvasAccordionItems;
|
||||
}
|
||||
|
||||
return {
|
||||
openAccordions,
|
||||
disabledParameterPanels,
|
||||
};
|
||||
});
|
||||
);
|
||||
|
||||
export type ParametersAccordionItem = {
|
||||
name: string;
|
||||
@ -53,7 +58,7 @@ type ParametersAccordionProps = {
|
||||
* Main container for generation and processing parameters.
|
||||
*/
|
||||
const ParametersAccordion = ({ accordionItems }: ParametersAccordionProps) => {
|
||||
const { openAccordions, disabledParameterPanels } = useAppSelector(
|
||||
const { openAccordions, disabledFeatures } = useAppSelector(
|
||||
parametersAccordionSelector
|
||||
);
|
||||
|
||||
@ -68,20 +73,16 @@ const ParametersAccordion = ({ accordionItems }: ParametersAccordionProps) => {
|
||||
};
|
||||
|
||||
// Render function for accordion items
|
||||
const renderAccordionItems = useCallback(() => {
|
||||
// Filter out disabled accordions
|
||||
const filteredAccordionItems = filter(
|
||||
accordionItems,
|
||||
(item) => disabledParameterPanels.indexOf(item.name) === -1
|
||||
);
|
||||
|
||||
return filteredAccordionItems.map((accordionItem) => (
|
||||
<InvokeAccordionItem
|
||||
key={accordionItem.name}
|
||||
accordionItem={accordionItem}
|
||||
/>
|
||||
));
|
||||
}, [disabledParameterPanels, accordionItems]);
|
||||
const renderAccordionItems = useCallback(
|
||||
() =>
|
||||
map(accordionItems, (accordionItem) => (
|
||||
<InvokeAccordionItem
|
||||
key={accordionItem.name}
|
||||
accordionItem={accordionItem}
|
||||
/>
|
||||
)),
|
||||
[accordionItems]
|
||||
);
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
|
@ -110,7 +110,7 @@ const Console = () => {
|
||||
position: 'fixed',
|
||||
insetInlineStart: 0,
|
||||
bottom: 0,
|
||||
zIndex: 9999,
|
||||
zIndex: 1,
|
||||
}}
|
||||
maxHeight="90vh"
|
||||
>
|
||||
@ -128,6 +128,7 @@ const Console = () => {
|
||||
borderTopWidth: 5,
|
||||
bg: 'base.850',
|
||||
borderColor: 'base.700',
|
||||
zIndex: 2,
|
||||
}}
|
||||
ref={viewerRef}
|
||||
onScroll={handleOnScroll}
|
||||
@ -166,7 +167,7 @@ const Console = () => {
|
||||
position: 'fixed',
|
||||
insetInlineStart: 2,
|
||||
bottom: 12,
|
||||
zIndex: '10000',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
@ -184,7 +185,7 @@ const Console = () => {
|
||||
position: 'fixed',
|
||||
insetInlineStart: 2,
|
||||
bottom: 2,
|
||||
zIndex: '10000',
|
||||
zIndex: 1,
|
||||
}}
|
||||
colorScheme={hasError || !wasErrorSeen ? 'error' : 'base'}
|
||||
/>
|
||||
|
@ -8,8 +8,13 @@ import ModelManagerModal from './ModelManager/ModelManagerModal';
|
||||
import SettingsModal from './SettingsModal/SettingsModal';
|
||||
import ThemeChanger from './ThemeChanger';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import { RootState } from 'app/store';
|
||||
|
||||
const SiteHeaderMenu = () => {
|
||||
const disabledFeatures = useAppSelector(
|
||||
(state: RootState) => state.system.disabledFeatures
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@ -18,17 +23,19 @@ const SiteHeaderMenu = () => {
|
||||
flexDirection={{ base: 'column', xl: 'row' }}
|
||||
gap={{ base: 4, xl: 1 }}
|
||||
>
|
||||
<ModelManagerModal>
|
||||
<IAIIconButton
|
||||
aria-label={t('modelManager.modelManager')}
|
||||
tooltip={t('modelManager.modelManager')}
|
||||
size="sm"
|
||||
variant="link"
|
||||
data-variant="link"
|
||||
fontSize={20}
|
||||
icon={<FaCube />}
|
||||
/>
|
||||
</ModelManagerModal>
|
||||
{!disabledFeatures.includes('modelManager') && (
|
||||
<ModelManagerModal>
|
||||
<IAIIconButton
|
||||
aria-label={t('modelManager.modelManager')}
|
||||
tooltip={t('modelManager.modelManager')}
|
||||
size="sm"
|
||||
variant="link"
|
||||
data-variant="link"
|
||||
fontSize={20}
|
||||
icon={<FaCube />}
|
||||
/>
|
||||
</ModelManagerModal>
|
||||
)}
|
||||
|
||||
<HotkeysModal>
|
||||
<IAIIconButton
|
||||
@ -44,55 +51,61 @@ const SiteHeaderMenu = () => {
|
||||
|
||||
<ThemeChanger />
|
||||
|
||||
<LanguagePicker />
|
||||
{!disabledFeatures.includes('localization') && <LanguagePicker />}
|
||||
|
||||
<Link
|
||||
isExternal
|
||||
href="http://github.com/invoke-ai/InvokeAI/issues"
|
||||
marginBottom="-0.25rem"
|
||||
>
|
||||
<IAIIconButton
|
||||
aria-label={t('common.reportBugLabel')}
|
||||
tooltip={t('common.reportBugLabel')}
|
||||
variant="link"
|
||||
data-variant="link"
|
||||
fontSize={20}
|
||||
size="sm"
|
||||
icon={<FaBug />}
|
||||
/>
|
||||
</Link>
|
||||
{!disabledFeatures.includes('bugLink') && (
|
||||
<Link
|
||||
isExternal
|
||||
href="http://github.com/invoke-ai/InvokeAI/issues"
|
||||
marginBottom="-0.25rem"
|
||||
>
|
||||
<IAIIconButton
|
||||
aria-label={t('common.reportBugLabel')}
|
||||
tooltip={t('common.reportBugLabel')}
|
||||
variant="link"
|
||||
data-variant="link"
|
||||
fontSize={20}
|
||||
size="sm"
|
||||
icon={<FaBug />}
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<Link
|
||||
isExternal
|
||||
href="http://github.com/invoke-ai/InvokeAI"
|
||||
marginBottom="-0.25rem"
|
||||
>
|
||||
<IAIIconButton
|
||||
aria-label={t('common.githubLabel')}
|
||||
tooltip={t('common.githubLabel')}
|
||||
variant="link"
|
||||
data-variant="link"
|
||||
fontSize={20}
|
||||
size="sm"
|
||||
icon={<FaGithub />}
|
||||
/>
|
||||
</Link>
|
||||
{!disabledFeatures.includes('githubLink') && (
|
||||
<Link
|
||||
isExternal
|
||||
href="http://github.com/invoke-ai/InvokeAI"
|
||||
marginBottom="-0.25rem"
|
||||
>
|
||||
<IAIIconButton
|
||||
aria-label={t('common.githubLabel')}
|
||||
tooltip={t('common.githubLabel')}
|
||||
variant="link"
|
||||
data-variant="link"
|
||||
fontSize={20}
|
||||
size="sm"
|
||||
icon={<FaGithub />}
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<Link
|
||||
isExternal
|
||||
href="https://discord.gg/ZmtBAhwWhy"
|
||||
marginBottom="-0.25rem"
|
||||
>
|
||||
<IAIIconButton
|
||||
aria-label={t('common.discordLabel')}
|
||||
tooltip={t('common.discordLabel')}
|
||||
variant="link"
|
||||
data-variant="link"
|
||||
fontSize={20}
|
||||
size="sm"
|
||||
icon={<FaDiscord />}
|
||||
/>
|
||||
</Link>
|
||||
{!disabledFeatures.includes('discordLink') && (
|
||||
<Link
|
||||
isExternal
|
||||
href="https://discord.gg/ZmtBAhwWhy"
|
||||
marginBottom="-0.25rem"
|
||||
>
|
||||
<IAIIconButton
|
||||
aria-label={t('common.discordLabel')}
|
||||
tooltip={t('common.discordLabel')}
|
||||
variant="link"
|
||||
data-variant="link"
|
||||
fontSize={20}
|
||||
size="sm"
|
||||
icon={<FaDiscord />}
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<SettingsModal>
|
||||
<IAIIconButton
|
||||
|
@ -0,0 +1,46 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
const isApplicationReadySelector = createSelector(
|
||||
[(state: RootState) => state.system],
|
||||
(system) => {
|
||||
const {
|
||||
disabledFeatures,
|
||||
disabledTabs,
|
||||
wereModelsReceived,
|
||||
wasSchemaParsed,
|
||||
} = system;
|
||||
|
||||
return {
|
||||
disabledTabs,
|
||||
disabledFeatures,
|
||||
wereModelsReceived,
|
||||
wasSchemaParsed,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const useIsApplicationReady = () => {
|
||||
const {
|
||||
disabledTabs,
|
||||
disabledFeatures,
|
||||
wereModelsReceived,
|
||||
wasSchemaParsed,
|
||||
} = useAppSelector(isApplicationReadySelector);
|
||||
|
||||
const isApplicationReady = useMemo(() => {
|
||||
if (!wereModelsReceived) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!disabledTabs.includes('nodes') && !wasSchemaParsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [disabledTabs, wereModelsReceived, wasSchemaParsed]);
|
||||
|
||||
return isApplicationReady;
|
||||
};
|
@ -19,6 +19,8 @@ const itemsToBlacklist: (keyof SystemState)[] = [
|
||||
'isCancelScheduled',
|
||||
'sessionId',
|
||||
'progressImage',
|
||||
'wereModelsReceived',
|
||||
'wasSchemaParsed',
|
||||
];
|
||||
|
||||
export const systemBlacklist = itemsToBlacklist.map(
|
||||
|
@ -19,6 +19,9 @@ import { ProgressImage } from 'services/events/types';
|
||||
import { initialImageSelected } from 'features/parameters/store/generationSlice';
|
||||
import { makeToast } from '../hooks/useToastWatcher';
|
||||
import { sessionCanceled, sessionInvoked } from 'services/thunks/session';
|
||||
import { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
import { receivedModels } from 'services/thunks/model';
|
||||
import { receivedOpenAPISchema } from 'services/thunks/schema';
|
||||
|
||||
export type LogLevel = 'info' | 'warning' | 'error';
|
||||
|
||||
@ -32,10 +35,18 @@ export interface Log {
|
||||
[index: number]: LogEntry;
|
||||
}
|
||||
|
||||
export type ReadinessPayload = {
|
||||
isReady: boolean;
|
||||
reasonsWhyNotReady: string[];
|
||||
};
|
||||
/**
|
||||
* A disable-able application feature
|
||||
*/
|
||||
export type ApplicationFeature =
|
||||
| 'faceRestore'
|
||||
| 'upscaling'
|
||||
| 'lightbox'
|
||||
| 'modelManager'
|
||||
| 'githubLink'
|
||||
| 'discordLink'
|
||||
| 'bugLink'
|
||||
| 'localization';
|
||||
|
||||
export type InProgressImageType = 'none' | 'full-res' | 'latents';
|
||||
|
||||
@ -96,6 +107,22 @@ export interface SystemState
|
||||
* Whether or not URLs should be transformed to use a different host
|
||||
*/
|
||||
shouldTransformUrls: boolean;
|
||||
/**
|
||||
* Array of disabled tabs
|
||||
*/
|
||||
disabledTabs: InvokeTabName[];
|
||||
/**
|
||||
* Array of disabled features
|
||||
*/
|
||||
disabledFeatures: ApplicationFeature[];
|
||||
/**
|
||||
* Whether or not the available models were received
|
||||
*/
|
||||
wereModelsReceived: boolean;
|
||||
/**
|
||||
* Whether or not the OpenAPI schema was received and parsed
|
||||
*/
|
||||
wasSchemaParsed: boolean;
|
||||
}
|
||||
|
||||
const initialSystemState: SystemState = {
|
||||
@ -144,6 +171,10 @@ const initialSystemState: SystemState = {
|
||||
isCancelScheduled: false,
|
||||
subscribedNodeIds: [],
|
||||
shouldTransformUrls: false,
|
||||
disabledTabs: [],
|
||||
disabledFeatures: [],
|
||||
wereModelsReceived: false,
|
||||
wasSchemaParsed: false,
|
||||
};
|
||||
|
||||
export const systemSlice = createSlice({
|
||||
@ -347,6 +378,21 @@ export const systemSlice = createSlice({
|
||||
shouldTransformUrlsChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldTransformUrls = action.payload;
|
||||
},
|
||||
/**
|
||||
* `disabledTabs` was changed
|
||||
*/
|
||||
disabledTabsChanged: (state, action: PayloadAction<InvokeTabName[]>) => {
|
||||
state.disabledTabs = action.payload;
|
||||
},
|
||||
/**
|
||||
* `disabledFeatures` was changed
|
||||
*/
|
||||
disabledFeaturesChanged: (
|
||||
state,
|
||||
action: PayloadAction<ApplicationFeature[]>
|
||||
) => {
|
||||
state.disabledFeatures = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
/**
|
||||
@ -417,7 +463,8 @@ export const systemSlice = createSlice({
|
||||
step,
|
||||
total_steps,
|
||||
progress_image,
|
||||
invocation,
|
||||
node,
|
||||
source_node_id,
|
||||
graph_execution_state_id,
|
||||
} = action.payload.data;
|
||||
|
||||
@ -514,6 +561,20 @@ export const systemSlice = createSlice({
|
||||
builder.addCase(initialImageSelected, (state) => {
|
||||
state.toastQueue.push(makeToast(i18n.t('toast.sentToImageToImage')));
|
||||
});
|
||||
|
||||
/**
|
||||
* Received available models from the backend
|
||||
*/
|
||||
builder.addCase(receivedModels.fulfilled, (state, action) => {
|
||||
state.wereModelsReceived = true;
|
||||
});
|
||||
|
||||
/**
|
||||
* OpenAPI schema was received and parsed
|
||||
*/
|
||||
builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => {
|
||||
state.wasSchemaParsed = true;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -554,6 +615,8 @@ export const {
|
||||
cancelTypeChanged,
|
||||
subscribedNodeIdsSet,
|
||||
shouldTransformUrlsChanged,
|
||||
disabledTabsChanged,
|
||||
disabledFeaturesChanged,
|
||||
} = systemSlice.actions;
|
||||
|
||||
export default systemSlice.reducer;
|
||||
|
@ -64,8 +64,13 @@ export default function InvokeTabs() {
|
||||
(state: RootState) => state.lightbox.isLightboxOpen
|
||||
);
|
||||
|
||||
const { shouldPinGallery, disabledTabs, shouldPinParametersPanel } =
|
||||
useAppSelector((state: RootState) => state.ui);
|
||||
const { shouldPinGallery, shouldPinParametersPanel } = useAppSelector(
|
||||
(state: RootState) => state.ui
|
||||
);
|
||||
|
||||
const disabledTabs = useAppSelector(
|
||||
(state: RootState) => state.system.disabledTabs
|
||||
);
|
||||
|
||||
const activeTabs = buildTabs(disabledTabs);
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { initialImageSelected } from 'features/parameters/store/generationSlice';
|
||||
import { setActiveTabReducer } from './extraReducers';
|
||||
import { InvokeTabName, tabMap } from './tabMap';
|
||||
import { AddNewModelType, UIState } from './uiTypes';
|
||||
|
||||
const initialtabsState: UIState = {
|
||||
const initialUIState: UIState = {
|
||||
activeTab: 0,
|
||||
currentTheme: 'dark',
|
||||
parametersPanelScrollPosition: 0,
|
||||
@ -19,13 +18,11 @@ const initialtabsState: UIState = {
|
||||
shouldPinGallery: true,
|
||||
shouldShowGallery: true,
|
||||
shouldHidePreview: false,
|
||||
disabledParameterPanels: [],
|
||||
disabledTabs: [],
|
||||
openLinearAccordionItems: [],
|
||||
openUnifiedCanvasAccordionItems: [],
|
||||
};
|
||||
|
||||
const initialState: UIState = initialtabsState;
|
||||
const initialState: UIState = initialUIState;
|
||||
|
||||
export const uiSlice = createSlice({
|
||||
name: 'ui',
|
||||
@ -98,12 +95,6 @@ export const uiSlice = createSlice({
|
||||
state.shouldShowParametersPanel = true;
|
||||
}
|
||||
},
|
||||
setDisabledPanels: (state, action: PayloadAction<string[]>) => {
|
||||
state.disabledParameterPanels = action.payload;
|
||||
},
|
||||
setDisabledTabs: (state, action: PayloadAction<InvokeTabName[]>) => {
|
||||
state.disabledTabs = action.payload;
|
||||
},
|
||||
openAccordionItemsChanged: (state, action: PayloadAction<number[]>) => {
|
||||
if (tabMap[state.activeTab] === 'linear') {
|
||||
state.openLinearAccordionItems = action.payload;
|
||||
@ -135,8 +126,6 @@ export const {
|
||||
togglePinParametersPanel,
|
||||
toggleParametersPanel,
|
||||
toggleGalleryPanel,
|
||||
setDisabledPanels,
|
||||
setDisabledTabs,
|
||||
openAccordionItemsChanged,
|
||||
} = uiSlice.actions;
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { InvokeTabName } from './tabMap';
|
||||
|
||||
export type AddNewModelType = 'ckpt' | 'diffusers' | null;
|
||||
|
||||
export interface UIState {
|
||||
@ -16,8 +14,6 @@ export interface UIState {
|
||||
shouldHidePreview: boolean;
|
||||
shouldPinGallery: boolean;
|
||||
shouldShowGallery: boolean;
|
||||
disabledParameterPanels: string[];
|
||||
disabledTabs: InvokeTabName[];
|
||||
openLinearAccordionItems: number[];
|
||||
openUnifiedCanvasAccordionItems: number[];
|
||||
}
|
||||
|
@ -92,7 +92,9 @@ export const socketMiddleware = () => {
|
||||
socket.on('connect', () => {
|
||||
dispatch(socketConnected({ timestamp: getTimestamp() }));
|
||||
|
||||
const { results, uploads, models, nodes } = getState();
|
||||
const { results, uploads, models, nodes, system } = getState();
|
||||
|
||||
const { disabledTabs } = system;
|
||||
|
||||
// These thunks need to be dispatch in middleware; cannot handle in a reducer
|
||||
if (!results.ids.length) {
|
||||
@ -107,7 +109,7 @@ export const socketMiddleware = () => {
|
||||
dispatch(receivedModels());
|
||||
}
|
||||
|
||||
if (!nodes.schema) {
|
||||
if (!nodes.schema && !disabledTabs.includes('nodes')) {
|
||||
dispatch(receivedOpenAPISchema());
|
||||
}
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { parseSchema } from 'features/nodes/util/parseSchema';
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
|
||||
export const receivedOpenAPISchema = createAsyncThunk(
|
||||
@ -9,6 +10,10 @@ export const receivedOpenAPISchema = createAsyncThunk(
|
||||
|
||||
console.debug('OpenAPI schema: ', jsonData);
|
||||
|
||||
return jsonData;
|
||||
const parsedSchema = parseSchema(jsonData);
|
||||
|
||||
console.debug('Parsed schema: ', parsedSchema);
|
||||
|
||||
return parsedSchema;
|
||||
}
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user