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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||||
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
<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">
|
<link rel="stylesheet" href="./assets/index.bb945c0a.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -16,6 +16,9 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { GalleryState } from '../features/gallery/gallerySlice';
|
import { GalleryState } from '../features/gallery/gallerySlice';
|
||||||
import { OptionsState } from '../features/options/optionsSlice';
|
import { OptionsState } from '../features/options/optionsSlice';
|
||||||
import { activeTabNameSelector } from '../features/options/optionsSelectors';
|
import { activeTabNameSelector } from '../features/options/optionsSelectors';
|
||||||
|
import { SystemState } from '../features/system/systemSlice';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { Model } from './invokeai';
|
||||||
|
|
||||||
keepGUIAlive();
|
keepGUIAlive();
|
||||||
|
|
||||||
@ -23,9 +26,15 @@ const appSelector = createSelector(
|
|||||||
[
|
[
|
||||||
(state: RootState) => state.gallery,
|
(state: RootState) => state.gallery,
|
||||||
(state: RootState) => state.options,
|
(state: RootState) => state.options,
|
||||||
|
(state: RootState) => state.system,
|
||||||
activeTabNameSelector,
|
activeTabNameSelector,
|
||||||
],
|
],
|
||||||
(gallery: GalleryState, options: OptionsState, activeTabName) => {
|
(
|
||||||
|
gallery: GalleryState,
|
||||||
|
options: OptionsState,
|
||||||
|
system: SystemState,
|
||||||
|
activeTabName
|
||||||
|
) => {
|
||||||
const { shouldShowGallery, shouldHoldGalleryOpen, shouldPinGallery } =
|
const { shouldShowGallery, shouldHoldGalleryOpen, shouldPinGallery } =
|
||||||
gallery;
|
gallery;
|
||||||
const {
|
const {
|
||||||
@ -34,17 +43,36 @@ const appSelector = createSelector(
|
|||||||
shouldPinOptionsPanel,
|
shouldPinOptionsPanel,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
return {
|
const modelStatusText = _.reduce(
|
||||||
shouldShowGalleryButton: !(
|
system.model_list,
|
||||||
|
(acc: string, cur: Model, key: string) => {
|
||||||
|
if (cur.status === 'active') acc = key;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
const shouldShowGalleryButton = !(
|
||||||
shouldShowGallery ||
|
shouldShowGallery ||
|
||||||
(shouldHoldGalleryOpen && !shouldPinGallery)
|
(shouldHoldGalleryOpen && !shouldPinGallery)
|
||||||
),
|
);
|
||||||
shouldShowOptionsPanelButton:
|
|
||||||
|
const shouldShowOptionsPanelButton =
|
||||||
!(
|
!(
|
||||||
shouldShowOptionsPanel ||
|
shouldShowOptionsPanel ||
|
||||||
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
|
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
|
||||||
) && ['txt2img', 'img2img', 'inpainting'].includes(activeTabName),
|
) && ['txt2img', 'img2img', 'inpainting'].includes(activeTabName);
|
||||||
|
|
||||||
|
return {
|
||||||
|
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 { useAppDispatch, useAppSelector } from '../../app/store';
|
||||||
import { RootState } from '../../app/store';
|
import { RootState } from '../../app/store';
|
||||||
import {
|
import {
|
||||||
|
OptionsState,
|
||||||
setActiveTab,
|
setActiveTab,
|
||||||
setAllParameters,
|
setAllParameters,
|
||||||
setInitialImage,
|
setInitialImage,
|
||||||
@ -26,15 +27,49 @@ import { useToast } from '@chakra-ui/react';
|
|||||||
import { FaCopy, FaPaintBrush, FaSeedling } from 'react-icons/fa';
|
import { FaCopy, FaPaintBrush, FaSeedling } from 'react-icons/fa';
|
||||||
import { setImageToInpaint } from '../tabs/Inpainting/inpaintingSlice';
|
import { setImageToInpaint } from '../tabs/Inpainting/inpaintingSlice';
|
||||||
import { hoverableImageSelector } from './gallerySliceSelectors';
|
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(
|
const systemSelector = createSelector(
|
||||||
|
[
|
||||||
(state: RootState) => state.system,
|
(state: RootState) => state.system,
|
||||||
(system: SystemState) => {
|
(state: RootState) => state.options,
|
||||||
|
intermediateImageSelector,
|
||||||
|
activeTabNameSelector,
|
||||||
|
],
|
||||||
|
(
|
||||||
|
system: SystemState,
|
||||||
|
options: OptionsState,
|
||||||
|
intermediateImage,
|
||||||
|
activeTabName
|
||||||
|
) => {
|
||||||
|
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
|
||||||
|
system;
|
||||||
|
|
||||||
|
const { upscalingLevel, facetoolStrength, shouldShowImageDetails } =
|
||||||
|
options;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isProcessing: system.isProcessing,
|
isProcessing,
|
||||||
isConnected: system.isConnected,
|
isConnected,
|
||||||
isGFPGANAvailable: system.isGFPGANAvailable,
|
isGFPGANAvailable,
|
||||||
isESRGANAvailable: system.isESRGANAvailable,
|
isESRGANAvailable,
|
||||||
|
upscalingLevel,
|
||||||
|
facetoolStrength,
|
||||||
|
intermediateImage,
|
||||||
|
shouldShowImageDetails,
|
||||||
|
activeTabName,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -54,29 +89,20 @@ type CurrentImageButtonsProps = {
|
|||||||
*/
|
*/
|
||||||
const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
|
const CurrentImageButtons = ({ image }: CurrentImageButtonsProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { activeTabName } = useAppSelector(hoverableImageSelector);
|
const {
|
||||||
|
isProcessing,
|
||||||
const shouldShowImageDetails = useAppSelector(
|
isConnected,
|
||||||
(state: RootState) => state.options.shouldShowImageDetails
|
isGFPGANAvailable,
|
||||||
);
|
isESRGANAvailable,
|
||||||
|
upscalingLevel,
|
||||||
|
facetoolStrength,
|
||||||
|
intermediateImage,
|
||||||
|
shouldShowImageDetails,
|
||||||
|
activeTabName,
|
||||||
|
} = useAppSelector(systemSelector);
|
||||||
|
|
||||||
const toast = useToast();
|
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 = () => {
|
const handleClickUseAsInitialImage = () => {
|
||||||
dispatch(setInitialImage(image));
|
dispatch(setInitialImage(image));
|
||||||
dispatch(setActiveTab(1));
|
dispatch(setActiveTab(1));
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { BsImageFill, BsPlayFill } from 'react-icons/bs';
|
import { FaPlay } from 'react-icons/fa';
|
||||||
import { FaPlay, FaPlayCircle } from 'react-icons/fa';
|
import { readinessSelector } from '../../../app/selectors/readinessSelector';
|
||||||
import { IoPlay } from 'react-icons/io5';
|
|
||||||
import { generateImage } from '../../../app/socketio/actions';
|
import { generateImage } from '../../../app/socketio/actions';
|
||||||
import { useAppDispatch, useAppSelector } from '../../../app/store';
|
import { useAppDispatch, useAppSelector } from '../../../app/store';
|
||||||
import IAIButton, {
|
import IAIButton, {
|
||||||
IAIButtonProps,
|
IAIButtonProps,
|
||||||
} from '../../../common/components/IAIButton';
|
} from '../../../common/components/IAIButton';
|
||||||
import IAIIconButton from '../../../common/components/IAIIconButton';
|
import IAIIconButton from '../../../common/components/IAIIconButton';
|
||||||
import useCheckParameters from '../../../common/hooks/useCheckParameters';
|
|
||||||
import { activeTabNameSelector } from '../optionsSelectors';
|
import { activeTabNameSelector } from '../optionsSelectors';
|
||||||
|
|
||||||
interface InvokeButton extends Omit<IAIButtonProps, 'label'> {
|
interface InvokeButton extends Omit<IAIButtonProps, 'label'> {
|
||||||
@ -18,8 +16,7 @@ interface InvokeButton extends Omit<IAIButtonProps, 'label'> {
|
|||||||
export default function InvokeButton(props: InvokeButton) {
|
export default function InvokeButton(props: InvokeButton) {
|
||||||
const { iconButton = false, ...rest } = props;
|
const { iconButton = false, ...rest } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isReady = useCheckParameters();
|
const isReady = useAppSelector(readinessSelector);
|
||||||
|
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
|
|
||||||
const handleClickGenerate = () => {
|
const handleClickGenerate = () => {
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { FaRecycle } from 'react-icons/fa';
|
import { FaRecycle } from 'react-icons/fa';
|
||||||
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
||||||
import IAIIconButton from '../../../common/components/IAIIconButton';
|
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 LoopbackButton = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { shouldLoopback } = useAppSelector(
|
const shouldLoopback = useAppSelector(loopbackSelector);
|
||||||
(state: RootState) => state.options
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label="Loopback"
|
aria-label="Loopback"
|
||||||
|
@ -6,9 +6,9 @@ import { generateImage } from '../../../app/socketio/actions';
|
|||||||
import { OptionsState, setPrompt } from '../optionsSlice';
|
import { OptionsState, setPrompt } from '../optionsSlice';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import useCheckParameters from '../../../common/hooks/useCheckParameters';
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { activeTabNameSelector } from '../optionsSelectors';
|
import { activeTabNameSelector } from '../optionsSelectors';
|
||||||
|
import { readinessSelector } from '../../../app/selectors/readinessSelector';
|
||||||
|
|
||||||
const promptInputSelector = createSelector(
|
const promptInputSelector = createSelector(
|
||||||
[(state: RootState) => state.options, activeTabNameSelector],
|
[(state: RootState) => state.options, activeTabNameSelector],
|
||||||
@ -29,10 +29,11 @@ const promptInputSelector = createSelector(
|
|||||||
* Prompt input text area.
|
* Prompt input text area.
|
||||||
*/
|
*/
|
||||||
const PromptInput = () => {
|
const PromptInput = () => {
|
||||||
const promptRef = useRef<HTMLTextAreaElement>(null);
|
|
||||||
const { prompt, activeTabName } = useAppSelector(promptInputSelector);
|
|
||||||
const dispatch = useAppDispatch();
|
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>) => {
|
const handleChangePrompt = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
dispatch(setPrompt(e.target.value));
|
dispatch(setPrompt(e.target.value));
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import _ from 'lodash';
|
||||||
import { RootState } from '../../app/store';
|
import { RootState } from '../../app/store';
|
||||||
import { tabMap } from '../tabs/InvokeTabs';
|
import { tabMap } from '../tabs/InvokeTabs';
|
||||||
import { OptionsState } from './optionsSlice';
|
import { OptionsState } from './optionsSlice';
|
||||||
|
|
||||||
export const activeTabNameSelector = createSelector(
|
export const activeTabNameSelector = createSelector(
|
||||||
(state: RootState) => state.options,
|
(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 { Tooltip } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import _ from 'lodash';
|
||||||
import { MouseEvent, ReactNode, useCallback, useRef } from 'react';
|
import { MouseEvent, ReactNode, useCallback, useRef } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
||||||
@ -33,6 +34,11 @@ const optionsPanelSelector = createSelector(
|
|||||||
shouldPinOptionsPanel,
|
shouldPinOptionsPanel,
|
||||||
optionsPanelScrollPosition,
|
optionsPanelScrollPosition,
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user