mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Fixes re-renders triggered by typing prompt
This commit is contained in:
parent
7b329b7c91
commit
c40278dae7
517
frontend/dist/assets/index.86b555db.js
vendored
Normal file
517
frontend/dist/assets/index.86b555db.js
vendored
Normal file
File diff suppressed because one or more lines are too long
517
frontend/dist/assets/index.fb948d0a.js
vendored
517
frontend/dist/assets/index.fb948d0a.js
vendored
File diff suppressed because one or more lines are too long
2
frontend/dist/index.html
vendored
2
frontend/dist/index.html
vendored
@ -6,7 +6,7 @@
|
||||
<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" />
|
||||
<script type="module" crossorigin src="./assets/index.fb948d0a.js"></script>
|
||||
<script type="module" crossorigin src="./assets/index.86b555db.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.bb945c0a.css">
|
||||
</head>
|
||||
|
||||
|
@ -16,6 +16,9 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import { GalleryState } from '../features/gallery/gallerySlice';
|
||||
import { OptionsState } from '../features/options/optionsSlice';
|
||||
import { activeTabNameSelector } from '../features/options/optionsSelectors';
|
||||
import { SystemState } from '../features/system/systemSlice';
|
||||
import _ from 'lodash';
|
||||
import { Model } from './invokeai';
|
||||
|
||||
keepGUIAlive();
|
||||
|
||||
@ -23,9 +26,15 @@ const appSelector = createSelector(
|
||||
[
|
||||
(state: RootState) => state.gallery,
|
||||
(state: RootState) => state.options,
|
||||
(state: RootState) => state.system,
|
||||
activeTabNameSelector,
|
||||
],
|
||||
(gallery: GalleryState, options: OptionsState, activeTabName) => {
|
||||
(
|
||||
gallery: GalleryState,
|
||||
options: OptionsState,
|
||||
system: SystemState,
|
||||
activeTabName
|
||||
) => {
|
||||
const { shouldShowGallery, shouldHoldGalleryOpen, shouldPinGallery } =
|
||||
gallery;
|
||||
const {
|
||||
@ -34,17 +43,36 @@ const appSelector = createSelector(
|
||||
shouldPinOptionsPanel,
|
||||
} = options;
|
||||
|
||||
const modelStatusText = _.reduce(
|
||||
system.model_list,
|
||||
(acc: string, cur: Model, key: string) => {
|
||||
if (cur.status === 'active') acc = key;
|
||||
return acc;
|
||||
},
|
||||
''
|
||||
);
|
||||
|
||||
const shouldShowGalleryButton = !(
|
||||
shouldShowGallery ||
|
||||
(shouldHoldGalleryOpen && !shouldPinGallery)
|
||||
);
|
||||
|
||||
const shouldShowOptionsPanelButton =
|
||||
!(
|
||||
shouldShowOptionsPanel ||
|
||||
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
|
||||
) && ['txt2img', 'img2img', 'inpainting'].includes(activeTabName);
|
||||
|
||||
return {
|
||||
shouldShowGalleryButton: !(
|
||||
shouldShowGallery ||
|
||||
(shouldHoldGalleryOpen && !shouldPinGallery)
|
||||
),
|
||||
shouldShowOptionsPanelButton:
|
||||
!(
|
||||
shouldShowOptionsPanel ||
|
||||
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
|
||||
) && ['txt2img', 'img2img', 'inpainting'].includes(activeTabName),
|
||||
modelStatusText,
|
||||
shouldShowGalleryButton,
|
||||
shouldShowOptionsPanelButton,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: _.isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
83
frontend/src/app/selectors/readinessSelector.ts
Normal file
83
frontend/src/app/selectors/readinessSelector.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import _ from 'lodash';
|
||||
import { RootState } from '../../app/store';
|
||||
import { activeTabNameSelector } from '../../features/options/optionsSelectors';
|
||||
import { OptionsState } from '../../features/options/optionsSlice';
|
||||
|
||||
import { SystemState } from '../../features/system/systemSlice';
|
||||
import { InpaintingState } from '../../features/tabs/Inpainting/inpaintingSlice';
|
||||
import { validateSeedWeights } from '../../common/util/seedWeightPairs';
|
||||
|
||||
export const readinessSelector = createSelector(
|
||||
[
|
||||
(state: RootState) => state.options,
|
||||
(state: RootState) => state.system,
|
||||
(state: RootState) => state.inpainting,
|
||||
activeTabNameSelector,
|
||||
],
|
||||
(
|
||||
options: OptionsState,
|
||||
system: SystemState,
|
||||
inpainting: InpaintingState,
|
||||
activeTabName
|
||||
) => {
|
||||
const {
|
||||
prompt,
|
||||
shouldGenerateVariations,
|
||||
seedWeights,
|
||||
maskPath,
|
||||
initialImage,
|
||||
seed,
|
||||
} = options;
|
||||
|
||||
const { isProcessing, isConnected } = system;
|
||||
|
||||
const { imageToInpaint } = inpainting;
|
||||
|
||||
// Cannot generate without a prompt
|
||||
if (!prompt || Boolean(prompt.match(/^[\s\r\n]+$/))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (activeTabName === 'img2img' && !initialImage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (activeTabName === 'inpainting' && !imageToInpaint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cannot generate with a mask without img2img
|
||||
if (maskPath && !initialImage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: job queue
|
||||
// Cannot generate if already processing an image
|
||||
if (isProcessing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cannot generate if not connected
|
||||
if (!isConnected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cannot generate variations without valid seed weights
|
||||
if (
|
||||
shouldGenerateVariations &&
|
||||
(!(validateSeedWeights(seedWeights) || seedWeights === '') || seed === -1)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// All good
|
||||
return true;
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
equalityCheck: _.isEqual,
|
||||
resultEqualityCheck: _.isEqual,
|
||||
},
|
||||
}
|
||||
);
|
@ -1,115 +0,0 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import _ from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
import { useAppSelector } from '../../app/store';
|
||||
import { RootState } from '../../app/store';
|
||||
import { activeTabNameSelector } from '../../features/options/optionsSelectors';
|
||||
import { OptionsState } from '../../features/options/optionsSlice';
|
||||
|
||||
import { SystemState } from '../../features/system/systemSlice';
|
||||
import { InpaintingState } from '../../features/tabs/Inpainting/inpaintingSlice';
|
||||
import { validateSeedWeights } from '../util/seedWeightPairs';
|
||||
|
||||
export const useCheckParametersSelector = createSelector(
|
||||
[
|
||||
(state: RootState) => state.options,
|
||||
(state: RootState) => state.system,
|
||||
(state: RootState) => state.inpainting,
|
||||
activeTabNameSelector
|
||||
],
|
||||
(options: OptionsState, system: SystemState, inpainting: InpaintingState, activeTabName) => {
|
||||
return {
|
||||
// options
|
||||
prompt: options.prompt,
|
||||
shouldGenerateVariations: options.shouldGenerateVariations,
|
||||
seedWeights: options.seedWeights,
|
||||
maskPath: options.maskPath,
|
||||
initialImage: options.initialImage,
|
||||
seed: options.seed,
|
||||
activeTabName,
|
||||
// system
|
||||
isProcessing: system.isProcessing,
|
||||
isConnected: system.isConnected,
|
||||
// inpainting
|
||||
hasInpaintingImage: Boolean(inpainting.imageToInpaint),
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: _.isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
/**
|
||||
* Checks relevant pieces of state to confirm generation will not deterministically fail.
|
||||
* This is used to prevent the 'Generate' button from being clicked.
|
||||
*/
|
||||
const useCheckParameters = (): boolean => {
|
||||
const {
|
||||
prompt,
|
||||
shouldGenerateVariations,
|
||||
seedWeights,
|
||||
maskPath,
|
||||
initialImage,
|
||||
seed,
|
||||
activeTabName,
|
||||
isProcessing,
|
||||
isConnected,
|
||||
hasInpaintingImage,
|
||||
} = useAppSelector(useCheckParametersSelector);
|
||||
|
||||
return useMemo(() => {
|
||||
// Cannot generate without a prompt
|
||||
if (!prompt || Boolean(prompt.match(/^[\s\r\n]+$/))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (activeTabName === 'img2img' && !initialImage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (activeTabName === 'inpainting' && !hasInpaintingImage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cannot generate with a mask without img2img
|
||||
if (maskPath && !initialImage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: job queue
|
||||
// Cannot generate if already processing an image
|
||||
if (isProcessing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cannot generate if not connected
|
||||
if (!isConnected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cannot generate variations without valid seed weights
|
||||
if (
|
||||
shouldGenerateVariations &&
|
||||
(!(validateSeedWeights(seedWeights) || seedWeights === '') || seed === -1)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// All good
|
||||
return true;
|
||||
}, [
|
||||
prompt,
|
||||
maskPath,
|
||||
isProcessing,
|
||||
initialImage,
|
||||
isConnected,
|
||||
shouldGenerateVariations,
|
||||
seedWeights,
|
||||
seed,
|
||||
activeTabName,
|
||||
hasInpaintingImage,
|
||||
]);
|
||||
};
|
||||
|
||||
export default useCheckParameters;
|
@ -6,6 +6,7 @@ import * as InvokeAI from '../../app/invokeai';
|
||||
import { useAppDispatch, useAppSelector } from '../../app/store';
|
||||
import { RootState } from '../../app/store';
|
||||
import {
|
||||
OptionsState,
|
||||
setActiveTab,
|
||||
setAllParameters,
|
||||
setInitialImage,
|
||||
@ -26,15 +27,49 @@ import { useToast } from '@chakra-ui/react';
|
||||
import { FaCopy, FaPaintBrush, FaSeedling } from 'react-icons/fa';
|
||||
import { setImageToInpaint } from '../tabs/Inpainting/inpaintingSlice';
|
||||
import { hoverableImageSelector } from './gallerySliceSelectors';
|
||||
import { GalleryState } from './gallerySlice';
|
||||
import { activeTabNameSelector } from '../options/optionsSelectors';
|
||||
|
||||
const intermediateImageSelector = createSelector(
|
||||
(state: RootState) => state.gallery,
|
||||
(gallery: GalleryState) => gallery.intermediateImage,
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: (a, b) =>
|
||||
(a === undefined && b === undefined) || a.uuid === b.uuid,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const systemSelector = createSelector(
|
||||
(state: RootState) => state.system,
|
||||
(system: SystemState) => {
|
||||
[
|
||||
(state: RootState) => state.system,
|
||||
(state: RootState) => state.options,
|
||||
intermediateImageSelector,
|
||||
activeTabNameSelector,
|
||||
],
|
||||
(
|
||||
system: SystemState,
|
||||
options: OptionsState,
|
||||
intermediateImage,
|
||||
activeTabName
|
||||
) => {
|
||||
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
|
||||
system;
|
||||
|
||||
const { upscalingLevel, facetoolStrength, shouldShowImageDetails } =
|
||||
options;
|
||||
|
||||
return {
|
||||
isProcessing: system.isProcessing,
|
||||
isConnected: system.isConnected,
|
||||
isGFPGANAvailable: system.isGFPGANAvailable,
|
||||
isESRGANAvailable: system.isESRGANAvailable,
|
||||
isProcessing,
|
||||
isConnected,
|
||||
isGFPGANAvailable,
|
||||
isESRGANAvailable,
|
||||
upscalingLevel,
|
||||
facetoolStrength,
|
||||
intermediateImage,
|
||||
shouldShowImageDetails,
|
||||
activeTabName,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -54,29 +89,20 @@ type CurrentImageButtonsProps = {
|
||||
*/
|
||||
const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { activeTabName } = useAppSelector(hoverableImageSelector);
|
||||
|
||||
const shouldShowImageDetails = useAppSelector(
|
||||
(state: RootState) => state.options.shouldShowImageDetails
|
||||
);
|
||||
const {
|
||||
isProcessing,
|
||||
isConnected,
|
||||
isGFPGANAvailable,
|
||||
isESRGANAvailable,
|
||||
upscalingLevel,
|
||||
facetoolStrength,
|
||||
intermediateImage,
|
||||
shouldShowImageDetails,
|
||||
activeTabName,
|
||||
} = useAppSelector(systemSelector);
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const intermediateImage = useAppSelector(
|
||||
(state: RootState) => state.gallery.intermediateImage
|
||||
);
|
||||
|
||||
const upscalingLevel = useAppSelector(
|
||||
(state: RootState) => state.options.upscalingLevel
|
||||
);
|
||||
|
||||
const facetoolStrength = useAppSelector(
|
||||
(state: RootState) => state.options.facetoolStrength
|
||||
);
|
||||
|
||||
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
|
||||
useAppSelector(systemSelector);
|
||||
|
||||
const handleClickUseAsInitialImage = () => {
|
||||
dispatch(setInitialImage(image));
|
||||
dispatch(setActiveTab(1));
|
||||
|
@ -1,14 +1,12 @@
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { BsImageFill, BsPlayFill } from 'react-icons/bs';
|
||||
import { FaPlay, FaPlayCircle } from 'react-icons/fa';
|
||||
import { IoPlay } from 'react-icons/io5';
|
||||
import { FaPlay } from 'react-icons/fa';
|
||||
import { readinessSelector } from '../../../app/selectors/readinessSelector';
|
||||
import { generateImage } from '../../../app/socketio/actions';
|
||||
import { useAppDispatch, useAppSelector } from '../../../app/store';
|
||||
import IAIButton, {
|
||||
IAIButtonProps,
|
||||
} from '../../../common/components/IAIButton';
|
||||
import IAIIconButton from '../../../common/components/IAIIconButton';
|
||||
import useCheckParameters from '../../../common/hooks/useCheckParameters';
|
||||
import { activeTabNameSelector } from '../optionsSelectors';
|
||||
|
||||
interface InvokeButton extends Omit<IAIButtonProps, 'label'> {
|
||||
@ -18,8 +16,7 @@ interface InvokeButton extends Omit<IAIButtonProps, 'label'> {
|
||||
export default function InvokeButton(props: InvokeButton) {
|
||||
const { iconButton = false, ...rest } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const isReady = useCheckParameters();
|
||||
|
||||
const isReady = useAppSelector(readinessSelector);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
const handleClickGenerate = () => {
|
||||
|
@ -1,13 +1,18 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { FaRecycle } from 'react-icons/fa';
|
||||
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
||||
import IAIIconButton from '../../../common/components/IAIIconButton';
|
||||
import { setShouldLoopback } from '../optionsSlice';
|
||||
import { OptionsState, setShouldLoopback } from '../optionsSlice';
|
||||
|
||||
const loopbackSelector = createSelector(
|
||||
(state: RootState) => state.options,
|
||||
(options: OptionsState) => options.shouldLoopback
|
||||
);
|
||||
|
||||
const LoopbackButton = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { shouldLoopback } = useAppSelector(
|
||||
(state: RootState) => state.options
|
||||
);
|
||||
const shouldLoopback = useAppSelector(loopbackSelector);
|
||||
|
||||
return (
|
||||
<IAIIconButton
|
||||
aria-label="Loopback"
|
||||
|
@ -6,9 +6,9 @@ import { generateImage } from '../../../app/socketio/actions';
|
||||
import { OptionsState, setPrompt } from '../optionsSlice';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import _ from 'lodash';
|
||||
import useCheckParameters from '../../../common/hooks/useCheckParameters';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { activeTabNameSelector } from '../optionsSelectors';
|
||||
import { readinessSelector } from '../../../app/selectors/readinessSelector';
|
||||
|
||||
const promptInputSelector = createSelector(
|
||||
[(state: RootState) => state.options, activeTabNameSelector],
|
||||
@ -29,10 +29,11 @@ const promptInputSelector = createSelector(
|
||||
* Prompt input text area.
|
||||
*/
|
||||
const PromptInput = () => {
|
||||
const promptRef = useRef<HTMLTextAreaElement>(null);
|
||||
const { prompt, activeTabName } = useAppSelector(promptInputSelector);
|
||||
const dispatch = useAppDispatch();
|
||||
const isReady = useCheckParameters();
|
||||
const { prompt, activeTabName } = useAppSelector(promptInputSelector);
|
||||
const isReady = useAppSelector(readinessSelector);
|
||||
|
||||
const promptRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const handleChangePrompt = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
dispatch(setPrompt(e.target.value));
|
||||
|
@ -1,9 +1,15 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import _ from 'lodash';
|
||||
import { RootState } from '../../app/store';
|
||||
import { tabMap } from '../tabs/InvokeTabs';
|
||||
import { OptionsState } from './optionsSlice';
|
||||
|
||||
export const activeTabNameSelector = createSelector(
|
||||
(state: RootState) => state.options,
|
||||
(options: OptionsState) => tabMap[options.activeTab]
|
||||
(options: OptionsState) => tabMap[options.activeTab],
|
||||
{
|
||||
memoizeOptions: {
|
||||
equalityCheck: _.isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import _ from 'lodash';
|
||||
import { MouseEvent, ReactNode, useCallback, useRef } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
||||
@ -33,6 +34,11 @@ const optionsPanelSelector = createSelector(
|
||||
shouldPinOptionsPanel,
|
||||
optionsPanelScrollPosition,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: _.isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user