mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add config slice, configuration default values
This commit is contained in:
parent
55e33eaf4c
commit
0a936696c3
@ -15,62 +15,39 @@ import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel';
|
||||
import Lightbox from 'features/lightbox/components/Lightbox';
|
||||
import { useAppDispatch, useAppSelector } from './storeHooks';
|
||||
import { PropsWithChildren, useCallback, useEffect, useState } from 'react';
|
||||
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 {
|
||||
disabledFeaturesChanged,
|
||||
disabledTabsChanged,
|
||||
} from 'features/system/store/systemSlice';
|
||||
import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady';
|
||||
import { ApplicationFeature } from './invokeai';
|
||||
import { AppConfig } from './invokeai';
|
||||
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
|
||||
import { configChanged } from 'features/system/store/configSlice';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
|
||||
keepGUIAlive();
|
||||
|
||||
interface Props extends PropsWithChildren {
|
||||
options: {
|
||||
disabledTabs: InvokeTabName[];
|
||||
disabledFeatures: ApplicationFeature[];
|
||||
shouldTransformUrls?: boolean;
|
||||
shouldFetchImages: boolean;
|
||||
};
|
||||
config?: Partial<AppConfig>;
|
||||
}
|
||||
|
||||
const App = (props: Props) => {
|
||||
const App = ({ config = {}, children }: Props) => {
|
||||
useToastWatcher();
|
||||
useGlobalHotkeys();
|
||||
|
||||
const currentTheme = useAppSelector((state) => state.ui.currentTheme);
|
||||
const disabledFeatures = useAppSelector(
|
||||
(state) => state.system.disabledFeatures
|
||||
);
|
||||
|
||||
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
||||
|
||||
const isApplicationReady = useIsApplicationReady();
|
||||
|
||||
const [loadingOverridden, setLoadingOverridden] = useState(false);
|
||||
|
||||
const { setColorMode } = useColorMode();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(disabledFeaturesChanged(props.options.disabledFeatures));
|
||||
}, [dispatch, props.options.disabledFeatures]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(disabledTabsChanged(props.options.disabledTabs));
|
||||
}, [dispatch, props.options.disabledTabs]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(
|
||||
shouldTransformUrlsChanged(Boolean(props.options.shouldTransformUrls))
|
||||
);
|
||||
}, [dispatch, props.options.shouldTransformUrls]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setShouldFetchImages(props.options.shouldFetchImages));
|
||||
}, [dispatch, props.options.shouldFetchImages]);
|
||||
console.log('Received config: ', config);
|
||||
dispatch(configChanged(config));
|
||||
}, [dispatch, config]);
|
||||
|
||||
useEffect(() => {
|
||||
setColorMode(['light'].includes(currentTheme) ? 'light' : 'dark');
|
||||
@ -82,7 +59,7 @@ const App = (props: Props) => {
|
||||
|
||||
return (
|
||||
<Grid w="100vw" h="100vh" position="relative">
|
||||
{!disabledFeatures.includes('lightbox') && <Lightbox />}
|
||||
{isLightboxEnabled && <Lightbox />}
|
||||
<ImageUploader>
|
||||
<ProgressBar />
|
||||
<Grid
|
||||
@ -92,7 +69,7 @@ const App = (props: Props) => {
|
||||
w={APP_WIDTH}
|
||||
h={APP_HEIGHT}
|
||||
>
|
||||
{props.children || <SiteHeader />}
|
||||
{children || <SiteHeader />}
|
||||
<Flex
|
||||
gap={4}
|
||||
w={{ base: '100vw', xl: 'full' }}
|
||||
|
104
invokeai/frontend/web/src/app/invokeai.d.ts
vendored
104
invokeai/frontend/web/src/app/invokeai.d.ts
vendored
@ -12,24 +12,12 @@
|
||||
* 'gfpgan'.
|
||||
*/
|
||||
|
||||
import { FacetoolType } from 'features/parameters/store/postprocessingSlice';
|
||||
import { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
import { IRect } from 'konva/lib/types';
|
||||
import { ImageMetadata, ImageType } from 'services/api';
|
||||
import { AnyInvocation } from 'services/events/types';
|
||||
|
||||
/**
|
||||
* A disable-able application feature
|
||||
*/
|
||||
export declare type ApplicationFeature =
|
||||
| 'faceRestore'
|
||||
| 'upscaling'
|
||||
| 'lightbox'
|
||||
| 'modelManager'
|
||||
| 'githubLink'
|
||||
| 'discordLink'
|
||||
| 'bugLink'
|
||||
| 'localization';
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* Once an image has been generated, if it is postprocessed again,
|
||||
@ -347,3 +335,93 @@ export declare type UploadOutpaintingMergeImagePayload = {
|
||||
dataURL: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A disable-able application feature
|
||||
*/
|
||||
export declare type AppFeature =
|
||||
| 'faceRestore'
|
||||
| 'upscaling'
|
||||
| 'lightbox'
|
||||
| 'modelManager'
|
||||
| 'githubLink'
|
||||
| 'discordLink'
|
||||
| 'bugLink'
|
||||
| 'localization';
|
||||
|
||||
/**
|
||||
* A disable-able Stable Diffusion feature
|
||||
*/
|
||||
export declare type StableDiffusionFeature =
|
||||
| 'noiseConfig'
|
||||
| 'variations'
|
||||
| 'symmetry'
|
||||
| 'tiling'
|
||||
| 'hires';
|
||||
|
||||
/**
|
||||
* Configuration options for the InvokeAI UI.
|
||||
* Distinct from system settings which may be changed inside the app.
|
||||
*/
|
||||
export declare type AppConfig = {
|
||||
/**
|
||||
* Whether or not URLs should be transformed to use a different host
|
||||
*/
|
||||
shouldTransformUrls: boolean;
|
||||
/**
|
||||
* Whether or not we need to re-fetch images
|
||||
*/
|
||||
shouldFetchImages: boolean;
|
||||
disabledTabs: InvokeTabName[];
|
||||
disabledFeatures: AppFeature[];
|
||||
sd: {
|
||||
iterations: {
|
||||
initial: number;
|
||||
min: number;
|
||||
sliderMax: number;
|
||||
inputMax: number;
|
||||
fineStep: number;
|
||||
coarseStep: number;
|
||||
};
|
||||
width: {
|
||||
initial: number;
|
||||
min: number;
|
||||
sliderMax: number;
|
||||
inputMax: number;
|
||||
fineStep: number;
|
||||
coarseStep: number;
|
||||
};
|
||||
height: {
|
||||
initial: number;
|
||||
min: number;
|
||||
sliderMax: number;
|
||||
inputMax: number;
|
||||
fineStep: number;
|
||||
coarseStep: number;
|
||||
};
|
||||
steps: {
|
||||
initial: number;
|
||||
min: number;
|
||||
sliderMax: number;
|
||||
inputMax: number;
|
||||
fineStep: number;
|
||||
coarseStep: number;
|
||||
};
|
||||
guidance: {
|
||||
initial: number;
|
||||
min: number;
|
||||
sliderMax: number;
|
||||
inputMax: number;
|
||||
fineStep: number;
|
||||
coarseStep: number;
|
||||
};
|
||||
img2imgStrength: {
|
||||
initial: number;
|
||||
min: number;
|
||||
sliderMax: number;
|
||||
inputMax: number;
|
||||
fineStep: number;
|
||||
coarseStep: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -13,6 +13,7 @@ import lightboxReducer from 'features/lightbox/store/lightboxSlice';
|
||||
import generationReducer from 'features/parameters/store/generationSlice';
|
||||
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
|
||||
import systemReducer from 'features/system/store/systemSlice';
|
||||
import configReducer from 'features/system/store/configSlice';
|
||||
import uiReducer from 'features/ui/store/uiSlice';
|
||||
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
|
||||
import modelsReducer from 'features/system/store/modelSlice';
|
||||
@ -54,6 +55,7 @@ const rootReducer = combineReducers({
|
||||
postprocessing: postprocessingReducer,
|
||||
results: resultsReducer,
|
||||
system: systemReducer,
|
||||
config: configReducer,
|
||||
ui: uiReducer,
|
||||
uploads: uploadsReducer,
|
||||
hotkeys: hotkeysReducer,
|
||||
@ -78,6 +80,7 @@ const rootPersistConfig = getPersistConfig({
|
||||
// ...uploadsBlacklist,
|
||||
'uploads',
|
||||
'hotkeys',
|
||||
'config',
|
||||
],
|
||||
debounce: 300,
|
||||
});
|
||||
|
@ -26,7 +26,15 @@ import {
|
||||
import { clamp } from 'lodash';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FocusEvent, memo, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
FocusEvent,
|
||||
memo,
|
||||
MouseEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { BiReset } from 'react-icons/bi';
|
||||
import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton';
|
||||
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
|
||||
@ -109,33 +117,52 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
||||
[max, sliderNumberInputProps?.max]
|
||||
);
|
||||
|
||||
const handleSliderChange = (v: number) => {
|
||||
onChange(v);
|
||||
};
|
||||
const handleSliderChange = useCallback(
|
||||
(v: number) => {
|
||||
onChange(v);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const handleInputBlur = (e: FocusEvent<HTMLInputElement>) => {
|
||||
if (e.target.value === '') e.target.value = String(min);
|
||||
const clamped = clamp(
|
||||
isInteger ? Math.floor(Number(e.target.value)) : Number(localInputValue),
|
||||
min,
|
||||
numberInputMax
|
||||
);
|
||||
const quantized = roundDownToMultiple(clamped, step);
|
||||
onChange(quantized);
|
||||
setLocalInputValue(quantized);
|
||||
};
|
||||
const handleInputBlur = useCallback(
|
||||
(e: FocusEvent<HTMLInputElement>) => {
|
||||
if (e.target.value === '') {
|
||||
e.target.value = String(min);
|
||||
}
|
||||
const clamped = clamp(
|
||||
isInteger
|
||||
? Math.floor(Number(e.target.value))
|
||||
: Number(localInputValue),
|
||||
min,
|
||||
numberInputMax
|
||||
);
|
||||
const quantized = roundDownToMultiple(clamped, step);
|
||||
onChange(quantized);
|
||||
setLocalInputValue(quantized);
|
||||
},
|
||||
[isInteger, localInputValue, min, numberInputMax, onChange, step]
|
||||
);
|
||||
|
||||
const handleInputChange = (v: number | string) => {
|
||||
const handleInputChange = useCallback((v: number | string) => {
|
||||
setLocalInputValue(v);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleResetDisable = () => {
|
||||
if (!handleReset) return;
|
||||
const handleResetDisable = useCallback(() => {
|
||||
if (!handleReset) {
|
||||
return;
|
||||
}
|
||||
handleReset();
|
||||
};
|
||||
}, [handleReset]);
|
||||
|
||||
const forceInputBlur = useCallback((e: MouseEvent) => {
|
||||
if (e.target instanceof HTMLDivElement) {
|
||||
e.target.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
onClick={forceInputBlur}
|
||||
sx={
|
||||
isCompact
|
||||
? {
|
||||
@ -218,6 +245,7 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
||||
value={localInputValue}
|
||||
onChange={handleInputChange}
|
||||
onBlur={handleInputBlur}
|
||||
focusInputOnChange={false}
|
||||
{...sliderNumberInputProps}
|
||||
>
|
||||
<NumberInputField
|
||||
@ -240,7 +268,7 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
aria-label={t('accessibility.reset')}
|
||||
tooltip="Reset"
|
||||
tooltip={t('accessibility.reset')}
|
||||
icon={<BiReset />}
|
||||
isDisabled={isDisabled}
|
||||
onClick={handleResetDisable}
|
||||
|
@ -1,160 +0,0 @@
|
||||
// import WorkInProgress from './WorkInProgress';
|
||||
// import ReactFlow, {
|
||||
// applyEdgeChanges,
|
||||
// applyNodeChanges,
|
||||
// Background,
|
||||
// Controls,
|
||||
// Edge,
|
||||
// Handle,
|
||||
// Node,
|
||||
// NodeTypes,
|
||||
// OnEdgesChange,
|
||||
// OnNodesChange,
|
||||
// Position,
|
||||
// } from 'reactflow';
|
||||
|
||||
// import 'reactflow/dist/style.css';
|
||||
// import {
|
||||
// Fragment,
|
||||
// FunctionComponent,
|
||||
// ReactNode,
|
||||
// useCallback,
|
||||
// useMemo,
|
||||
// useState,
|
||||
// } from 'react';
|
||||
// import { OpenAPIV3 } from 'openapi-types';
|
||||
// import { filter, map, reduce } from 'lodash';
|
||||
// import {
|
||||
// Box,
|
||||
// Flex,
|
||||
// FormControl,
|
||||
// FormLabel,
|
||||
// Input,
|
||||
// Select,
|
||||
// Switch,
|
||||
// Text,
|
||||
// NumberInput,
|
||||
// NumberInputField,
|
||||
// NumberInputStepper,
|
||||
// NumberIncrementStepper,
|
||||
// NumberDecrementStepper,
|
||||
// Tooltip,
|
||||
// chakra,
|
||||
// Badge,
|
||||
// Heading,
|
||||
// VStack,
|
||||
// HStack,
|
||||
// Menu,
|
||||
// MenuButton,
|
||||
// MenuList,
|
||||
// MenuItem,
|
||||
// MenuItemOption,
|
||||
// MenuGroup,
|
||||
// MenuOptionGroup,
|
||||
// MenuDivider,
|
||||
// IconButton,
|
||||
// } from '@chakra-ui/react';
|
||||
// import { FaPlus } from 'react-icons/fa';
|
||||
// import {
|
||||
// FIELD_NAMES as FIELD_NAMES,
|
||||
// FIELDS,
|
||||
// INVOCATION_NAMES as INVOCATION_NAMES,
|
||||
// INVOCATIONS,
|
||||
// } from 'features/nodeEditor/constants';
|
||||
|
||||
// console.log('invocations', INVOCATIONS);
|
||||
|
||||
// const nodeTypes = reduce(
|
||||
// INVOCATIONS,
|
||||
// (acc, val, key) => {
|
||||
// acc[key] = val.component;
|
||||
// return acc;
|
||||
// },
|
||||
// {} as NodeTypes
|
||||
// );
|
||||
|
||||
// console.log('nodeTypes', nodeTypes);
|
||||
|
||||
// // make initial nodes one of every node for now
|
||||
// let n = 0;
|
||||
// const initialNodes = map(INVOCATIONS, (i) => ({
|
||||
// id: i.type,
|
||||
// type: i.title,
|
||||
// position: { x: (n += 20), y: (n += 20) },
|
||||
// data: {},
|
||||
// }));
|
||||
|
||||
// console.log('initialNodes', initialNodes);
|
||||
|
||||
// export default function NodesWIP() {
|
||||
// const [nodes, setNodes] = useState<Node[]>([]);
|
||||
// const [edges, setEdges] = useState<Edge[]>([]);
|
||||
|
||||
// const onNodesChange: OnNodesChange = useCallback(
|
||||
// (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
||||
// []
|
||||
// );
|
||||
|
||||
// const onEdgesChange: OnEdgesChange = useCallback(
|
||||
// (changes) => setEdges((eds: Edge[]) => applyEdgeChanges(changes, eds)),
|
||||
// []
|
||||
// );
|
||||
|
||||
// return (
|
||||
// <Box
|
||||
// sx={{
|
||||
// position: 'relative',
|
||||
// width: 'full',
|
||||
// height: 'full',
|
||||
// borderRadius: 'md',
|
||||
// }}
|
||||
// >
|
||||
// <ReactFlow
|
||||
// nodeTypes={nodeTypes}
|
||||
// nodes={nodes}
|
||||
// edges={edges}
|
||||
// onNodesChange={onNodesChange}
|
||||
// onEdgesChange={onEdgesChange}
|
||||
// >
|
||||
// <Background />
|
||||
// <Controls />
|
||||
// </ReactFlow>
|
||||
// <HStack sx={{ position: 'absolute', top: 2, right: 2 }}>
|
||||
// {FIELD_NAMES.map((field) => (
|
||||
// <Badge
|
||||
// key={field}
|
||||
// colorScheme={FIELDS[field].color}
|
||||
// sx={{ userSelect: 'none' }}
|
||||
// >
|
||||
// {field}
|
||||
// </Badge>
|
||||
// ))}
|
||||
// </HStack>
|
||||
// <Menu>
|
||||
// <MenuButton
|
||||
// as={IconButton}
|
||||
// aria-label="Options"
|
||||
// icon={<FaPlus />}
|
||||
// sx={{ position: 'absolute', top: 2, left: 2 }}
|
||||
// />
|
||||
// <MenuList>
|
||||
// {INVOCATION_NAMES.map((name) => {
|
||||
// const invocation = INVOCATIONS[name];
|
||||
// return (
|
||||
// <Tooltip
|
||||
// key={name}
|
||||
// label={invocation.description}
|
||||
// placement="end"
|
||||
// hasArrow
|
||||
// >
|
||||
// <MenuItem>{invocation.title}</MenuItem>
|
||||
// </Tooltip>
|
||||
// );
|
||||
// })}
|
||||
// </MenuList>
|
||||
// </Menu>
|
||||
// </Box>
|
||||
// );
|
||||
// }
|
||||
|
||||
export default {};
|
@ -18,6 +18,8 @@ const globalHotkeysSelector = createSelector(
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: Does not catch keypresses while focused in an input. Maybe there is a way?
|
||||
|
||||
export const useGlobalHotkeys = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { shift } = useAppSelector(globalHotkeysSelector);
|
||||
|
@ -12,7 +12,7 @@ export const getUrlAlt = (url: string, shouldTransformUrls: boolean) => {
|
||||
|
||||
export const useGetUrl = () => {
|
||||
const shouldTransformUrls = useAppSelector(
|
||||
(state: RootState) => state.system.shouldTransformUrls
|
||||
(state: RootState) => state.config.shouldTransformUrls
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -1,10 +1,9 @@
|
||||
import React, { lazy, PropsWithChildren, useEffect, useState } from 'react';
|
||||
import React, { lazy, PropsWithChildren, useEffect } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
import { buildMiddleware, store } from './app/store';
|
||||
import { persistor } from './persistor';
|
||||
import { OpenAPI } from 'services/api';
|
||||
import { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
import '@fontsource/inter/100.css';
|
||||
import '@fontsource/inter/200.css';
|
||||
import '@fontsource/inter/300.css';
|
||||
@ -16,41 +15,21 @@ import '@fontsource/inter/800.css';
|
||||
import '@fontsource/inter/900.css';
|
||||
|
||||
import Loading from './common/components/Loading/Loading';
|
||||
|
||||
// Localization
|
||||
import './i18n';
|
||||
import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
|
||||
import { ApplicationFeature } from 'app/invokeai';
|
||||
import { AppConfig } from 'app/invokeai';
|
||||
|
||||
import './i18n';
|
||||
|
||||
const App = lazy(() => import('./app/App'));
|
||||
const ThemeLocaleProvider = lazy(() => import('./app/ThemeLocaleProvider'));
|
||||
|
||||
interface Props extends PropsWithChildren {
|
||||
apiUrl?: string;
|
||||
disabledPanels?: string[];
|
||||
disabledTabs?: InvokeTabName[];
|
||||
disabledFeatures?: ApplicationFeature[];
|
||||
token?: string;
|
||||
shouldTransformUrls?: boolean;
|
||||
shouldFetchImages?: boolean;
|
||||
config?: Partial<AppConfig>;
|
||||
}
|
||||
|
||||
export default function Component({
|
||||
apiUrl,
|
||||
disabledTabs = [],
|
||||
disabledFeatures = [
|
||||
'lightbox',
|
||||
'bugLink',
|
||||
'discordLink',
|
||||
'githubLink',
|
||||
'localization',
|
||||
'modelManager',
|
||||
],
|
||||
token,
|
||||
children,
|
||||
shouldTransformUrls,
|
||||
shouldFetchImages = false,
|
||||
}: Props) {
|
||||
export default function Component({ apiUrl, token, config, children }: Props) {
|
||||
useEffect(() => {
|
||||
// configure API client token
|
||||
if (token) {
|
||||
@ -80,16 +59,7 @@ export default function Component({
|
||||
<PersistGate loading={<Loading />} persistor={persistor}>
|
||||
<React.Suspense fallback={<Loading />}>
|
||||
<ThemeLocaleProvider>
|
||||
<App
|
||||
options={{
|
||||
disabledTabs,
|
||||
disabledFeatures,
|
||||
shouldTransformUrls,
|
||||
shouldFetchImages,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</App>
|
||||
<App config={config}>{children}</App>
|
||||
</ThemeLocaleProvider>
|
||||
</React.Suspense>
|
||||
</PersistGate>
|
||||
|
@ -65,6 +65,7 @@ import { useCallback } from 'react';
|
||||
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
import { useGetUrl } from 'common/util/getUrl';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
|
||||
const currentImageButtonsSelector = createSelector(
|
||||
[
|
||||
@ -88,8 +89,6 @@ const currentImageButtonsSelector = createSelector(
|
||||
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
|
||||
system;
|
||||
|
||||
const { disabledFeatures } = system;
|
||||
|
||||
const { upscalingLevel, facetoolStrength } = postprocessing;
|
||||
|
||||
const { isLightboxOpen } = lightbox;
|
||||
@ -99,7 +98,6 @@ const currentImageButtonsSelector = createSelector(
|
||||
const { intermediateImage, currentImage } = gallery;
|
||||
|
||||
return {
|
||||
disabledFeatures,
|
||||
isProcessing,
|
||||
isConnected,
|
||||
isGFPGANAvailable,
|
||||
@ -144,9 +142,12 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
activeTabName,
|
||||
shouldHidePreview,
|
||||
selectedImage,
|
||||
disabledFeatures,
|
||||
} = useAppSelector(currentImageButtonsSelector);
|
||||
|
||||
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
||||
const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled;
|
||||
const isFaceRestoreEnabled = useFeatureStatus('faceRestore').isFeatureEnabled;
|
||||
|
||||
const { getUrl, shouldTransformUrls } = useGetUrl();
|
||||
|
||||
const toast = useToast();
|
||||
@ -345,7 +346,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
{
|
||||
enabled: () =>
|
||||
Boolean(
|
||||
!disabledFeatures.includes('upscaling') &&
|
||||
isUpscalingEnabled &&
|
||||
isESRGANAvailable &&
|
||||
!shouldDisableToolbarButtons &&
|
||||
isConnected &&
|
||||
@ -354,7 +355,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
),
|
||||
},
|
||||
[
|
||||
disabledFeatures,
|
||||
isUpscalingEnabled,
|
||||
selectedImage,
|
||||
isESRGANAvailable,
|
||||
shouldDisableToolbarButtons,
|
||||
@ -376,7 +377,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
{
|
||||
enabled: () =>
|
||||
Boolean(
|
||||
!disabledFeatures.includes('faceRestore') &&
|
||||
isFaceRestoreEnabled &&
|
||||
isGFPGANAvailable &&
|
||||
!shouldDisableToolbarButtons &&
|
||||
isConnected &&
|
||||
@ -386,7 +387,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
},
|
||||
|
||||
[
|
||||
disabledFeatures,
|
||||
isFaceRestoreEnabled,
|
||||
selectedImage,
|
||||
isGFPGANAvailable,
|
||||
shouldDisableToolbarButtons,
|
||||
@ -517,7 +518,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
isChecked={shouldHidePreview}
|
||||
onClick={handlePreviewVisibility}
|
||||
/>
|
||||
{!disabledFeatures.includes('lightbox') && (
|
||||
{isLightboxEnabled && (
|
||||
<IAIIconButton
|
||||
icon={<FaExpand />}
|
||||
tooltip={
|
||||
@ -566,12 +567,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
||||
{!(
|
||||
disabledFeatures.includes('faceRestore') &&
|
||||
disabledFeatures.includes('upscaling')
|
||||
) && (
|
||||
{(isUpscalingEnabled || isFaceRestoreEnabled) && (
|
||||
<ButtonGroup isAttached={true}>
|
||||
{!disabledFeatures.includes('faceRestore') && (
|
||||
{isFaceRestoreEnabled && (
|
||||
<IAIPopover
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
@ -602,7 +600,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
</IAIPopover>
|
||||
)}
|
||||
|
||||
{!disabledFeatures.includes('upscaling') && (
|
||||
{isUpscalingEnabled && (
|
||||
<IAIPopover
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
|
@ -158,7 +158,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
};
|
||||
|
||||
const handleDragStart = (e: DragEvent<HTMLDivElement>) => {
|
||||
console.log('drag started');
|
||||
e.dataTransfer.setData('invokeai/imageName', image.name);
|
||||
e.dataTransfer.setData('invokeai/imageType', image.type);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { RootState } from 'app/store';
|
||||
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
|
||||
import { configSelector } from 'features/system/store/configSelectors';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import {
|
||||
activeTabNameSelector,
|
||||
@ -68,8 +69,14 @@ export const imageGallerySelector = createSelector(
|
||||
);
|
||||
|
||||
export const hoverableImageSelector = createSelector(
|
||||
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
||||
(gallery, system, lightbox, activeTabName) => {
|
||||
[
|
||||
gallerySelector,
|
||||
systemSelector,
|
||||
configSelector,
|
||||
lightboxSelector,
|
||||
activeTabNameSelector,
|
||||
],
|
||||
(gallery, system, config, lightbox, activeTabName) => {
|
||||
return {
|
||||
mayDeleteImage: system.isConnected && !system.isProcessing,
|
||||
galleryImageObjectFit: gallery.galleryImageObjectFit,
|
||||
@ -77,7 +84,7 @@ export const hoverableImageSelector = createSelector(
|
||||
shouldUseSingleGalleryColumn: gallery.shouldUseSingleGalleryColumn,
|
||||
activeTabName,
|
||||
isLightboxOpen: lightbox.isLightboxOpen,
|
||||
disabledFeatures: system.disabledFeatures,
|
||||
disabledFeatures: config.disabledFeatures,
|
||||
};
|
||||
},
|
||||
{
|
||||
|
@ -1,8 +1,4 @@
|
||||
import {
|
||||
PayloadAction,
|
||||
createEntityAdapter,
|
||||
createSlice,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
|
||||
import { Image } from 'app/invokeai';
|
||||
import { invocationComplete } from 'services/events/actions';
|
||||
|
||||
@ -19,37 +15,24 @@ import {
|
||||
import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
|
||||
import { imageReceived, thumbnailReceived } from 'services/thunks/image';
|
||||
|
||||
// use `createEntityAdapter` to create a slice for results images
|
||||
// https://redux-toolkit.js.org/api/createEntityAdapter#overview
|
||||
|
||||
// the "Entity" is InvokeAI.ResultImage, while the "entities" are instances of that type
|
||||
export const resultsAdapter = createEntityAdapter<Image>({
|
||||
// Provide a callback to get a stable, unique identifier for each entity. This defaults to
|
||||
// `(item) => item.id`, but for our result images, the `name` is the unique identifier.
|
||||
selectId: (image) => image.name,
|
||||
// Order all images by their time (in descending order)
|
||||
sortComparer: (a, b) => b.metadata.created - a.metadata.created,
|
||||
});
|
||||
|
||||
// This type is intersected with the Entity type to create the shape of the state
|
||||
type AdditionalResultsState = {
|
||||
// these are a bit misleading; they refer to sessions, not results, but we don't have a route
|
||||
// to list all images directly at this time...
|
||||
page: number; // current page we are on
|
||||
pages: number; // the total number of pages available
|
||||
isLoading: boolean; // whether we are loading more images or not, mostly a placeholder
|
||||
nextPage: number; // the next page to request
|
||||
shouldFetchImages: boolean; // whether we need to re-fetch images or not
|
||||
page: number;
|
||||
pages: number;
|
||||
isLoading: boolean;
|
||||
nextPage: number;
|
||||
};
|
||||
|
||||
export const initialResultsState =
|
||||
resultsAdapter.getInitialState<AdditionalResultsState>({
|
||||
// provide the additional initial state
|
||||
page: 0,
|
||||
pages: 0,
|
||||
isLoading: false,
|
||||
nextPage: 0,
|
||||
shouldFetchImages: false,
|
||||
});
|
||||
|
||||
export type ResultsState = typeof initialResultsState;
|
||||
@ -58,21 +41,9 @@ const resultsSlice = createSlice({
|
||||
name: 'results',
|
||||
initialState: initialResultsState,
|
||||
reducers: {
|
||||
// the adapter provides some helper reducers; see the docs for all of them
|
||||
// can use them as helper functions within a reducer, or use the function itself as a reducer
|
||||
|
||||
// here we just use the function itself as the reducer. we'll call this on `invocation_complete`
|
||||
// to add a single result
|
||||
resultAdded: resultsAdapter.upsertOne,
|
||||
|
||||
setShouldFetchImages: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldFetchImages = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// here we can respond to a fulfilled call of the `getNextResultsPage` thunk
|
||||
// because we pass in the fulfilled thunk action creator, everything is typed
|
||||
|
||||
/**
|
||||
* Received Result Images Page - PENDING
|
||||
*/
|
||||
@ -90,7 +61,6 @@ const resultsSlice = createSlice({
|
||||
deserializeImageResponse(image)
|
||||
);
|
||||
|
||||
// use the adapter reducer to append all the results to state
|
||||
resultsAdapter.addMany(state, resultImages);
|
||||
|
||||
state.page = page;
|
||||
@ -103,14 +73,15 @@ const resultsSlice = createSlice({
|
||||
* Invocation Complete
|
||||
*/
|
||||
builder.addCase(invocationComplete, (state, action) => {
|
||||
const { data } = action.payload;
|
||||
const { data, shouldFetchImages } = action.payload;
|
||||
const { result, node, graph_execution_state_id } = data;
|
||||
|
||||
if (isImageOutput(result)) {
|
||||
const name = result.image.image_name;
|
||||
const type = result.image.image_type;
|
||||
|
||||
// if we need to refetch, set URLs to placeholder for now
|
||||
const { url, thumbnail } = state.shouldFetchImages
|
||||
const { url, thumbnail } = shouldFetchImages
|
||||
? { url: '', thumbnail: '' }
|
||||
: buildImageUrls(type, name);
|
||||
|
||||
@ -123,7 +94,7 @@ const resultsSlice = createSlice({
|
||||
thumbnail,
|
||||
metadata: {
|
||||
created: timestamp,
|
||||
width: result.width, // TODO: add tese dimensions
|
||||
width: result.width,
|
||||
height: result.height,
|
||||
invokeai: {
|
||||
session_id: graph_execution_state_id,
|
||||
@ -162,8 +133,6 @@ const resultsSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
// Create a set of memoized selectors based on the location of this entity state
|
||||
// to be used as selectors in a `useAppSelector()` call
|
||||
export const {
|
||||
selectAll: selectResultsAll,
|
||||
selectById: selectResultsById,
|
||||
@ -172,6 +141,6 @@ export const {
|
||||
selectTotal: selectResultsTotal,
|
||||
} = resultsAdapter.getSelectors<RootState>((state) => state.results);
|
||||
|
||||
export const { resultAdded, setShouldFetchImages } = resultsSlice.actions;
|
||||
export const { resultAdded } = resultsSlice.actions;
|
||||
|
||||
export default resultsSlice.reducer;
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { InputFieldTemplate, InputFieldValue } from 'features/nodes/types';
|
||||
import {
|
||||
InputFieldTemplate,
|
||||
InputFieldValue,
|
||||
} from 'features/nodes/types/types';
|
||||
|
||||
export type FieldComponentProps<
|
||||
V extends InputFieldValue,
|
||||
|
@ -3,7 +3,10 @@ import { NodesState } from './nodesSlice';
|
||||
/**
|
||||
* Nodes slice persist blacklist
|
||||
*/
|
||||
const itemsToBlacklist: (keyof NodesState)[] = ['schema', 'invocations'];
|
||||
const itemsToBlacklist: (keyof NodesState)[] = [
|
||||
'schema',
|
||||
'invocationTemplates',
|
||||
];
|
||||
|
||||
export const nodesBlacklist = itemsToBlacklist.map(
|
||||
(blacklistItem) => `nodes.${blacklistItem}`
|
||||
|
@ -1,46 +1,73 @@
|
||||
import { RootState } from 'app/store';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import { setImg2imgStrength } from 'features/parameters/store/generationSlice';
|
||||
import { configSelector } from 'features/system/store/configSelectors';
|
||||
import { hotkeysSelector } from 'features/ui/store/hotkeysSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface ImageToImageStrengthProps {
|
||||
label?: string;
|
||||
}
|
||||
const selector = createSelector(
|
||||
[generationSelector, hotkeysSelector, configSelector],
|
||||
(generation, hotkeys, config) => {
|
||||
const { initial, min, sliderMax, inputMax, fineStep, coarseStep } =
|
||||
config.sd.img2imgStrength;
|
||||
const { img2imgStrength, isImageToImageEnabled } = generation;
|
||||
|
||||
export default function ImageToImageStrength(props: ImageToImageStrengthProps) {
|
||||
const { t } = useTranslation();
|
||||
const { label = `${t('parameters.strength')}` } = props;
|
||||
const img2imgStrength = useAppSelector(
|
||||
(state: RootState) => state.generation.img2imgStrength
|
||||
);
|
||||
const isImageToImageEnabled = useAppSelector(
|
||||
(state: RootState) => state.generation.isImageToImageEnabled
|
||||
);
|
||||
const step = hotkeys.shift ? fineStep : coarseStep;
|
||||
|
||||
return {
|
||||
img2imgStrength,
|
||||
isImageToImageEnabled,
|
||||
initial,
|
||||
min,
|
||||
sliderMax,
|
||||
inputMax,
|
||||
step,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const ImageToImageStrength = () => {
|
||||
const {
|
||||
img2imgStrength,
|
||||
isImageToImageEnabled,
|
||||
initial,
|
||||
min,
|
||||
sliderMax,
|
||||
inputMax,
|
||||
step,
|
||||
} = useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeStrength = (v: number) => dispatch(setImg2imgStrength(v));
|
||||
const handleChange = useCallback(
|
||||
(v: number) => dispatch(setImg2imgStrength(v)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleImg2ImgStrengthReset = () => {
|
||||
dispatch(setImg2imgStrength(0.75));
|
||||
};
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(setImg2imgStrength(initial));
|
||||
}, [dispatch, initial]);
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
label={label}
|
||||
step={0.01}
|
||||
min={0.01}
|
||||
max={1}
|
||||
onChange={handleChangeStrength}
|
||||
label={`${t('parameters.strength')}`}
|
||||
step={step}
|
||||
min={min}
|
||||
max={sliderMax}
|
||||
onChange={handleChange}
|
||||
handleReset={handleReset}
|
||||
value={img2imgStrength}
|
||||
isInteger={false}
|
||||
withInput
|
||||
withSliderMarks
|
||||
inputWidth={22}
|
||||
withReset
|
||||
handleReset={handleImg2ImgStrengthReset}
|
||||
isDisabled={!isImageToImageEnabled}
|
||||
sliderNumberInputProps={{ max: inputMax }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(ImageToImageStrength);
|
||||
|
@ -1,37 +1,57 @@
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { RootState } from 'app/store';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import { setHeight } from 'features/parameters/store/generationSlice';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { configSelector } from 'features/system/store/configSelectors';
|
||||
import { hotkeysSelector } from 'features/ui/store/hotkeysSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const HeightSlider = (props: BoxProps) => {
|
||||
const height = useAppSelector((state: RootState) => state.generation.height);
|
||||
const shift = useAppSelector((state: RootState) => state.hotkeys.shift);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const selector = createSelector(
|
||||
[generationSelector, hotkeysSelector, configSelector],
|
||||
(generation, hotkeys, config) => {
|
||||
const { initial, min, sliderMax, inputMax, fineStep, coarseStep } =
|
||||
config.sd.height;
|
||||
const { height } = generation;
|
||||
|
||||
const step = hotkeys.shift ? fineStep : coarseStep;
|
||||
|
||||
return { height, initial, min, sliderMax, inputMax, step };
|
||||
}
|
||||
);
|
||||
|
||||
const HeightSlider = () => {
|
||||
const { height, initial, min, sliderMax, inputMax, step } =
|
||||
useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(setHeight(v));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(setHeight(initial));
|
||||
}, [dispatch, initial]);
|
||||
|
||||
return (
|
||||
<Box {...props}>
|
||||
<IAISlider
|
||||
isDisabled={activeTabName === 'unifiedCanvas'}
|
||||
label={t('parameters.height')}
|
||||
value={height}
|
||||
min={64}
|
||||
step={shift ? 8 : 64}
|
||||
max={2048}
|
||||
onChange={(v) => dispatch(setHeight(v))}
|
||||
handleReset={() => dispatch(setHeight(512))}
|
||||
withInput
|
||||
withReset
|
||||
withSliderMarks
|
||||
sliderNumberInputProps={{ max: 15360 }}
|
||||
/>
|
||||
</Box>
|
||||
<IAISlider
|
||||
label={t('parameters.height')}
|
||||
value={height}
|
||||
min={min}
|
||||
step={step}
|
||||
max={sliderMax}
|
||||
onChange={handleChange}
|
||||
handleReset={handleReset}
|
||||
withInput
|
||||
withReset
|
||||
withSliderMarks
|
||||
sliderNumberInputProps={{ max: inputMax }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,46 +1,85 @@
|
||||
import { RootState } from 'app/store';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAINumberInput from 'common/components/IAINumberInput';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import { setCfgScale } from 'features/parameters/store/generationSlice';
|
||||
import { configSelector } from 'features/system/store/configSelectors';
|
||||
import { hotkeysSelector } from 'features/ui/store/hotkeysSlice';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function MainCFGScale() {
|
||||
const selector = createSelector(
|
||||
[generationSelector, configSelector, uiSelector, hotkeysSelector],
|
||||
(generation, config, ui, hotkeys) => {
|
||||
const { initial, min, sliderMax, inputMax } = config.sd.guidance;
|
||||
const { cfgScale } = generation;
|
||||
const { shouldUseSliders } = ui;
|
||||
const { shift } = hotkeys;
|
||||
|
||||
return {
|
||||
cfgScale,
|
||||
initial,
|
||||
min,
|
||||
sliderMax,
|
||||
inputMax,
|
||||
shouldUseSliders,
|
||||
shift,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const GuidanceScale = () => {
|
||||
const {
|
||||
cfgScale,
|
||||
initial,
|
||||
min,
|
||||
sliderMax,
|
||||
inputMax,
|
||||
shouldUseSliders,
|
||||
shift,
|
||||
} = useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
const cfgScale = useAppSelector(
|
||||
(state: RootState) => state.generation.cfgScale
|
||||
);
|
||||
const shouldUseSliders = useAppSelector(
|
||||
(state: RootState) => state.ui.shouldUseSliders
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeCfgScale = (v: number) => dispatch(setCfgScale(v));
|
||||
const handleChange = useCallback(
|
||||
(v: number) => dispatch(setCfgScale(v)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleReset = useCallback(
|
||||
() => dispatch(setCfgScale(initial)),
|
||||
[dispatch, initial]
|
||||
);
|
||||
|
||||
return shouldUseSliders ? (
|
||||
<IAISlider
|
||||
label={t('parameters.cfgScale')}
|
||||
step={0.5}
|
||||
min={1.01}
|
||||
max={30}
|
||||
onChange={handleChangeCfgScale}
|
||||
handleReset={() => dispatch(setCfgScale(7.5))}
|
||||
step={shift ? 0.1 : 0.5}
|
||||
min={min}
|
||||
max={sliderMax}
|
||||
onChange={handleChange}
|
||||
handleReset={handleReset}
|
||||
value={cfgScale}
|
||||
sliderNumberInputProps={{ max: 200 }}
|
||||
sliderNumberInputProps={{ max: inputMax }}
|
||||
withInput
|
||||
withReset
|
||||
withSliderMarks
|
||||
isInteger={false}
|
||||
/>
|
||||
) : (
|
||||
<IAINumberInput
|
||||
label={t('parameters.cfgScale')}
|
||||
step={0.5}
|
||||
min={1.01}
|
||||
max={200}
|
||||
onChange={handleChangeCfgScale}
|
||||
min={min}
|
||||
max={inputMax}
|
||||
onChange={handleChange}
|
||||
value={cfgScale}
|
||||
isInteger={false}
|
||||
numberInputFieldProps={{ textAlign: 'center' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(GuidanceScale);
|
||||
|
@ -1,48 +1,86 @@
|
||||
import type { RootState } from 'app/store';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAINumberInput from 'common/components/IAINumberInput';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import { setIterations } from 'features/parameters/store/generationSlice';
|
||||
|
||||
import { configSelector } from 'features/system/store/configSelectors';
|
||||
import { hotkeysSelector } from 'features/ui/store/hotkeysSlice';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function MainIterations() {
|
||||
const iterations = useAppSelector(
|
||||
(state: RootState) => state.generation.iterations
|
||||
);
|
||||
const selector = createSelector(
|
||||
[generationSelector, configSelector, uiSelector, hotkeysSelector],
|
||||
(generation, config, ui, hotkeys) => {
|
||||
const { initial, min, sliderMax, inputMax, fineStep, coarseStep } =
|
||||
config.sd.iterations;
|
||||
const { iterations } = generation;
|
||||
const { shouldUseSliders } = ui;
|
||||
|
||||
const shouldUseSliders = useAppSelector(
|
||||
(state: RootState) => state.ui.shouldUseSliders
|
||||
);
|
||||
const step = hotkeys.shift ? fineStep : coarseStep;
|
||||
|
||||
return {
|
||||
iterations,
|
||||
initial,
|
||||
min,
|
||||
sliderMax,
|
||||
inputMax,
|
||||
step,
|
||||
shouldUseSliders,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const MainIterations = () => {
|
||||
const {
|
||||
iterations,
|
||||
initial,
|
||||
min,
|
||||
sliderMax,
|
||||
inputMax,
|
||||
step,
|
||||
shouldUseSliders,
|
||||
} = useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeIterations = (v: number) => dispatch(setIterations(v));
|
||||
const handleChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(setIterations(v));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(setIterations(initial));
|
||||
}, [dispatch, initial]);
|
||||
|
||||
return shouldUseSliders ? (
|
||||
<IAISlider
|
||||
label={t('parameters.images')}
|
||||
step={1}
|
||||
min={1}
|
||||
max={16}
|
||||
onChange={handleChangeIterations}
|
||||
handleReset={() => dispatch(setIterations(1))}
|
||||
step={step}
|
||||
min={min}
|
||||
max={sliderMax}
|
||||
onChange={handleChange}
|
||||
handleReset={handleReset}
|
||||
value={iterations}
|
||||
withInput
|
||||
withReset
|
||||
withSliderMarks
|
||||
sliderNumberInputProps={{ max: 9999 }}
|
||||
sliderNumberInputProps={{ max: inputMax }}
|
||||
/>
|
||||
) : (
|
||||
<IAINumberInput
|
||||
label={t('parameters.images')}
|
||||
step={1}
|
||||
min={1}
|
||||
max={9999}
|
||||
onChange={handleChangeIterations}
|
||||
step={step}
|
||||
min={min}
|
||||
max={inputMax}
|
||||
onChange={handleChange}
|
||||
value={iterations}
|
||||
numberInputFieldProps={{ textAlign: 'center' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(MainIterations);
|
||||
|
@ -1,35 +1,32 @@
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { DIFFUSERS_SAMPLERS, SAMPLERS } from 'app/constants';
|
||||
import { DIFFUSERS_SAMPLERS } from 'app/constants';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISelect from 'common/components/IAISelect';
|
||||
import { setSampler } from 'features/parameters/store/generationSlice';
|
||||
import { activeModelSelector } from 'features/system/store/systemSelectors';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function MainSampler(props: BoxProps) {
|
||||
const Scheduler = () => {
|
||||
const sampler = useAppSelector(
|
||||
(state: RootState) => state.generation.sampler
|
||||
);
|
||||
const activeModel = useAppSelector(activeModelSelector);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeSampler = (e: ChangeEvent<HTMLSelectElement>) =>
|
||||
dispatch(setSampler(e.target.value));
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLSelectElement>) => dispatch(setSampler(e.target.value)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box {...props}>
|
||||
<IAISelect
|
||||
label={t('parameters.sampler')}
|
||||
value={sampler}
|
||||
onChange={handleChangeSampler}
|
||||
validValues={
|
||||
activeModel.format === 'diffusers' ? DIFFUSERS_SAMPLERS : SAMPLERS
|
||||
}
|
||||
minWidth={36}
|
||||
/>
|
||||
</Box>
|
||||
<IAISelect
|
||||
label={t('parameters.sampler')}
|
||||
value={sampler}
|
||||
onChange={handleChange}
|
||||
validValues={DIFFUSERS_SAMPLERS}
|
||||
minWidth={36}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(Scheduler);
|
||||
|
@ -1,17 +1,16 @@
|
||||
import { Divider, Flex, VStack } from '@chakra-ui/react';
|
||||
import { Box, Flex, VStack } from '@chakra-ui/react';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import { ModelSelect } from 'exports';
|
||||
import { memo } from 'react';
|
||||
import HeightSlider from './HeightSlider';
|
||||
import MainCFGScale from './MainCFGScale';
|
||||
import MainHeight from './MainHeight';
|
||||
import MainIterations from './MainIterations';
|
||||
import MainSampler from './MainSampler';
|
||||
import MainSteps from './MainSteps';
|
||||
import MainWidth from './MainWidth';
|
||||
import WidthSlider from './WidthSlider';
|
||||
|
||||
export default function MainSettings() {
|
||||
const MainSettings = () => {
|
||||
const shouldUseSliders = useAppSelector(
|
||||
(state: RootState) => state.ui.shouldUseSliders
|
||||
);
|
||||
@ -21,9 +20,16 @@ export default function MainSettings() {
|
||||
<MainIterations />
|
||||
<MainSteps />
|
||||
<MainCFGScale />
|
||||
<MainWidth />
|
||||
<MainHeight />
|
||||
<MainSampler />
|
||||
<WidthSlider />
|
||||
<HeightSlider />
|
||||
<Flex gap={3} w="full">
|
||||
<Box flexGrow={2}>
|
||||
<MainSampler />
|
||||
</Box>
|
||||
<Box flexGrow={3}>
|
||||
<ModelSelect />
|
||||
</Box>
|
||||
</Flex>
|
||||
</VStack>
|
||||
) : (
|
||||
<Flex gap={3} flexDirection="column">
|
||||
@ -32,12 +38,18 @@ export default function MainSettings() {
|
||||
<MainSteps />
|
||||
<MainCFGScale />
|
||||
</Flex>
|
||||
<Flex gap={3}>
|
||||
<MainSampler flexGrow={2} />
|
||||
<ModelSelect flexGrow={3} />
|
||||
</Flex>
|
||||
<WidthSlider />
|
||||
<HeightSlider />
|
||||
<Flex gap={3} w="full">
|
||||
<Box flexGrow={2}>
|
||||
<MainSampler />
|
||||
</Box>
|
||||
<Box flexGrow={3}>
|
||||
<ModelSelect />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(MainSettings);
|
||||
|
@ -1,53 +1,87 @@
|
||||
import { RootState } from 'app/store';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAINumberInput from 'common/components/IAINumberInput';
|
||||
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import {
|
||||
clampSymmetrySteps,
|
||||
setSteps,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import { configSelector } from 'features/system/store/configSelectors';
|
||||
import { hotkeysSelector } from 'features/ui/store/hotkeysSlice';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function MainSteps() {
|
||||
const selector = createSelector(
|
||||
[generationSelector, configSelector, uiSelector, hotkeysSelector],
|
||||
(generation, config, ui, hotkeys) => {
|
||||
const { initial, min, sliderMax, inputMax, fineStep, coarseStep } =
|
||||
config.sd.steps;
|
||||
const { steps } = generation;
|
||||
const { shouldUseSliders } = ui;
|
||||
|
||||
const step = hotkeys.shift ? fineStep : coarseStep;
|
||||
|
||||
return {
|
||||
steps,
|
||||
initial,
|
||||
min,
|
||||
sliderMax,
|
||||
inputMax,
|
||||
step,
|
||||
shouldUseSliders,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const MainSteps = () => {
|
||||
const { steps, initial, min, sliderMax, inputMax, step, shouldUseSliders } =
|
||||
useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
const steps = useAppSelector((state: RootState) => state.generation.steps);
|
||||
const shouldUseSliders = useAppSelector(
|
||||
(state: RootState) => state.ui.shouldUseSliders
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeSteps = (v: number) => {
|
||||
dispatch(setSteps(v));
|
||||
};
|
||||
const handleChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(setSteps(v));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(setSteps(initial));
|
||||
}, [dispatch, initial]);
|
||||
|
||||
const handleBlur = () => {
|
||||
const handleBlur = useCallback(() => {
|
||||
dispatch(clampSymmetrySteps());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
return shouldUseSliders ? (
|
||||
<IAISlider
|
||||
label={t('parameters.steps')}
|
||||
min={1}
|
||||
step={1}
|
||||
onChange={handleChangeSteps}
|
||||
handleReset={() => dispatch(setSteps(20))}
|
||||
min={min}
|
||||
max={sliderMax}
|
||||
step={step}
|
||||
onChange={handleChange}
|
||||
handleReset={handleReset}
|
||||
value={steps}
|
||||
withInput
|
||||
withReset
|
||||
withSliderMarks
|
||||
sliderNumberInputProps={{ max: 9999 }}
|
||||
sliderNumberInputProps={{ max: inputMax }}
|
||||
/>
|
||||
) : (
|
||||
<IAINumberInput
|
||||
label={t('parameters.steps')}
|
||||
min={1}
|
||||
max={9999}
|
||||
step={1}
|
||||
onChange={handleChangeSteps}
|
||||
min={min}
|
||||
max={inputMax}
|
||||
step={step}
|
||||
onChange={handleChange}
|
||||
value={steps}
|
||||
numberInputFieldProps={{ textAlign: 'center' }}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(MainSteps);
|
||||
|
@ -1,36 +1,57 @@
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { RootState } from 'app/store';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import { setWidth } from 'features/parameters/store/generationSlice';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo } from 'react';
|
||||
import { configSelector } from 'features/system/store/configSelectors';
|
||||
import { hotkeysSelector } from 'features/ui/store/hotkeysSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const WidthSlider = (props: BoxProps) => {
|
||||
const width = useAppSelector((state: RootState) => state.generation.width);
|
||||
const shift = useAppSelector((state: RootState) => state.hotkeys.shift);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const { t } = useTranslation();
|
||||
const selector = createSelector(
|
||||
[generationSelector, hotkeysSelector, configSelector],
|
||||
(generation, hotkeys, config) => {
|
||||
const { initial, min, sliderMax, inputMax, fineStep, coarseStep } =
|
||||
config.sd.width;
|
||||
const { width } = generation;
|
||||
|
||||
const step = hotkeys.shift ? fineStep : coarseStep;
|
||||
|
||||
return { width, initial, min, sliderMax, inputMax, step };
|
||||
}
|
||||
);
|
||||
|
||||
const WidthSlider = () => {
|
||||
const { width, initial, min, sliderMax, inputMax, step } =
|
||||
useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(setWidth(v));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(setWidth(initial));
|
||||
}, [dispatch, initial]);
|
||||
|
||||
return (
|
||||
<Box {...props}>
|
||||
<IAISlider
|
||||
isDisabled={activeTabName === 'unifiedCanvas'}
|
||||
label={t('parameters.width')}
|
||||
value={width}
|
||||
min={64}
|
||||
step={shift ? 8 : 64}
|
||||
max={2048}
|
||||
onChange={(v) => dispatch(setWidth(v))}
|
||||
handleReset={() => dispatch(setWidth(512))}
|
||||
withInput
|
||||
withReset
|
||||
withSliderMarks
|
||||
sliderNumberInputProps={{ max: 15360 }}
|
||||
/>
|
||||
</Box>
|
||||
<IAISlider
|
||||
label={t('parameters.width')}
|
||||
value={width}
|
||||
min={min}
|
||||
step={step}
|
||||
max={sliderMax}
|
||||
onChange={handleChange}
|
||||
handleReset={handleReset}
|
||||
withInput
|
||||
withReset
|
||||
withSliderMarks
|
||||
sliderNumberInputProps={{ max: inputMax }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -2,44 +2,34 @@ 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 {
|
||||
activeTabNameSelector,
|
||||
uiSelector,
|
||||
} from 'features/ui/store/uiSelectors';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { openAccordionItemsChanged } from 'features/ui/store/uiSlice';
|
||||
import { filter, map } from 'lodash';
|
||||
import { map } from 'lodash';
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import InvokeAccordionItem from './AccordionItems/InvokeAccordionItem';
|
||||
|
||||
const parametersAccordionSelector = createSelector(
|
||||
[uiSelector, systemSelector],
|
||||
(uiSlice, system) => {
|
||||
const {
|
||||
activeTab,
|
||||
openLinearAccordionItems,
|
||||
openUnifiedCanvasAccordionItems,
|
||||
} = uiSlice;
|
||||
const parametersAccordionSelector = createSelector([uiSelector], (uiSlice) => {
|
||||
const {
|
||||
activeTab,
|
||||
openLinearAccordionItems,
|
||||
openUnifiedCanvasAccordionItems,
|
||||
} = uiSlice;
|
||||
|
||||
const { disabledFeatures } = system;
|
||||
let openAccordions: number[] = [];
|
||||
|
||||
let openAccordions: number[] = [];
|
||||
|
||||
if (tabMap[activeTab] === 'generate') {
|
||||
openAccordions = openLinearAccordionItems;
|
||||
}
|
||||
|
||||
if (tabMap[activeTab] === 'unifiedCanvas') {
|
||||
openAccordions = openUnifiedCanvasAccordionItems;
|
||||
}
|
||||
|
||||
return {
|
||||
openAccordions,
|
||||
disabledFeatures,
|
||||
};
|
||||
if (tabMap[activeTab] === 'generate') {
|
||||
openAccordions = openLinearAccordionItems;
|
||||
}
|
||||
);
|
||||
|
||||
if (tabMap[activeTab] === 'unifiedCanvas') {
|
||||
openAccordions = openUnifiedCanvasAccordionItems;
|
||||
}
|
||||
|
||||
return {
|
||||
openAccordions,
|
||||
};
|
||||
});
|
||||
|
||||
export type ParametersAccordionItem = {
|
||||
name: string;
|
||||
@ -61,9 +51,7 @@ type ParametersAccordionProps = {
|
||||
* Main container for generation and processing parameters.
|
||||
*/
|
||||
const ParametersAccordion = ({ accordionItems }: ParametersAccordionProps) => {
|
||||
const { openAccordions, disabledFeatures } = useAppSelector(
|
||||
parametersAccordionSelector
|
||||
);
|
||||
const { openAccordions } = useAppSelector(parametersAccordionSelector);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Box, BoxProps, Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { ChangeEvent, memo } from 'react';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -30,7 +29,7 @@ const selector = createSelector(
|
||||
}
|
||||
);
|
||||
|
||||
const ModelSelect = (props: BoxProps) => {
|
||||
const ModelSelect = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { allModelNames, selectedModel } = useAppSelector(selector);
|
||||
@ -39,18 +38,16 @@ const ModelSelect = (props: BoxProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box {...props}>
|
||||
<IAISelect
|
||||
label={t('modelManager.model')}
|
||||
style={{ fontSize: 'sm' }}
|
||||
aria-label={t('accessibility.modelSelect')}
|
||||
tooltip={selectedModel?.description || ''}
|
||||
value={selectedModel?.name || undefined}
|
||||
validValues={allModelNames}
|
||||
onChange={handleChangeModel}
|
||||
/>
|
||||
</Box>
|
||||
<IAISelect
|
||||
label={t('modelManager.model')}
|
||||
style={{ fontSize: 'sm' }}
|
||||
aria-label={t('accessibility.modelSelect')}
|
||||
tooltip={selectedModel?.description || ''}
|
||||
value={selectedModel?.name || undefined}
|
||||
validValues={allModelNames}
|
||||
onChange={handleChangeModel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelSelect;
|
||||
export default memo(ModelSelect);
|
||||
|
@ -8,22 +8,26 @@ 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';
|
||||
import { useFeatureStatus } from '../hooks/useFeatureStatus';
|
||||
|
||||
const SiteHeaderMenu = () => {
|
||||
const disabledFeatures = useAppSelector(
|
||||
(state: RootState) => state.system.disabledFeatures
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isModelManagerEnabled =
|
||||
useFeatureStatus('modelManager').isFeatureEnabled;
|
||||
const isLocalizationEnabled =
|
||||
useFeatureStatus('localization').isFeatureEnabled;
|
||||
const isBugLinkEnabled = useFeatureStatus('bugLink').isFeatureEnabled;
|
||||
const isDiscordLinkEnabled = useFeatureStatus('discordLink').isFeatureEnabled;
|
||||
const isGithubLinkEnabled = useFeatureStatus('githubLink').isFeatureEnabled;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
alignItems="center"
|
||||
flexDirection={{ base: 'column', xl: 'row' }}
|
||||
gap={{ base: 4, xl: 1 }}
|
||||
>
|
||||
{!disabledFeatures.includes('modelManager') && (
|
||||
{isModelManagerEnabled && (
|
||||
<ModelManagerModal>
|
||||
<IAIIconButton
|
||||
aria-label={t('modelManager.modelManager')}
|
||||
@ -51,9 +55,9 @@ const SiteHeaderMenu = () => {
|
||||
|
||||
<ThemeChanger />
|
||||
|
||||
{!disabledFeatures.includes('localization') && <LanguagePicker />}
|
||||
{isLocalizationEnabled && <LanguagePicker />}
|
||||
|
||||
{!disabledFeatures.includes('bugLink') && (
|
||||
{isBugLinkEnabled && (
|
||||
<Link
|
||||
isExternal
|
||||
href="http://github.com/invoke-ai/InvokeAI/issues"
|
||||
@ -71,7 +75,7 @@ const SiteHeaderMenu = () => {
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!disabledFeatures.includes('githubLink') && (
|
||||
{isGithubLinkEnabled && (
|
||||
<Link
|
||||
isExternal
|
||||
href="http://github.com/invoke-ai/InvokeAI"
|
||||
@ -89,7 +93,7 @@ const SiteHeaderMenu = () => {
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!disabledFeatures.includes('discordLink') && (
|
||||
{isDiscordLinkEnabled && (
|
||||
<Link
|
||||
isExternal
|
||||
href="https://discord.gg/ZmtBAhwWhy"
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { AppFeature } from 'app/invokeai';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useFeatureStatus = (feature: AppFeature) => {
|
||||
const disabledFeatures = useAppSelector(
|
||||
(state: RootState) => state.config.disabledFeatures
|
||||
);
|
||||
|
||||
const isFeatureDisabled = useMemo(
|
||||
() => disabledFeatures.includes(feature),
|
||||
[disabledFeatures, feature]
|
||||
);
|
||||
|
||||
const isFeatureEnabled = useMemo(
|
||||
() => !disabledFeatures.includes(feature),
|
||||
[disabledFeatures, feature]
|
||||
);
|
||||
|
||||
return { isFeatureDisabled, isFeatureEnabled };
|
||||
};
|
@ -2,20 +2,18 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import { useMemo } from 'react';
|
||||
import { configSelector } from '../store/configSelectors';
|
||||
import { systemSelector } from '../store/systemSelectors';
|
||||
|
||||
const isApplicationReadySelector = createSelector(
|
||||
[(state: RootState) => state.system],
|
||||
(system) => {
|
||||
const {
|
||||
disabledFeatures,
|
||||
disabledTabs,
|
||||
wereModelsReceived,
|
||||
wasSchemaParsed,
|
||||
} = system;
|
||||
[systemSelector, configSelector],
|
||||
(system, config) => {
|
||||
const { wereModelsReceived, wasSchemaParsed } = system;
|
||||
|
||||
const { disabledTabs } = config;
|
||||
|
||||
return {
|
||||
disabledTabs,
|
||||
disabledFeatures,
|
||||
wereModelsReceived,
|
||||
wasSchemaParsed,
|
||||
};
|
||||
@ -23,12 +21,9 @@ const isApplicationReadySelector = createSelector(
|
||||
);
|
||||
|
||||
export const useIsApplicationReady = () => {
|
||||
const {
|
||||
disabledTabs,
|
||||
disabledFeatures,
|
||||
wereModelsReceived,
|
||||
wasSchemaParsed,
|
||||
} = useAppSelector(isApplicationReadySelector);
|
||||
const { disabledTabs, wereModelsReceived, wasSchemaParsed } = useAppSelector(
|
||||
isApplicationReadySelector
|
||||
);
|
||||
|
||||
const isApplicationReady = useMemo(() => {
|
||||
if (!wereModelsReceived) {
|
||||
|
@ -0,0 +1,17 @@
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useIsTabDisabled = () => {
|
||||
const disabledTabs = useAppSelector(
|
||||
(state: RootState) => state.config.disabledTabs
|
||||
);
|
||||
|
||||
const isTabDisabled = useCallback(
|
||||
(tab: InvokeTabName) => disabledTabs.includes(tab),
|
||||
[disabledTabs]
|
||||
);
|
||||
|
||||
return isTabDisabled;
|
||||
};
|
@ -0,0 +1,3 @@
|
||||
import { RootState } from 'app/store';
|
||||
|
||||
export const configSelector = (state: RootState) => state.config;
|
@ -0,0 +1,75 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { AppConfig } from 'app/invokeai';
|
||||
import { cloneDeep, defaultsDeep } from 'lodash';
|
||||
|
||||
const initialConfigState: AppConfig = {
|
||||
shouldTransformUrls: false,
|
||||
shouldFetchImages: false,
|
||||
disabledTabs: [],
|
||||
disabledFeatures: [],
|
||||
sd: {
|
||||
iterations: {
|
||||
initial: 1,
|
||||
min: 1,
|
||||
sliderMax: 20,
|
||||
inputMax: 9999,
|
||||
fineStep: 1,
|
||||
coarseStep: 1,
|
||||
},
|
||||
width: {
|
||||
initial: 512,
|
||||
min: 64,
|
||||
sliderMax: 1536,
|
||||
inputMax: 4096,
|
||||
fineStep: 8,
|
||||
coarseStep: 64,
|
||||
},
|
||||
height: {
|
||||
initial: 512,
|
||||
min: 64,
|
||||
sliderMax: 1536,
|
||||
inputMax: 4096,
|
||||
fineStep: 8,
|
||||
coarseStep: 64,
|
||||
},
|
||||
steps: {
|
||||
initial: 30,
|
||||
min: 1,
|
||||
sliderMax: 100,
|
||||
inputMax: 500,
|
||||
fineStep: 1,
|
||||
coarseStep: 1,
|
||||
},
|
||||
guidance: {
|
||||
initial: 7,
|
||||
min: 1,
|
||||
sliderMax: 20,
|
||||
inputMax: 200,
|
||||
fineStep: 0.1,
|
||||
coarseStep: 0.5,
|
||||
},
|
||||
img2imgStrength: {
|
||||
initial: 0.7,
|
||||
min: 0,
|
||||
sliderMax: 1,
|
||||
inputMax: 1,
|
||||
fineStep: 0.01,
|
||||
coarseStep: 0.05,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const configSlice = createSlice({
|
||||
name: 'config',
|
||||
initialState: initialConfigState,
|
||||
reducers: {
|
||||
configChanged: (state, action: PayloadAction<Partial<AppConfig>>) => {
|
||||
defaultsDeep(state, cloneDeep(action.payload));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { configChanged } = configSlice.actions;
|
||||
|
||||
export default configSlice.reducer;
|
@ -90,18 +90,18 @@ export interface SystemState
|
||||
* Array of node IDs that we want to handle when events received
|
||||
*/
|
||||
subscribedNodeIds: string[];
|
||||
/**
|
||||
* 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: InvokeAI.ApplicationFeature[];
|
||||
// /**
|
||||
// * 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: InvokeAI.AppFeature[];
|
||||
/**
|
||||
* Whether or not the available models were received
|
||||
*/
|
||||
@ -157,9 +157,9 @@ const initialSystemState: SystemState = {
|
||||
cancelType: 'immediate',
|
||||
isCancelScheduled: false,
|
||||
subscribedNodeIds: [],
|
||||
shouldTransformUrls: false,
|
||||
disabledTabs: [],
|
||||
disabledFeatures: [],
|
||||
// shouldTransformUrls: false,
|
||||
// disabledTabs: [],
|
||||
// disabledFeatures: [],
|
||||
wereModelsReceived: false,
|
||||
wasSchemaParsed: false,
|
||||
};
|
||||
@ -359,27 +359,27 @@ export const systemSlice = createSlice({
|
||||
subscribedNodeIdsSet: (state, action: PayloadAction<string[]>) => {
|
||||
state.subscribedNodeIds = action.payload;
|
||||
},
|
||||
/**
|
||||
* `shouldTransformUrls` was changed
|
||||
*/
|
||||
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<InvokeAI.ApplicationFeature[]>
|
||||
) => {
|
||||
state.disabledFeatures = action.payload;
|
||||
},
|
||||
// /**
|
||||
// * `shouldTransformUrls` was changed
|
||||
// */
|
||||
// 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<InvokeAI.AppFeature[]>
|
||||
// ) => {
|
||||
// state.disabledFeatures = action.payload;
|
||||
// },
|
||||
},
|
||||
extraReducers(builder) {
|
||||
/**
|
||||
@ -601,9 +601,9 @@ export const {
|
||||
scheduledCancelAborted,
|
||||
cancelTypeChanged,
|
||||
subscribedNodeIdsSet,
|
||||
shouldTransformUrlsChanged,
|
||||
disabledTabsChanged,
|
||||
disabledFeaturesChanged,
|
||||
// shouldTransformUrlsChanged,
|
||||
// disabledTabsChanged,
|
||||
// disabledFeaturesChanged,
|
||||
} = systemSlice.actions;
|
||||
|
||||
export default systemSlice.reducer;
|
||||
|
@ -27,6 +27,7 @@ import GenerateWorkspace from './tabs/Generate/GenerateWorkspace';
|
||||
import { FaImage } from 'react-icons/fa';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { BsLightningChargeFill, BsLightningFill } from 'react-icons/bs';
|
||||
import { configSelector } from 'features/system/store/configSelectors';
|
||||
|
||||
export interface InvokeTabInfo {
|
||||
id: InvokeTabName;
|
||||
@ -56,14 +57,11 @@ const tabs: InvokeTabInfo[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const enabledTabsSelector = createSelector(
|
||||
(state: RootState) => state.ui,
|
||||
(ui) => {
|
||||
const { disabledTabs } = ui;
|
||||
const enabledTabsSelector = createSelector(configSelector, (config) => {
|
||||
const { disabledTabs } = config;
|
||||
|
||||
return tabs.filter((tab) => !disabledTabs.includes(tab.id));
|
||||
}
|
||||
);
|
||||
return tabs.filter((tab) => !disabledTabs.includes(tab.id));
|
||||
});
|
||||
|
||||
export default function InvokeTabs() {
|
||||
const activeTab = useAppSelector(activeTabIndexSelector);
|
||||
@ -76,10 +74,6 @@ export default function InvokeTabs() {
|
||||
(state: RootState) => state.ui
|
||||
);
|
||||
|
||||
const disabledTabs = useAppSelector(
|
||||
(state: RootState) => state.system.disabledTabs
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
@ -32,7 +32,7 @@ export default function UnifiedCanvasParameters() {
|
||||
name: 'unifiedCanvasImg2Img',
|
||||
header: `${t('parameters.imageToImage')}`,
|
||||
feature: undefined,
|
||||
content: <ImageToImageStrength label={t('parameters.img2imgStrength')} />,
|
||||
content: <ImageToImageStrength />,
|
||||
},
|
||||
seed: {
|
||||
name: 'seed',
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { RootState } from 'app/store';
|
||||
|
||||
type HotkeysState = {
|
||||
shift: boolean;
|
||||
@ -24,3 +25,5 @@ export const hotkeysSlice = createSlice({
|
||||
export const { shiftKeyPressed } = hotkeysSlice.actions;
|
||||
|
||||
export default hotkeysSlice.reducer;
|
||||
|
||||
export const hotkeysSelector = (state: RootState) => state.hotkeys;
|
||||
|
@ -19,8 +19,6 @@ const initialUIState: UIState = {
|
||||
shouldShowGallery: true,
|
||||
shouldHidePreview: false,
|
||||
openLinearAccordionItems: [],
|
||||
disabledParameterPanels: [],
|
||||
disabledTabs: [],
|
||||
openGenerateAccordionItems: [],
|
||||
openUnifiedCanvasAccordionItems: [],
|
||||
};
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { InvokeTabName } from './tabMap';
|
||||
|
||||
export type AddNewModelType = 'ckpt' | 'diffusers' | null;
|
||||
|
||||
export interface UIState {
|
||||
@ -15,8 +17,6 @@ export interface UIState {
|
||||
shouldPinGallery: boolean;
|
||||
shouldShowGallery: boolean;
|
||||
openLinearAccordionItems: number[];
|
||||
disabledParameterPanels: string[];
|
||||
disabledTabs: InvokeTabName[];
|
||||
openGenerateAccordionItems: number[];
|
||||
openUnifiedCanvasAccordionItems: number[];
|
||||
}
|
||||
|
@ -1,7 +1,13 @@
|
||||
import { AppConfig } from 'app/invokeai';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
|
||||
import Component from './component';
|
||||
|
||||
const testConfig: Partial<AppConfig> = {
|
||||
disabledTabs: ['nodes'],
|
||||
disabledFeatures: ['upscaling'],
|
||||
};
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<Component />
|
||||
<Component config={testConfig} />
|
||||
);
|
||||
|
@ -35,7 +35,10 @@ export const invocationStarted = createAction<
|
||||
>('socket/invocationStarted');
|
||||
|
||||
export const invocationComplete = createAction<
|
||||
BaseSocketPayload & { data: InvocationCompleteEvent }
|
||||
BaseSocketPayload & {
|
||||
data: InvocationCompleteEvent;
|
||||
shouldFetchImages: boolean;
|
||||
}
|
||||
>('socket/invocationComplete');
|
||||
|
||||
export const invocationError = createAction<
|
||||
|
@ -92,9 +92,9 @@ export const socketMiddleware = () => {
|
||||
socket.on('connect', () => {
|
||||
dispatch(socketConnected({ timestamp: getTimestamp() }));
|
||||
|
||||
const { results, uploads, models, nodes, system } = getState();
|
||||
const { results, uploads, models, nodes, config } = getState();
|
||||
|
||||
const { disabledTabs } = system;
|
||||
const { disabledTabs } = config;
|
||||
|
||||
// These thunks need to be dispatch in middleware; cannot handle in a reducer
|
||||
if (!results.ids.length) {
|
||||
@ -203,13 +203,20 @@ export const socketMiddleware = () => {
|
||||
const sessionId = data.graph_execution_state_id;
|
||||
|
||||
const { cancelType, isCancelScheduled } = getState().system;
|
||||
const { shouldFetchImages } = getState().config;
|
||||
|
||||
// Handle scheduled cancelation
|
||||
if (cancelType === 'scheduled' && isCancelScheduled) {
|
||||
dispatch(sessionCanceled({ sessionId }));
|
||||
}
|
||||
|
||||
dispatch(invocationComplete({ data, timestamp: getTimestamp() }));
|
||||
dispatch(
|
||||
invocationComplete({
|
||||
data,
|
||||
timestamp: getTimestamp(),
|
||||
shouldFetchImages,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -218,9 +225,9 @@ export const socketMiddleware = () => {
|
||||
}
|
||||
|
||||
if (invocationComplete.match(action)) {
|
||||
const { results } = getState();
|
||||
const { config } = getState();
|
||||
|
||||
if (results.shouldFetchImages) {
|
||||
if (config.shouldFetchImages) {
|
||||
const { result } = action.payload.data;
|
||||
if (isImageOutput(result)) {
|
||||
const imageName = result.image.image_name;
|
||||
|
Loading…
Reference in New Issue
Block a user