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 Lightbox from 'features/lightbox/components/Lightbox';
|
||||||
import { useAppDispatch, useAppSelector } from './storeHooks';
|
import { useAppDispatch, useAppSelector } from './storeHooks';
|
||||||
import { PropsWithChildren, useCallback, useEffect, useState } from 'react';
|
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 { motion, AnimatePresence } from 'framer-motion';
|
||||||
import Loading from 'common/components/Loading/Loading';
|
import Loading from 'common/components/Loading/Loading';
|
||||||
import {
|
|
||||||
disabledFeaturesChanged,
|
|
||||||
disabledTabsChanged,
|
|
||||||
} from 'features/system/store/systemSlice';
|
|
||||||
import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady';
|
import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady';
|
||||||
import { ApplicationFeature } from './invokeai';
|
import { AppConfig } from './invokeai';
|
||||||
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
|
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
|
||||||
|
import { configChanged } from 'features/system/store/configSlice';
|
||||||
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
|
|
||||||
keepGUIAlive();
|
keepGUIAlive();
|
||||||
|
|
||||||
interface Props extends PropsWithChildren {
|
interface Props extends PropsWithChildren {
|
||||||
options: {
|
config?: Partial<AppConfig>;
|
||||||
disabledTabs: InvokeTabName[];
|
|
||||||
disabledFeatures: ApplicationFeature[];
|
|
||||||
shouldTransformUrls?: boolean;
|
|
||||||
shouldFetchImages: boolean;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const App = (props: Props) => {
|
const App = ({ config = {}, children }: Props) => {
|
||||||
useToastWatcher();
|
useToastWatcher();
|
||||||
useGlobalHotkeys();
|
useGlobalHotkeys();
|
||||||
|
|
||||||
const currentTheme = useAppSelector((state) => state.ui.currentTheme);
|
const currentTheme = useAppSelector((state) => state.ui.currentTheme);
|
||||||
const disabledFeatures = useAppSelector(
|
|
||||||
(state) => state.system.disabledFeatures
|
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
||||||
);
|
|
||||||
|
|
||||||
const isApplicationReady = useIsApplicationReady();
|
const isApplicationReady = useIsApplicationReady();
|
||||||
|
|
||||||
const [loadingOverridden, setLoadingOverridden] = useState(false);
|
const [loadingOverridden, setLoadingOverridden] = useState(false);
|
||||||
|
|
||||||
const { setColorMode } = useColorMode();
|
const { setColorMode } = useColorMode();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(disabledFeaturesChanged(props.options.disabledFeatures));
|
console.log('Received config: ', config);
|
||||||
}, [dispatch, props.options.disabledFeatures]);
|
dispatch(configChanged(config));
|
||||||
|
}, [dispatch, config]);
|
||||||
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]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setColorMode(['light'].includes(currentTheme) ? 'light' : 'dark');
|
setColorMode(['light'].includes(currentTheme) ? 'light' : 'dark');
|
||||||
@ -82,7 +59,7 @@ const App = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid w="100vw" h="100vh" position="relative">
|
<Grid w="100vw" h="100vh" position="relative">
|
||||||
{!disabledFeatures.includes('lightbox') && <Lightbox />}
|
{isLightboxEnabled && <Lightbox />}
|
||||||
<ImageUploader>
|
<ImageUploader>
|
||||||
<ProgressBar />
|
<ProgressBar />
|
||||||
<Grid
|
<Grid
|
||||||
@ -92,7 +69,7 @@ const App = (props: Props) => {
|
|||||||
w={APP_WIDTH}
|
w={APP_WIDTH}
|
||||||
h={APP_HEIGHT}
|
h={APP_HEIGHT}
|
||||||
>
|
>
|
||||||
{props.children || <SiteHeader />}
|
{children || <SiteHeader />}
|
||||||
<Flex
|
<Flex
|
||||||
gap={4}
|
gap={4}
|
||||||
w={{ base: '100vw', xl: 'full' }}
|
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'.
|
* 'gfpgan'.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { FacetoolType } from 'features/parameters/store/postprocessingSlice';
|
||||||
import { InvokeTabName } from 'features/ui/store/tabMap';
|
import { InvokeTabName } from 'features/ui/store/tabMap';
|
||||||
import { IRect } from 'konva/lib/types';
|
import { IRect } from 'konva/lib/types';
|
||||||
import { ImageMetadata, ImageType } from 'services/api';
|
import { ImageMetadata, ImageType } from 'services/api';
|
||||||
import { AnyInvocation } from 'services/events/types';
|
import { AnyInvocation } from 'services/events/types';
|
||||||
|
|
||||||
/**
|
|
||||||
* A disable-able application feature
|
|
||||||
*/
|
|
||||||
export declare type ApplicationFeature =
|
|
||||||
| 'faceRestore'
|
|
||||||
| 'upscaling'
|
|
||||||
| 'lightbox'
|
|
||||||
| 'modelManager'
|
|
||||||
| 'githubLink'
|
|
||||||
| 'discordLink'
|
|
||||||
| 'bugLink'
|
|
||||||
| 'localization';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO:
|
* TODO:
|
||||||
* Once an image has been generated, if it is postprocessed again,
|
* Once an image has been generated, if it is postprocessed again,
|
||||||
@ -347,3 +335,93 @@ export declare type UploadOutpaintingMergeImagePayload = {
|
|||||||
dataURL: string;
|
dataURL: string;
|
||||||
name: 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 generationReducer from 'features/parameters/store/generationSlice';
|
||||||
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
|
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
|
||||||
import systemReducer from 'features/system/store/systemSlice';
|
import systemReducer from 'features/system/store/systemSlice';
|
||||||
|
import configReducer from 'features/system/store/configSlice';
|
||||||
import uiReducer from 'features/ui/store/uiSlice';
|
import uiReducer from 'features/ui/store/uiSlice';
|
||||||
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
|
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
|
||||||
import modelsReducer from 'features/system/store/modelSlice';
|
import modelsReducer from 'features/system/store/modelSlice';
|
||||||
@ -54,6 +55,7 @@ const rootReducer = combineReducers({
|
|||||||
postprocessing: postprocessingReducer,
|
postprocessing: postprocessingReducer,
|
||||||
results: resultsReducer,
|
results: resultsReducer,
|
||||||
system: systemReducer,
|
system: systemReducer,
|
||||||
|
config: configReducer,
|
||||||
ui: uiReducer,
|
ui: uiReducer,
|
||||||
uploads: uploadsReducer,
|
uploads: uploadsReducer,
|
||||||
hotkeys: hotkeysReducer,
|
hotkeys: hotkeysReducer,
|
||||||
@ -78,6 +80,7 @@ const rootPersistConfig = getPersistConfig({
|
|||||||
// ...uploadsBlacklist,
|
// ...uploadsBlacklist,
|
||||||
'uploads',
|
'uploads',
|
||||||
'hotkeys',
|
'hotkeys',
|
||||||
|
'config',
|
||||||
],
|
],
|
||||||
debounce: 300,
|
debounce: 300,
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,15 @@ import {
|
|||||||
import { clamp } from 'lodash';
|
import { clamp } from 'lodash';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
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 { BiReset } from 'react-icons/bi';
|
||||||
import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton';
|
import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton';
|
||||||
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
|
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
|
||||||
@ -109,33 +117,52 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
|||||||
[max, sliderNumberInputProps?.max]
|
[max, sliderNumberInputProps?.max]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSliderChange = (v: number) => {
|
const handleSliderChange = useCallback(
|
||||||
onChange(v);
|
(v: number) => {
|
||||||
};
|
onChange(v);
|
||||||
|
},
|
||||||
|
[onChange]
|
||||||
|
);
|
||||||
|
|
||||||
const handleInputBlur = (e: FocusEvent<HTMLInputElement>) => {
|
const handleInputBlur = useCallback(
|
||||||
if (e.target.value === '') e.target.value = String(min);
|
(e: FocusEvent<HTMLInputElement>) => {
|
||||||
const clamped = clamp(
|
if (e.target.value === '') {
|
||||||
isInteger ? Math.floor(Number(e.target.value)) : Number(localInputValue),
|
e.target.value = String(min);
|
||||||
min,
|
}
|
||||||
numberInputMax
|
const clamped = clamp(
|
||||||
);
|
isInteger
|
||||||
const quantized = roundDownToMultiple(clamped, step);
|
? Math.floor(Number(e.target.value))
|
||||||
onChange(quantized);
|
: Number(localInputValue),
|
||||||
setLocalInputValue(quantized);
|
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);
|
setLocalInputValue(v);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleResetDisable = () => {
|
const handleResetDisable = useCallback(() => {
|
||||||
if (!handleReset) return;
|
if (!handleReset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
handleReset();
|
handleReset();
|
||||||
};
|
}, [handleReset]);
|
||||||
|
|
||||||
|
const forceInputBlur = useCallback((e: MouseEvent) => {
|
||||||
|
if (e.target instanceof HTMLDivElement) {
|
||||||
|
e.target.focus();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl
|
<FormControl
|
||||||
|
onClick={forceInputBlur}
|
||||||
sx={
|
sx={
|
||||||
isCompact
|
isCompact
|
||||||
? {
|
? {
|
||||||
@ -218,6 +245,7 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
|||||||
value={localInputValue}
|
value={localInputValue}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
onBlur={handleInputBlur}
|
onBlur={handleInputBlur}
|
||||||
|
focusInputOnChange={false}
|
||||||
{...sliderNumberInputProps}
|
{...sliderNumberInputProps}
|
||||||
>
|
>
|
||||||
<NumberInputField
|
<NumberInputField
|
||||||
@ -240,7 +268,7 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
|||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
aria-label={t('accessibility.reset')}
|
aria-label={t('accessibility.reset')}
|
||||||
tooltip="Reset"
|
tooltip={t('accessibility.reset')}
|
||||||
icon={<BiReset />}
|
icon={<BiReset />}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
onClick={handleResetDisable}
|
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 = () => {
|
export const useGlobalHotkeys = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { shift } = useAppSelector(globalHotkeysSelector);
|
const { shift } = useAppSelector(globalHotkeysSelector);
|
||||||
|
@ -12,7 +12,7 @@ export const getUrlAlt = (url: string, shouldTransformUrls: boolean) => {
|
|||||||
|
|
||||||
export const useGetUrl = () => {
|
export const useGetUrl = () => {
|
||||||
const shouldTransformUrls = useAppSelector(
|
const shouldTransformUrls = useAppSelector(
|
||||||
(state: RootState) => state.system.shouldTransformUrls
|
(state: RootState) => state.config.shouldTransformUrls
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
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 { Provider } from 'react-redux';
|
||||||
import { PersistGate } from 'redux-persist/integration/react';
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
import { buildMiddleware, store } from './app/store';
|
import { buildMiddleware, store } from './app/store';
|
||||||
import { persistor } from './persistor';
|
import { persistor } from './persistor';
|
||||||
import { OpenAPI } from 'services/api';
|
import { OpenAPI } from 'services/api';
|
||||||
import { InvokeTabName } from 'features/ui/store/tabMap';
|
|
||||||
import '@fontsource/inter/100.css';
|
import '@fontsource/inter/100.css';
|
||||||
import '@fontsource/inter/200.css';
|
import '@fontsource/inter/200.css';
|
||||||
import '@fontsource/inter/300.css';
|
import '@fontsource/inter/300.css';
|
||||||
@ -16,41 +15,21 @@ import '@fontsource/inter/800.css';
|
|||||||
import '@fontsource/inter/900.css';
|
import '@fontsource/inter/900.css';
|
||||||
|
|
||||||
import Loading from './common/components/Loading/Loading';
|
import Loading from './common/components/Loading/Loading';
|
||||||
|
|
||||||
// Localization
|
|
||||||
import './i18n';
|
|
||||||
import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
|
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 App = lazy(() => import('./app/App'));
|
||||||
const ThemeLocaleProvider = lazy(() => import('./app/ThemeLocaleProvider'));
|
const ThemeLocaleProvider = lazy(() => import('./app/ThemeLocaleProvider'));
|
||||||
|
|
||||||
interface Props extends PropsWithChildren {
|
interface Props extends PropsWithChildren {
|
||||||
apiUrl?: string;
|
apiUrl?: string;
|
||||||
disabledPanels?: string[];
|
|
||||||
disabledTabs?: InvokeTabName[];
|
|
||||||
disabledFeatures?: ApplicationFeature[];
|
|
||||||
token?: string;
|
token?: string;
|
||||||
shouldTransformUrls?: boolean;
|
config?: Partial<AppConfig>;
|
||||||
shouldFetchImages?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Component({
|
export default function Component({ apiUrl, token, config, children }: Props) {
|
||||||
apiUrl,
|
|
||||||
disabledTabs = [],
|
|
||||||
disabledFeatures = [
|
|
||||||
'lightbox',
|
|
||||||
'bugLink',
|
|
||||||
'discordLink',
|
|
||||||
'githubLink',
|
|
||||||
'localization',
|
|
||||||
'modelManager',
|
|
||||||
],
|
|
||||||
token,
|
|
||||||
children,
|
|
||||||
shouldTransformUrls,
|
|
||||||
shouldFetchImages = false,
|
|
||||||
}: Props) {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// configure API client token
|
// configure API client token
|
||||||
if (token) {
|
if (token) {
|
||||||
@ -80,16 +59,7 @@ export default function Component({
|
|||||||
<PersistGate loading={<Loading />} persistor={persistor}>
|
<PersistGate loading={<Loading />} persistor={persistor}>
|
||||||
<React.Suspense fallback={<Loading />}>
|
<React.Suspense fallback={<Loading />}>
|
||||||
<ThemeLocaleProvider>
|
<ThemeLocaleProvider>
|
||||||
<App
|
<App config={config}>{children}</App>
|
||||||
options={{
|
|
||||||
disabledTabs,
|
|
||||||
disabledFeatures,
|
|
||||||
shouldTransformUrls,
|
|
||||||
shouldFetchImages,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</App>
|
|
||||||
</ThemeLocaleProvider>
|
</ThemeLocaleProvider>
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
</PersistGate>
|
</PersistGate>
|
||||||
|
@ -65,6 +65,7 @@ import { useCallback } from 'react';
|
|||||||
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
|
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
|
||||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||||
import { useGetUrl } from 'common/util/getUrl';
|
import { useGetUrl } from 'common/util/getUrl';
|
||||||
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
|
|
||||||
const currentImageButtonsSelector = createSelector(
|
const currentImageButtonsSelector = createSelector(
|
||||||
[
|
[
|
||||||
@ -88,8 +89,6 @@ const currentImageButtonsSelector = createSelector(
|
|||||||
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
|
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
|
||||||
system;
|
system;
|
||||||
|
|
||||||
const { disabledFeatures } = system;
|
|
||||||
|
|
||||||
const { upscalingLevel, facetoolStrength } = postprocessing;
|
const { upscalingLevel, facetoolStrength } = postprocessing;
|
||||||
|
|
||||||
const { isLightboxOpen } = lightbox;
|
const { isLightboxOpen } = lightbox;
|
||||||
@ -99,7 +98,6 @@ const currentImageButtonsSelector = createSelector(
|
|||||||
const { intermediateImage, currentImage } = gallery;
|
const { intermediateImage, currentImage } = gallery;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
disabledFeatures,
|
|
||||||
isProcessing,
|
isProcessing,
|
||||||
isConnected,
|
isConnected,
|
||||||
isGFPGANAvailable,
|
isGFPGANAvailable,
|
||||||
@ -144,9 +142,12 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
activeTabName,
|
activeTabName,
|
||||||
shouldHidePreview,
|
shouldHidePreview,
|
||||||
selectedImage,
|
selectedImage,
|
||||||
disabledFeatures,
|
|
||||||
} = useAppSelector(currentImageButtonsSelector);
|
} = useAppSelector(currentImageButtonsSelector);
|
||||||
|
|
||||||
|
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
||||||
|
const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled;
|
||||||
|
const isFaceRestoreEnabled = useFeatureStatus('faceRestore').isFeatureEnabled;
|
||||||
|
|
||||||
const { getUrl, shouldTransformUrls } = useGetUrl();
|
const { getUrl, shouldTransformUrls } = useGetUrl();
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@ -345,7 +346,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
{
|
{
|
||||||
enabled: () =>
|
enabled: () =>
|
||||||
Boolean(
|
Boolean(
|
||||||
!disabledFeatures.includes('upscaling') &&
|
isUpscalingEnabled &&
|
||||||
isESRGANAvailable &&
|
isESRGANAvailable &&
|
||||||
!shouldDisableToolbarButtons &&
|
!shouldDisableToolbarButtons &&
|
||||||
isConnected &&
|
isConnected &&
|
||||||
@ -354,7 +355,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
disabledFeatures,
|
isUpscalingEnabled,
|
||||||
selectedImage,
|
selectedImage,
|
||||||
isESRGANAvailable,
|
isESRGANAvailable,
|
||||||
shouldDisableToolbarButtons,
|
shouldDisableToolbarButtons,
|
||||||
@ -376,7 +377,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
{
|
{
|
||||||
enabled: () =>
|
enabled: () =>
|
||||||
Boolean(
|
Boolean(
|
||||||
!disabledFeatures.includes('faceRestore') &&
|
isFaceRestoreEnabled &&
|
||||||
isGFPGANAvailable &&
|
isGFPGANAvailable &&
|
||||||
!shouldDisableToolbarButtons &&
|
!shouldDisableToolbarButtons &&
|
||||||
isConnected &&
|
isConnected &&
|
||||||
@ -386,7 +387,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
[
|
[
|
||||||
disabledFeatures,
|
isFaceRestoreEnabled,
|
||||||
selectedImage,
|
selectedImage,
|
||||||
isGFPGANAvailable,
|
isGFPGANAvailable,
|
||||||
shouldDisableToolbarButtons,
|
shouldDisableToolbarButtons,
|
||||||
@ -517,7 +518,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
isChecked={shouldHidePreview}
|
isChecked={shouldHidePreview}
|
||||||
onClick={handlePreviewVisibility}
|
onClick={handlePreviewVisibility}
|
||||||
/>
|
/>
|
||||||
{!disabledFeatures.includes('lightbox') && (
|
{isLightboxEnabled && (
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
icon={<FaExpand />}
|
icon={<FaExpand />}
|
||||||
tooltip={
|
tooltip={
|
||||||
@ -566,12 +567,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
{!(
|
{(isUpscalingEnabled || isFaceRestoreEnabled) && (
|
||||||
disabledFeatures.includes('faceRestore') &&
|
|
||||||
disabledFeatures.includes('upscaling')
|
|
||||||
) && (
|
|
||||||
<ButtonGroup isAttached={true}>
|
<ButtonGroup isAttached={true}>
|
||||||
{!disabledFeatures.includes('faceRestore') && (
|
{isFaceRestoreEnabled && (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
@ -602,7 +600,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
</IAIPopover>
|
</IAIPopover>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!disabledFeatures.includes('upscaling') && (
|
{isUpscalingEnabled && (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
|
@ -158,7 +158,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDragStart = (e: DragEvent<HTMLDivElement>) => {
|
const handleDragStart = (e: DragEvent<HTMLDivElement>) => {
|
||||||
console.log('drag started');
|
|
||||||
e.dataTransfer.setData('invokeai/imageName', image.name);
|
e.dataTransfer.setData('invokeai/imageName', image.name);
|
||||||
e.dataTransfer.setData('invokeai/imageType', image.type);
|
e.dataTransfer.setData('invokeai/imageType', image.type);
|
||||||
e.dataTransfer.effectAllowed = 'move';
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { RootState } from 'app/store';
|
import { RootState } from 'app/store';
|
||||||
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
|
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
|
||||||
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
import {
|
import {
|
||||||
activeTabNameSelector,
|
activeTabNameSelector,
|
||||||
@ -68,8 +69,14 @@ export const imageGallerySelector = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const hoverableImageSelector = createSelector(
|
export const hoverableImageSelector = createSelector(
|
||||||
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
[
|
||||||
(gallery, system, lightbox, activeTabName) => {
|
gallerySelector,
|
||||||
|
systemSelector,
|
||||||
|
configSelector,
|
||||||
|
lightboxSelector,
|
||||||
|
activeTabNameSelector,
|
||||||
|
],
|
||||||
|
(gallery, system, config, lightbox, activeTabName) => {
|
||||||
return {
|
return {
|
||||||
mayDeleteImage: system.isConnected && !system.isProcessing,
|
mayDeleteImage: system.isConnected && !system.isProcessing,
|
||||||
galleryImageObjectFit: gallery.galleryImageObjectFit,
|
galleryImageObjectFit: gallery.galleryImageObjectFit,
|
||||||
@ -77,7 +84,7 @@ export const hoverableImageSelector = createSelector(
|
|||||||
shouldUseSingleGalleryColumn: gallery.shouldUseSingleGalleryColumn,
|
shouldUseSingleGalleryColumn: gallery.shouldUseSingleGalleryColumn,
|
||||||
activeTabName,
|
activeTabName,
|
||||||
isLightboxOpen: lightbox.isLightboxOpen,
|
isLightboxOpen: lightbox.isLightboxOpen,
|
||||||
disabledFeatures: system.disabledFeatures,
|
disabledFeatures: config.disabledFeatures,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
|
||||||
PayloadAction,
|
|
||||||
createEntityAdapter,
|
|
||||||
createSlice,
|
|
||||||
} from '@reduxjs/toolkit';
|
|
||||||
import { Image } from 'app/invokeai';
|
import { Image } from 'app/invokeai';
|
||||||
import { invocationComplete } from 'services/events/actions';
|
import { invocationComplete } from 'services/events/actions';
|
||||||
|
|
||||||
@ -19,37 +15,24 @@ import {
|
|||||||
import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
|
import { deserializeImageResponse } from 'services/util/deserializeImageResponse';
|
||||||
import { imageReceived, thumbnailReceived } from 'services/thunks/image';
|
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>({
|
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,
|
selectId: (image) => image.name,
|
||||||
// Order all images by their time (in descending order)
|
|
||||||
sortComparer: (a, b) => b.metadata.created - a.metadata.created,
|
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 = {
|
type AdditionalResultsState = {
|
||||||
// these are a bit misleading; they refer to sessions, not results, but we don't have a route
|
page: number;
|
||||||
// to list all images directly at this time...
|
pages: number;
|
||||||
page: number; // current page we are on
|
isLoading: boolean;
|
||||||
pages: number; // the total number of pages available
|
nextPage: number;
|
||||||
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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialResultsState =
|
export const initialResultsState =
|
||||||
resultsAdapter.getInitialState<AdditionalResultsState>({
|
resultsAdapter.getInitialState<AdditionalResultsState>({
|
||||||
// provide the additional initial state
|
|
||||||
page: 0,
|
page: 0,
|
||||||
pages: 0,
|
pages: 0,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
nextPage: 0,
|
nextPage: 0,
|
||||||
shouldFetchImages: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ResultsState = typeof initialResultsState;
|
export type ResultsState = typeof initialResultsState;
|
||||||
@ -58,21 +41,9 @@ const resultsSlice = createSlice({
|
|||||||
name: 'results',
|
name: 'results',
|
||||||
initialState: initialResultsState,
|
initialState: initialResultsState,
|
||||||
reducers: {
|
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,
|
resultAdded: resultsAdapter.upsertOne,
|
||||||
|
|
||||||
setShouldFetchImages: (state, action: PayloadAction<boolean>) => {
|
|
||||||
state.shouldFetchImages = action.payload;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
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
|
* Received Result Images Page - PENDING
|
||||||
*/
|
*/
|
||||||
@ -90,7 +61,6 @@ const resultsSlice = createSlice({
|
|||||||
deserializeImageResponse(image)
|
deserializeImageResponse(image)
|
||||||
);
|
);
|
||||||
|
|
||||||
// use the adapter reducer to append all the results to state
|
|
||||||
resultsAdapter.addMany(state, resultImages);
|
resultsAdapter.addMany(state, resultImages);
|
||||||
|
|
||||||
state.page = page;
|
state.page = page;
|
||||||
@ -103,14 +73,15 @@ const resultsSlice = createSlice({
|
|||||||
* Invocation Complete
|
* Invocation Complete
|
||||||
*/
|
*/
|
||||||
builder.addCase(invocationComplete, (state, action) => {
|
builder.addCase(invocationComplete, (state, action) => {
|
||||||
const { data } = action.payload;
|
const { data, shouldFetchImages } = action.payload;
|
||||||
const { result, node, graph_execution_state_id } = data;
|
const { result, node, graph_execution_state_id } = data;
|
||||||
|
|
||||||
if (isImageOutput(result)) {
|
if (isImageOutput(result)) {
|
||||||
const name = result.image.image_name;
|
const name = result.image.image_name;
|
||||||
const type = result.image.image_type;
|
const type = result.image.image_type;
|
||||||
|
|
||||||
// if we need to refetch, set URLs to placeholder for now
|
// if we need to refetch, set URLs to placeholder for now
|
||||||
const { url, thumbnail } = state.shouldFetchImages
|
const { url, thumbnail } = shouldFetchImages
|
||||||
? { url: '', thumbnail: '' }
|
? { url: '', thumbnail: '' }
|
||||||
: buildImageUrls(type, name);
|
: buildImageUrls(type, name);
|
||||||
|
|
||||||
@ -123,7 +94,7 @@ const resultsSlice = createSlice({
|
|||||||
thumbnail,
|
thumbnail,
|
||||||
metadata: {
|
metadata: {
|
||||||
created: timestamp,
|
created: timestamp,
|
||||||
width: result.width, // TODO: add tese dimensions
|
width: result.width,
|
||||||
height: result.height,
|
height: result.height,
|
||||||
invokeai: {
|
invokeai: {
|
||||||
session_id: graph_execution_state_id,
|
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 {
|
export const {
|
||||||
selectAll: selectResultsAll,
|
selectAll: selectResultsAll,
|
||||||
selectById: selectResultsById,
|
selectById: selectResultsById,
|
||||||
@ -172,6 +141,6 @@ export const {
|
|||||||
selectTotal: selectResultsTotal,
|
selectTotal: selectResultsTotal,
|
||||||
} = resultsAdapter.getSelectors<RootState>((state) => state.results);
|
} = resultsAdapter.getSelectors<RootState>((state) => state.results);
|
||||||
|
|
||||||
export const { resultAdded, setShouldFetchImages } = resultsSlice.actions;
|
export const { resultAdded } = resultsSlice.actions;
|
||||||
|
|
||||||
export default resultsSlice.reducer;
|
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<
|
export type FieldComponentProps<
|
||||||
V extends InputFieldValue,
|
V extends InputFieldValue,
|
||||||
|
@ -3,7 +3,10 @@ import { NodesState } from './nodesSlice';
|
|||||||
/**
|
/**
|
||||||
* Nodes slice persist blacklist
|
* Nodes slice persist blacklist
|
||||||
*/
|
*/
|
||||||
const itemsToBlacklist: (keyof NodesState)[] = ['schema', 'invocations'];
|
const itemsToBlacklist: (keyof NodesState)[] = [
|
||||||
|
'schema',
|
||||||
|
'invocationTemplates',
|
||||||
|
];
|
||||||
|
|
||||||
export const nodesBlacklist = itemsToBlacklist.map(
|
export const nodesBlacklist = itemsToBlacklist.map(
|
||||||
(blacklistItem) => `nodes.${blacklistItem}`
|
(blacklistItem) => `nodes.${blacklistItem}`
|
||||||
|
@ -1,46 +1,73 @@
|
|||||||
import { RootState } from 'app/store';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
|
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||||
import { setImg2imgStrength } from 'features/parameters/store/generationSlice';
|
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';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface ImageToImageStrengthProps {
|
const selector = createSelector(
|
||||||
label?: string;
|
[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 step = hotkeys.shift ? fineStep : coarseStep;
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
img2imgStrength,
|
||||||
|
isImageToImageEnabled,
|
||||||
|
initial,
|
||||||
|
min,
|
||||||
|
sliderMax,
|
||||||
|
inputMax,
|
||||||
|
step,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const ImageToImageStrength = () => {
|
||||||
|
const {
|
||||||
|
img2imgStrength,
|
||||||
|
isImageToImageEnabled,
|
||||||
|
initial,
|
||||||
|
min,
|
||||||
|
sliderMax,
|
||||||
|
inputMax,
|
||||||
|
step,
|
||||||
|
} = useAppSelector(selector);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChangeStrength = (v: number) => dispatch(setImg2imgStrength(v));
|
const handleChange = useCallback(
|
||||||
|
(v: number) => dispatch(setImg2imgStrength(v)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
const handleImg2ImgStrengthReset = () => {
|
const handleReset = useCallback(() => {
|
||||||
dispatch(setImg2imgStrength(0.75));
|
dispatch(setImg2imgStrength(initial));
|
||||||
};
|
}, [dispatch, initial]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={label}
|
label={`${t('parameters.strength')}`}
|
||||||
step={0.01}
|
step={step}
|
||||||
min={0.01}
|
min={min}
|
||||||
max={1}
|
max={sliderMax}
|
||||||
onChange={handleChangeStrength}
|
onChange={handleChange}
|
||||||
|
handleReset={handleReset}
|
||||||
value={img2imgStrength}
|
value={img2imgStrength}
|
||||||
isInteger={false}
|
isInteger={false}
|
||||||
withInput
|
withInput
|
||||||
withSliderMarks
|
withSliderMarks
|
||||||
inputWidth={22}
|
|
||||||
withReset
|
withReset
|
||||||
handleReset={handleImg2ImgStrengthReset}
|
|
||||||
isDisabled={!isImageToImageEnabled}
|
isDisabled={!isImageToImageEnabled}
|
||||||
|
sliderNumberInputProps={{ max: inputMax }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default memo(ImageToImageStrength);
|
||||||
|
@ -1,37 +1,57 @@
|
|||||||
import { Box, BoxProps } from '@chakra-ui/react';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { RootState } from 'app/store';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
|
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||||
import { setHeight } from 'features/parameters/store/generationSlice';
|
import { setHeight } from 'features/parameters/store/generationSlice';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
import { memo } from 'react';
|
import { hotkeysSelector } from 'features/ui/store/hotkeysSlice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const HeightSlider = (props: BoxProps) => {
|
const selector = createSelector(
|
||||||
const height = useAppSelector((state: RootState) => state.generation.height);
|
[generationSelector, hotkeysSelector, configSelector],
|
||||||
const shift = useAppSelector((state: RootState) => state.hotkeys.shift);
|
(generation, hotkeys, config) => {
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
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 dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(v: number) => {
|
||||||
|
dispatch(setHeight(v));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
dispatch(setHeight(initial));
|
||||||
|
}, [dispatch, initial]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box {...props}>
|
<IAISlider
|
||||||
<IAISlider
|
label={t('parameters.height')}
|
||||||
isDisabled={activeTabName === 'unifiedCanvas'}
|
value={height}
|
||||||
label={t('parameters.height')}
|
min={min}
|
||||||
value={height}
|
step={step}
|
||||||
min={64}
|
max={sliderMax}
|
||||||
step={shift ? 8 : 64}
|
onChange={handleChange}
|
||||||
max={2048}
|
handleReset={handleReset}
|
||||||
onChange={(v) => dispatch(setHeight(v))}
|
withInput
|
||||||
handleReset={() => dispatch(setHeight(512))}
|
withReset
|
||||||
withInput
|
withSliderMarks
|
||||||
withReset
|
sliderNumberInputProps={{ max: inputMax }}
|
||||||
withSliderMarks
|
/>
|
||||||
sliderNumberInputProps={{ max: 15360 }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,46 +1,85 @@
|
|||||||
import { RootState } from 'app/store';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import IAINumberInput from 'common/components/IAINumberInput';
|
import IAINumberInput from 'common/components/IAINumberInput';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
|
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||||
import { setCfgScale } from 'features/parameters/store/generationSlice';
|
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';
|
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 dispatch = useAppDispatch();
|
||||||
const cfgScale = useAppSelector(
|
|
||||||
(state: RootState) => state.generation.cfgScale
|
|
||||||
);
|
|
||||||
const shouldUseSliders = useAppSelector(
|
|
||||||
(state: RootState) => state.ui.shouldUseSliders
|
|
||||||
);
|
|
||||||
const { t } = useTranslation();
|
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 ? (
|
return shouldUseSliders ? (
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={t('parameters.cfgScale')}
|
label={t('parameters.cfgScale')}
|
||||||
step={0.5}
|
step={shift ? 0.1 : 0.5}
|
||||||
min={1.01}
|
min={min}
|
||||||
max={30}
|
max={sliderMax}
|
||||||
onChange={handleChangeCfgScale}
|
onChange={handleChange}
|
||||||
handleReset={() => dispatch(setCfgScale(7.5))}
|
handleReset={handleReset}
|
||||||
value={cfgScale}
|
value={cfgScale}
|
||||||
sliderNumberInputProps={{ max: 200 }}
|
sliderNumberInputProps={{ max: inputMax }}
|
||||||
withInput
|
withInput
|
||||||
withReset
|
withReset
|
||||||
withSliderMarks
|
withSliderMarks
|
||||||
|
isInteger={false}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<IAINumberInput
|
<IAINumberInput
|
||||||
label={t('parameters.cfgScale')}
|
label={t('parameters.cfgScale')}
|
||||||
step={0.5}
|
step={0.5}
|
||||||
min={1.01}
|
min={min}
|
||||||
max={200}
|
max={inputMax}
|
||||||
onChange={handleChangeCfgScale}
|
onChange={handleChange}
|
||||||
value={cfgScale}
|
value={cfgScale}
|
||||||
isInteger={false}
|
isInteger={false}
|
||||||
numberInputFieldProps={{ textAlign: 'center' }}
|
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 { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import IAINumberInput from 'common/components/IAINumberInput';
|
import IAINumberInput from 'common/components/IAINumberInput';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
|
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||||
import { setIterations } from 'features/parameters/store/generationSlice';
|
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';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default function MainIterations() {
|
const selector = createSelector(
|
||||||
const iterations = useAppSelector(
|
[generationSelector, configSelector, uiSelector, hotkeysSelector],
|
||||||
(state: RootState) => state.generation.iterations
|
(generation, config, ui, hotkeys) => {
|
||||||
);
|
const { initial, min, sliderMax, inputMax, fineStep, coarseStep } =
|
||||||
|
config.sd.iterations;
|
||||||
|
const { iterations } = generation;
|
||||||
|
const { shouldUseSliders } = ui;
|
||||||
|
|
||||||
const shouldUseSliders = useAppSelector(
|
const step = hotkeys.shift ? fineStep : coarseStep;
|
||||||
(state: RootState) => state.ui.shouldUseSliders
|
|
||||||
);
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
iterations,
|
||||||
|
initial,
|
||||||
|
min,
|
||||||
|
sliderMax,
|
||||||
|
inputMax,
|
||||||
|
step,
|
||||||
|
shouldUseSliders,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const MainIterations = () => {
|
||||||
|
const {
|
||||||
|
iterations,
|
||||||
|
initial,
|
||||||
|
min,
|
||||||
|
sliderMax,
|
||||||
|
inputMax,
|
||||||
|
step,
|
||||||
|
shouldUseSliders,
|
||||||
|
} = useAppSelector(selector);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
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 ? (
|
return shouldUseSliders ? (
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={t('parameters.images')}
|
label={t('parameters.images')}
|
||||||
step={1}
|
step={step}
|
||||||
min={1}
|
min={min}
|
||||||
max={16}
|
max={sliderMax}
|
||||||
onChange={handleChangeIterations}
|
onChange={handleChange}
|
||||||
handleReset={() => dispatch(setIterations(1))}
|
handleReset={handleReset}
|
||||||
value={iterations}
|
value={iterations}
|
||||||
withInput
|
withInput
|
||||||
withReset
|
withReset
|
||||||
withSliderMarks
|
withSliderMarks
|
||||||
sliderNumberInputProps={{ max: 9999 }}
|
sliderNumberInputProps={{ max: inputMax }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<IAINumberInput
|
<IAINumberInput
|
||||||
label={t('parameters.images')}
|
label={t('parameters.images')}
|
||||||
step={1}
|
step={step}
|
||||||
min={1}
|
min={min}
|
||||||
max={9999}
|
max={inputMax}
|
||||||
onChange={handleChangeIterations}
|
onChange={handleChange}
|
||||||
value={iterations}
|
value={iterations}
|
||||||
numberInputFieldProps={{ textAlign: 'center' }}
|
numberInputFieldProps={{ textAlign: 'center' }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default memo(MainIterations);
|
||||||
|
@ -1,35 +1,32 @@
|
|||||||
import { Box, BoxProps } from '@chakra-ui/react';
|
import { DIFFUSERS_SAMPLERS } from 'app/constants';
|
||||||
import { DIFFUSERS_SAMPLERS, SAMPLERS } from 'app/constants';
|
|
||||||
import { RootState } from 'app/store';
|
import { RootState } from 'app/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import IAISelect from 'common/components/IAISelect';
|
import IAISelect from 'common/components/IAISelect';
|
||||||
import { setSampler } from 'features/parameters/store/generationSlice';
|
import { setSampler } from 'features/parameters/store/generationSlice';
|
||||||
import { activeModelSelector } from 'features/system/store/systemSelectors';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
import { ChangeEvent } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default function MainSampler(props: BoxProps) {
|
const Scheduler = () => {
|
||||||
const sampler = useAppSelector(
|
const sampler = useAppSelector(
|
||||||
(state: RootState) => state.generation.sampler
|
(state: RootState) => state.generation.sampler
|
||||||
);
|
);
|
||||||
const activeModel = useAppSelector(activeModelSelector);
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChangeSampler = (e: ChangeEvent<HTMLSelectElement>) =>
|
const handleChange = useCallback(
|
||||||
dispatch(setSampler(e.target.value));
|
(e: ChangeEvent<HTMLSelectElement>) => dispatch(setSampler(e.target.value)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box {...props}>
|
<IAISelect
|
||||||
<IAISelect
|
label={t('parameters.sampler')}
|
||||||
label={t('parameters.sampler')}
|
value={sampler}
|
||||||
value={sampler}
|
onChange={handleChange}
|
||||||
onChange={handleChangeSampler}
|
validValues={DIFFUSERS_SAMPLERS}
|
||||||
validValues={
|
minWidth={36}
|
||||||
activeModel.format === 'diffusers' ? DIFFUSERS_SAMPLERS : SAMPLERS
|
/>
|
||||||
}
|
|
||||||
minWidth={36}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
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 { RootState } from 'app/store';
|
||||||
import { useAppSelector } from 'app/storeHooks';
|
import { useAppSelector } from 'app/storeHooks';
|
||||||
import { ModelSelect } from 'exports';
|
import { ModelSelect } from 'exports';
|
||||||
|
import { memo } from 'react';
|
||||||
import HeightSlider from './HeightSlider';
|
import HeightSlider from './HeightSlider';
|
||||||
import MainCFGScale from './MainCFGScale';
|
import MainCFGScale from './MainCFGScale';
|
||||||
import MainHeight from './MainHeight';
|
|
||||||
import MainIterations from './MainIterations';
|
import MainIterations from './MainIterations';
|
||||||
import MainSampler from './MainSampler';
|
import MainSampler from './MainSampler';
|
||||||
import MainSteps from './MainSteps';
|
import MainSteps from './MainSteps';
|
||||||
import MainWidth from './MainWidth';
|
|
||||||
import WidthSlider from './WidthSlider';
|
import WidthSlider from './WidthSlider';
|
||||||
|
|
||||||
export default function MainSettings() {
|
const MainSettings = () => {
|
||||||
const shouldUseSliders = useAppSelector(
|
const shouldUseSliders = useAppSelector(
|
||||||
(state: RootState) => state.ui.shouldUseSliders
|
(state: RootState) => state.ui.shouldUseSliders
|
||||||
);
|
);
|
||||||
@ -21,9 +20,16 @@ export default function MainSettings() {
|
|||||||
<MainIterations />
|
<MainIterations />
|
||||||
<MainSteps />
|
<MainSteps />
|
||||||
<MainCFGScale />
|
<MainCFGScale />
|
||||||
<MainWidth />
|
<WidthSlider />
|
||||||
<MainHeight />
|
<HeightSlider />
|
||||||
<MainSampler />
|
<Flex gap={3} w="full">
|
||||||
|
<Box flexGrow={2}>
|
||||||
|
<MainSampler />
|
||||||
|
</Box>
|
||||||
|
<Box flexGrow={3}>
|
||||||
|
<ModelSelect />
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
</VStack>
|
</VStack>
|
||||||
) : (
|
) : (
|
||||||
<Flex gap={3} flexDirection="column">
|
<Flex gap={3} flexDirection="column">
|
||||||
@ -32,12 +38,18 @@ export default function MainSettings() {
|
|||||||
<MainSteps />
|
<MainSteps />
|
||||||
<MainCFGScale />
|
<MainCFGScale />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex gap={3}>
|
|
||||||
<MainSampler flexGrow={2} />
|
|
||||||
<ModelSelect flexGrow={3} />
|
|
||||||
</Flex>
|
|
||||||
<WidthSlider />
|
<WidthSlider />
|
||||||
<HeightSlider />
|
<HeightSlider />
|
||||||
|
<Flex gap={3} w="full">
|
||||||
|
<Box flexGrow={2}>
|
||||||
|
<MainSampler />
|
||||||
|
</Box>
|
||||||
|
<Box flexGrow={3}>
|
||||||
|
<ModelSelect />
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
</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 { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import IAINumberInput from 'common/components/IAINumberInput';
|
import IAINumberInput from 'common/components/IAINumberInput';
|
||||||
|
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
|
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||||
import {
|
import {
|
||||||
clampSymmetrySteps,
|
clampSymmetrySteps,
|
||||||
setSteps,
|
setSteps,
|
||||||
} from 'features/parameters/store/generationSlice';
|
} 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';
|
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 dispatch = useAppDispatch();
|
||||||
const steps = useAppSelector((state: RootState) => state.generation.steps);
|
|
||||||
const shouldUseSliders = useAppSelector(
|
|
||||||
(state: RootState) => state.ui.shouldUseSliders
|
|
||||||
);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChangeSteps = (v: number) => {
|
const handleChange = useCallback(
|
||||||
dispatch(setSteps(v));
|
(v: number) => {
|
||||||
};
|
dispatch(setSteps(v));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
dispatch(setSteps(initial));
|
||||||
|
}, [dispatch, initial]);
|
||||||
|
|
||||||
const handleBlur = () => {
|
const handleBlur = useCallback(() => {
|
||||||
dispatch(clampSymmetrySteps());
|
dispatch(clampSymmetrySteps());
|
||||||
};
|
}, [dispatch]);
|
||||||
|
|
||||||
return shouldUseSliders ? (
|
return shouldUseSliders ? (
|
||||||
<IAISlider
|
<IAISlider
|
||||||
label={t('parameters.steps')}
|
label={t('parameters.steps')}
|
||||||
min={1}
|
min={min}
|
||||||
step={1}
|
max={sliderMax}
|
||||||
onChange={handleChangeSteps}
|
step={step}
|
||||||
handleReset={() => dispatch(setSteps(20))}
|
onChange={handleChange}
|
||||||
|
handleReset={handleReset}
|
||||||
value={steps}
|
value={steps}
|
||||||
withInput
|
withInput
|
||||||
withReset
|
withReset
|
||||||
withSliderMarks
|
withSliderMarks
|
||||||
sliderNumberInputProps={{ max: 9999 }}
|
sliderNumberInputProps={{ max: inputMax }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<IAINumberInput
|
<IAINumberInput
|
||||||
label={t('parameters.steps')}
|
label={t('parameters.steps')}
|
||||||
min={1}
|
min={min}
|
||||||
max={9999}
|
max={inputMax}
|
||||||
step={1}
|
step={step}
|
||||||
onChange={handleChangeSteps}
|
onChange={handleChange}
|
||||||
value={steps}
|
value={steps}
|
||||||
numberInputFieldProps={{ textAlign: 'center' }}
|
numberInputFieldProps={{ textAlign: 'center' }}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default memo(MainSteps);
|
||||||
|
@ -1,36 +1,57 @@
|
|||||||
import { Box, BoxProps } from '@chakra-ui/react';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { RootState } from 'app/store';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
|
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||||
import { setWidth } from 'features/parameters/store/generationSlice';
|
import { setWidth } from 'features/parameters/store/generationSlice';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
import { memo } from 'react';
|
import { hotkeysSelector } from 'features/ui/store/hotkeysSlice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const WidthSlider = (props: BoxProps) => {
|
const selector = createSelector(
|
||||||
const width = useAppSelector((state: RootState) => state.generation.width);
|
[generationSelector, hotkeysSelector, configSelector],
|
||||||
const shift = useAppSelector((state: RootState) => state.hotkeys.shift);
|
(generation, hotkeys, config) => {
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const { initial, min, sliderMax, inputMax, fineStep, coarseStep } =
|
||||||
const { t } = useTranslation();
|
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 dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(v: number) => {
|
||||||
|
dispatch(setWidth(v));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
dispatch(setWidth(initial));
|
||||||
|
}, [dispatch, initial]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box {...props}>
|
<IAISlider
|
||||||
<IAISlider
|
label={t('parameters.width')}
|
||||||
isDisabled={activeTabName === 'unifiedCanvas'}
|
value={width}
|
||||||
label={t('parameters.width')}
|
min={min}
|
||||||
value={width}
|
step={step}
|
||||||
min={64}
|
max={sliderMax}
|
||||||
step={shift ? 8 : 64}
|
onChange={handleChange}
|
||||||
max={2048}
|
handleReset={handleReset}
|
||||||
onChange={(v) => dispatch(setWidth(v))}
|
withInput
|
||||||
handleReset={() => dispatch(setWidth(512))}
|
withReset
|
||||||
withInput
|
withSliderMarks
|
||||||
withReset
|
sliderNumberInputProps={{ max: inputMax }}
|
||||||
withSliderMarks
|
/>
|
||||||
sliderNumberInputProps={{ max: 15360 }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,44 +2,34 @@ import { Accordion } from '@chakra-ui/react';
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { Feature } from 'app/features';
|
import { Feature } from 'app/features';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
|
||||||
import { tabMap } from 'features/ui/store/tabMap';
|
import { tabMap } from 'features/ui/store/tabMap';
|
||||||
import {
|
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||||
activeTabNameSelector,
|
|
||||||
uiSelector,
|
|
||||||
} from 'features/ui/store/uiSelectors';
|
|
||||||
import { openAccordionItemsChanged } from 'features/ui/store/uiSlice';
|
import { openAccordionItemsChanged } from 'features/ui/store/uiSlice';
|
||||||
import { filter, map } from 'lodash';
|
import { map } from 'lodash';
|
||||||
import { ReactNode, useCallback } from 'react';
|
import { ReactNode, useCallback } from 'react';
|
||||||
import InvokeAccordionItem from './AccordionItems/InvokeAccordionItem';
|
import InvokeAccordionItem from './AccordionItems/InvokeAccordionItem';
|
||||||
|
|
||||||
const parametersAccordionSelector = createSelector(
|
const parametersAccordionSelector = createSelector([uiSelector], (uiSlice) => {
|
||||||
[uiSelector, systemSelector],
|
const {
|
||||||
(uiSlice, system) => {
|
activeTab,
|
||||||
const {
|
openLinearAccordionItems,
|
||||||
activeTab,
|
openUnifiedCanvasAccordionItems,
|
||||||
openLinearAccordionItems,
|
} = uiSlice;
|
||||||
openUnifiedCanvasAccordionItems,
|
|
||||||
} = uiSlice;
|
|
||||||
|
|
||||||
const { disabledFeatures } = system;
|
let openAccordions: number[] = [];
|
||||||
|
|
||||||
let openAccordions: number[] = [];
|
if (tabMap[activeTab] === 'generate') {
|
||||||
|
openAccordions = openLinearAccordionItems;
|
||||||
if (tabMap[activeTab] === 'generate') {
|
|
||||||
openAccordions = openLinearAccordionItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tabMap[activeTab] === 'unifiedCanvas') {
|
|
||||||
openAccordions = openUnifiedCanvasAccordionItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
openAccordions,
|
|
||||||
disabledFeatures,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
if (tabMap[activeTab] === 'unifiedCanvas') {
|
||||||
|
openAccordions = openUnifiedCanvasAccordionItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
openAccordions,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
export type ParametersAccordionItem = {
|
export type ParametersAccordionItem = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -61,9 +51,7 @@ type ParametersAccordionProps = {
|
|||||||
* Main container for generation and processing parameters.
|
* Main container for generation and processing parameters.
|
||||||
*/
|
*/
|
||||||
const ParametersAccordion = ({ accordionItems }: ParametersAccordionProps) => {
|
const ParametersAccordion = ({ accordionItems }: ParametersAccordionProps) => {
|
||||||
const { openAccordions, disabledFeatures } = useAppSelector(
|
const { openAccordions } = useAppSelector(parametersAccordionSelector);
|
||||||
parametersAccordionSelector
|
|
||||||
);
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Box, BoxProps, Flex } from '@chakra-ui/react';
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent, memo } from 'react';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ const selector = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const ModelSelect = (props: BoxProps) => {
|
const ModelSelect = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { allModelNames, selectedModel } = useAppSelector(selector);
|
const { allModelNames, selectedModel } = useAppSelector(selector);
|
||||||
@ -39,18 +38,16 @@ const ModelSelect = (props: BoxProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box {...props}>
|
<IAISelect
|
||||||
<IAISelect
|
label={t('modelManager.model')}
|
||||||
label={t('modelManager.model')}
|
style={{ fontSize: 'sm' }}
|
||||||
style={{ fontSize: 'sm' }}
|
aria-label={t('accessibility.modelSelect')}
|
||||||
aria-label={t('accessibility.modelSelect')}
|
tooltip={selectedModel?.description || ''}
|
||||||
tooltip={selectedModel?.description || ''}
|
value={selectedModel?.name || undefined}
|
||||||
value={selectedModel?.name || undefined}
|
validValues={allModelNames}
|
||||||
validValues={allModelNames}
|
onChange={handleChangeModel}
|
||||||
onChange={handleChangeModel}
|
/>
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ModelSelect;
|
export default memo(ModelSelect);
|
||||||
|
@ -8,22 +8,26 @@ import ModelManagerModal from './ModelManager/ModelManagerModal';
|
|||||||
import SettingsModal from './SettingsModal/SettingsModal';
|
import SettingsModal from './SettingsModal/SettingsModal';
|
||||||
import ThemeChanger from './ThemeChanger';
|
import ThemeChanger from './ThemeChanger';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { useAppSelector } from 'app/storeHooks';
|
import { useFeatureStatus } from '../hooks/useFeatureStatus';
|
||||||
import { RootState } from 'app/store';
|
|
||||||
|
|
||||||
const SiteHeaderMenu = () => {
|
const SiteHeaderMenu = () => {
|
||||||
const disabledFeatures = useAppSelector(
|
|
||||||
(state: RootState) => state.system.disabledFeatures
|
|
||||||
);
|
|
||||||
const { t } = useTranslation();
|
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 (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
flexDirection={{ base: 'column', xl: 'row' }}
|
flexDirection={{ base: 'column', xl: 'row' }}
|
||||||
gap={{ base: 4, xl: 1 }}
|
gap={{ base: 4, xl: 1 }}
|
||||||
>
|
>
|
||||||
{!disabledFeatures.includes('modelManager') && (
|
{isModelManagerEnabled && (
|
||||||
<ModelManagerModal>
|
<ModelManagerModal>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label={t('modelManager.modelManager')}
|
aria-label={t('modelManager.modelManager')}
|
||||||
@ -51,9 +55,9 @@ const SiteHeaderMenu = () => {
|
|||||||
|
|
||||||
<ThemeChanger />
|
<ThemeChanger />
|
||||||
|
|
||||||
{!disabledFeatures.includes('localization') && <LanguagePicker />}
|
{isLocalizationEnabled && <LanguagePicker />}
|
||||||
|
|
||||||
{!disabledFeatures.includes('bugLink') && (
|
{isBugLinkEnabled && (
|
||||||
<Link
|
<Link
|
||||||
isExternal
|
isExternal
|
||||||
href="http://github.com/invoke-ai/InvokeAI/issues"
|
href="http://github.com/invoke-ai/InvokeAI/issues"
|
||||||
@ -71,7 +75,7 @@ const SiteHeaderMenu = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!disabledFeatures.includes('githubLink') && (
|
{isGithubLinkEnabled && (
|
||||||
<Link
|
<Link
|
||||||
isExternal
|
isExternal
|
||||||
href="http://github.com/invoke-ai/InvokeAI"
|
href="http://github.com/invoke-ai/InvokeAI"
|
||||||
@ -89,7 +93,7 @@ const SiteHeaderMenu = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!disabledFeatures.includes('discordLink') && (
|
{isDiscordLinkEnabled && (
|
||||||
<Link
|
<Link
|
||||||
isExternal
|
isExternal
|
||||||
href="https://discord.gg/ZmtBAhwWhy"
|
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 { RootState } from 'app/store';
|
||||||
import { useAppSelector } from 'app/storeHooks';
|
import { useAppSelector } from 'app/storeHooks';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { configSelector } from '../store/configSelectors';
|
||||||
|
import { systemSelector } from '../store/systemSelectors';
|
||||||
|
|
||||||
const isApplicationReadySelector = createSelector(
|
const isApplicationReadySelector = createSelector(
|
||||||
[(state: RootState) => state.system],
|
[systemSelector, configSelector],
|
||||||
(system) => {
|
(system, config) => {
|
||||||
const {
|
const { wereModelsReceived, wasSchemaParsed } = system;
|
||||||
disabledFeatures,
|
|
||||||
disabledTabs,
|
const { disabledTabs } = config;
|
||||||
wereModelsReceived,
|
|
||||||
wasSchemaParsed,
|
|
||||||
} = system;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
disabledTabs,
|
disabledTabs,
|
||||||
disabledFeatures,
|
|
||||||
wereModelsReceived,
|
wereModelsReceived,
|
||||||
wasSchemaParsed,
|
wasSchemaParsed,
|
||||||
};
|
};
|
||||||
@ -23,12 +21,9 @@ const isApplicationReadySelector = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const useIsApplicationReady = () => {
|
export const useIsApplicationReady = () => {
|
||||||
const {
|
const { disabledTabs, wereModelsReceived, wasSchemaParsed } = useAppSelector(
|
||||||
disabledTabs,
|
isApplicationReadySelector
|
||||||
disabledFeatures,
|
);
|
||||||
wereModelsReceived,
|
|
||||||
wasSchemaParsed,
|
|
||||||
} = useAppSelector(isApplicationReadySelector);
|
|
||||||
|
|
||||||
const isApplicationReady = useMemo(() => {
|
const isApplicationReady = useMemo(() => {
|
||||||
if (!wereModelsReceived) {
|
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
|
* Array of node IDs that we want to handle when events received
|
||||||
*/
|
*/
|
||||||
subscribedNodeIds: string[];
|
subscribedNodeIds: string[];
|
||||||
/**
|
// /**
|
||||||
* Whether or not URLs should be transformed to use a different host
|
// * Whether or not URLs should be transformed to use a different host
|
||||||
*/
|
// */
|
||||||
shouldTransformUrls: boolean;
|
// shouldTransformUrls: boolean;
|
||||||
/**
|
// /**
|
||||||
* Array of disabled tabs
|
// * Array of disabled tabs
|
||||||
*/
|
// */
|
||||||
disabledTabs: InvokeTabName[];
|
// disabledTabs: InvokeTabName[];
|
||||||
/**
|
// /**
|
||||||
* Array of disabled features
|
// * Array of disabled features
|
||||||
*/
|
// */
|
||||||
disabledFeatures: InvokeAI.ApplicationFeature[];
|
// disabledFeatures: InvokeAI.AppFeature[];
|
||||||
/**
|
/**
|
||||||
* Whether or not the available models were received
|
* Whether or not the available models were received
|
||||||
*/
|
*/
|
||||||
@ -157,9 +157,9 @@ const initialSystemState: SystemState = {
|
|||||||
cancelType: 'immediate',
|
cancelType: 'immediate',
|
||||||
isCancelScheduled: false,
|
isCancelScheduled: false,
|
||||||
subscribedNodeIds: [],
|
subscribedNodeIds: [],
|
||||||
shouldTransformUrls: false,
|
// shouldTransformUrls: false,
|
||||||
disabledTabs: [],
|
// disabledTabs: [],
|
||||||
disabledFeatures: [],
|
// disabledFeatures: [],
|
||||||
wereModelsReceived: false,
|
wereModelsReceived: false,
|
||||||
wasSchemaParsed: false,
|
wasSchemaParsed: false,
|
||||||
};
|
};
|
||||||
@ -359,27 +359,27 @@ export const systemSlice = createSlice({
|
|||||||
subscribedNodeIdsSet: (state, action: PayloadAction<string[]>) => {
|
subscribedNodeIdsSet: (state, action: PayloadAction<string[]>) => {
|
||||||
state.subscribedNodeIds = action.payload;
|
state.subscribedNodeIds = action.payload;
|
||||||
},
|
},
|
||||||
/**
|
// /**
|
||||||
* `shouldTransformUrls` was changed
|
// * `shouldTransformUrls` was changed
|
||||||
*/
|
// */
|
||||||
shouldTransformUrlsChanged: (state, action: PayloadAction<boolean>) => {
|
// shouldTransformUrlsChanged: (state, action: PayloadAction<boolean>) => {
|
||||||
state.shouldTransformUrls = action.payload;
|
// state.shouldTransformUrls = action.payload;
|
||||||
},
|
// },
|
||||||
/**
|
// /**
|
||||||
* `disabledTabs` was changed
|
// * `disabledTabs` was changed
|
||||||
*/
|
// */
|
||||||
disabledTabsChanged: (state, action: PayloadAction<InvokeTabName[]>) => {
|
// disabledTabsChanged: (state, action: PayloadAction<InvokeTabName[]>) => {
|
||||||
state.disabledTabs = action.payload;
|
// state.disabledTabs = action.payload;
|
||||||
},
|
// },
|
||||||
/**
|
// /**
|
||||||
* `disabledFeatures` was changed
|
// * `disabledFeatures` was changed
|
||||||
*/
|
// */
|
||||||
disabledFeaturesChanged: (
|
// disabledFeaturesChanged: (
|
||||||
state,
|
// state,
|
||||||
action: PayloadAction<InvokeAI.ApplicationFeature[]>
|
// action: PayloadAction<InvokeAI.AppFeature[]>
|
||||||
) => {
|
// ) => {
|
||||||
state.disabledFeatures = action.payload;
|
// state.disabledFeatures = action.payload;
|
||||||
},
|
// },
|
||||||
},
|
},
|
||||||
extraReducers(builder) {
|
extraReducers(builder) {
|
||||||
/**
|
/**
|
||||||
@ -601,9 +601,9 @@ export const {
|
|||||||
scheduledCancelAborted,
|
scheduledCancelAborted,
|
||||||
cancelTypeChanged,
|
cancelTypeChanged,
|
||||||
subscribedNodeIdsSet,
|
subscribedNodeIdsSet,
|
||||||
shouldTransformUrlsChanged,
|
// shouldTransformUrlsChanged,
|
||||||
disabledTabsChanged,
|
// disabledTabsChanged,
|
||||||
disabledFeaturesChanged,
|
// disabledFeaturesChanged,
|
||||||
} = systemSlice.actions;
|
} = systemSlice.actions;
|
||||||
|
|
||||||
export default systemSlice.reducer;
|
export default systemSlice.reducer;
|
||||||
|
@ -27,6 +27,7 @@ import GenerateWorkspace from './tabs/Generate/GenerateWorkspace';
|
|||||||
import { FaImage } from 'react-icons/fa';
|
import { FaImage } from 'react-icons/fa';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { BsLightningChargeFill, BsLightningFill } from 'react-icons/bs';
|
import { BsLightningChargeFill, BsLightningFill } from 'react-icons/bs';
|
||||||
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
|
|
||||||
export interface InvokeTabInfo {
|
export interface InvokeTabInfo {
|
||||||
id: InvokeTabName;
|
id: InvokeTabName;
|
||||||
@ -56,14 +57,11 @@ const tabs: InvokeTabInfo[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const enabledTabsSelector = createSelector(
|
const enabledTabsSelector = createSelector(configSelector, (config) => {
|
||||||
(state: RootState) => state.ui,
|
const { disabledTabs } = config;
|
||||||
(ui) => {
|
|
||||||
const { disabledTabs } = ui;
|
|
||||||
|
|
||||||
return tabs.filter((tab) => !disabledTabs.includes(tab.id));
|
return tabs.filter((tab) => !disabledTabs.includes(tab.id));
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export default function InvokeTabs() {
|
export default function InvokeTabs() {
|
||||||
const activeTab = useAppSelector(activeTabIndexSelector);
|
const activeTab = useAppSelector(activeTabIndexSelector);
|
||||||
@ -76,10 +74,6 @@ export default function InvokeTabs() {
|
|||||||
(state: RootState) => state.ui
|
(state: RootState) => state.ui
|
||||||
);
|
);
|
||||||
|
|
||||||
const disabledTabs = useAppSelector(
|
|
||||||
(state: RootState) => state.system.disabledTabs
|
|
||||||
);
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
@ -32,7 +32,7 @@ export default function UnifiedCanvasParameters() {
|
|||||||
name: 'unifiedCanvasImg2Img',
|
name: 'unifiedCanvasImg2Img',
|
||||||
header: `${t('parameters.imageToImage')}`,
|
header: `${t('parameters.imageToImage')}`,
|
||||||
feature: undefined,
|
feature: undefined,
|
||||||
content: <ImageToImageStrength label={t('parameters.img2imgStrength')} />,
|
content: <ImageToImageStrength />,
|
||||||
},
|
},
|
||||||
seed: {
|
seed: {
|
||||||
name: 'seed',
|
name: 'seed',
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import { RootState } from 'app/store';
|
||||||
|
|
||||||
type HotkeysState = {
|
type HotkeysState = {
|
||||||
shift: boolean;
|
shift: boolean;
|
||||||
@ -24,3 +25,5 @@ export const hotkeysSlice = createSlice({
|
|||||||
export const { shiftKeyPressed } = hotkeysSlice.actions;
|
export const { shiftKeyPressed } = hotkeysSlice.actions;
|
||||||
|
|
||||||
export default hotkeysSlice.reducer;
|
export default hotkeysSlice.reducer;
|
||||||
|
|
||||||
|
export const hotkeysSelector = (state: RootState) => state.hotkeys;
|
||||||
|
@ -19,8 +19,6 @@ const initialUIState: UIState = {
|
|||||||
shouldShowGallery: true,
|
shouldShowGallery: true,
|
||||||
shouldHidePreview: false,
|
shouldHidePreview: false,
|
||||||
openLinearAccordionItems: [],
|
openLinearAccordionItems: [],
|
||||||
disabledParameterPanels: [],
|
|
||||||
disabledTabs: [],
|
|
||||||
openGenerateAccordionItems: [],
|
openGenerateAccordionItems: [],
|
||||||
openUnifiedCanvasAccordionItems: [],
|
openUnifiedCanvasAccordionItems: [],
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { InvokeTabName } from './tabMap';
|
||||||
|
|
||||||
export type AddNewModelType = 'ckpt' | 'diffusers' | null;
|
export type AddNewModelType = 'ckpt' | 'diffusers' | null;
|
||||||
|
|
||||||
export interface UIState {
|
export interface UIState {
|
||||||
@ -15,8 +17,6 @@ export interface UIState {
|
|||||||
shouldPinGallery: boolean;
|
shouldPinGallery: boolean;
|
||||||
shouldShowGallery: boolean;
|
shouldShowGallery: boolean;
|
||||||
openLinearAccordionItems: number[];
|
openLinearAccordionItems: number[];
|
||||||
disabledParameterPanels: string[];
|
|
||||||
disabledTabs: InvokeTabName[];
|
|
||||||
openGenerateAccordionItems: number[];
|
openGenerateAccordionItems: number[];
|
||||||
openUnifiedCanvasAccordionItems: number[];
|
openUnifiedCanvasAccordionItems: number[];
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
|
import { AppConfig } from 'app/invokeai';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
|
|
||||||
import Component from './component';
|
import Component from './component';
|
||||||
|
|
||||||
|
const testConfig: Partial<AppConfig> = {
|
||||||
|
disabledTabs: ['nodes'],
|
||||||
|
disabledFeatures: ['upscaling'],
|
||||||
|
};
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
<Component />
|
<Component config={testConfig} />
|
||||||
);
|
);
|
||||||
|
@ -35,7 +35,10 @@ export const invocationStarted = createAction<
|
|||||||
>('socket/invocationStarted');
|
>('socket/invocationStarted');
|
||||||
|
|
||||||
export const invocationComplete = createAction<
|
export const invocationComplete = createAction<
|
||||||
BaseSocketPayload & { data: InvocationCompleteEvent }
|
BaseSocketPayload & {
|
||||||
|
data: InvocationCompleteEvent;
|
||||||
|
shouldFetchImages: boolean;
|
||||||
|
}
|
||||||
>('socket/invocationComplete');
|
>('socket/invocationComplete');
|
||||||
|
|
||||||
export const invocationError = createAction<
|
export const invocationError = createAction<
|
||||||
|
@ -92,9 +92,9 @@ export const socketMiddleware = () => {
|
|||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
dispatch(socketConnected({ timestamp: getTimestamp() }));
|
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
|
// These thunks need to be dispatch in middleware; cannot handle in a reducer
|
||||||
if (!results.ids.length) {
|
if (!results.ids.length) {
|
||||||
@ -203,13 +203,20 @@ export const socketMiddleware = () => {
|
|||||||
const sessionId = data.graph_execution_state_id;
|
const sessionId = data.graph_execution_state_id;
|
||||||
|
|
||||||
const { cancelType, isCancelScheduled } = getState().system;
|
const { cancelType, isCancelScheduled } = getState().system;
|
||||||
|
const { shouldFetchImages } = getState().config;
|
||||||
|
|
||||||
// Handle scheduled cancelation
|
// Handle scheduled cancelation
|
||||||
if (cancelType === 'scheduled' && isCancelScheduled) {
|
if (cancelType === 'scheduled' && isCancelScheduled) {
|
||||||
dispatch(sessionCanceled({ sessionId }));
|
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)) {
|
if (invocationComplete.match(action)) {
|
||||||
const { results } = getState();
|
const { config } = getState();
|
||||||
|
|
||||||
if (results.shouldFetchImages) {
|
if (config.shouldFetchImages) {
|
||||||
const { result } = action.payload.data;
|
const { result } = action.payload.data;
|
||||||
if (isImageOutput(result)) {
|
if (isImageOutput(result)) {
|
||||||
const imageName = result.image.image_name;
|
const imageName = result.image.image_name;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user