Reorganises internal state

`options` slice was huge and managed a mix of generation parameters and general app settings. It has been split up:

- Generation parameters are now in `generationSlice`.
- Postprocessing parameters are now in `postprocessingSlice`
- UI related things are now in `uiSlice`

There is probably more to be done, like `gallerySlice` perhaps should only manage internal gallery state, and not if the gallery is displayed.

Full-slice selectors have been made for each slice.

Other organisational tweaks.
This commit is contained in:
psychedelicious 2023-02-04 11:32:22 +11:00 committed by mauwii
parent 511df2963b
commit f80a64a0f4
No known key found for this signature in database
GPG Key ID: D923DB04ADB3F5AB
179 changed files with 7463 additions and 1165 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,8 +7,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
<<<<<<< HEAD
<script type="module" crossorigin src="./assets/index.f3fa9388.js"></script>
<link rel="stylesheet" href="./assets/index.1536494e.css">
=======
<script type="module" crossorigin src="./assets/index.5c5bc3f1.js"></script>
<link rel="stylesheet" href="./assets/index.8badc8b4.css">
>>>>>>> 64c0b743 (Reorganises internal state)
<script type="module">try{import.meta.url;import("_").catch(()=>1);}catch(e){}window.__vite_is_modern_browser=true;</script>
<script type="module">!function(){if(window.__vite_is_modern_browser)return;console.warn("vite: loading legacy build because dynamic import or import.meta.url is unsupported, syntax error above should be ignored");var e=document.getElementById("vite-legacy-polyfill"),n=document.createElement("script");n.src=e.src,n.onload=function(){System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))},document.body.appendChild(n)}();</script>
</head>
@ -18,6 +23,10 @@
<script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
<script nomodule crossorigin id="vite-legacy-polyfill" src="./assets/polyfills-legacy-dde3a68a.js"></script>
<<<<<<< HEAD
<script nomodule crossorigin id="vite-legacy-entry" data-src="./assets/index-legacy-4add591a.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
=======
<script nomodule crossorigin id="vite-legacy-entry" data-src="./assets/index-legacy-35973932.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
>>>>>>> 64c0b743 (Reorganises internal state)
</body>
</html>

View File

@ -48,8 +48,8 @@
"sendTo": "Send to",
"sendToImg2Img": "Send to Image to Image",
"sendToUnifiedCanvas": "Send To Unified Canvas",
"copyImage": "Copy Image",
"copyImageToLink": "Copy Image To Link",
"copyImage": "Copy Image",
"downloadImage": "Download Image",
"openInViewer": "Open In Viewer",
"closeViewer": "Close Viewer",

View File

@ -2,13 +2,13 @@ import ProgressBar from 'features/system/components/ProgressBar';
import SiteHeader from 'features/system/components/SiteHeader';
import Console from 'features/system/components/Console';
import { keepGUIAlive } from './utils';
import InvokeTabs from 'features/tabs/components/InvokeTabs';
import InvokeTabs from 'features/ui/components/InvokeTabs';
import ImageUploader from 'common/components/ImageUploader';
import useToastWatcher from 'features/system/hooks/useToastWatcher';
import FloatingOptionsPanelButtons from 'features/tabs/components/FloatingOptionsPanelButtons';
import FloatingGalleryButton from 'features/tabs/components/FloatingGalleryButton';
import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons';
import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton';
keepGUIAlive();
@ -27,7 +27,7 @@ const App = () => {
<Console />
</div>
</ImageUploader>
<FloatingOptionsPanelButtons />
<FloatingParametersPanelButtons />
<FloatingGalleryButton />
</div>
);

View File

@ -12,7 +12,7 @@
* 'gfpgan'.
*/
import { InvokeTabName } from 'features/tabs/tabMap';
import { InvokeTabName } from 'features/ui/store/tabMap';
import { IRect } from 'konva/lib/types';
/**

View File

@ -1,32 +1,26 @@
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
import { RootState } from 'app/store';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { OptionsState } from 'features/options/store/optionsSlice';
import { SystemState } from 'features/system/store/systemSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
import { validateSeedWeights } from 'common/util/seedWeightPairs';
import { initialCanvasImageSelector } from 'features/canvas/store/canvasSelectors';
import { generationSelector } from 'features/parameters/store/generationSelectors';
export const readinessSelector = createSelector(
[
(state: RootState) => state.options,
(state: RootState) => state.system,
generationSelector,
systemSelector,
initialCanvasImageSelector,
activeTabNameSelector,
],
(
options: OptionsState,
system: SystemState,
initialCanvasImage,
activeTabName
) => {
(generation, system, initialCanvasImage, activeTabName) => {
const {
prompt,
shouldGenerateVariations,
seedWeights,
initialImage,
seed,
} = options;
} = generation;
const { isProcessing, isConnected } = system;

View File

@ -1,6 +1,6 @@
import { createAction } from '@reduxjs/toolkit';
import { GalleryCategory } from 'features/gallery/store/gallerySlice';
import { InvokeTabName } from 'features/tabs/tabMap';
import { InvokeTabName } from 'features/ui/store/tabMap';
import * as InvokeAI from 'app/invokeai';
/**

View File

@ -10,14 +10,13 @@ import {
GalleryState,
removeImage,
} from 'features/gallery/store/gallerySlice';
import { OptionsState } from 'features/options/store/optionsSlice';
import {
addLogEntry,
generationRequested,
modelChangeRequested,
setIsProcessing,
} from 'features/system/store/systemSlice';
import { InvokeTabName } from 'features/tabs/tabMap';
import { InvokeTabName } from 'features/ui/store/tabMap';
import * as InvokeAI from 'app/invokeai';
import type { RootState } from 'app/store';
@ -39,7 +38,8 @@ const makeSocketIOEmitters = (
const state: RootState = getState();
const {
options: optionsState,
generation: generationState,
postprocessing: postprocessingState,
system: systemState,
canvas: canvasState,
} = state;
@ -47,7 +47,8 @@ const makeSocketIOEmitters = (
const frontendToBackendParametersConfig: FrontendToBackendParametersConfig =
{
generationMode,
optionsState,
generationState,
postprocessingState,
canvasState,
systemState,
};
@ -90,8 +91,11 @@ const makeSocketIOEmitters = (
},
emitRunESRGAN: (imageToProcess: InvokeAI.Image) => {
dispatch(setIsProcessing(true));
const options: OptionsState = getState().options;
const { upscalingLevel, upscalingStrength } = options;
const {
postprocessing: { upscalingLevel, upscalingStrength },
} = getState();
const esrganParameters = {
upscale: [upscalingLevel, upscalingStrength],
};
@ -111,8 +115,10 @@ const makeSocketIOEmitters = (
},
emitRunFacetool: (imageToProcess: InvokeAI.Image) => {
dispatch(setIsProcessing(true));
const options: OptionsState = getState().options;
const { facetoolType, facetoolStrength, codeformerFidelity } = options;
const {
postprocessing: { facetoolType, facetoolStrength, codeformerFidelity },
} = getState();
const facetoolParameters: Record<string, unknown> = {
facetool_strength: facetoolStrength,

View File

@ -35,14 +35,14 @@ import {
setInfillMethod,
setInitialImage,
setMaskPath,
} from 'features/options/store/optionsSlice';
} from 'features/parameters/store/generationSlice';
import {
requestImages,
requestNewImages,
requestSystemConfig,
} from './actions';
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
import { tabMap } from 'features/tabs/tabMap';
import { tabMap } from 'features/ui/store/tabMap';
import type { RootState } from 'app/store';
/**
@ -104,8 +104,9 @@ const makeSocketIOListeners = (
*/
onGenerationResult: (data: InvokeAI.ImageResultResponse) => {
try {
const state: RootState = getState();
const { shouldLoopback, activeTab } = state.options;
const state = getState();
const { activeTab } = state.ui;
const { shouldLoopback } = state.postprocessing;
const { boundingBox: _, generationMode, ...rest } = data;
const newImage = {
@ -327,7 +328,9 @@ const makeSocketIOListeners = (
dispatch(removeImage(data));
// remove references to image in options
const { initialImage, maskPath } = getState().options;
const {
generation: { initialImage, maskPath },
} = getState();
if (
initialImage === url ||

View File

@ -5,10 +5,13 @@ import storage from 'redux-persist/lib/storage'; // defaults to localStorage for
import { getPersistConfig } from 'redux-deep-persist';
import optionsReducer from 'features/options/store/optionsSlice';
import generationReducer from 'features/parameters/store/generationSlice';
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
import galleryReducer from 'features/gallery/store/gallerySlice';
import systemReducer from 'features/system/store/systemSlice';
import canvasReducer from 'features/canvas/store/canvasSlice';
import uiReducer from 'features/ui/store/uiSlice';
import lightboxReducer from 'features/lightbox/store/lightboxSlice';
import { socketioMiddleware } from './socketio/middleware';
@ -58,10 +61,13 @@ const galleryBlacklist = [
].map((blacklistItem) => `gallery.${blacklistItem}`);
const rootReducer = combineReducers({
options: optionsReducer,
generation: generationReducer,
postprocessing: postprocessingReducer,
gallery: galleryReducer,
system: systemReducer,
canvas: canvasReducer,
ui: uiReducer,
lightbox: lightboxReducer,
});
const rootPersistConfig = getPersistConfig({
@ -89,8 +95,8 @@ export const store = configureStore({
'canvas/setStageCoordinates',
'canvas/setStageScale',
'canvas/setIsDrawing',
// 'canvas/setBoundingBoxCoordinates',
// 'canvas/setBoundingBoxDimensions',
'canvas/setBoundingBoxCoordinates',
'canvas/setBoundingBoxDimensions',
'canvas/setIsDrawing',
'canvas/addPointToCurrentLine',
],

View File

@ -7,23 +7,23 @@ import {
} from '@chakra-ui/react';
import { SystemState } from 'features/system/store/systemSlice';
import { useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store';
import { createSelector } from '@reduxjs/toolkit';
import { ReactElement } from 'react';
import { Feature, useFeatureHelpInfo } from 'app/features';
import { systemSelector } from 'features/system/store/systemSelectors';
type GuideProps = {
children: ReactElement;
feature: Feature;
};
const systemSelector = createSelector(
(state: RootState) => state.system,
const guidePopoverSelector = createSelector(
systemSelector,
(system: SystemState) => system.shouldDisplayGuides
);
const GuidePopover = ({ children, feature }: GuideProps) => {
const shouldDisplayGuides = useAppSelector(systemSelector);
const shouldDisplayGuides = useAppSelector(guidePopoverSelector);
const { text } = useFeatureHelpInfo(feature);
if (!shouldDisplayGuides) return null;

View File

@ -9,8 +9,8 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { FileRejection, useDropzone } from 'react-dropzone';
import { useToast } from '@chakra-ui/react';
import { ImageUploaderTriggerContext } from 'app/contexts/ImageUploaderTriggerContext';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { tabDict } from 'features/tabs/components/InvokeTabs';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { tabDict } from 'features/ui/components/InvokeTabs';
import ImageUploadOverlay from './ImageUploadOverlay';
import { uploadImage } from 'features/gallery/store/thunks/uploadImage';
import useImageUploader from 'common/hooks/useImageUploader';

View File

@ -1,12 +1,12 @@
import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants';
import { OptionsState } from 'features/options/store/optionsSlice';
import { GenerationState } from 'features/parameters/store/generationSlice';
import { SystemState } from 'features/system/store/systemSlice';
import { Vector2d } from 'konva/lib/types';
import { Dimensions } from 'features/canvas/store/canvasTypes';
import { stringToSeedWeightsArray } from './seedWeightPairs';
import randomInt from './randomInt';
import { InvokeTabName } from 'features/tabs/tabMap';
import { InvokeTabName } from 'features/ui/store/tabMap';
import {
CanvasState,
isCanvasMaskLine,
@ -17,11 +17,13 @@ import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import type {
UpscalingLevel,
FacetoolType,
} from 'features/options/store/optionsSlice';
} from 'features/parameters/store/postprocessingSlice';
import { PostprocessingState } from 'features/parameters/store/postprocessingSlice';
export type FrontendToBackendParametersConfig = {
generationMode: InvokeTabName;
optionsState: OptionsState;
generationState: GenerationState;
postprocessingState: PostprocessingState;
canvasState: CanvasState;
systemState: SystemState;
imageToProcessUrl?: string;
@ -91,16 +93,31 @@ export const frontendToBackendParameters = (
): BackendParameters => {
const canvasBaseLayer = getCanvasBaseLayer();
const { generationMode, optionsState, canvasState, systemState } = config;
const {
generationMode,
generationState,
postprocessingState,
canvasState,
systemState,
} = config;
const {
cfgScale,
codeformerFidelity,
facetoolStrength,
facetoolType,
height,
hiresFix,
hiresStrength,
shouldRunESRGAN,
shouldRunFacetool,
upscalingLevel,
upscalingStrength,
} = postprocessingState;
const {
cfgScale,
height,
img2imgStrength,
infillMethod,
initialImage,
@ -119,16 +136,14 @@ export const frontendToBackendParameters = (
shouldFitToWidthHeight,
shouldGenerateVariations,
shouldRandomizeSeed,
shouldRunESRGAN,
shouldRunFacetool,
steps,
threshold,
tileSize,
upscalingLevel,
upscalingStrength,
variationAmount,
width,
} = optionsState;
} = generationState;
const {
shouldDisplayInProgressType,

View File

@ -1,7 +1,7 @@
import { Spinner } from '@chakra-ui/react';
import { useLayoutEffect, useRef } from 'react';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import {
resizeAndScaleCanvas,
resizeCanvas,

View File

@ -3,7 +3,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { FaRedo } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import _ from 'lodash';

View File

@ -6,7 +6,7 @@ import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import _ from 'lodash';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { undo } from 'features/canvas/store/canvasSlice';
import { systemSelector } from 'features/system/store/systemSelectors';
import { useTranslation } from 'react-i18next';

View File

@ -1,7 +1,7 @@
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
import { useHotkeys } from 'react-hotkeys-hook';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import {
clearMask,
resetCanvasInteractionState,

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node';
import _ from 'lodash';

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import Konva from 'konva';
import { Vector2d } from 'konva/lib/types';
import _ from 'lodash';

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import Konva from 'konva';
import _ from 'lodash';
import { MutableRefObject, useCallback } from 'react';

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
import { CanvasImage, CanvasState, isCanvasBaseImage } from './canvasTypes';

View File

@ -1,28 +1,42 @@
import { createSelector } from '@reduxjs/toolkit';
import { isEqual } from 'lodash';
import { ButtonGroup, Link, useToast } from '@chakra-ui/react';
import { runESRGAN, runFacetool } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { RootState } from 'app/store';
import IAIButton from 'common/components/IAIButton';
import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover';
import { getPromptAndNegative } from 'common/util/getPromptAndNegative';
import {
setDoesCanvasNeedScaling,
setInitialCanvasImage,
} from 'features/canvas/store/canvasSlice';
import { GalleryState } from 'features/gallery/store/gallerySlice';
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
import FaceRestoreSettings from 'features/parameters/components/AdvancedParameters/FaceRestore/FaceRestoreSettings';
import UpscaleSettings from 'features/parameters/components/AdvancedParameters/Upscale/UpscaleSettings';
import {
OptionsState,
setActiveTab,
setAllParameters,
setInitialImage,
setIsLightBoxOpen,
setNegativePrompt,
setPrompt,
setSeed,
setShouldShowImageDetails,
} from 'features/options/store/optionsSlice';
import DeleteImageModal from './DeleteImageModal';
} from 'features/parameters/store/generationSlice';
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
import { SystemState } from 'features/system/store/systemSlice';
import IAIButton from 'common/components/IAIButton';
import { runESRGAN, runFacetool } from 'app/socketio/actions';
import IAIIconButton from 'common/components/IAIIconButton';
import UpscaleOptions from 'features/options/components/AdvancedOptions/Upscale/UpscaleOptions';
import FaceRestoreOptions from 'features/options/components/AdvancedOptions/FaceRestore/FaceRestoreOptions';
import {
activeTabNameSelector,
uiSelector,
} from 'features/ui/store/uiSelectors';
import {
setActiveTab,
setShouldShowImageDetails,
} from 'features/ui/store/uiSlice';
import { useHotkeys } from 'react-hotkeys-hook';
import { ButtonGroup, Link, useToast } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import {
FaAsterisk,
FaCode,
@ -37,38 +51,34 @@ import {
FaShareAlt,
FaTrash,
} from 'react-icons/fa';
import {
setDoesCanvasNeedScaling,
setInitialCanvasImage,
} from 'features/canvas/store/canvasSlice';
import { GalleryState } from 'features/gallery/store/gallerySlice';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import IAIPopover from 'common/components/IAIPopover';
import { useTranslation } from 'react-i18next';
import { getPromptAndNegative } from 'common/util/getPromptAndNegative';
import { gallerySelector } from '../store/gallerySelectors';
import DeleteImageModal from './DeleteImageModal';
const systemSelector = createSelector(
const currentImageButtonsSelector = createSelector(
[
(state: RootState) => state.system,
(state: RootState) => state.options,
(state: RootState) => state.gallery,
systemSelector,
gallerySelector,
postprocessingSelector,
uiSelector,
lightboxSelector,
activeTabNameSelector,
],
(
system: SystemState,
options: OptionsState,
gallery: GalleryState,
postprocessing,
ui,
lightbox,
activeTabName
) => {
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
system;
const {
upscalingLevel,
facetoolStrength,
shouldShowImageDetails,
isLightBoxOpen,
} = options;
const { upscalingLevel, facetoolStrength } = postprocessing;
const { isLightboxOpen } = lightbox;
const { shouldShowImageDetails } = ui;
const { intermediateImage, currentImage } = gallery;
@ -83,7 +93,7 @@ const systemSelector = createSelector(
currentImage,
shouldShowImageDetails,
activeTabName,
isLightBoxOpen,
isLightboxOpen,
};
},
{
@ -109,36 +119,20 @@ const CurrentImageButtons = () => {
shouldDisableToolbarButtons,
shouldShowImageDetails,
currentImage,
isLightBoxOpen,
isLightboxOpen,
activeTabName,
} = useAppSelector(systemSelector);
} = useAppSelector(currentImageButtonsSelector);
const toast = useToast();
const { t } = useTranslation();
const handleClickUseAsInitialImage = () => {
if (!currentImage) return;
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
if (isLightboxOpen) dispatch(setIsLightboxOpen(false));
dispatch(setInitialImage(currentImage));
dispatch(setActiveTab('img2img'));
};
const handleCopyImage = async () => {
if (!currentImage) return;
const blob = await fetch(currentImage.url).then((res) => res.blob());
const data = [new ClipboardItem({ [blob.type]: blob })];
await navigator.clipboard.write(data);
toast({
title: t('toast:imageCopied'),
status: 'success',
duration: 2500,
isClosable: true,
});
};
const handleCopyImageLink = () => {
navigator.clipboard
.writeText(
@ -353,7 +347,7 @@ const CurrentImageButtons = () => {
const handleSendToCanvas = () => {
if (!currentImage) return;
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
if (isLightboxOpen) dispatch(setIsLightboxOpen(false));
dispatch(setInitialCanvasImage(currentImage));
dispatch(setDoesCanvasNeedScaling(true));
@ -388,7 +382,7 @@ const CurrentImageButtons = () => {
);
const handleLightBox = () => {
dispatch(setIsLightBoxOpen(!isLightBoxOpen));
dispatch(setIsLightboxOpen(!isLightboxOpen));
};
return (
@ -398,7 +392,7 @@ const CurrentImageButtons = () => {
trigger="hover"
triggerComponent={
<IAIIconButton
aria-label={`${t('options:sendTo')}...`}
aria-label={`${t('parameters:sendTo')}...`}
icon={<FaShareAlt />}
/>
}
@ -409,34 +403,26 @@ const CurrentImageButtons = () => {
onClick={handleClickUseAsInitialImage}
leftIcon={<FaShare />}
>
{t('options:sendToImg2Img')}
{t('parameters:sendToImg2Img')}
</IAIButton>
<IAIButton
size={'sm'}
onClick={handleSendToCanvas}
leftIcon={<FaShare />}
>
{t('options:sendToUnifiedCanvas')}
</IAIButton>
<IAIButton
size={'sm'}
onClick={handleCopyImage}
leftIcon={<FaCopy />}
>
{t('options:copyImage')}
{t('parameters:sendToUnifiedCanvas')}
</IAIButton>
<IAIButton
size={'sm'}
onClick={handleCopyImageLink}
leftIcon={<FaCopy />}
>
{t('options:copyImageToLink')}
{t('parameters:copyImageToLink')}
</IAIButton>
<Link download={true} href={currentImage?.url}>
<IAIButton leftIcon={<FaDownload />} size={'sm'} w="100%">
{t('options:downloadImage')}
{t('parameters:downloadImage')}
</IAIButton>
</Link>
</div>
@ -444,16 +430,16 @@ const CurrentImageButtons = () => {
<IAIIconButton
icon={<FaExpand />}
tooltip={
!isLightBoxOpen
? `${t('options:openInViewer')} (Z)`
: `${t('options:closeViewer')} (Z)`
!isLightboxOpen
? `${t('parameters:openInViewer')} (Z)`
: `${t('parameters:closeViewer')} (Z)`
}
aria-label={
!isLightBoxOpen
? `${t('options:openInViewer')} (Z)`
: `${t('options:closeViewer')} (Z)`
!isLightboxOpen
? `${t('parameters:openInViewer')} (Z)`
: `${t('parameters:closeViewer')} (Z)`
}
data-selected={isLightBoxOpen}
data-selected={isLightboxOpen}
onClick={handleLightBox}
/>
</ButtonGroup>
@ -461,24 +447,24 @@ const CurrentImageButtons = () => {
<ButtonGroup isAttached={true}>
<IAIIconButton
icon={<FaQuoteRight />}
tooltip={`${t('options:usePrompt')} (P)`}
aria-label={`${t('options:usePrompt')} (P)`}
tooltip={`${t('parameters:usePrompt')} (P)`}
aria-label={`${t('parameters:usePrompt')} (P)`}
isDisabled={!currentImage?.metadata?.image?.prompt}
onClick={handleClickUsePrompt}
/>
<IAIIconButton
icon={<FaSeedling />}
tooltip={`${t('options:useSeed')} (S)`}
aria-label={`${t('options:useSeed')} (S)`}
tooltip={`${t('parameters:useSeed')} (S)`}
aria-label={`${t('parameters:useSeed')} (S)`}
isDisabled={!currentImage?.metadata?.image?.seed}
onClick={handleClickUseSeed}
/>
<IAIIconButton
icon={<FaAsterisk />}
tooltip={`${t('options:useAll')} (A)`}
aria-label={`${t('options:useAll')} (A)`}
tooltip={`${t('parameters:useAll')} (A)`}
aria-label={`${t('parameters:useAll')} (A)`}
isDisabled={
!['txt2img', 'img2img'].includes(
currentImage?.metadata?.image?.type
@ -494,12 +480,12 @@ const CurrentImageButtons = () => {
triggerComponent={
<IAIIconButton
icon={<FaGrinStars />}
aria-label={t('options:restoreFaces')}
aria-label={t('parameters:restoreFaces')}
/>
}
>
<div className="current-image-postprocessing-popover">
<FaceRestoreOptions />
<FaceRestoreSettings />
<IAIButton
isDisabled={
!isGFPGANAvailable ||
@ -509,7 +495,7 @@ const CurrentImageButtons = () => {
}
onClick={handleClickFixFaces}
>
{t('options:restoreFaces')}
{t('parameters:restoreFaces')}
</IAIButton>
</div>
</IAIPopover>
@ -519,12 +505,12 @@ const CurrentImageButtons = () => {
triggerComponent={
<IAIIconButton
icon={<FaExpandArrowsAlt />}
aria-label={t('options:upscale')}
aria-label={t('parameters:upscale')}
/>
}
>
<div className="current-image-postprocessing-popover">
<UpscaleOptions />
<UpscaleSettings />
<IAIButton
isDisabled={
!isESRGANAvailable ||
@ -534,7 +520,7 @@ const CurrentImageButtons = () => {
}
onClick={handleClickUpscale}
>
{t('options:upscaleImage')}
{t('parameters:upscaleImage')}
</IAIButton>
</div>
</IAIPopover>
@ -543,8 +529,8 @@ const CurrentImageButtons = () => {
<ButtonGroup isAttached={true}>
<IAIIconButton
icon={<FaCode />}
tooltip={`${t('options:info')} (I)`}
aria-label={`${t('options:info')} (I)`}
tooltip={`${t('parameters:info')} (I)`}
aria-label={`${t('parameters:info')} (I)`}
data-selected={shouldShowImageDetails}
onClick={handleClickShowImageDetails}
/>
@ -553,8 +539,8 @@ const CurrentImageButtons = () => {
<DeleteImageModal image={currentImage}>
<IAIIconButton
icon={<FaTrash />}
tooltip={`${t('options:deleteImage')} (Del)`}
aria-label={`${t('options:deleteImage')} (Del)`}
tooltip={`${t('parameters:deleteImage')} (Del)`}
aria-label={`${t('parameters:deleteImage')} (Del)`}
isDisabled={!currentImage || !isConnected || isProcessing}
style={{ backgroundColor: 'var(--btn-delete-image)' }}
/>

View File

@ -1,23 +1,21 @@
import { RootState } from 'app/store';
import { useAppSelector } from 'app/storeHooks';
import CurrentImageButtons from './CurrentImageButtons';
import { MdPhoto } from 'react-icons/md';
import CurrentImagePreview from './CurrentImagePreview';
import { GalleryState } from 'features/gallery/store/gallerySlice';
import { OptionsState } from 'features/options/store/optionsSlice';
import _ from 'lodash';
import { createSelector } from '@reduxjs/toolkit';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import {
activeTabNameSelector,
uiSelector,
} from 'features/ui/store/uiSelectors';
import { gallerySelector } from '../store/gallerySelectors';
export const currentImageDisplaySelector = createSelector(
[
(state: RootState) => state.gallery,
(state: RootState) => state.options,
activeTabNameSelector,
],
(gallery: GalleryState, options: OptionsState, activeTabName) => {
[gallerySelector, uiSelector, activeTabNameSelector],
(gallery: GalleryState, ui, activeTabName) => {
const { currentImage, intermediateImage } = gallery;
const { shouldShowImageDetails } = options;
const { shouldShowImageDetails } = ui;
return {
activeTabName,

View File

@ -1,7 +1,6 @@
import { IconButton, Image } from '@chakra-ui/react';
import { useState } from 'react';
import { FaAngleLeft, FaAngleRight } from 'react-icons/fa';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import {
GalleryCategory,
@ -11,14 +10,15 @@ import {
} from 'features/gallery/store/gallerySlice';
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
import { OptionsState } from 'features/options/store/optionsSlice';
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
import { uiSelector } from 'features/ui/store/uiSelectors';
import { gallerySelector } from '../store/gallerySelectors';
export const imagesSelector = createSelector(
[(state: RootState) => state.gallery, (state: RootState) => state.options],
(gallery: GalleryState, options: OptionsState) => {
[gallerySelector, uiSelector],
(gallery: GalleryState, ui) => {
const { currentCategory, currentImage, intermediateImage } = gallery;
const { shouldShowImageDetails } = options;
const { shouldShowImageDetails } = ui;
const tempImages =
gallery.categories[

View File

@ -18,7 +18,6 @@ import { ChangeEvent, ReactElement, SyntheticEvent } from 'react';
import { cloneElement, forwardRef, useRef } from 'react';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { deleteImage } from 'app/socketio/actions';
import { RootState } from 'app/store';
import {
setShouldConfirmOnDelete,
SystemState,
@ -26,9 +25,10 @@ import {
import * as InvokeAI from 'app/invokeai';
import { useHotkeys } from 'react-hotkeys-hook';
import _ from 'lodash';
import { systemSelector } from 'features/system/store/systemSelectors';
const systemSelector = createSelector(
(state: RootState) => state.system,
const deleteImageModalSelector = createSelector(
systemSelector,
(system: SystemState) => {
const { shouldConfirmOnDelete, isConnected, isProcessing } = system;
return { shouldConfirmOnDelete, isConnected, isProcessing };
@ -60,8 +60,9 @@ const DeleteImageModal = forwardRef(
({ image, children }: DeleteImageModalProps, ref) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const dispatch = useAppDispatch();
const { shouldConfirmOnDelete, isConnected, isProcessing } =
useAppSelector(systemSelector);
const { shouldConfirmOnDelete, isConnected, isProcessing } = useAppSelector(
deleteImageModalSelector
);
const cancelRef = useRef<HTMLButtonElement>(null);
const handleClickDelete = (e: SyntheticEvent) => {

View File

@ -4,28 +4,28 @@ import {
setCurrentImage,
setShouldHoldGalleryOpen,
} from 'features/gallery/store/gallerySlice';
import { FaCheck, FaTrashAlt } from 'react-icons/fa';
import DeleteImageModal from './DeleteImageModal';
import { DragEvent, memo, useState } from 'react';
import {
setActiveTab,
setAllImageToImageParameters,
setAllParameters,
setInitialImage,
setIsLightBoxOpen,
setNegativePrompt,
setPrompt,
setSeed,
} from 'features/options/store/optionsSlice';
import * as InvokeAI from 'app/invokeai';
} from 'features/parameters/store/generationSlice';
import { DragEvent, memo, useState } from 'react';
import { FaCheck, FaTrashAlt } from 'react-icons/fa';
import DeleteImageModal from './DeleteImageModal';
import * as ContextMenu from '@radix-ui/react-context-menu';
import * as InvokeAI from 'app/invokeai';
import { getPromptAndNegative } from 'common/util/getPromptAndNegative';
import {
resizeAndScaleCanvas,
setInitialCanvasImage,
} from 'features/canvas/store/canvasSlice';
import { hoverableImageSelector } from 'features/gallery/store/gallerySliceSelectors';
import { hoverableImageSelector } from 'features/gallery/store/gallerySelectors';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { useTranslation } from 'react-i18next';
import { getPromptAndNegative } from 'common/util/getPromptAndNegative';
interface HoverableImageProps {
image: InvokeAI.Image;
@ -47,7 +47,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
galleryImageObjectFit,
galleryImageMinimumWidth,
mayDeleteImage,
isLightBoxOpen,
shouldUseSingleGalleryColumn,
} = useAppSelector(hoverableImageSelector);
const { image, isSelected } = props;
@ -94,7 +93,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
};
const handleSendToImageToImage = () => {
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
dispatch(setInitialImage(image));
if (activeTabName !== 'img2img') {
dispatch(setActiveTab('img2img'));
@ -108,8 +106,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
};
const handleSendToCanvas = () => {
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
dispatch(setInitialCanvasImage(image));
dispatch(resizeAndScaleCanvas());
@ -168,7 +164,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
};
const handleLightBox = () => {
dispatch(setIsLightBoxOpen(true));
dispatch(setCurrentImage(image));
};
@ -212,7 +207,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
<div className="hoverable-image-delete-button">
<DeleteImageModal image={image}>
<IconButton
aria-label={t('options:deleteImage')}
aria-label={t('parameters:deleteImage')}
icon={<FaTrashAlt />}
size="xs"
variant={'imageHoverIconButton'}
@ -232,20 +227,20 @@ const HoverableImage = memo((props: HoverableImageProps) => {
}}
>
<ContextMenu.Item onClickCapture={handleLightBox}>
{t('options:openInViewer')}
{t('parameters:openInViewer')}
</ContextMenu.Item>
<ContextMenu.Item
onClickCapture={handleUsePrompt}
disabled={image?.metadata?.image?.prompt === undefined}
>
{t('options:usePrompt')}
{t('parameters:usePrompt')}
</ContextMenu.Item>
<ContextMenu.Item
onClickCapture={handleUseSeed}
disabled={image?.metadata?.image?.seed === undefined}
>
{t('options:useSeed')}
{t('parameters:useSeed')}
</ContextMenu.Item>
<ContextMenu.Item
onClickCapture={handleUseAllParameters}
@ -253,23 +248,23 @@ const HoverableImage = memo((props: HoverableImageProps) => {
!['txt2img', 'img2img'].includes(image?.metadata?.image?.type)
}
>
{t('options:useAll')}
{t('parameters:useAll')}
</ContextMenu.Item>
<ContextMenu.Item
onClickCapture={handleUseInitialImage}
disabled={image?.metadata?.image?.type !== 'img2img'}
>
{t('options:useInitImg')}
{t('parameters:useInitImg')}
</ContextMenu.Item>
<ContextMenu.Item onClickCapture={handleSendToImageToImage}>
{t('options:sendToImg2Img')}
{t('parameters:sendToImg2Img')}
</ContextMenu.Item>
<ContextMenu.Item onClickCapture={handleSendToCanvas}>
{t('options:sendToUnifiedCanvas')}
{t('parameters:sendToUnifiedCanvas')}
</ContextMenu.Item>
<ContextMenu.Item data-warning>
<DeleteImageModal image={image}>
<p>{t('options:deleteImage')}</p>
<p>{t('parameters:deleteImage')}</p>
</DeleteImageModal>
</ContextMenu.Item>
</ContextMenu.Content>

View File

@ -32,7 +32,7 @@ import { setShouldShowGallery } from 'features/gallery/store/gallerySlice';
import { ButtonGroup } from '@chakra-ui/react';
import { CSSTransition } from 'react-transition-group';
import { Direction } from 're-resizable/lib/resizer';
import { imageGallerySelector } from 'features/gallery/store/gallerySliceSelectors';
import { imageGallerySelector } from 'features/gallery/store/gallerySelectors';
import { FaImage, FaUser, FaWrench } from 'react-icons/fa';
import IAIPopover from 'common/components/IAIPopover';
import IAISlider from 'common/components/IAISlider';
@ -41,7 +41,7 @@ import IAICheckbox from 'common/components/IAICheckbox';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
import _ from 'lodash';
import IAIButton from 'common/components/IAIButton';
import { InvokeTabName } from 'features/tabs/tabMap';
import { InvokeTabName } from 'features/ui/store/tabMap';
import { useTranslation } from 'react-i18next';
const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 320;
@ -81,13 +81,13 @@ export default function ImageGallery() {
shouldAutoSwitchToNewImages,
areMoreImagesAvailable,
galleryWidth,
isLightBoxOpen,
isLightboxOpen,
isStaging,
shouldEnableResize,
shouldUseSingleGalleryColumn,
} = useAppSelector(imageGallerySelector);
const { galleryMinWidth, galleryMaxWidth } = isLightBoxOpen
const { galleryMinWidth, galleryMaxWidth } = isLightboxOpen
? {
galleryMinWidth: LIGHTBOX_GALLERY_WIDTH,
galleryMaxWidth: LIGHTBOX_GALLERY_WIDTH,

View File

@ -14,11 +14,7 @@ import { useAppDispatch } from 'app/storeHooks';
import * as InvokeAI from 'app/invokeai';
import {
setCfgScale,
setFacetoolStrength,
setCodeformerFidelity,
setFacetoolType,
setHeight,
setHiresFix,
setImg2imgStrength,
setMaskPath,
setPrompt,
@ -28,14 +24,20 @@ import {
setSeedWeights,
setShouldFitToWidthHeight,
setSteps,
setUpscalingLevel,
setUpscalingStrength,
setWidth,
setInitialImage,
setShouldShowImageDetails,
setThreshold,
setPerlin,
} from 'features/options/store/optionsSlice';
} from 'features/parameters/store/generationSlice';
import {
setFacetoolStrength,
setCodeformerFidelity,
setFacetoolType,
setHiresFix,
setUpscalingLevel,
setUpscalingStrength,
} from 'features/parameters/store/postprocessingSlice';
import { setShouldShowImageDetails } from 'features/ui/store/uiSlice';
import promptToString from 'common/util/promptToString';
import { seedWeightsToString } from 'common/util/seedWeightPairs';
import { FaCopy } from 'react-icons/fa';

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks';
import { gallerySelector } from '../store/gallerySliceSelectors';
import { gallerySelector } from '../store/gallerySelectors';
const selector = createSelector(gallerySelector, (gallery) => ({
resultImages: gallery.categories.result.images,

View File

@ -1,20 +1,17 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { OptionsState } from 'features/options/store/optionsSlice';
import { SystemState } from 'features/system/store/systemSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { GalleryState } from './gallerySlice';
import _ from 'lodash';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
export const gallerySelector = (state: RootState) => state.gallery;
export const imageGallerySelector = createSelector(
[
(state: RootState) => state.gallery,
(state: RootState) => state.options,
isStagingSelector,
activeTabNameSelector,
],
(gallery: GalleryState, options: OptionsState, isStaging, activeTabName) => {
[gallerySelector, lightboxSelector, isStagingSelector, activeTabNameSelector],
(gallery: GalleryState, lightbox, isStaging, activeTabName) => {
const {
categories,
currentCategory,
@ -30,7 +27,7 @@ export const imageGallerySelector = createSelector(
shouldUseSingleGalleryColumn,
} = gallery;
const { isLightBoxOpen } = options;
const { isLightboxOpen } = lightbox;
return {
currentImageUuid,
@ -50,10 +47,10 @@ export const imageGallerySelector = createSelector(
categories[currentCategory].areMoreImagesAvailable,
currentCategory,
galleryWidth,
isLightBoxOpen,
isLightboxOpen,
isStaging,
shouldEnableResize:
isLightBoxOpen ||
isLightboxOpen ||
(activeTabName === 'unifiedCanvas' && shouldPinGallery)
? false
: true,
@ -68,25 +65,15 @@ export const imageGallerySelector = createSelector(
);
export const hoverableImageSelector = createSelector(
[
(state: RootState) => state.options,
(state: RootState) => state.gallery,
(state: RootState) => state.system,
activeTabNameSelector,
],
(
options: OptionsState,
gallery: GalleryState,
system: SystemState,
activeTabName
) => {
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
(gallery: GalleryState, system, lightbox, activeTabName) => {
return {
mayDeleteImage: system.isConnected && !system.isProcessing,
galleryImageObjectFit: gallery.galleryImageObjectFit,
galleryImageMinimumWidth: gallery.galleryImageMinimumWidth,
shouldUseSingleGalleryColumn: gallery.shouldUseSingleGalleryColumn,
activeTabName,
isLightBoxOpen: options.isLightBoxOpen,
isLightboxOpen: lightbox.isLightboxOpen,
};
},
{
@ -95,5 +82,3 @@ export const hoverableImageSelector = createSelector(
},
}
);
export const gallerySelector = (state: RootState) => state.gallery;

View File

@ -3,7 +3,7 @@ import type { PayloadAction } from '@reduxjs/toolkit';
import _, { clamp } from 'lodash';
import * as InvokeAI from 'app/invokeai';
import { IRect } from 'konva/lib/types';
import { InvokeTabName } from 'features/tabs/tabMap';
import { InvokeTabName } from 'features/ui/store/tabMap';
export type GalleryCategory = 'user' | 'result';
@ -241,6 +241,7 @@ export const gallerySlice = createSlice({
setShouldShowGallery: (state, action: PayloadAction<boolean>) => {
state.shouldShowGallery = action.payload;
},
setGalleryScrollPosition: (state, action: PayloadAction<number>) => {
state.galleryScrollPosition = action.payload;
},

View File

@ -2,9 +2,9 @@ import { AnyAction, ThunkAction } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import * as InvokeAI from 'app/invokeai';
import { v4 as uuidv4 } from 'uuid';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { setInitialImage } from 'features/options/store/optionsSlice';
import { setInitialImage } from 'features/parameters/store/generationSlice';
import { addImage } from '../gallerySlice';
type UploadImageConfig = {

View File

@ -10,7 +10,7 @@ import {
} from 'features/gallery/store/gallerySlice';
import ImageGallery from 'features/gallery/components/ImageGallery';
import ImageMetadataViewer from 'features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer';
import { setIsLightBoxOpen } from 'features/options/store/optionsSlice';
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
import React, { useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { BiExit } from 'react-icons/bi';
@ -20,7 +20,7 @@ import ReactPanZoom from './ReactPanZoom';
export default function Lightbox() {
const dispatch = useAppDispatch();
const isLightBoxOpen = useAppSelector(
(state: RootState) => state.options.isLightBoxOpen
(state: RootState) => state.lightbox.isLightboxOpen
);
const {
@ -52,7 +52,7 @@ export default function Lightbox() {
useHotkeys(
'Esc',
() => {
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
if (isLightBoxOpen) dispatch(setIsLightboxOpen(false));
},
[isLightBoxOpen]
);
@ -64,7 +64,7 @@ export default function Lightbox() {
aria-label="Exit Viewer"
className="lightbox-close-btn"
onClick={() => {
dispatch(setIsLightBoxOpen(false));
dispatch(setIsLightboxOpen(false));
}}
fontSize={20}
/>

View File

@ -0,0 +1,13 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import _ from 'lodash';
export const lightboxSelector = createSelector(
(state: RootState) => state.lightbox,
(lightbox) => lightbox,
{
memoizeOptions: {
equalityCheck: _.isEqual,
},
}
);

View File

@ -0,0 +1,26 @@
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
export interface LightboxState {
isLightboxOpen: boolean;
}
const initialLightboxState: LightboxState = {
isLightboxOpen: false,
};
const initialState: LightboxState = initialLightboxState;
export const lightboxSlice = createSlice({
name: 'lightbox',
initialState,
reducers: {
setIsLightboxOpen: (state, action: PayloadAction<boolean>) => {
state.isLightboxOpen = action.payload;
},
},
});
export const { setIsLightboxOpen } = lightboxSlice.actions;
export default lightboxSlice.reducer;

View File

@ -1,12 +0,0 @@
import { Flex } from '@chakra-ui/react';
import SeamlessOptions from './SeamlessOptions';
const ImageToImageOutputOptions = () => {
return (
<Flex gap={2} direction={'column'}>
<SeamlessOptions />
</Flex>
);
};
export default ImageToImageOutputOptions;

View File

@ -1,14 +0,0 @@
import { Flex } from '@chakra-ui/react';
import HiresOptions from './HiresOptions';
import SeamlessOptions from './SeamlessOptions';
const OutputOptions = () => {
return (
<Flex gap={2} direction={'column'}>
<SeamlessOptions />
<HiresOptions />
</Flex>
);
};
export default OutputOptions;

View File

@ -1,24 +0,0 @@
import React, { ChangeEvent } from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShowAdvancedOptions } from 'features/options/store/optionsSlice';
export default function MainAdvancedOptionsCheckbox() {
const showAdvancedOptions = useAppSelector(
(state: RootState) => state.options.showAdvancedOptions
);
const dispatch = useAppDispatch();
const handleShowAdvancedOptions = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShowAdvancedOptions(e.target.checked));
return (
<IAICheckbox
label="Advanced Options"
styleClass="advanced-options-checkbox"
onChange={handleShowAdvancedOptions}
isChecked={showAdvancedOptions}
/>
);
}

View File

@ -1,32 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
import { RootState } from 'app/store';
import { tabMap } from 'features/tabs/tabMap';
import { OptionsState } from './optionsSlice';
export const activeTabNameSelector = createSelector(
(state: RootState) => state.options,
(options: OptionsState) => tabMap[options.activeTab],
{
memoizeOptions: {
equalityCheck: _.isEqual,
},
}
);
export const mayGenerateMultipleImagesSelector = createSelector(
(state: RootState) => state.options,
(options: OptionsState) => {
const { shouldRandomizeSeed, shouldGenerateVariations } = options;
return shouldRandomizeSeed || shouldGenerateVariations;
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export const optionsSelector = (state: RootState): OptionsState =>
state.options;

View File

@ -1,12 +1,12 @@
@use '../../../../styles/Mixins/' as *;
.advanced-settings {
.advanced-parameters {
padding-top: 0.5rem;
display: grid;
row-gap: 0.5rem;
}
.advanced-settings-item {
.advanced-parameters-item {
display: grid;
max-width: $options-bar-max-width;
border: none;
@ -20,7 +20,7 @@
}
}
.advanced-settings-panel {
.advanced-parameters-panel {
background-color: var(--tab-panel-bg);
border-radius: 0 0 0.4rem 0.4rem;
padding: 1rem;
@ -39,7 +39,7 @@
}
}
.advanced-settings-header {
.advanced-parameters-header {
border-radius: 0.4rem;
font-weight: bold;

View File

@ -21,8 +21,8 @@ export default function InvokeAccordionItem(props: InvokeAccordionItemProps) {
const { header, feature, content, additionalHeaderComponents } = props;
return (
<AccordionItem className="advanced-settings-item">
<AccordionButton className="advanced-settings-header">
<AccordionItem className="advanced-parameters-item">
<AccordionButton className="advanced-parameters-header">
<Flex width={'100%'} gap={'0.5rem'} align={'center'}>
<Box flexGrow={1} textAlign={'left'}>
{header}
@ -32,7 +32,7 @@ export default function InvokeAccordionItem(props: InvokeAccordionItemProps) {
<AccordionIcon />
</Flex>
</AccordionButton>
<AccordionPanel className="advanced-settings-panel">
<AccordionPanel className="advanced-parameters-panel">
{content}
</AccordionPanel>
</AccordionItem>

View File

@ -69,7 +69,7 @@ const BoundingBoxSettings = () => {
return (
<Flex direction="column" gap="1rem">
<IAISlider
label={t('options:width')}
label={t('parameters:width')}
min={64}
max={1024}
step={64}
@ -83,7 +83,7 @@ const BoundingBoxSettings = () => {
handleReset={handleResetWidth}
/>
<IAISlider
label={t('options:height')}
label={t('parameters:height')}
min={64}
max={1024}
step={64}
@ -106,7 +106,7 @@ export const BoundingBoxSettingsHeader = () => {
const { t } = useTranslation();
return (
<Box flex="1" textAlign="left">
{t('options:boundingBoxHeader')}
{t('parameters:boundingBoxHeader')}
</Box>
);
};

View File

@ -12,20 +12,20 @@ import {
BoundingBoxScale,
BOUNDING_BOX_SCALES_DICT,
} from 'features/canvas/store/canvasTypes';
import { optionsSelector } from 'features/options/store/optionsSelectors';
import { generationSelector } from 'features/parameters/store/generationSelectors';
import {
setInfillMethod,
setTileSize,
} from 'features/options/store/optionsSlice';
} from 'features/parameters/store/generationSlice';
import { systemSelector } from 'features/system/store/systemSelectors';
import _ from 'lodash';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
[optionsSelector, systemSelector, canvasSelector],
(options, system, canvas) => {
const { tileSize, infillMethod } = options;
[generationSelector, systemSelector, canvasSelector],
(parameters, system, canvas) => {
const { tileSize, infillMethod } = parameters;
const { infill_methods: availableInfillMethods } = system;
@ -50,7 +50,7 @@ const selector = createSelector(
}
);
const InfillAndScalingOptions = () => {
const InfillAndScalingSettings = () => {
const dispatch = useAppDispatch();
const {
tileSize,
@ -108,7 +108,7 @@ const InfillAndScalingOptions = () => {
return (
<Flex direction="column" gap="1rem">
<IAISelect
label={t('options:scaleBeforeProcessing')}
label={t('parameters:scaleBeforeProcessing')}
validValues={BOUNDING_BOX_SCALES_DICT}
value={boundingBoxScale}
onChange={handleChangeBoundingBoxScaleMethod}
@ -117,7 +117,7 @@ const InfillAndScalingOptions = () => {
isInputDisabled={!isManual}
isResetDisabled={!isManual}
isSliderDisabled={!isManual}
label={t('options:scaledWidth')}
label={t('parameters:scaledWidth')}
min={64}
max={1024}
step={64}
@ -134,7 +134,7 @@ const InfillAndScalingOptions = () => {
isInputDisabled={!isManual}
isResetDisabled={!isManual}
isSliderDisabled={!isManual}
label={t('options:scaledHeight')}
label={t('parameters:scaledHeight')}
min={64}
max={1024}
step={64}
@ -148,7 +148,7 @@ const InfillAndScalingOptions = () => {
handleReset={handleResetScaledHeight}
/>
<IAISelect
label={t('options:infillMethod')}
label={t('parameters:infillMethod')}
value={infillMethod}
validValues={availableInfillMethods}
onChange={(e) => dispatch(setInfillMethod(e.target.value))}
@ -158,7 +158,7 @@ const InfillAndScalingOptions = () => {
isResetDisabled={infillMethod !== 'tile'}
isSliderDisabled={infillMethod !== 'tile'}
sliderMarkRightOffset={-4}
label={t('options:tileSize')}
label={t('parameters:tileSize')}
min={16}
max={64}
sliderNumberInputProps={{ max: 256 }}
@ -177,4 +177,4 @@ const InfillAndScalingOptions = () => {
);
};
export default InfillAndScalingOptions;
export default InfillAndScalingSettings;

View File

@ -1,19 +1,20 @@
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setSeamBlur } from 'features/options/store/optionsSlice';
import React from 'react';
import { setSeamBlur } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function SeamBlur() {
const dispatch = useAppDispatch();
const seamBlur = useAppSelector((state: RootState) => state.options.seamBlur);
const seamBlur = useAppSelector(
(state: RootState) => state.generation.seamBlur
);
const { t } = useTranslation();
return (
<IAISlider
sliderMarkRightOffset={-4}
label={t('options:seamBlur')}
label={t('parameters:seamBlur')}
min={0}
max={64}
sliderNumberInputProps={{ max: 512 }}

View File

@ -4,7 +4,7 @@ import SeamSize from './SeamSize';
import SeamSteps from './SeamSteps';
import SeamStrength from './SeamStrength';
const SeamCorrectionOptions = () => {
const SeamCorrectionSettings = () => {
return (
<Flex direction="column" gap="1rem">
<SeamSize />
@ -15,4 +15,4 @@ const SeamCorrectionOptions = () => {
);
};
export default SeamCorrectionOptions;
export default SeamCorrectionSettings;

View File

@ -1,7 +1,7 @@
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setSeamSize } from 'features/options/store/optionsSlice';
import { setSeamSize } from 'features/parameters/store/generationSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
@ -9,12 +9,12 @@ export default function SeamSize() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const seamSize = useAppSelector((state: RootState) => state.options.seamSize);
const seamSize = useAppSelector((state: RootState) => state.generation.seamSize);
return (
<IAISlider
sliderMarkRightOffset={-6}
label={t('options:seamSize')}
label={t('parameters:seamSize')}
min={1}
max={256}
sliderNumberInputProps={{ max: 512 }}

View File

@ -1,21 +1,21 @@
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setSeamSteps } from 'features/options/store/optionsSlice';
import { setSeamSteps } from 'features/parameters/store/generationSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
export default function SeamSteps() {
const { t } = useTranslation();
const seamSteps = useAppSelector(
(state: RootState) => state.options.seamSteps
(state: RootState) => state.generation.seamSteps
);
const dispatch = useAppDispatch();
return (
<IAISlider
sliderMarkRightOffset={-4}
label={t('options:seamSteps')}
label={t('parameters:seamSteps')}
min={1}
max={100}
sliderNumberInputProps={{ max: 999 }}

View File

@ -1,7 +1,7 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setSeamStrength } from 'features/options/store/optionsSlice';
import { setSeamStrength } from 'features/parameters/store/generationSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
@ -9,13 +9,13 @@ export default function SeamStrength() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const seamStrength = useAppSelector(
(state: RootState) => state.options.seamStrength
(state: RootState) => state.generation.seamStrength
);
return (
<IAISlider
sliderMarkRightOffset={-7}
label={t('options:seamStrength')}
label={t('parameters:seamStrength')}
min={0.01}
max={0.99}
step={0.01}

View File

@ -1,46 +1,36 @@
import { Flex } from '@chakra-ui/react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { FacetoolType } from 'features/parameters/store/postprocessingSlice';
import {
FacetoolType,
OptionsState,
setCodeformerFidelity,
setFacetoolStrength,
setFacetoolType,
} from 'features/options/store/optionsSlice';
} from 'features/parameters/store/postprocessingSlice';
import { createSelector } from '@reduxjs/toolkit';
import { isEqual } from 'lodash';
import { SystemState } from 'features/system/store/systemSlice';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISelect from 'common/components/IAISelect';
import { FACETOOL_TYPES } from 'app/constants';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
const optionsSelector = createSelector(
(state: RootState) => state.options,
(options: OptionsState) => {
[postprocessingSelector, systemSelector],
(
{ facetoolStrength, facetoolType, codeformerFidelity },
{ isGFPGANAvailable }
) => {
return {
facetoolStrength: options.facetoolStrength,
facetoolType: options.facetoolType,
codeformerFidelity: options.codeformerFidelity,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
const systemSelector = createSelector(
(state: RootState) => state.system,
(system: SystemState) => {
return {
isGFPGANAvailable: system.isGFPGANAvailable,
facetoolStrength,
facetoolType,
codeformerFidelity,
isGFPGANAvailable,
};
},
{
@ -53,11 +43,14 @@ const systemSelector = createSelector(
/**
* Displays face-fixing/GFPGAN options (strength).
*/
const FaceRestoreOptions = () => {
const FaceRestoreSettings = () => {
const dispatch = useAppDispatch();
const { facetoolStrength, facetoolType, codeformerFidelity } =
useAppSelector(optionsSelector);
const { isGFPGANAvailable } = useAppSelector(systemSelector);
const {
facetoolStrength,
facetoolType,
codeformerFidelity,
isGFPGANAvailable,
} = useAppSelector(optionsSelector);
const handleChangeStrength = (v: number) => dispatch(setFacetoolStrength(v));
@ -72,14 +65,14 @@ const FaceRestoreOptions = () => {
return (
<Flex direction={'column'} gap={2}>
<IAISelect
label={t('options:type')}
label={t('parameters:type')}
validValues={FACETOOL_TYPES.concat()}
value={facetoolType}
onChange={handleChangeFacetoolType}
/>
<IAINumberInput
isDisabled={!isGFPGANAvailable}
label={t('options:strength')}
label={t('parameters:strength')}
step={0.05}
min={0}
max={1}
@ -91,7 +84,7 @@ const FaceRestoreOptions = () => {
{facetoolType === 'codeformer' && (
<IAINumberInput
isDisabled={!isGFPGANAvailable}
label={t('options:codeformerFidelity')}
label={t('parameters:codeformerFidelity')}
step={0.05}
min={0}
max={1}
@ -105,4 +98,4 @@ const FaceRestoreOptions = () => {
);
};
export default FaceRestoreOptions;
export default FaceRestoreSettings;

View File

@ -2,7 +2,7 @@ import { ChangeEvent } from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldRunFacetool } from 'features/options/store/optionsSlice';
import { setShouldRunFacetool } from 'features/parameters/store/postprocessingSlice';
export default function FaceRestoreToggle() {
const isGFPGANAvailable = useAppSelector(
@ -10,7 +10,7 @@ export default function FaceRestoreToggle() {
);
const shouldRunFacetool = useAppSelector(
(state: RootState) => state.options.shouldRunFacetool
(state: RootState) => state.postprocessing.shouldRunFacetool
);
const dispatch = useAppDispatch();

View File

@ -2,14 +2,14 @@ import React, { ChangeEvent } from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldFitToWidthHeight } from 'features/options/store/optionsSlice';
import { setShouldFitToWidthHeight } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function ImageFit() {
const dispatch = useAppDispatch();
const shouldFitToWidthHeight = useAppSelector(
(state: RootState) => state.options.shouldFitToWidthHeight
(state: RootState) => state.generation.shouldFitToWidthHeight
);
const handleChangeFit = (e: ChangeEvent<HTMLInputElement>) =>
@ -19,7 +19,7 @@ export default function ImageFit() {
return (
<IAISwitch
label={t('options:imageFit')}
label={t('parameters:imageFit')}
isChecked={shouldFitToWidthHeight}
onChange={handleChangeFit}
/>

View File

@ -2,7 +2,7 @@ import React from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setImg2imgStrength } from 'features/options/store/optionsSlice';
import { setImg2imgStrength } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
interface ImageToImageStrengthProps {
@ -12,9 +12,9 @@ interface ImageToImageStrengthProps {
export default function ImageToImageStrength(props: ImageToImageStrengthProps) {
const { t } = useTranslation();
const { label = `${t('options:strength')}`, styleClass } = props;
const { label = `${t('parameters:strength')}`, styleClass } = props;
const img2imgStrength = useAppSelector(
(state: RootState) => state.options.img2imgStrength
(state: RootState) => state.generation.img2imgStrength
);
const dispatch = useAppDispatch();

View File

@ -1,20 +1,30 @@
import { Flex } from '@chakra-ui/react';
import { ChangeEvent } from 'react';
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import IAISwitch from 'common/components/IAISwitch';
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
import {
setHiresFix,
setHiresStrength,
} from 'features/options/store/optionsSlice';
} from 'features/parameters/store/postprocessingSlice';
import { isEqual } from 'lodash';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
import IAISlider from 'common/components/IAISlider';
function HighResStrength() {
const hiresFix = useAppSelector((state: RootState) => state.options.hiresFix);
const hiresStrength = useAppSelector(
(state: RootState) => state.options.hiresStrength
);
const hiresStrengthSelector = createSelector(
[postprocessingSelector],
({ hiresFix, hiresStrength }) => ({ hiresFix, hiresStrength }),
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
const HiresStrength = () => {
const { hiresFix, hiresStrength } = useAppSelector(hiresStrengthSelector);
const dispatch = useAppDispatch();
@ -30,7 +40,7 @@ function HighResStrength() {
return (
<IAISlider
label={t('options:hiresStrength')}
label={t('parameters:hiresStrength')}
step={0.01}
min={0.01}
max={0.99}
@ -47,15 +57,17 @@ function HighResStrength() {
isResetDisabled={!hiresFix}
/>
);
}
};
/**
* Hires Fix Toggle
*/
const HiresOptions = () => {
const HiresSettings = () => {
const dispatch = useAppDispatch();
const hiresFix = useAppSelector((state: RootState) => state.options.hiresFix);
const hiresFix = useAppSelector(
(state: RootState) => state.postprocessing.hiresFix
);
const { t } = useTranslation();
@ -65,14 +77,14 @@ const HiresOptions = () => {
return (
<Flex gap={2} direction={'column'}>
<IAISwitch
label={t('options:hiresOptim')}
label={t('parameters:hiresOptim')}
fontSize={'md'}
isChecked={hiresFix}
onChange={handleChangeHiresFix}
/>
<HighResStrength />
<HiresStrength />
</Flex>
);
};
export default HiresOptions;
export default HiresSettings;

View File

@ -0,0 +1,12 @@
import { Flex } from '@chakra-ui/react';
import SeamlessSettings from './SeamlessSettings';
const ImageToImageOutputSettings = () => {
return (
<Flex gap={2} direction={'column'}>
<SeamlessSettings />
</Flex>
);
};
export default ImageToImageOutputSettings;

View File

@ -0,0 +1,14 @@
import { Flex } from '@chakra-ui/react';
import HiresSettings from './HiresSettings';
import SeamlessSettings from './SeamlessSettings';
const OutputSettings = () => {
return (
<Flex gap={2} direction={'column'}>
<SeamlessSettings />
<HiresSettings />
</Flex>
);
};
export default OutputSettings;

View File

@ -3,16 +3,16 @@ import { ChangeEvent } from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setSeamless } from 'features/options/store/optionsSlice';
import { setSeamless } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
/**
* Seamless tiling toggle
*/
const SeamlessOptions = () => {
const SeamlessSettings = () => {
const dispatch = useAppDispatch();
const seamless = useAppSelector((state: RootState) => state.options.seamless);
const seamless = useAppSelector((state: RootState) => state.generation.seamless);
const handleChangeSeamless = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setSeamless(e.target.checked));
@ -22,7 +22,7 @@ const SeamlessOptions = () => {
return (
<Flex gap={2} direction={'column'}>
<IAISwitch
label={t('options:seamlessTiling')}
label={t('parameters:seamlessTiling')}
fontSize={'md'}
isChecked={seamless}
onChange={handleChangeSeamless}
@ -31,4 +31,4 @@ const SeamlessOptions = () => {
);
};
export default SeamlessOptions;
export default SeamlessSettings;

View File

@ -2,19 +2,19 @@ import React from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import { setPerlin } from 'features/options/store/optionsSlice';
import { setPerlin } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function Perlin() {
const dispatch = useAppDispatch();
const perlin = useAppSelector((state: RootState) => state.options.perlin);
const perlin = useAppSelector((state: RootState) => state.generation.perlin);
const { t } = useTranslation();
const handleChangePerlin = (v: number) => dispatch(setPerlin(v));
return (
<IAINumberInput
label={t('options:perlinNoise')}
label={t('parameters:perlinNoise')}
min={0}
max={1}
step={0.05}

View File

@ -4,7 +4,7 @@ import React from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldRandomizeSeed } from 'features/options/store/optionsSlice';
import { setShouldRandomizeSeed } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function RandomizeSeed() {
@ -12,7 +12,7 @@ export default function RandomizeSeed() {
const { t } = useTranslation();
const shouldRandomizeSeed = useAppSelector(
(state: RootState) => state.options.shouldRandomizeSeed
(state: RootState) => state.generation.shouldRandomizeSeed
);
const handleChangeShouldRandomizeSeed = (e: ChangeEvent<HTMLInputElement>) =>
@ -20,7 +20,7 @@ export default function RandomizeSeed() {
return (
<IAISwitch
label={t('options:randomizeSeed')}
label={t('parameters:randomizeSeed')}
isChecked={shouldRandomizeSeed}
onChange={handleChangeShouldRandomizeSeed}
/>

View File

@ -3,16 +3,16 @@ import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import { setSeed } from 'features/options/store/optionsSlice';
import { setSeed } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function Seed() {
const seed = useAppSelector((state: RootState) => state.options.seed);
const seed = useAppSelector((state: RootState) => state.generation.seed);
const shouldRandomizeSeed = useAppSelector(
(state: RootState) => state.options.shouldRandomizeSeed
(state: RootState) => state.generation.shouldRandomizeSeed
);
const shouldGenerateVariations = useAppSelector(
(state: RootState) => state.options.shouldGenerateVariations
(state: RootState) => state.generation.shouldGenerateVariations
);
const { t } = useTranslation();
@ -23,7 +23,7 @@ export default function Seed() {
return (
<IAINumberInput
label={t('options:seed')}
label={t('parameters:seed')}
step={1}
precision={0}
flexGrow={1}

View File

@ -8,7 +8,7 @@ import Perlin from './Perlin';
/**
* Seed & variation options. Includes iteration, seed, seed randomization, variation options.
*/
const SeedOptions = () => {
const SeedSettings = () => {
return (
<Flex gap={2} direction={'column'}>
<RandomizeSeed />
@ -26,4 +26,4 @@ const SeedOptions = () => {
);
};
export default SeedOptions;
export default SeedSettings;

View File

@ -4,13 +4,13 @@ import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import randomInt from 'common/util/randomInt';
import { setSeed } from 'features/options/store/optionsSlice';
import { setSeed } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function ShuffleSeed() {
const dispatch = useAppDispatch();
const shouldRandomizeSeed = useAppSelector(
(state: RootState) => state.options.shouldRandomizeSeed
(state: RootState) => state.generation.shouldRandomizeSeed
);
const { t } = useTranslation();
@ -24,7 +24,7 @@ export default function ShuffleSeed() {
onClick={handleClickRandomizeSeed}
padding="0 1.5rem"
>
<p>{t('options:shuffle')}</p>
<p>{t('parameters:shuffle')}</p>
</Button>
);
}

View File

@ -2,13 +2,13 @@ import React from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import { setThreshold } from 'features/options/store/optionsSlice';
import { setThreshold } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function Threshold() {
const dispatch = useAppDispatch();
const threshold = useAppSelector(
(state: RootState) => state.options.threshold
(state: RootState) => state.generation.threshold
);
const { t } = useTranslation();
@ -16,7 +16,7 @@ export default function Threshold() {
return (
<IAINumberInput
label={t('options:noiseThreshold')}
label={t('parameters:noiseThreshold')}
min={0}
max={1000}
step={0.1}

View File

@ -1,4 +1,4 @@
.upscale-options {
.upscale-settings {
display: grid;
grid-template-columns: auto 1fr;
column-gap: 1rem;

View File

@ -1,42 +1,29 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { UpscalingLevel } from 'features/parameters/store/postprocessingSlice';
import {
setUpscalingLevel,
setUpscalingStrength,
UpscalingLevel,
OptionsState,
} from 'features/options/store/optionsSlice';
} from 'features/parameters/store/postprocessingSlice';
import { UPSCALING_LEVELS } from 'app/constants';
import { createSelector } from '@reduxjs/toolkit';
import { isEqual } from 'lodash';
import { SystemState } from 'features/system/store/systemSlice';
import { ChangeEvent } from 'react';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISelect from 'common/components/IAISelect';
import { useTranslation } from 'react-i18next';
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
const optionsSelector = createSelector(
(state: RootState) => state.options,
(options: OptionsState) => {
return {
upscalingLevel: options.upscalingLevel,
upscalingStrength: options.upscalingStrength,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
const parametersSelector = createSelector(
[postprocessingSelector, systemSelector],
const systemSelector = createSelector(
(state: RootState) => state.system,
(system: SystemState) => {
({ upscalingLevel, upscalingStrength }, { isESRGANAvailable }) => {
return {
isESRGANAvailable: system.isESRGANAvailable,
upscalingLevel,
upscalingStrength,
isESRGANAvailable,
};
},
{
@ -49,11 +36,10 @@ const systemSelector = createSelector(
/**
* Displays upscaling/ESRGAN options (level and strength).
*/
const UpscaleOptions = () => {
const UpscaleSettings = () => {
const dispatch = useAppDispatch();
const { upscalingLevel, upscalingStrength } = useAppSelector(optionsSelector);
const { isESRGANAvailable } = useAppSelector(systemSelector);
const { upscalingLevel, upscalingStrength, isESRGANAvailable } =
useAppSelector(parametersSelector);
const { t } = useTranslation();
@ -63,17 +49,17 @@ const UpscaleOptions = () => {
const handleChangeStrength = (v: number) => dispatch(setUpscalingStrength(v));
return (
<div className="upscale-options">
<div className="upscale-settings">
<IAISelect
isDisabled={!isESRGANAvailable}
label={t('options:scale')}
label={t('parameters:scale')}
value={upscalingLevel}
onChange={handleChangeLevel}
validValues={UPSCALING_LEVELS}
/>
<IAINumberInput
isDisabled={!isESRGANAvailable}
label={t('options:strength')}
label={t('parameters:strength')}
step={0.05}
min={0}
max={1}
@ -85,4 +71,4 @@ const UpscaleOptions = () => {
);
};
export default UpscaleOptions;
export default UpscaleSettings;

View File

@ -2,7 +2,7 @@ import { ChangeEvent } from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldRunESRGAN } from 'features/options/store/optionsSlice';
import { setShouldRunESRGAN } from 'features/parameters/store/postprocessingSlice';
export default function UpscaleToggle() {
const isESRGANAvailable = useAppSelector(
@ -10,7 +10,7 @@ export default function UpscaleToggle() {
);
const shouldRunESRGAN = useAppSelector(
(state: RootState) => state.options.shouldRunESRGAN
(state: RootState) => state.postprocessing.shouldRunESRGAN
);
const dispatch = useAppDispatch();

View File

@ -2,11 +2,11 @@ import React, { ChangeEvent } from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldGenerateVariations } from 'features/options/store/optionsSlice';
import { setShouldGenerateVariations } from 'features/parameters/store/generationSlice';
export default function GenerateVariationsToggle() {
const shouldGenerateVariations = useAppSelector(
(state: RootState) => state.options.shouldGenerateVariations
(state: RootState) => state.generation.shouldGenerateVariations
);
const dispatch = useAppDispatch();

View File

@ -3,16 +3,16 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAIInput from 'common/components/IAIInput';
import { validateSeedWeights } from 'common/util/seedWeightPairs';
import { setSeedWeights } from 'features/options/store/optionsSlice';
import { setSeedWeights } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function SeedWeights() {
const seedWeights = useAppSelector(
(state: RootState) => state.options.seedWeights
(state: RootState) => state.generation.seedWeights
);
const shouldGenerateVariations = useAppSelector(
(state: RootState) => state.options.shouldGenerateVariations
(state: RootState) => state.generation.shouldGenerateVariations
);
const { t } = useTranslation();
@ -24,7 +24,7 @@ export default function SeedWeights() {
return (
<IAIInput
label={t('options:seedWeights')}
label={t('parameters:seedWeights')}
value={seedWeights}
isInvalid={
shouldGenerateVariations &&

View File

@ -2,16 +2,16 @@ import React from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import { setVariationAmount } from 'features/options/store/optionsSlice';
import { setVariationAmount } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function VariationAmount() {
const variationAmount = useAppSelector(
(state: RootState) => state.options.variationAmount
(state: RootState) => state.generation.variationAmount
);
const shouldGenerateVariations = useAppSelector(
(state: RootState) => state.options.shouldGenerateVariations
(state: RootState) => state.generation.shouldGenerateVariations
);
const { t } = useTranslation();
@ -22,7 +22,7 @@ export default function VariationAmount() {
return (
<IAINumberInput
label={t('options:variationAmount')}
label={t('parameters:variationAmount')}
value={variationAmount}
step={0.01}
min={0}

View File

@ -5,7 +5,7 @@ import VariationAmount from './VariationAmount';
/**
* Seed & variation options. Includes iteration, seed, seed randomization, variation options.
*/
const VariationsOptions = () => {
const VariationsSettings = () => {
return (
<Flex gap={2} direction={'column'}>
<VariationAmount />
@ -14,4 +14,4 @@ const VariationsOptions = () => {
);
};
export default VariationsOptions;
export default VariationsSettings;

View File

@ -2,26 +2,26 @@ import React from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import { setCfgScale } from 'features/options/store/optionsSlice';
import { setCfgScale } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function MainCFGScale() {
const dispatch = useAppDispatch();
const cfgScale = useAppSelector((state: RootState) => state.options.cfgScale);
const cfgScale = useAppSelector((state: RootState) => state.generation.cfgScale);
const { t } = useTranslation();
const handleChangeCfgScale = (v: number) => dispatch(setCfgScale(v));
return (
<IAINumberInput
label={t('options:cfgScale')}
label={t('parameters:cfgScale')}
step={0.5}
min={1.01}
max={200}
onChange={handleChangeCfgScale}
value={cfgScale}
width="auto"
styleClass="main-option-block"
styleClass="main-settings-block"
textAlign="center"
isInteger={false}
/>

View File

@ -4,12 +4,12 @@ import { HEIGHTS } from 'app/constants';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISelect from 'common/components/IAISelect';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { setHeight } from 'features/options/store/optionsSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { setHeight } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function MainHeight() {
const height = useAppSelector((state: RootState) => state.options.height);
const height = useAppSelector((state: RootState) => state.generation.height);
const activeTabName = useAppSelector(activeTabNameSelector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
@ -20,12 +20,12 @@ export default function MainHeight() {
return (
<IAISelect
isDisabled={activeTabName === 'unifiedCanvas'}
label={t('options:height')}
label={t('parameters:height')}
value={height}
flexGrow={1}
onChange={handleChangeHeight}
validValues={HEIGHTS}
styleClass="main-option-block"
styleClass="main-settings-block"
/>
);
}

View File

@ -5,15 +5,15 @@ import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import {
OptionsState,
GenerationState,
setIterations,
} from 'features/options/store/optionsSlice';
} from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
const mainIterationsSelector = createSelector(
[(state: RootState) => state.options],
(options: OptionsState) => {
const { iterations } = options;
[(state: RootState) => state.generation],
(parameters: GenerationState) => {
const { iterations } = parameters;
return {
iterations,
@ -35,7 +35,7 @@ export default function MainIterations() {
return (
<IAINumberInput
label={t('options:images')}
label={t('parameters:images')}
step={1}
min={1}
max={9999}
@ -43,7 +43,7 @@ export default function MainIterations() {
value={iterations}
width="auto"
labelFontSize={0.5}
styleClass="main-option-block"
styleClass="main-settings-block"
textAlign="center"
/>
);

View File

@ -1,23 +1,23 @@
@use '../../../../styles/Mixins/' as *;
.main-options {
.main-settings {
display: grid;
row-gap: 1rem;
}
.main-options-list {
.main-settings-list {
display: grid;
row-gap: 1rem;
}
.main-options-row {
.main-settings-row {
display: grid;
grid-template-columns: repeat(3, auto);
column-gap: 0.5rem;
max-width: $options-bar-max-width;
}
.main-option-block {
.main-settings-block {
border-radius: 0.5rem;
display: grid !important;
grid-template-columns: auto !important;
@ -33,10 +33,3 @@
margin: 0;
}
}
.advanced-options-checkbox {
background-color: var(--background-color-secondary);
padding: 0.5rem 1rem;
border-radius: 0.4rem;
font-weight: bold;
}

View File

@ -7,16 +7,16 @@ import MainWidth from './MainWidth';
export const inputWidth = 'auto';
export default function MainOptions() {
export default function MainSettings() {
return (
<div className="main-options">
<div className="main-options-list">
<div className="main-options-row">
<div className="main-settings">
<div className="main-settings-list">
<div className="main-settings-row">
<MainIterations />
<MainSteps />
<MainCFGScale />
</div>
<div className="main-options-row">
<div className="main-settings-row">
<MainWidth />
<MainHeight />
<MainSampler />

View File

@ -1,15 +1,16 @@
import React, { ChangeEvent } from 'react';
import { DIFFUSERS_SAMPLERS, SAMPLERS } from 'app/constants';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISelect from 'common/components/IAISelect';
import { setSampler } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
import _ from 'lodash';
import { setSampler } from 'features/parameters/store/generationSlice';
import { activeModelSelector } from 'features/system/store/systemSelectors';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
export default function MainSampler() {
const sampler = useAppSelector((state: RootState) => state.options.sampler);
const sampler = useAppSelector(
(state: RootState) => state.generation.sampler
);
const activeModel = useAppSelector(activeModelSelector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
@ -19,7 +20,7 @@ export default function MainSampler() {
return (
<IAISelect
label={t('options:sampler')}
label={t('parameters:sampler')}
value={sampler}
onChange={handleChangeSampler}
validValues={

View File

@ -2,26 +2,26 @@ import React from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import { setSteps } from 'features/options/store/optionsSlice';
import { setSteps } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function MainSteps() {
const dispatch = useAppDispatch();
const steps = useAppSelector((state: RootState) => state.options.steps);
const steps = useAppSelector((state: RootState) => state.generation.steps);
const { t } = useTranslation();
const handleChangeSteps = (v: number) => dispatch(setSteps(v));
return (
<IAINumberInput
label={t('options:steps')}
label={t('parameters:steps')}
min={1}
max={9999}
step={1}
onChange={handleChangeSteps}
value={steps}
width="auto"
styleClass="main-option-block"
styleClass="main-settings-block"
textAlign="center"
/>
);

View File

@ -3,12 +3,12 @@ import { WIDTHS } from 'app/constants';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISelect from 'common/components/IAISelect';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { setWidth } from 'features/options/store/optionsSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { setWidth } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function MainWidth() {
const width = useAppSelector((state: RootState) => state.options.width);
const width = useAppSelector((state: RootState) => state.generation.width);
const activeTabName = useAppSelector(activeTabNameSelector);
const { t } = useTranslation();
@ -20,12 +20,12 @@ export default function MainWidth() {
return (
<IAISelect
isDisabled={activeTabName === 'unifiedCanvas'}
label={t('options:width')}
label={t('parameters:width')}
value={width}
flexGrow={1}
onChange={handleChangeWidth}
validValues={WIDTHS}
styleClass="main-option-block"
styleClass="main-settings-block"
/>
);
}

View File

@ -7,18 +7,18 @@ import InvokeAccordionItem, {
} from './AccordionItems/InvokeAccordionItem';
import { ReactElement } from 'react';
type OptionsAccordionType = {
[optionAccordionKey: string]: InvokeAccordionItemProps;
type ParametersAccordionType = {
[parametersAccordionKey: string]: InvokeAccordionItemProps;
};
type OptionAccordionsType = {
accordionInfo: OptionsAccordionType;
type ParametersAccordionsType = {
accordionInfo: ParametersAccordionType;
};
/**
* Main container for generation and processing parameters.
*/
const OptionsAccordion = (props: OptionAccordionsType) => {
const ParametersAccordion = (props: ParametersAccordionsType) => {
const { accordionInfo } = props;
const openAccordions = useAppSelector(
@ -59,11 +59,11 @@ const OptionsAccordion = (props: OptionAccordionsType) => {
allowMultiple
reduceMotion
onChange={handleChangeAccordionState}
className="advanced-settings"
className="advanced-parameters"
>
{renderAccordions()}
</Accordion>
);
};
export default OptionsAccordion;
export default ParametersAccordion;

View File

@ -1,6 +1,5 @@
import { MdCancel } from 'react-icons/md';
import { cancelProcessing } from 'app/socketio/actions';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAIIconButton, {
IAIIconButtonProps,
@ -10,9 +9,10 @@ import { createSelector } from '@reduxjs/toolkit';
import { SystemState } from 'features/system/store/systemSlice';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
import { systemSelector } from 'features/system/store/systemSelectors';
const cancelButtonSelector = createSelector(
(state: RootState) => state.system,
systemSelector,
(system: SystemState) => {
return {
isProcessing: system.isProcessing,
@ -51,8 +51,8 @@ export default function CancelButton(
return (
<IAIIconButton
icon={<MdCancel />}
tooltip={t('options:cancel')}
aria-label={t('options:cancel')}
tooltip={t('parameters:cancel')}
aria-label={t('parameters:cancel')}
isDisabled={!isConnected || !isProcessing || !isCancelable}
onClick={handleClickCancel}
styleClass="cancel-btn"

View File

@ -7,7 +7,7 @@ import IAIButton, { IAIButtonProps } from 'common/components/IAIButton';
import IAIIconButton, {
IAIIconButtonProps,
} from 'common/components/IAIIconButton';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useTranslation } from 'react-i18next';
interface InvokeButton
@ -44,19 +44,19 @@ export default function InvokeButton(props: InvokeButton) {
<div style={{ flexGrow: 4 }}>
{iconButton ? (
<IAIIconButton
aria-label={t('options:invoke')}
aria-label={t('parameters:invoke')}
type="submit"
icon={<FaPlay />}
isDisabled={!isReady}
onClick={handleClickGenerate}
className="invoke-btn"
tooltip={t('options:invoke')}
tooltip={t('parameters:invoke')}
tooltipProps={{ placement: 'bottom' }}
{...rest}
/>
) : (
<IAIButton
aria-label={t('options:invoke')}
aria-label={t('parameters:invoke')}
type="submit"
isDisabled={!isReady}
onClick={handleClickGenerate}

View File

@ -1,17 +1,14 @@
import { createSelector } from '@reduxjs/toolkit';
import { FaRecycle } from 'react-icons/fa';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import {
OptionsState,
setShouldLoopback,
} from 'features/options/store/optionsSlice';
import { setShouldLoopback } from 'features/parameters/store/postprocessingSlice';
import { useTranslation } from 'react-i18next';
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
const loopbackSelector = createSelector(
(state: RootState) => state.options,
(options: OptionsState) => options.shouldLoopback
postprocessingSelector,
({ shouldLoopback }) => shouldLoopback
);
const LoopbackButton = () => {
@ -22,8 +19,8 @@ const LoopbackButton = () => {
return (
<IAIIconButton
aria-label={t('options:toggleLoopback')}
tooltip={t('options:toggleLoopback')}
aria-label={t('parameters:toggleLoopback')}
tooltip={t('parameters:toggleLoopback')}
styleClass="loopback-btn"
asCheckbox={true}
isChecked={shouldLoopback}

View File

@ -2,7 +2,7 @@ import InvokeButton from './InvokeButton';
import CancelButton from './CancelButton';
import LoopbackButton from './Loopback';
import { useAppSelector } from 'app/storeHooks';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
/**
* Buttons to start and cancel image generation.

View File

@ -1,12 +1,12 @@
import { FormControl, Textarea } from '@chakra-ui/react';
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { setNegativePrompt } from 'features/options/store/optionsSlice';
import { setNegativePrompt } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export function NegativePromptInput() {
const NegativePromptInput = () => {
const negativePrompt = useAppSelector(
(state: RootState) => state.options.negativePrompt
(state: RootState) => state.generation.negativePrompt
);
const dispatch = useAppDispatch();
@ -20,7 +20,7 @@ export function NegativePromptInput() {
value={negativePrompt}
onChange={(e) => dispatch(setNegativePrompt(e.target.value))}
background="var(--prompt-bg-color)"
placeholder={t('options:negativePrompts')}
placeholder={t('parameters:negativePrompts')}
_placeholder={{ fontSize: '0.8rem' }}
borderColor="var(--border-color)"
_hover={{
@ -35,4 +35,6 @@ export function NegativePromptInput() {
/>
</FormControl>
);
}
};
export default NegativePromptInput;

Some files were not shown because too many files have changed in this diff Show More