perf(ui): optimize all selectors 2

Mostly selector optimization. Still a few places to tidy up but I'll get to that later.
This commit is contained in:
psychedelicious 2024-08-27 15:57:20 +10:00
parent a41406ca9a
commit dbef1a9e06
164 changed files with 906 additions and 689 deletions

View File

@ -18,7 +18,7 @@ import { ClearQueueConfirmationsAlertDialog } from 'features/queue/components/Cl
import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal'; import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal';
import { activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice'; import { activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice';
import { configChanged } from 'features/system/store/configSlice'; import { configChanged } from 'features/system/store/configSlice';
import { languageSelector } from 'features/system/store/systemSelectors'; import { selectLanguage } from 'features/system/store/systemSelectors';
import { AppContent } from 'features/ui/components/AppContent'; import { AppContent } from 'features/ui/components/AppContent';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import type { TabName } from 'features/ui/store/uiTypes'; import type { TabName } from 'features/ui/store/uiTypes';
@ -53,7 +53,7 @@ const App = ({
selectedStylePresetId, selectedStylePresetId,
destination, destination,
}: Props) => { }: Props) => {
const language = useAppSelector(languageSelector); const language = useAppSelector(selectLanguage);
const logger = useLogger('system'); const logger = useLogger('system');
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const clearStorage = useClearStorage(); const clearStorage = useClearStorage();

View File

@ -1,21 +1,20 @@
import { createSelector } from '@reduxjs/toolkit';
import { createLogWriter } from '@roarr/browser-log-writer'; import { createLogWriter } from '@roarr/browser-log-writer';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectSystemSlice } from 'features/system/store/systemSlice'; import {
selectSystemLogIsEnabled,
selectSystemLogLevel,
selectSystemLogNamespaces,
} from 'features/system/store/systemSlice';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { ROARR, Roarr } from 'roarr'; import { ROARR, Roarr } from 'roarr';
import type { LogNamespace } from './logger'; import type { LogNamespace } from './logger';
import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger'; import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger';
const selectLogLevel = createSelector(selectSystemSlice, (system) => system.logLevel);
const selectLogNamespaces = createSelector(selectSystemSlice, (system) => system.logNamespaces);
const selectLogIsEnabled = createSelector(selectSystemSlice, (system) => system.logIsEnabled);
export const useLogger = (namespace: LogNamespace) => { export const useLogger = (namespace: LogNamespace) => {
const logLevel = useAppSelector(selectLogLevel); const logLevel = useAppSelector(selectSystemLogLevel);
const logNamespaces = useAppSelector(selectLogNamespaces); const logNamespaces = useAppSelector(selectSystemLogNamespaces);
const logIsEnabled = useAppSelector(selectLogIsEnabled); const logIsEnabled = useAppSelector(selectSystemLogIsEnabled);
// The provided Roarr browser log writer uses localStorage to config logging to console // The provided Roarr browser log writer uses localStorage to config logging to console
useEffect(() => { useEffect(() => {

View File

@ -1,2 +1,3 @@
export const STORAGE_PREFIX = '@@invokeai-'; export const STORAGE_PREFIX = '@@invokeai-';
export const EMPTY_ARRAY = []; export const EMPTY_ARRAY = [];
export const EMPTY_OBJECT = {};

View File

@ -1,7 +1,8 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { getImageUsage } from 'features/deleteImageModal/store/selectors'; import { getImageUsage } from 'features/deleteImageModal/store/selectors';
import { nodeEditorReset, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => { export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => {

View File

@ -1,5 +1,6 @@
import { enqueueRequested } from 'app/store/actions'; import { enqueueRequested } from 'app/store/actions';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph'; import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph';
import { buildWorkflowWithValidation } from 'features/nodes/util/workflow/buildWorkflow'; import { buildWorkflowWithValidation } from 'features/nodes/util/workflow/buildWorkflow';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
@ -11,12 +12,12 @@ export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) =
enqueueRequested.match(action) && action.payload.tabName === 'workflows', enqueueRequested.match(action) && action.payload.tabName === 'workflows',
effect: async (action, { getState, dispatch }) => { effect: async (action, { getState, dispatch }) => {
const state = getState(); const state = getState();
const { nodes, edges } = state.nodes.present; const nodes = selectNodesSlice(state);
const workflow = state.workflow; const workflow = state.workflow;
const graph = buildNodesGraph(state.nodes.present); const graph = buildNodesGraph(nodes);
const builtWorkflow = buildWorkflowWithValidation({ const builtWorkflow = buildWorkflowWithValidation({
nodes, nodes: nodes.nodes,
edges, edges: nodes.edges,
workflow, workflow,
}); });

View File

@ -2,6 +2,7 @@ import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { updateAllNodesRequested } from 'features/nodes/store/actions'; import { updateAllNodesRequested } from 'features/nodes/store/actions';
import { $templates, nodesChanged } from 'features/nodes/store/nodesSlice'; import { $templates, nodesChanged } from 'features/nodes/store/nodesSlice';
import { selectNodes } from 'features/nodes/store/selectors';
import { NodeUpdateError } from 'features/nodes/types/error'; import { NodeUpdateError } from 'features/nodes/types/error';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate'; import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate';
@ -14,7 +15,7 @@ export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartLi
startAppListening({ startAppListening({
actionCreator: updateAllNodesRequested, actionCreator: updateAllNodesRequested,
effect: (action, { dispatch, getState }) => { effect: (action, { dispatch, getState }) => {
const { nodes } = getState().nodes.present; const nodes = selectNodes(getState());
const templates = $templates.get(); const templates = $templates.get();
let unableToUpdateCount = 0; let unableToUpdateCount = 0;

View File

@ -32,7 +32,10 @@ type Props = {
children: ReactElement; children: ReactElement;
}; };
const selectShouldEnableInformationalPopovers = createSelector(selectSystemSlice, system => system.shouldEnableInformationalPopovers); const selectShouldEnableInformationalPopovers = createSelector(
selectSystemSlice,
(system) => system.shouldEnableInformationalPopovers
);
export const InformationalPopover = memo(({ feature, children, inPortal = true, ...rest }: Props) => { export const InformationalPopover = memo(({ feature, children, inPortal = true, ...rest }: Props) => {
const shouldEnableInformationalPopovers = useAppSelector(selectShouldEnableInformationalPopovers); const shouldEnableInformationalPopovers = useAppSelector(selectShouldEnableInformationalPopovers);

View File

@ -6,7 +6,8 @@ import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { $templates } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import type { Templates } from 'features/nodes/store/types'; import type { Templates } from 'features/nodes/store/types';
import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice'; import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';

View File

@ -1,4 +1,4 @@
import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; import { createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store'; import type { PersistConfig, RootState } from 'app/store/store';
import type { LoRA } from 'features/controlLayers/store/types'; import type { LoRA } from 'features/controlLayers/store/types';
import { zModelIdentifierField } from 'features/nodes/types/common'; import { zModelIdentifierField } from 'features/nodes/types/common';
@ -65,8 +65,6 @@ export const lorasSlice = createSlice({
export const { loraAdded, loraRecalled, loraDeleted, loraWeightChanged, loraIsEnabledChanged, loraAllDeleted } = export const { loraAdded, loraRecalled, loraDeleted, loraWeightChanged, loraIsEnabledChanged, loraAllDeleted } =
lorasSlice.actions; lorasSlice.actions;
export const selectLoRAsSlice = (state: RootState) => state.loras;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrate = (state: any): any => { const migrate = (state: any): any => {
return state; return state;
@ -78,3 +76,6 @@ export const lorasPersistConfig: PersistConfig<LoRAsState> = {
migrate, migrate,
persistDenylist: [], persistDenylist: [],
}; };
export const selectLoRAsSlice = (state: RootState) => state.loras;
export const selectAddedLoRAs = createSelector(selectLoRAsSlice, (loras) => loras.loras);

View File

@ -1,4 +1,5 @@
import { createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction, Selector } from '@reduxjs/toolkit';
import { createSelector, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store'; import type { PersistConfig, RootState } from 'app/store/store';
import type { RgbaColor } from 'features/controlLayers/store/types'; import type { RgbaColor } from 'features/controlLayers/store/types';
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants'; import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
@ -270,9 +271,6 @@ export const {
modelChanged, modelChanged,
} = paramsSlice.actions; } = paramsSlice.actions;
export const selectParamsSlice = (state: RootState) => state.params;
export const selectBase = createSelector(selectParamsSlice, (params) => params.model?.base);
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrate = (state: any): any => { const migrate = (state: any): any => {
return state; return state;
@ -284,3 +282,55 @@ export const paramsPersistConfig: PersistConfig<ParamsState> = {
migrate, migrate,
persistDenylist: [], persistDenylist: [],
}; };
export const selectParamsSlice = (state: RootState) => state.params;
export const createParamsSelector = <T>(selector: Selector<ParamsState, T>) =>
createSelector(selectParamsSlice, selector);
export const selectBase = createParamsSelector((params) => params.model?.base);
export const selectIsSDXL = createParamsSelector((params) => params.model?.base === 'sdxl');
export const selectModel = createParamsSelector((params) => params.model);
export const selectModelKey = createParamsSelector((params) => params.model?.key);
export const selectVAE = createParamsSelector((params) => params.vae);
export const selectVAEKey = createParamsSelector((params) => params.vae?.key);
export const selectCFGScale = createParamsSelector((params) => params.cfgScale);
export const selectSteps = createParamsSelector((params) => params.steps);
export const selectCFGRescaleMultiplier = createParamsSelector((params) => params.cfgRescaleMultiplier);
export const selectCLIPSKip = createParamsSelector((params) => params.clipSkip);
export const selectCanvasCoherenceEdgeSize = createParamsSelector((params) => params.canvasCoherenceEdgeSize);
export const selectCanvasCoherenceMinDenoise = createParamsSelector((params) => params.canvasCoherenceMinDenoise);
export const selectCanvasCoherenceMode = createParamsSelector((params) => params.canvasCoherenceMode);
export const selectMaskBlur = createParamsSelector((params) => params.maskBlur);
export const selectInfillMethod = createParamsSelector((params) => params.infillMethod);
export const selectInfillTileSize = createParamsSelector((params) => params.infillTileSize);
export const selectInfillPatchmatchDownscaleSize = createParamsSelector(
(params) => params.infillPatchmatchDownscaleSize
);
export const selectInfillColorValue = createParamsSelector((params) => params.infillColorValue);
export const selectImg2imgStrength = createParamsSelector((params) => params.img2imgStrength);
export const selectPositivePrompt = createParamsSelector((params) => params.positivePrompt);
export const selectNegativePrompt = createParamsSelector((params) => params.negativePrompt);
export const selectPositivePrompt2 = createParamsSelector((params) => params.positivePrompt2);
export const selectNegativePrompt2 = createParamsSelector((params) => params.negativePrompt2);
export const selectShouldConcatPrompts = createParamsSelector((params) => params.shouldConcatPrompts);
export const selectScheduler = createParamsSelector((params) => params.scheduler);
export const selectSeamlessXAxis = createParamsSelector((params) => params.seamlessXAxis);
export const selectSeamlessYAxis = createParamsSelector((params) => params.seamlessYAxis);
export const selectSeed = createParamsSelector((params) => params.seed);
export const selectShouldRandomizeSeed = createParamsSelector((params) => params.shouldConcatPrompts);
export const selectVAEPrecision = createParamsSelector((params) => params.vaePrecision);
export const selectIterations = createParamsSelector((params) => params.iterations);
export const selectShouldUseCPUNoise = createParamsSelector((params) => params.shouldUseCpuNoise);
export const selectRefinerCFGScale = createParamsSelector((params) => params.refinerCFGScale);
export const selectRefinerModel = createParamsSelector((params) => params.refinerModel);
export const selectIsRefinerModelSelected = createParamsSelector((params) => Boolean(params.refinerModel));
export const selectRefinerPositiveAestheticScore = createParamsSelector(
(params) => params.refinerPositiveAestheticScore
);
export const selectRefinerNegativeAestheticScore = createParamsSelector(
(params) => params.refinerNegativeAestheticScore
);
export const selectRefinerScheduler = createParamsSelector((params) => params.refinerScheduler);
export const selectRefinerStart = createParamsSelector((params) => params.refinerStart);
export const selectRefinerSteps = createParamsSelector((params) => params.refinerSteps);

View File

@ -11,7 +11,7 @@ import {
selectDeleteImageModalSlice, selectDeleteImageModalSlice,
} from 'features/deleteImageModal/store/slice'; } from 'features/deleteImageModal/store/slice';
import type { ImageUsage } from 'features/deleteImageModal/store/types'; import type { ImageUsage } from 'features/deleteImageModal/store/types';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/selectors';
import { selectSystemSlice, setShouldConfirmOnDelete } from 'features/system/store/systemSlice'; import { selectSystemSlice, setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
import { some } from 'lodash-es'; import { some } from 'lodash-es';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';

View File

@ -2,7 +2,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import type { CanvasState } from 'features/controlLayers/store/types'; import type { CanvasState } from 'features/controlLayers/store/types';
import { selectDeleteImageModalSlice } from 'features/deleteImageModal/store/slice'; import { selectDeleteImageModalSlice } from 'features/deleteImageModal/store/slice';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/selectors';
import type { NodesState } from 'features/nodes/store/types'; import type { NodesState } from 'features/nodes/store/types';
import { isImageFieldInputInstance } from 'features/nodes/types/field'; import { isImageFieldInputInstance } from 'features/nodes/types/field';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';

View File

@ -1,20 +1,19 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { maxPromptsChanged, selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import {
import { selectConfigSlice } from 'features/system/store/configSlice'; maxPromptsChanged,
selectDynamicPromptsCombinatorial,
selectDynamicPromptsMaxPrompts,
} from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { selectMaxPromptsConfig } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selectMaxPrompts = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.maxPrompts);
const selectMaxPromptsConfig = createSelector(selectConfigSlice, (config) => config.sd.dynamicPrompts.maxPrompts);
const selectIsDisabled = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => !dynamicPrompts.combinatorial);
const ParamDynamicPromptsMaxPrompts = () => { const ParamDynamicPromptsMaxPrompts = () => {
const maxPrompts = useAppSelector(selectMaxPrompts); const maxPrompts = useAppSelector(selectDynamicPromptsMaxPrompts);
const config = useAppSelector(selectMaxPromptsConfig); const config = useAppSelector(selectMaxPromptsConfig);
const isDisabled = useAppSelector(selectIsDisabled); const combinatorial = useAppSelector(selectDynamicPromptsCombinatorial);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
@ -26,7 +25,7 @@ const ParamDynamicPromptsMaxPrompts = () => {
); );
return ( return (
<FormControl isDisabled={isDisabled}> <FormControl isDisabled={!combinatorial}>
<InformationalPopover feature="dynamicPromptsMaxPrompts" inPortal={false}> <InformationalPopover feature="dynamicPromptsMaxPrompts" inPortal={false}>
<FormLabel>{t('dynamicPrompts.maxPrompts')}</FormLabel> <FormLabel>{t('dynamicPrompts.maxPrompts')}</FormLabel>
</InformationalPopover> </InformationalPopover>

View File

@ -1,11 +1,15 @@
import type { ChakraProps } from '@invoke-ai/ui-library'; import type { ChakraProps } from '@invoke-ai/ui-library';
import { Flex, FormControl, FormLabel, ListItem, OrderedList, Spinner, Text } from '@invoke-ai/ui-library'; import { Flex, FormControl, FormLabel, ListItem, OrderedList, Spinner, Text } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import {
selectDynamicPromptsIsError,
selectDynamicPromptsIsLoading,
selectDynamicPromptsParsingError,
selectDynamicPromptsPrompts,
} from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiWarningCircleBold } from 'react-icons/pi'; import { PiWarningCircleBold } from 'react-icons/pi';
@ -14,17 +18,12 @@ const listItemStyles: ChakraProps['sx'] = {
'&::marker': { color: 'base.500' }, '&::marker': { color: 'base.500' },
}; };
const selectPrompts = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.prompts);
const selectParsingError = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.parsingError);
const selectIsError = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.isError);
const selectIsLoading = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.isLoading);
const ParamDynamicPromptsPreview = () => { const ParamDynamicPromptsPreview = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const parsingError = useAppSelector(selectParsingError); const parsingError = useAppSelector(selectDynamicPromptsParsingError);
const isError = useAppSelector(selectIsError); const isError = useAppSelector(selectDynamicPromptsIsError);
const isLoading = useAppSelector(selectIsLoading); const isLoading = useAppSelector(selectDynamicPromptsIsLoading);
const prompts = useAppSelector(selectPrompts); const prompts = useAppSelector(selectDynamicPromptsPrompts);
const label = useMemo(() => { const label = useMemo(() => {
let _label = `${t('dynamicPrompts.promptsPreview')} (${prompts.length})`; let _label = `${t('dynamicPrompts.promptsPreview')} (${prompts.length})`;

View File

@ -1,22 +1,19 @@
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library'; import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { import {
isSeedBehaviour, isSeedBehaviour,
seedBehaviourChanged, seedBehaviourChanged,
selectDynamicPromptsSlice, selectDynamicPromptsSeedBehaviour,
} from 'features/dynamicPrompts/store/dynamicPromptsSlice'; } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selectSeedBehaviour = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.seedBehaviour);
const ParamDynamicPromptsSeedBehaviour = () => { const ParamDynamicPromptsSeedBehaviour = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const seedBehaviour = useAppSelector(selectSeedBehaviour); const seedBehaviour = useAppSelector(selectDynamicPromptsSeedBehaviour);
const options = useMemo<ComboboxOption[]>(() => { const options = useMemo<ComboboxOption[]>(() => {
return [ return [

View File

@ -1,9 +1,11 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library'; import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { IconButton, spinAnimation, Tooltip } from '@invoke-ai/ui-library'; import { IconButton, spinAnimation, Tooltip } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useDynamicPromptsModal } from 'features/dynamicPrompts/hooks/useDynamicPromptsModal'; import { useDynamicPromptsModal } from 'features/dynamicPrompts/hooks/useDynamicPromptsModal';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import {
selectDynamicPromptsIsError,
selectDynamicPromptsIsLoading,
} from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { memo } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { BsBracesAsterisk } from 'react-icons/bs'; import { BsBracesAsterisk } from 'react-icons/bs';
@ -12,15 +14,10 @@ const loadingStyles: SystemStyleObject = {
svg: { animation: spinAnimation }, svg: { animation: spinAnimation },
}; };
const selectIsError = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) =>
Boolean(dynamicPrompts.isError || dynamicPrompts.parsingError)
);
const selectIsLoading = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.isLoading);
export const ShowDynamicPromptsPreviewButton = memo(() => { export const ShowDynamicPromptsPreviewButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const isLoading = useAppSelector(selectIsLoading); const isLoading = useAppSelector(selectDynamicPromptsIsLoading);
const isError = useAppSelector(selectIsError); const isError = useAppSelector(selectDynamicPromptsIsError);
const { isOpen, onOpen } = useDynamicPromptsModal(); const { isOpen, onOpen } = useDynamicPromptsModal();
return ( return (

View File

@ -1,5 +1,5 @@
import type { PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction, Selector } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSelector, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store'; import type { PersistConfig, RootState } from 'app/store/store';
import { z } from 'zod'; import { z } from 'zod';
@ -72,8 +72,6 @@ export const {
seedBehaviourChanged, seedBehaviourChanged,
} = dynamicPromptsSlice.actions; } = dynamicPromptsSlice.actions;
export const selectDynamicPromptsSlice = (state: RootState) => state.dynamicPrompts;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateDynamicPromptsState = (state: any): any => { const migrateDynamicPromptsState = (state: any): any => {
if (!('_version' in state)) { if (!('_version' in state)) {
@ -88,3 +86,23 @@ export const dynamicPromptsPersistConfig: PersistConfig<DynamicPromptsState> = {
migrate: migrateDynamicPromptsState, migrate: migrateDynamicPromptsState,
persistDenylist: ['prompts'], persistDenylist: ['prompts'],
}; };
export const selectDynamicPromptsSlice = (state: RootState) => state.dynamicPrompts;
const createDynamicPromptsSelector = <T>(selector: Selector<DynamicPromptsState, T>) =>
createSelector(selectDynamicPromptsSlice, selector);
export const selectDynamicPromptsMaxPrompts = createDynamicPromptsSelector(
(dynamicPrompts) => dynamicPrompts.maxPrompts
);
export const selectDynamicPromptsCombinatorial = createDynamicPromptsSelector(
(dynamicPrompts) => dynamicPrompts.combinatorial
);
export const selectDynamicPromptsPrompts = createDynamicPromptsSelector((dynamicPrompts) => dynamicPrompts.prompts);
export const selectDynamicPromptsParsingError = createDynamicPromptsSelector(
(dynamicPrompts) => dynamicPrompts.parsingError
);
export const selectDynamicPromptsIsError = createDynamicPromptsSelector((dynamicPrompts) => dynamicPrompts.isError);
export const selectDynamicPromptsIsLoading = createDynamicPromptsSelector((dynamicPrompts) => dynamicPrompts.isLoading);
export const selectDynamicPromptsSeedBehaviour = createDynamicPromptsSelector(
(dynamicPrompts) => dynamicPrompts.seedBehaviour
);

View File

@ -22,7 +22,6 @@ type Props = {
setBoardToDelete: (board?: BoardDTO) => void; setBoardToDelete: (board?: BoardDTO) => void;
}; };
export const BoardsList = ({ isPrivate, setBoardToDelete }: Props) => { export const BoardsList = ({ isPrivate, setBoardToDelete }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const selectedBoardId = useAppSelector(selectSelectedBoardId); const selectedBoardId = useAppSelector(selectSelectedBoardId);

View File

@ -17,7 +17,7 @@ import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import ImageUsageMessage from 'features/deleteImageModal/components/ImageUsageMessage'; import ImageUsageMessage from 'features/deleteImageModal/components/ImageUsageMessage';
import { getImageUsage } from 'features/deleteImageModal/store/selectors'; import { getImageUsage } from 'features/deleteImageModal/store/selectors';
import type { ImageUsage } from 'features/deleteImageModal/store/types'; import type { ImageUsage } from 'features/deleteImageModal/store/types';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/selectors';
import { some } from 'lodash-es'; import { some } from 'lodash-es';
import { memo, useCallback, useMemo, useRef } from 'react'; import { memo, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -2,7 +2,10 @@ import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { handlers, parseAndRecallAllMetadata, parseAndRecallPrompts } from 'features/metadata/util/handlers'; import { handlers, parseAndRecallAllMetadata, parseAndRecallPrompts } from 'features/metadata/util/handlers';
import { $stylePresetModalState } from 'features/stylePresets/store/stylePresetModal'; import { $stylePresetModalState } from 'features/stylePresets/store/stylePresetModal';
import { activeStylePresetIdChanged, selectActivePresetId } from 'features/stylePresets/store/stylePresetSlice'; import {
activeStylePresetIdChanged,
selectStylePresetActivePresetId,
} from 'features/stylePresets/store/stylePresetSlice';
import { toast } from 'features/toast/toast'; import { toast } from 'features/toast/toast';
import { selectActiveTab } from 'features/ui/store/uiSelectors'; import { selectActiveTab } from 'features/ui/store/uiSelectors';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
@ -13,7 +16,7 @@ import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
export const useImageActions = (image_name?: string) => { export const useImageActions = (image_name?: string) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const activeStylePresetId = useAppSelector(selectActivePresetId); const activeStylePresetId = useAppSelector(selectStylePresetActivePresetId);
const activeTabName = useAppSelector(selectActiveTab); const activeTabName = useAppSelector(selectActiveTab);
const { metadata, isLoading: isLoadingMetadata } = useDebouncedMetadata(image_name); const { metadata, isLoading: isLoadingMetadata } = useDebouncedMetadata(image_name);
const [hasMetadata, setHasMetadata] = useState(false); const [hasMetadata, setHasMetadata] = useState(false);

View File

@ -1,5 +1,5 @@
import type { PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSelector, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store'; import type { PersistConfig, RootState } from 'app/store/store';
import type { ModelType } from 'services/api/types'; import type { ModelType } from 'services/api/types';
@ -50,8 +50,6 @@ export const modelManagerV2Slice = createSlice({
export const { setSelectedModelKey, setSearchTerm, setFilteredModelType, setSelectedModelMode, setScanPath } = export const { setSelectedModelKey, setSearchTerm, setFilteredModelType, setSelectedModelMode, setScanPath } =
modelManagerV2Slice.actions; modelManagerV2Slice.actions;
export const selectModelManagerV2Slice = (state: RootState) => state.modelmanagerV2;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateModelManagerState = (state: any): any => { const migrateModelManagerState = (state: any): any => {
if (!('_version' in state)) { if (!('_version' in state)) {
@ -66,3 +64,13 @@ export const modelManagerV2PersistConfig: PersistConfig<ModelManagerState> = {
migrate: migrateModelManagerState, migrate: migrateModelManagerState,
persistDenylist: ['selectedModelKey', 'selectedModelMode', 'filteredModelType', 'searchTerm'], persistDenylist: ['selectedModelKey', 'selectedModelMode', 'filteredModelType', 'searchTerm'],
}; };
export const selectModelManagerV2Slice = (state: RootState) => state.modelmanagerV2;
export const createModelManagerSelector = <T>(selector: (state: ModelManagerState) => T) =>
createSelector(selectModelManagerV2Slice, selector);
export const selectSelectedModelKey = createModelManagerSelector((modelManager) => modelManager.selectedModelKey);
export const selectSelectedModelMode = createModelManagerSelector((modelManager) => modelManager.selectedModelMode);
export const selectSearchTerm = createModelManagerSelector((mm) => mm.searchTerm);
export const selectFilteredModelType = createModelManagerSelector((mm) => mm.filteredModelType);

View File

@ -1,6 +1,6 @@
import { Button, Flex, FormControl, FormErrorMessage, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library'; import { Button, Flex, FormControl, FormErrorMessage, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setScanPath } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { createModelManagerSelector, setScanPath } from 'features/modelManagerV2/store/modelManagerV2Slice';
import type { ChangeEventHandler } from 'react'; import type { ChangeEventHandler } from 'react';
import { memo, useCallback, useState } from 'react'; import { memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -8,8 +8,10 @@ import { useLazyScanFolderQuery } from 'services/api/endpoints/models';
import { ScanModelsResults } from './ScanFolderResults'; import { ScanModelsResults } from './ScanFolderResults';
const selectScanPath = createModelManagerSelector((mm) => mm.scanPath);
export const ScanModelsForm = memo(() => { export const ScanModelsForm = memo(() => {
const scanPath = useAppSelector((state) => state.modelmanagerV2.scanPath); const scanPath = useAppSelector(selectScanPath);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [errorMessage, setErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState('');
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -1,7 +1,11 @@
import { Flex, Text } from '@invoke-ai/ui-library'; import { Flex, Text } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import type { FilterableModelType } from 'features/modelManagerV2/store/modelManagerV2Slice'; import {
type FilterableModelType,
selectFilteredModelType,
selectSearchTerm,
} from 'features/modelManagerV2/store/modelManagerV2Slice';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@ -23,8 +27,8 @@ import { FetchingModelsLoader } from './FetchingModelsLoader';
import { ModelListWrapper } from './ModelListWrapper'; import { ModelListWrapper } from './ModelListWrapper';
const ModelList = () => { const ModelList = () => {
const filteredModelType = useAppSelector((s) => s.modelmanagerV2.filteredModelType); const filteredModelType = useAppSelector(selectFilteredModelType);
const searchTerm = useAppSelector((s) => s.modelmanagerV2.searchTerm); const searchTerm = useAppSelector(selectSearchTerm);
const { t } = useTranslation(); const { t } = useTranslation();
const [mainModels, { isLoading: isLoadingMainModels }] = useMainModels(); const [mainModels, { isLoading: isLoadingMainModels }] = useMainModels();

View File

@ -1,6 +1,6 @@
import { Flex, IconButton, Input, InputGroup, InputRightElement, Spacer } from '@invoke-ai/ui-library'; import { Flex, IconButton, Input, InputGroup, InputRightElement, Spacer } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setSearchTerm } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { selectSearchTerm, setSearchTerm } from 'features/modelManagerV2/store/modelManagerV2Slice';
import { t } from 'i18next'; import { t } from 'i18next';
import type { ChangeEventHandler } from 'react'; import type { ChangeEventHandler } from 'react';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
@ -10,7 +10,7 @@ import { ModelTypeFilter } from './ModelTypeFilter';
export const ModelListNavigation = memo(() => { export const ModelListNavigation = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const searchTerm = useAppSelector((s) => s.modelmanagerV2.searchTerm); const searchTerm = useAppSelector(selectSearchTerm);
const handleSearch: ChangeEventHandler<HTMLInputElement> = useCallback( const handleSearch: ChangeEventHandler<HTMLInputElement> = useCallback(
(event) => { (event) => {

View File

@ -1,7 +1,7 @@
import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library'; import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import type { FilterableModelType } from 'features/modelManagerV2/store/modelManagerV2Slice'; import type { FilterableModelType } from 'features/modelManagerV2/store/modelManagerV2Slice';
import { setFilteredModelType } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { selectFilteredModelType, setFilteredModelType } from 'features/modelManagerV2/store/modelManagerV2Slice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiFunnelBold } from 'react-icons/pi'; import { PiFunnelBold } from 'react-icons/pi';
@ -26,7 +26,7 @@ export const ModelTypeFilter = memo(() => {
}), }),
[t] [t]
); );
const filteredModelType = useAppSelector((s) => s.modelmanagerV2.filteredModelType); const filteredModelType = useAppSelector(selectFilteredModelType);
const selectModelType = useCallback( const selectModelType = useCallback(
(option: FilterableModelType) => { (option: FilterableModelType) => {

View File

@ -1,12 +1,13 @@
import { Box } from '@invoke-ai/ui-library'; import { Box } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice';
import { memo } from 'react'; import { memo } from 'react';
import { InstallModels } from './InstallModels'; import { InstallModels } from './InstallModels';
import { Model } from './ModelPanel/Model'; import { Model } from './ModelPanel/Model';
export const ModelPane = memo(() => { export const ModelPane = memo(() => {
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); const selectedModelKey = useAppSelector(selectSelectedModelKey);
return ( return (
<Box layerStyle="first" p={4} borderRadius="base" w="50%" h="full"> <Box layerStyle="first" p={4} borderRadius="base" w="50%" h="full">
{selectedModelKey ? <Model key={selectedModelKey} /> : <InstallModels />} {selectedModelKey ? <Model key={selectedModelKey} /> : <InstallModels />}

View File

@ -2,6 +2,7 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
import { selectCFGRescaleMultiplierConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form'; import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form'; import { useController } from 'react-hook-form';
@ -14,14 +15,12 @@ type DefaultCfgRescaleMultiplierType = MainModelDefaultSettingsFormData['cfgResc
export const DefaultCfgRescaleMultiplier = memo((props: UseControllerProps<MainModelDefaultSettingsFormData>) => { export const DefaultCfgRescaleMultiplier = memo((props: UseControllerProps<MainModelDefaultSettingsFormData>) => {
const { field } = useController(props); const { field } = useController(props);
const sliderMin = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.sliderMin); const config = useAppSelector(selectCFGRescaleMultiplierConfig);
const sliderMax = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.fineStep);
const { t } = useTranslation(); const { t } = useTranslation();
const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]); const marks = useMemo(
() => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax],
[config.sliderMax, config.sliderMin]
);
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
@ -54,20 +53,20 @@ export const DefaultCfgRescaleMultiplier = memo((props: UseControllerProps<MainM
<Flex w="full" gap={4}> <Flex w="full" gap={4}>
<CompositeSlider <CompositeSlider
value={value} value={value}
min={sliderMin} min={config.sliderMin}
max={sliderMax} max={config.sliderMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
marks={marks} marks={marks}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
<CompositeNumberInput <CompositeNumberInput
value={value} value={value}
min={numberInputMin} min={config.numberInputMin}
max={numberInputMax} max={config.numberInputMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />

View File

@ -2,6 +2,7 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
import { selectCFGScaleConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form'; import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form'; import { useController } from 'react-hook-form';
@ -14,14 +15,12 @@ type DefaultCfgType = MainModelDefaultSettingsFormData['cfgScale'];
export const DefaultCfgScale = memo((props: UseControllerProps<MainModelDefaultSettingsFormData>) => { export const DefaultCfgScale = memo((props: UseControllerProps<MainModelDefaultSettingsFormData>) => {
const { field } = useController(props); const { field } = useController(props);
const sliderMin = useAppSelector((s) => s.config.sd.guidance.sliderMin); const config = useAppSelector(selectCFGScaleConfig);
const sliderMax = useAppSelector((s) => s.config.sd.guidance.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.guidance.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.guidance.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.guidance.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.guidance.fineStep);
const { t } = useTranslation(); const { t } = useTranslation();
const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]); const marks = useMemo(
() => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax],
[config.sliderMax, config.sliderMin]
);
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
@ -54,20 +53,20 @@ export const DefaultCfgScale = memo((props: UseControllerProps<MainModelDefaultS
<Flex w="full" gap={4}> <Flex w="full" gap={4}>
<CompositeSlider <CompositeSlider
value={value} value={value}
min={sliderMin} min={config.sliderMin}
max={sliderMax} max={config.sliderMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
marks={marks} marks={marks}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
<CompositeNumberInput <CompositeNumberInput
value={value} value={value}
min={numberInputMin} min={config.numberInputMin}
max={numberInputMax} max={config.numberInputMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />

View File

@ -2,6 +2,7 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
import { selectHeightConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form'; import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form'; import { useController } from 'react-hook-form';
@ -18,14 +19,12 @@ type Props = {
export const DefaultHeight = memo(({ control, optimalDimension }: Props) => { export const DefaultHeight = memo(({ control, optimalDimension }: Props) => {
const { field } = useController({ control, name: 'height' }); const { field } = useController({ control, name: 'height' });
const sliderMin = useAppSelector((s) => s.config.sd.height.sliderMin); const config = useAppSelector(selectHeightConfig);
const sliderMax = useAppSelector((s) => s.config.sd.height.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.height.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.height.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.height.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.height.fineStep);
const { t } = useTranslation(); const { t } = useTranslation();
const marks = useMemo(() => [sliderMin, optimalDimension, sliderMax], [sliderMin, optimalDimension, sliderMax]); const marks = useMemo(
() => [config.sliderMin, optimalDimension, config.sliderMax],
[config.sliderMin, optimalDimension, config.sliderMax]
);
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
@ -58,20 +57,20 @@ export const DefaultHeight = memo(({ control, optimalDimension }: Props) => {
<Flex w="full" gap={4}> <Flex w="full" gap={4}>
<CompositeSlider <CompositeSlider
value={value} value={value}
min={sliderMin} min={config.sliderMin}
max={sliderMax} max={config.sliderMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
marks={marks} marks={marks}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
<CompositeNumberInput <CompositeNumberInput
value={value} value={value}
min={numberInputMin} min={config.numberInputMin}
max={numberInputMax} max={config.numberInputMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />

View File

@ -2,6 +2,7 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
import { selectStepsConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form'; import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form'; import { useController } from 'react-hook-form';
@ -14,14 +15,12 @@ type DefaultSteps = MainModelDefaultSettingsFormData['steps'];
export const DefaultSteps = memo((props: UseControllerProps<MainModelDefaultSettingsFormData>) => { export const DefaultSteps = memo((props: UseControllerProps<MainModelDefaultSettingsFormData>) => {
const { field } = useController(props); const { field } = useController(props);
const sliderMin = useAppSelector((s) => s.config.sd.steps.sliderMin); const config = useAppSelector(selectStepsConfig);
const sliderMax = useAppSelector((s) => s.config.sd.steps.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.steps.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.steps.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.steps.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.steps.fineStep);
const { t } = useTranslation(); const { t } = useTranslation();
const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]); const marks = useMemo(
() => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax],
[config.sliderMax, config.sliderMin]
);
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
@ -54,20 +53,20 @@ export const DefaultSteps = memo((props: UseControllerProps<MainModelDefaultSett
<Flex w="full" gap={4}> <Flex w="full" gap={4}>
<CompositeSlider <CompositeSlider
value={value} value={value}
min={sliderMin} min={config.sliderMin}
max={sliderMax} max={config.sliderMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
marks={marks} marks={marks}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
<CompositeNumberInput <CompositeNumberInput
value={value} value={value}
min={numberInputMin} min={config.numberInputMin}
max={numberInputMax} max={config.numberInputMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />

View File

@ -3,6 +3,7 @@ import { Combobox, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query'; import { skipToken } from '@reduxjs/toolkit/query';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { selectSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form'; import type { UseControllerProps } from 'react-hook-form';
@ -18,7 +19,7 @@ type DefaultVaeType = MainModelDefaultSettingsFormData['vae'];
export const DefaultVae = memo((props: UseControllerProps<MainModelDefaultSettingsFormData>) => { export const DefaultVae = memo((props: UseControllerProps<MainModelDefaultSettingsFormData>) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { field } = useController(props); const { field } = useController(props);
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); const selectedModelKey = useAppSelector(selectSelectedModelKey);
const { data: modelData } = useGetModelConfigQuery(selectedModelKey ?? skipToken); const { data: modelData } = useGetModelConfigQuery(selectedModelKey ?? skipToken);
const [vaeModels] = useVAEModels(); const [vaeModels] = useVAEModels();

View File

@ -2,6 +2,7 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle'; import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
import { selectWidthConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form'; import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form'; import { useController } from 'react-hook-form';
@ -18,14 +19,12 @@ type Props = {
export const DefaultWidth = memo(({ control, optimalDimension }: Props) => { export const DefaultWidth = memo(({ control, optimalDimension }: Props) => {
const { field } = useController({ control, name: 'width' }); const { field } = useController({ control, name: 'width' });
const sliderMin = useAppSelector((s) => s.config.sd.width.sliderMin); const config = useAppSelector(selectWidthConfig);
const sliderMax = useAppSelector((s) => s.config.sd.width.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.width.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.width.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.width.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.width.fineStep);
const { t } = useTranslation(); const { t } = useTranslation();
const marks = useMemo(() => [sliderMin, optimalDimension, sliderMax], [sliderMin, optimalDimension, sliderMax]); const marks = useMemo(
() => [config.sliderMin, optimalDimension, config.sliderMax],
[config.sliderMin, optimalDimension, config.sliderMax]
);
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
@ -58,20 +57,20 @@ export const DefaultWidth = memo(({ control, optimalDimension }: Props) => {
<Flex w="full" gap={4}> <Flex w="full" gap={4}>
<CompositeSlider <CompositeSlider
value={value} value={value}
min={sliderMin} min={config.sliderMin}
max={sliderMax} max={config.sliderMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
marks={marks} marks={marks}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
<CompositeNumberInput <CompositeNumberInput
value={value} value={value}
min={numberInputMin} min={config.numberInputMin}
max={numberInputMax} max={config.numberInputMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />

View File

@ -1,6 +1,7 @@
import { Button, Flex, Heading, SimpleGrid } from '@invoke-ai/ui-library'; import { Button, Flex, Heading, SimpleGrid } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useMainModelDefaultSettings } from 'features/modelManagerV2/hooks/useMainModelDefaultSettings'; import { useMainModelDefaultSettings } from 'features/modelManagerV2/hooks/useMainModelDefaultSettings';
import { selectSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice';
import { DefaultHeight } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight'; import { DefaultHeight } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight';
import { DefaultWidth } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth'; import { DefaultWidth } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth';
import type { ParameterScheduler } from 'features/parameters/types/parameterSchemas'; import type { ParameterScheduler } from 'features/parameters/types/parameterSchemas';
@ -42,7 +43,7 @@ type Props = {
}; };
export const MainModelDefaultSettings = memo(({ modelConfig }: Props) => { export const MainModelDefaultSettings = memo(({ modelConfig }: Props) => {
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); const selectedModelKey = useAppSelector(selectSelectedModelKey);
const { t } = useTranslation(); const { t } = useTranslation();
const defaultSettingsDefaults = useMainModelDefaultSettings(modelConfig); const defaultSettingsDefaults = useMainModelDefaultSettings(modelConfig);

View File

@ -1,5 +1,6 @@
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback, IAINoContentFallbackWithSpinner } from 'common/components/IAIImageFallback'; import { IAINoContentFallback, IAINoContentFallbackWithSpinner } from 'common/components/IAIImageFallback';
import { selectSelectedModelKey, selectSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiExclamationMarkBold } from 'react-icons/pi'; import { PiExclamationMarkBold } from 'react-icons/pi';
@ -10,8 +11,8 @@ import { ModelView } from './ModelView';
export const Model = memo(() => { export const Model = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const selectedModelMode = useAppSelector((s) => s.modelmanagerV2.selectedModelMode); const selectedModelMode = useAppSelector(selectSelectedModelMode);
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey); const selectedModelKey = useAppSelector(selectSelectedModelKey);
const { data: modelConfigs, isLoading } = useGetModelConfigsQuery(); const { data: modelConfigs, isLoading } = useGetModelConfigsQuery();
const modelConfig = useMemo(() => { const modelConfig = useMemo(() => {
if (!modelConfigs) { if (!modelConfigs) {

View File

@ -18,6 +18,7 @@ import {
nodesChanged, nodesChanged,
openAddNodePopover, openAddNodePopover,
} from 'features/nodes/store/nodesSlice'; } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupiedPosition'; import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupiedPosition';
import { getFirstValidConnection } from 'features/nodes/store/util/getFirstValidConnection'; import { getFirstValidConnection } from 'features/nodes/store/util/getFirstValidConnection';
import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil'; import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
@ -137,7 +138,7 @@ const AddNodePopover = () => {
// Find a cozy spot for the node // Find a cozy spot for the node
const cursorPos = $cursorPos.get(); const cursorPos = $cursorPos.get();
const { nodes, edges } = store.getState().nodes.present; const { nodes, edges } = selectNodesSlice(store.getState());
node.position = findUnoccupiedPosition(nodes, cursorPos?.x ?? node.position.x, cursorPos?.y ?? node.position.y); node.position = findUnoccupiedPosition(nodes, cursorPos?.x ?? node.position.x, cursorPos?.y ?? node.position.y);
node.selected = true; node.selected = true;
@ -184,7 +185,7 @@ const AddNodePopover = () => {
const target = handleType === 'target' ? pendingConnection.nodeId : node.id; const target = handleType === 'target' ? pendingConnection.nodeId : node.id;
const targetHandle = handleType === 'target' ? pendingConnection.handleId : null; const targetHandle = handleType === 'target' ? pendingConnection.handleId : null;
const { nodes, edges } = store.getState().nodes.present; const { nodes, edges } = selectNodesSlice(store.getState());
const connection = getFirstValidConnection( const connection = getFirstValidConnection(
source, source,
sourceHandle, sourceHandle,

View File

@ -21,7 +21,15 @@ import {
undo, undo,
} from 'features/nodes/store/nodesSlice'; } from 'features/nodes/store/nodesSlice';
import { $flow, $needsFit } from 'features/nodes/store/reactFlowInstance'; import { $flow, $needsFit } from 'features/nodes/store/reactFlowInstance';
import {
selectEdges,
selectMayRedo,
selectMayUndo,
selectNodes,
selectNodesSlice,
} from 'features/nodes/store/selectors';
import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil'; import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
import { selectSelectionMode, selectShouldSnapToGrid } from 'features/nodes/store/workflowSettingsSlice';
import type { CSSProperties, MouseEvent } from 'react'; import type { CSSProperties, MouseEvent } from 'react';
import { memo, useCallback, useMemo, useRef } from 'react'; import { memo, useCallback, useMemo, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
@ -66,14 +74,14 @@ const selectCancelConnection = (state: ReactFlowState) => state.cancelConnection
export const Flow = memo(() => { export const Flow = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const nodes = useAppSelector((s) => s.nodes.present.nodes); const nodes = useAppSelector(selectNodes);
const edges = useAppSelector((s) => s.nodes.present.edges); const edges = useAppSelector(selectEdges);
const viewport = useStore($viewport); const viewport = useStore($viewport);
const needsFit = useStore($needsFit); const needsFit = useStore($needsFit);
const mayUndo = useAppSelector((s) => s.nodes.past.length > 0); const mayUndo = useAppSelector(selectMayUndo);
const mayRedo = useAppSelector((s) => s.nodes.future.length > 0); const mayRedo = useAppSelector(selectMayRedo);
const shouldSnapToGrid = useAppSelector((s) => s.workflowSettings.shouldSnapToGrid); const shouldSnapToGrid = useAppSelector(selectShouldSnapToGrid);
const selectionMode = useAppSelector((s) => s.workflowSettings.selectionMode); const selectionMode = useAppSelector(selectSelectionMode);
const { onConnectStart, onConnect, onConnectEnd } = useConnection(); const { onConnectStart, onConnect, onConnectEnd } = useConnection();
const flowWrapper = useRef<HTMLDivElement>(null); const flowWrapper = useRef<HTMLDivElement>(null);
const isValidConnection = useIsValidConnection(); const isValidConnection = useIsValidConnection();
@ -214,7 +222,7 @@ export const Flow = memo(() => {
const onSelectAllHotkey = useCallback( const onSelectAllHotkey = useCallback(
(e: KeyboardEvent) => { (e: KeyboardEvent) => {
e.preventDefault(); e.preventDefault();
const { nodes, edges } = store.getState().nodes.present; const { nodes, edges } = selectNodesSlice(store.getState());
const nodeChanges: NodeChange[] = []; const nodeChanges: NodeChange[] = [];
const edgeChanges: EdgeChange[] = []; const edgeChanges: EdgeChange[] = [];
nodes.forEach(({ id, selected }) => { nodes.forEach(({ id, selected }) => {
@ -280,7 +288,7 @@ export const Flow = memo(() => {
useHotkeys('esc', onEscapeHotkey); useHotkeys('esc', onEscapeHotkey);
const onDeleteHotkey = useCallback(() => { const onDeleteHotkey = useCallback(() => {
const { nodes, edges } = store.getState().nodes.present; const { nodes, edges } = selectNodesSlice(store.getState());
const nodeChanges: NodeChange[] = []; const nodeChanges: NodeChange[] = [];
const edgeChanges: EdgeChange[] = []; const edgeChanges: EdgeChange[] = [];
nodes nodes

View File

@ -3,6 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar'; import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor'; import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { $pendingConnection } from 'features/nodes/store/nodesSlice'; import { $pendingConnection } from 'features/nodes/store/nodesSlice';
import { selectShouldAnimateEdges, selectShouldColorEdges } from 'features/nodes/store/workflowSettingsSlice';
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import type { ConnectionLineComponentProps } from 'reactflow'; import type { ConnectionLineComponentProps } from 'reactflow';
@ -12,8 +13,8 @@ const pathStyles: CSSProperties = { opacity: 0.8 };
const CustomConnectionLine = ({ fromX, fromY, fromPosition, toX, toY, toPosition }: ConnectionLineComponentProps) => { const CustomConnectionLine = ({ fromX, fromY, fromPosition, toX, toY, toPosition }: ConnectionLineComponentProps) => {
const pendingConnection = useStore($pendingConnection); const pendingConnection = useStore($pendingConnection);
const shouldColorEdges = useAppSelector((state) => state.workflowSettings.shouldColorEdges); const shouldColorEdges = useAppSelector(selectShouldColorEdges);
const shouldAnimateEdges = useAppSelector((state) => state.workflowSettings.shouldAnimateEdges); const shouldAnimateEdges = useAppSelector(selectShouldAnimateEdges);
const stroke = useMemo(() => { const stroke = useMemo(() => {
if (shouldColorEdges && pendingConnection) { if (shouldColorEdges && pendingConnection) {
return getFieldColor(pendingConnection.fieldTemplate.type); return getFieldColor(pendingConnection.fieldTemplate.type);

View File

@ -3,6 +3,7 @@ import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { getEdgeStyles } from 'features/nodes/components/flow/edges/util/getEdgeColor'; import { getEdgeStyles } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { $templates } from 'features/nodes/store/nodesSlice'; import { $templates } from 'features/nodes/store/nodesSlice';
import { selectShouldShowEdgeLabels } from 'features/nodes/store/workflowSettingsSlice';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import type { EdgeProps } from 'reactflow'; import type { EdgeProps } from 'reactflow';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow'; import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
@ -30,7 +31,7 @@ const InvocationDefaultEdge = ({
); );
const { shouldAnimateEdges, areConnectedNodesSelected, stroke, label } = useAppSelector(selector); const { shouldAnimateEdges, areConnectedNodesSelected, stroke, label } = useAppSelector(selector);
const shouldShowEdgeLabels = useAppSelector((s) => s.workflowSettings.shouldShowEdgeLabels); const shouldShowEdgeLabels = useAppSelector(selectShouldShowEdgeLabels);
const [edgePath, labelX, labelY] = getBezierPath({ const [edgePath, labelX, labelY] = getBezierPath({
sourceX, sourceX,

View File

@ -1,7 +1,7 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar'; import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/selectors';
import type { Templates } from 'features/nodes/store/types'; import type { Templates } from 'features/nodes/store/types';
import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice'; import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';

View File

@ -1,7 +1,9 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import InvocationNode from 'features/nodes/components/flow/nodes/Invocation/InvocationNode'; import InvocationNode from 'features/nodes/components/flow/nodes/Invocation/InvocationNode';
import { $templates } from 'features/nodes/store/nodesSlice'; import { $templates } from 'features/nodes/store/nodesSlice';
import { selectNodes } from 'features/nodes/store/selectors';
import type { InvocationNodeData } from 'features/nodes/types/invocation'; import type { InvocationNodeData } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import type { NodeProps } from 'reactflow'; import type { NodeProps } from 'reactflow';
@ -13,7 +15,11 @@ const InvocationNodeWrapper = (props: NodeProps<InvocationNodeData>) => {
const { id: nodeId, type, isOpen, label } = data; const { id: nodeId, type, isOpen, label } = data;
const templates = useStore($templates); const templates = useStore($templates);
const hasTemplate = useMemo(() => Boolean(templates[type]), [templates, type]); const hasTemplate = useMemo(() => Boolean(templates[type]), [templates, type]);
const nodeExists = useAppSelector((s) => Boolean(s.nodes.present.nodes.find((n) => n.id === nodeId))); const selectNodeExists = useMemo(
() => createSelector(selectNodes, (nodes) => Boolean(nodes.find((n) => n.id === nodeId))),
[nodeId]
);
const nodeExists = useAppSelector(selectNodeExists);
if (!nodeExists) { if (!nodeExists) {
return null; return null;

View File

@ -2,8 +2,8 @@ import { Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { $templates } from 'features/nodes/store/nodesSlice';
import { selectInvocationNode } from 'features/nodes/store/selectors'; import { selectInvocationNode, selectNodesSlice } from 'features/nodes/store/selectors';
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox'; import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
import { fieldT5EncoderValueChanged } from 'features/nodes/store/nodesSlice'; import { fieldT5EncoderValueChanged } from 'features/nodes/store/nodesSlice';
import type { T5EncoderModelFieldInputInstance, T5EncoderModelFieldInputTemplate } from 'features/nodes/types/field'; import type { T5EncoderModelFieldInputInstance, T5EncoderModelFieldInputTemplate } from 'features/nodes/types/field';
import { selectIsModelsTabDisabled } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useT5EncoderModels } from 'services/api/hooks/modelsByType'; import { useT5EncoderModels } from 'services/api/hooks/modelsByType';
@ -15,7 +16,7 @@ type Props = FieldComponentProps<T5EncoderModelFieldInputInstance, T5EncoderMode
const T5EncoderModelFieldInputComponent = (props: Props) => { const T5EncoderModelFieldInputComponent = (props: Props) => {
const { nodeId, field } = props; const { nodeId, field } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const disabledTabs = useAppSelector((s) => s.config.disabledTabs); const isModelsTabDisabled = useAppSelector(selectIsModelsTabDisabled);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [modelConfigs, { isLoading }] = useT5EncoderModels(); const [modelConfigs, { isLoading }] = useT5EncoderModels();
const _onChange = useCallback( const _onChange = useCallback(
@ -42,7 +43,7 @@ const T5EncoderModelFieldInputComponent = (props: Props) => {
return ( return (
<Flex w="full" alignItems="center" gap={2}> <Flex w="full" alignItems="center" gap={2}>
<Tooltip label={!disabledTabs.includes('models') && t('modelManager.starterModelsInModelManager')}> <Tooltip label={!isModelsTabDisabled && t('modelManager.starterModelsInModelManager')}>
<FormControl className="nowheel nodrag" isDisabled={!options.length} isInvalid={!value}> <FormControl className="nowheel nodrag" isDisabled={!options.length} isInvalid={!value}>
<Combobox <Combobox
value={value} value={value}

View File

@ -5,6 +5,8 @@ import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay';
import { useExecutionState } from 'features/nodes/hooks/useExecutionState'; import { useExecutionState } from 'features/nodes/hooks/useExecutionState';
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode'; import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
import { nodesChanged } from 'features/nodes/store/nodesSlice'; import { nodesChanged } from 'features/nodes/store/nodesSlice';
import { selectNodes } from 'features/nodes/store/selectors';
import { selectNodeOpacity } from 'features/nodes/store/workflowSettingsSlice';
import { DRAG_HANDLE_CLASSNAME, NODE_WIDTH } from 'features/nodes/types/constants'; import { DRAG_HANDLE_CLASSNAME, NODE_WIDTH } from 'features/nodes/types/constants';
import { zNodeStatus } from 'features/nodes/types/invocation'; import { zNodeStatus } from 'features/nodes/types/invocation';
import type { MouseEvent, PropsWithChildren } from 'react'; import type { MouseEvent, PropsWithChildren } from 'react';
@ -33,13 +35,13 @@ const NodeWrapper = (props: NodeWrapperProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const opacity = useAppSelector((s) => s.workflowSettings.nodeOpacity); const opacity = useAppSelector(selectNodeOpacity);
const { onCloseGlobal } = useGlobalMenuClose(); const { onCloseGlobal } = useGlobalMenuClose();
const handleClick = useCallback( const handleClick = useCallback(
(e: MouseEvent<HTMLDivElement>) => { (e: MouseEvent<HTMLDivElement>) => {
if (!e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey) { if (!e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey) {
const { nodes } = store.getState().nodes.present; const nodes = selectNodes(store.getState());
const nodeChanges: NodeChange[] = []; const nodeChanges: NodeChange[] = [];
nodes.forEach(({ id, selected }) => { nodes.forEach(({ id, selected }) => {
if (selected !== (id === nodeId)) { if (selected !== (id === nodeId)) {

View File

@ -1,12 +1,12 @@
import { CompositeSlider, Flex } from '@invoke-ai/ui-library'; import { CompositeSlider, Flex } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { nodeOpacityChanged } from 'features/nodes/store/workflowSettingsSlice'; import { nodeOpacityChanged, selectNodeOpacity } from 'features/nodes/store/workflowSettingsSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const NodeOpacitySlider = () => { const NodeOpacitySlider = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const nodeOpacity = useAppSelector((s) => s.workflowSettings.nodeOpacity); const nodeOpacity = useAppSelector(selectNodeOpacity);
const { t } = useTranslation(); const { t } = useTranslation();
const handleChange = useCallback( const handleChange = useCallback(

View File

@ -1,6 +1,9 @@
import { ButtonGroup, IconButton } from '@invoke-ai/ui-library'; import { ButtonGroup, IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { shouldShowMinimapPanelChanged } from 'features/nodes/store/workflowSettingsSlice'; import {
selectShouldShowMinimapPanel,
shouldShowMinimapPanelChanged,
} from 'features/nodes/store/workflowSettingsSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@ -15,7 +18,7 @@ const ViewportControls = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { zoomIn, zoomOut, fitView } = useReactFlow(); const { zoomIn, zoomOut, fitView } = useReactFlow();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const shouldShowMinimapPanel = useAppSelector((s) => s.workflowSettings.shouldShowMinimapPanel); const shouldShowMinimapPanel = useAppSelector(selectShouldShowMinimapPanel);
const handleClickedZoomIn = useCallback(() => { const handleClickedZoomIn = useCallback(() => {
zoomIn({ duration: 300 }); zoomIn({ duration: 300 });

View File

@ -1,6 +1,7 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library'; import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { chakra, Flex } from '@invoke-ai/ui-library'; import { chakra, Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectShouldShowMinimapPanel } from 'features/nodes/store/workflowSettingsSlice';
import { memo } from 'react'; import { memo } from 'react';
import { MiniMap } from 'reactflow'; import { MiniMap } from 'reactflow';
@ -16,7 +17,7 @@ const minimapStyles: SystemStyleObject = {
}; };
const MinimapPanel = () => { const MinimapPanel = () => {
const shouldShowMinimapPanel = useAppSelector((s) => s.workflowSettings.shouldShowMinimapPanel); const shouldShowMinimapPanel = useAppSelector(selectShouldShowMinimapPanel);
return ( return (
<Flex gap={2} position="absolute" bottom={0} insetInlineEnd={0}> <Flex gap={2} position="absolute" bottom={0} insetInlineEnd={0}>

View File

@ -1,6 +1,7 @@
import { ConfirmationAlertDialog, Flex, IconButton, Text, useDisclosure } from '@invoke-ai/ui-library'; import { ConfirmationAlertDialog, Flex, IconButton, Text, useDisclosure } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
import { selectWorkflowIsTouched } from 'features/nodes/store/workflowSlice';
import { toast } from 'features/toast/toast'; import { toast } from 'features/toast/toast';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -10,7 +11,7 @@ const ClearFlowButton = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const isTouched = useAppSelector((s) => s.workflow.isTouched); const isTouched = useAppSelector(selectWorkflowIsTouched);
const handleNewWorkflow = useCallback(() => { const handleNewWorkflow = useCallback(() => {
dispatch(nodeEditorReset()); dispatch(nodeEditorReset());

View File

@ -1,6 +1,7 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { $builtWorkflow } from 'features/nodes/hooks/useWorkflowWatcher'; import { $builtWorkflow } from 'features/nodes/hooks/useWorkflowWatcher';
import { selectWorkflowIsTouched } from 'features/nodes/store/workflowSlice';
import { useSaveWorkflowAsDialog } from 'features/workflowLibrary/components/SaveWorkflowAsDialog/useSaveWorkflowAsDialog'; import { useSaveWorkflowAsDialog } from 'features/workflowLibrary/components/SaveWorkflowAsDialog/useSaveWorkflowAsDialog';
import { isWorkflowWithID, useSaveLibraryWorkflow } from 'features/workflowLibrary/hooks/useSaveWorkflow'; import { isWorkflowWithID, useSaveLibraryWorkflow } from 'features/workflowLibrary/hooks/useSaveWorkflow';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
@ -9,7 +10,7 @@ import { PiFloppyDiskBold } from 'react-icons/pi';
const SaveWorkflowButton = () => { const SaveWorkflowButton = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const isTouched = useAppSelector((s) => s.workflow.isTouched); const isTouched = useAppSelector(selectWorkflowIsTouched);
const { onOpen } = useSaveWorkflowAsDialog(); const { onOpen } = useSaveWorkflowAsDialog();
const { saveWorkflow } = useSaveLibraryWorkflow(); const { saveWorkflow } = useSaveLibraryWorkflow();

View File

@ -5,11 +5,12 @@ import ClearFlowButton from 'features/nodes/components/flow/panels/TopPanel/Clea
import SaveWorkflowButton from 'features/nodes/components/flow/panels/TopPanel/SaveWorkflowButton'; import SaveWorkflowButton from 'features/nodes/components/flow/panels/TopPanel/SaveWorkflowButton';
import UpdateNodesButton from 'features/nodes/components/flow/panels/TopPanel/UpdateNodesButton'; import UpdateNodesButton from 'features/nodes/components/flow/panels/TopPanel/UpdateNodesButton';
import { WorkflowName } from 'features/nodes/components/sidePanel/WorkflowName'; import { WorkflowName } from 'features/nodes/components/sidePanel/WorkflowName';
import { selectWorkflowName } from 'features/nodes/store/workflowSlice';
import WorkflowLibraryMenu from 'features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu'; import WorkflowLibraryMenu from 'features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu';
import { memo } from 'react'; import { memo } from 'react';
const TopCenterPanel = () => { const TopCenterPanel = () => {
const name = useAppSelector((s) => s.workflow.name); const name = useAppSelector(selectWorkflowName);
return ( return (
<Flex gap={2} top={0} left={0} right={0} position="absolute" alignItems="flex-start" pointerEvents="none"> <Flex gap={2} top={0} left={0} right={0} position="absolute" alignItems="flex-start" pointerEvents="none">
<Flex gap="2"> <Flex gap="2">

View File

@ -16,12 +16,16 @@ import {
Switch, Switch,
useDisclosure, useDisclosure,
} from '@invoke-ai/ui-library'; } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import ReloadNodeTemplatesButton from 'features/nodes/components/flow/panels/TopRightPanel/ReloadSchemaButton'; import ReloadNodeTemplatesButton from 'features/nodes/components/flow/panels/TopRightPanel/ReloadSchemaButton';
import { import {
selectionModeChanged, selectionModeChanged,
selectWorkflowSettingsSlice, selectSelectionMode,
selectShouldAnimateEdges,
selectShouldColorEdges,
selectShouldShouldValidateGraph,
selectShouldShowEdgeLabels,
selectShouldSnapToGrid,
shouldAnimateEdgesChanged, shouldAnimateEdgesChanged,
shouldColorEdgesChanged, shouldColorEdgesChanged,
shouldShowEdgeLabelsChanged, shouldShowEdgeLabelsChanged,
@ -35,25 +39,6 @@ import { SelectionMode } from 'reactflow';
const formLabelProps: FormLabelProps = { flexGrow: 1 }; const formLabelProps: FormLabelProps = { flexGrow: 1 };
const selector = createMemoizedSelector(selectWorkflowSettingsSlice, (workflowSettings) => {
const {
shouldAnimateEdges,
shouldValidateGraph,
shouldSnapToGrid,
shouldColorEdges,
shouldShowEdgeLabels,
selectionMode,
} = workflowSettings;
return {
shouldAnimateEdges,
shouldValidateGraph,
shouldSnapToGrid,
shouldColorEdges,
shouldShowEdgeLabels,
selectionModeIsChecked: selectionMode === SelectionMode.Full,
};
});
type Props = { type Props = {
children: (props: { onOpen: () => void }) => ReactNode; children: (props: { onOpen: () => void }) => ReactNode;
}; };
@ -61,14 +46,13 @@ type Props = {
const WorkflowEditorSettings = ({ children }: Props) => { const WorkflowEditorSettings = ({ children }: Props) => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const {
shouldAnimateEdges, const shouldSnapToGrid = useAppSelector(selectShouldSnapToGrid);
shouldValidateGraph, const selectionMode = useAppSelector(selectSelectionMode);
shouldSnapToGrid, const shouldColorEdges = useAppSelector(selectShouldColorEdges);
shouldColorEdges, const shouldAnimateEdges = useAppSelector(selectShouldAnimateEdges);
shouldShowEdgeLabels, const shouldShowEdgeLabels = useAppSelector(selectShouldShowEdgeLabels);
selectionModeIsChecked, const shouldValidateGraph = useAppSelector(selectShouldShouldValidateGraph);
} = useAppSelector(selector);
const handleChangeShouldValidate = useCallback( const handleChangeShouldValidate = useCallback(
(e: ChangeEvent<HTMLInputElement>) => { (e: ChangeEvent<HTMLInputElement>) => {
@ -154,7 +138,7 @@ const WorkflowEditorSettings = ({ children }: Props) => {
<FormControl> <FormControl>
<Flex w="full"> <Flex w="full">
<FormLabel>{t('nodes.fullyContainNodes')}</FormLabel> <FormLabel>{t('nodes.fullyContainNodes')}</FormLabel>
<Switch isChecked={selectionModeIsChecked} onChange={handleChangeSelectionMode} /> <Switch isChecked={selectionMode === SelectionMode.Full} onChange={handleChangeSelectionMode} />
</Flex> </Flex>
<FormHelperText>{t('nodes.fullyContainNodesHelp')}</FormHelperText> <FormHelperText>{t('nodes.fullyContainNodesHelp')}</FormHelperText>
</FormControl> </FormControl>

View File

@ -1,13 +1,13 @@
import { Flex, IconButton } from '@invoke-ai/ui-library'; import { Flex, IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { workflowModeChanged } from 'features/nodes/store/workflowSlice'; import { selectWorkflowMode, workflowModeChanged } from 'features/nodes/store/workflowSlice';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiEyeBold, PiPencilBold } from 'react-icons/pi'; import { PiEyeBold, PiPencilBold } from 'react-icons/pi';
export const ModeToggle = () => { export const ModeToggle = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const mode = useAppSelector((s) => s.workflow.mode); const mode = useAppSelector(selectWorkflowMode);
const { t } = useTranslation(); const { t } = useTranslation();
const onClickEdit = useCallback(() => { const onClickEdit = useCallback(() => {

View File

@ -2,6 +2,7 @@ import 'reactflow/dist/style.css';
import { Flex } from '@invoke-ai/ui-library'; import { Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectWorkflowMode } from 'features/nodes/store/workflowSlice';
import QueueControls from 'features/queue/components/QueueControls'; import QueueControls from 'features/queue/components/QueueControls';
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle'; import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage'; import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
@ -20,7 +21,7 @@ import { WorkflowName } from './WorkflowName';
const panelGroupStyles: CSSProperties = { height: '100%', width: '100%' }; const panelGroupStyles: CSSProperties = { height: '100%', width: '100%' };
const NodeEditorPanelGroup = () => { const NodeEditorPanelGroup = () => {
const mode = useAppSelector((s) => s.workflow.mode); const mode = useAppSelector(selectWorkflowMode);
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null); const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
const panelStorage = usePanelStorage(); const panelStorage = usePanelStorage();

View File

@ -1,12 +1,13 @@
import { Flex } from '@invoke-ai/ui-library'; import { Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import SaveWorkflowButton from 'features/nodes/components/flow/panels/TopPanel/SaveWorkflowButton'; import SaveWorkflowButton from 'features/nodes/components/flow/panels/TopPanel/SaveWorkflowButton';
import { selectWorkflowMode } from 'features/nodes/store/workflowSlice';
import { NewWorkflowButton } from 'features/workflowLibrary/components/NewWorkflowButton'; import { NewWorkflowButton } from 'features/workflowLibrary/components/NewWorkflowButton';
import { ModeToggle } from './ModeToggle'; import { ModeToggle } from './ModeToggle';
export const WorkflowMenu = () => { export const WorkflowMenu = () => {
const mode = useAppSelector((s) => s.workflow.mode); const mode = useAppSelector(selectWorkflowMode);
return ( return (
<Flex gap="2" alignItems="center"> <Flex gap="2" alignItems="center">

View File

@ -1,5 +1,6 @@
import { Flex, Icon, Text, Tooltip } from '@invoke-ai/ui-library'; import { Flex, Icon, Text, Tooltip } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectWorkflowIsTouched, selectWorkflowMode, selectWorkflowName } from 'features/nodes/store/workflowSlice';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiDotOutlineFill } from 'react-icons/pi'; import { PiDotOutlineFill } from 'react-icons/pi';
@ -8,9 +9,9 @@ import { WorkflowWarning } from './viewMode/WorkflowWarning';
export const WorkflowName = () => { export const WorkflowName = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const name = useAppSelector((s) => s.workflow.name); const name = useAppSelector(selectWorkflowName);
const isTouched = useAppSelector((s) => s.workflow.isTouched); const isTouched = useAppSelector(selectWorkflowIsTouched);
const mode = useAppSelector((s) => s.workflow.mode); const mode = useAppSelector(selectWorkflowMode);
return ( return (
<Flex gap="1" alignItems="center"> <Flex gap="1" alignItems="center">

View File

@ -2,8 +2,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer'; import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectLastSelectedNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { selectLastSelectedNode } from 'features/nodes/store/selectors';
import { memo } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -6,8 +6,8 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import NotesTextarea from 'features/nodes/components/flow/nodes/Invocation/NotesTextarea'; import NotesTextarea from 'features/nodes/components/flow/nodes/Invocation/NotesTextarea';
import { useNodeNeedsUpdate } from 'features/nodes/hooks/useNodeNeedsUpdate'; import { useNodeNeedsUpdate } from 'features/nodes/hooks/useNodeNeedsUpdate';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { $templates } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode } from 'features/nodes/store/selectors'; import { selectLastSelectedNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -6,8 +6,8 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer'; import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { useExecutionState } from 'features/nodes/hooks/useExecutionState'; import { useExecutionState } from 'features/nodes/hooks/useExecutionState';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { $templates } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode } from 'features/nodes/store/selectors'; import { selectLastSelectedNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -3,8 +3,8 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer'; import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { $templates } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode } from 'features/nodes/store/selectors'; import { selectLastSelectedNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -9,6 +9,7 @@ import {
$templates, $templates,
edgesChanged, edgesChanged,
} from 'features/nodes/store/nodesSlice'; } from 'features/nodes/store/nodesSlice';
import { selectNodes, selectNodesSlice } from 'features/nodes/store/selectors';
import { getFirstValidConnection } from 'features/nodes/store/util/getFirstValidConnection'; import { getFirstValidConnection } from 'features/nodes/store/util/getFirstValidConnection';
import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil'; import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
@ -24,7 +25,7 @@ export const useConnection = () => {
const onConnectStart = useCallback<OnConnectStart>( const onConnectStart = useCallback<OnConnectStart>(
(event, { nodeId, handleId, handleType }) => { (event, { nodeId, handleId, handleType }) => {
assert(nodeId && handleId && handleType, 'Invalid connection start event'); assert(nodeId && handleId && handleType, 'Invalid connection start event');
const nodes = store.getState().nodes.present.nodes; const nodes = selectNodes(store.getState());
const node = nodes.find((n) => n.id === nodeId); const node = nodes.find((n) => n.id === nodeId);
if (!node) { if (!node) {
@ -72,7 +73,7 @@ export const useConnection = () => {
if (!pendingConnection) { if (!pendingConnection) {
return; return;
} }
const { nodes, edges } = store.getState().nodes.present; const { nodes, edges } = selectNodesSlice(store.getState());
if (mouseOverNodeId) { if (mouseOverNodeId) {
const { handleType } = pendingConnection; const { handleType } = pendingConnection;
const source = handleType === 'source' ? pendingConnection.nodeId : mouseOverNodeId; const source = handleType === 'source' ? pendingConnection.nodeId : mouseOverNodeId;

View File

@ -1,7 +1,8 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { $edgePendingUpdate, $pendingConnection, $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { $edgePendingUpdate, $pendingConnection, $templates } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import { makeConnectionErrorSelector } from 'features/nodes/store/util/makeConnectionErrorSelector'; import { makeConnectionErrorSelector } from 'features/nodes/store/util/makeConnectionErrorSelector';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@ -7,8 +7,8 @@ import {
$edgesToCopiedNodes, $edgesToCopiedNodes,
edgesChanged, edgesChanged,
nodesChanged, nodesChanged,
selectNodesSlice,
} from 'features/nodes/store/nodesSlice'; } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupiedPosition'; import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupiedPosition';
import { isEqual, uniqWith } from 'lodash-es'; import { isEqual, uniqWith } from 'lodash-es';
import type { EdgeChange, NodeChange } from 'reactflow'; import type { EdgeChange, NodeChange } from 'reactflow';

View File

@ -1,7 +1,6 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodeData, selectNodesSlice } from 'features/nodes/store/selectors';
import { selectNodeData } from 'features/nodes/store/selectors';
import { useMemo } from 'react'; import { useMemo } from 'react';
export const useDoesInputHaveValue = (nodeId: string, fieldName: string): boolean => { export const useDoesInputHaveValue = (nodeId: string, fieldName: string): boolean => {

View File

@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/selectors';
import type { NodeExecutionStates } from 'features/nodes/store/types'; import type { NodeExecutionStates } from 'features/nodes/store/types';
import type { NodeExecutionState } from 'features/nodes/types/invocation'; import type { NodeExecutionState } from 'features/nodes/types/invocation';
import { zNodeStatus } from 'features/nodes/types/invocation'; import { zNodeStatus } from 'features/nodes/types/invocation';

View File

@ -1,7 +1,6 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectFieldInputInstance, selectNodesSlice } from 'features/nodes/store/selectors';
import { selectFieldInputInstance } from 'features/nodes/store/selectors';
import type { FieldInputInstance } from 'features/nodes/types/field'; import type { FieldInputInstance } from 'features/nodes/types/field';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@ -1,7 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectFieldInputInstance, selectNodesSlice } from 'features/nodes/store/selectors';
import { selectFieldInputInstance } from 'features/nodes/store/selectors';
import { useMemo } from 'react'; import { useMemo } from 'react';
export const useFieldLabel = (nodeId: string, fieldName: string): string | null => { export const useFieldLabel = (nodeId: string, fieldName: string): string | null => {

View File

@ -1,8 +1,8 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { $templates } from 'features/nodes/store/nodesSlice';
import { selectInvocationNodeType } from 'features/nodes/store/selectors'; import { selectInvocationNodeType, selectNodesSlice } from 'features/nodes/store/selectors';
import type { FieldInputTemplate, FieldOutputTemplate } from 'features/nodes/types/field'; import type { FieldInputTemplate, FieldOutputTemplate } from 'features/nodes/types/field';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { assert } from 'tsafe'; import { assert } from 'tsafe';

View File

@ -1,6 +1,6 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@ -1,7 +1,8 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { $templates } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
import { getNeedsUpdate } from 'features/nodes/util/node/nodeUpdate'; import { getNeedsUpdate } from 'features/nodes/util/node/nodeUpdate';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@ -1,7 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodeData, selectNodesSlice } from 'features/nodes/store/selectors';
import { selectNodeData } from 'features/nodes/store/selectors';
import { useMemo } from 'react'; import { useMemo } from 'react';
export const useIsIntermediate = (nodeId: string): boolean => { export const useIsIntermediate = (nodeId: string): boolean => {

View File

@ -2,7 +2,9 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { useAppSelector, useAppStore } from 'app/store/storeHooks'; import { useAppSelector, useAppStore } from 'app/store/storeHooks';
import { $edgePendingUpdate, $templates } from 'features/nodes/store/nodesSlice'; import { $edgePendingUpdate, $templates } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import { validateConnection } from 'features/nodes/store/util/validateConnection'; import { validateConnection } from 'features/nodes/store/util/validateConnection';
import { selectShouldShouldValidateGraph } from 'features/nodes/store/workflowSettingsSlice';
import { useCallback } from 'react'; import { useCallback } from 'react';
import type { Connection } from 'reactflow'; import type { Connection } from 'reactflow';
@ -14,7 +16,7 @@ import type { Connection } from 'reactflow';
export const useIsValidConnection = () => { export const useIsValidConnection = () => {
const store = useAppStore(); const store = useAppStore();
const templates = useStore($templates); const templates = useStore($templates);
const shouldValidateGraph = useAppSelector((s) => s.workflowSettings.shouldValidateGraph); const shouldValidateGraph = useAppSelector(selectShouldShouldValidateGraph);
const isValidConnection = useCallback( const isValidConnection = useCallback(
({ source, sourceHandle, target, targetHandle }: Connection): boolean => { ({ source, sourceHandle, target, targetHandle }: Connection): boolean => {
// Connection must have valid targets // Connection must have valid targets
@ -22,7 +24,7 @@ export const useIsValidConnection = () => {
return false; return false;
} }
const edgePendingUpdate = $edgePendingUpdate.get(); const edgePendingUpdate = $edgePendingUpdate.get();
const { nodes, edges } = store.getState().nodes.present; const { nodes, edges } = selectNodesSlice(store.getState());
const validationResult = validateConnection( const validationResult = validateConnection(
{ source, sourceHandle, target, targetHandle }, { source, sourceHandle, target, targetHandle },

View File

@ -1,7 +1,6 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodeData, selectNodesSlice } from 'features/nodes/store/selectors';
import { selectNodeData } from 'features/nodes/store/selectors';
import type { InvocationNodeData } from 'features/nodes/types/invocation'; import type { InvocationNodeData } from 'features/nodes/types/invocation';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/selectors';
import { useMemo } from 'react'; import { useMemo } from 'react';
export const useNodeLabel = (nodeId: string) => { export const useNodeLabel = (nodeId: string) => {

View File

@ -1,7 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodeData, selectNodesSlice } from 'features/nodes/store/selectors';
import { selectNodeData } from 'features/nodes/store/selectors';
import { useMemo } from 'react'; import { useMemo } from 'react';
export const useNodePack = (nodeId: string): string | null => { export const useNodePack = (nodeId: string): string | null => {

View File

@ -1,8 +1,8 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { $templates } from 'features/nodes/store/nodesSlice';
import { selectInvocationNodeType } from 'features/nodes/store/selectors'; import { selectInvocationNodeType, selectNodesSlice } from 'features/nodes/store/selectors';
import type { InvocationTemplate } from 'features/nodes/types/invocation'; import type { InvocationTemplate } from 'features/nodes/types/invocation';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { assert } from 'tsafe'; import { assert } from 'tsafe';

View File

@ -1,7 +1,8 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { $templates } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@ -1,7 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodeData, selectNodesSlice } from 'features/nodes/store/selectors';
import { selectNodeData } from 'features/nodes/store/selectors';
import { useMemo } from 'react'; import { useMemo } from 'react';
export const useUseCache = (nodeId: string) => { export const useUseCache = (nodeId: string) => {

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/selectors';
import { selectWorkflowSlice } from 'features/nodes/store/workflowSlice'; import { selectWorkflowSlice } from 'features/nodes/store/workflowSlice';
import type { WorkflowV3 } from 'features/nodes/types/workflow'; import type { WorkflowV3 } from 'features/nodes/types/workflow';
import type { BuildWorkflowArg } from 'features/nodes/util/workflow/buildWorkflow'; import type { BuildWorkflowArg } from 'features/nodes/util/workflow/buildWorkflow';

View File

@ -1,6 +1,6 @@
import type { PayloadAction, UnknownAction } from '@reduxjs/toolkit'; import type { PayloadAction, UnknownAction } from '@reduxjs/toolkit';
import { createSlice, isAnyOf } from '@reduxjs/toolkit'; import { createSlice, isAnyOf } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store'; import type { PersistConfig } from 'app/store/store';
import { workflowLoaded } from 'features/nodes/store/actions'; import { workflowLoaded } from 'features/nodes/store/actions';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants'; import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type { import type {
@ -452,8 +452,6 @@ export const openAddNodePopover = () => {
$isAddNodePopoverOpen.set(true); $isAddNodePopoverOpen.set(true);
}; };
export const selectNodesSlice = (state: RootState) => state.nodes.present;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateNodesState = (state: any): any => { const migrateNodesState = (state: any): any => {
if (!('_version' in state)) { if (!('_version' in state)) {

View File

@ -1,3 +1,6 @@
import type { Selector } from '@reduxjs/toolkit';
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import type { NodesState } from 'features/nodes/store/types'; import type { NodesState } from 'features/nodes/store/types';
import type { FieldInputInstance } from 'features/nodes/types/field'; import type { FieldInputInstance } from 'features/nodes/types/field';
import type { InvocationNode, InvocationNodeData } from 'features/nodes/types/invocation'; import type { InvocationNode, InvocationNodeData } from 'features/nodes/types/invocation';
@ -36,3 +39,17 @@ export const selectLastSelectedNode = (nodesSlice: NodesState) => {
} }
return null; return null;
}; };
export const selectNodesSlice = (state: RootState) => state.nodes.present;
const createNodesSelector = <T>(selector: Selector<NodesState, T>) => createSelector(selectNodesSlice, selector);
export const selectNodes = createNodesSelector((nodes) => nodes.nodes);
export const selectEdges = createNodesSelector((nodes) => nodes.edges);
export const selectMayUndo = createSelector(
(state: RootState) => state.nodes,
(nodes) => nodes.past.length > 0
);
export const selectMayRedo = createSelector(
(state: RootState) => state.nodes,
(nodes) => nodes.future.length > 0
);

View File

@ -1,6 +1,6 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/selectors';
import type { NodesState, PendingConnection, Templates } from 'features/nodes/store/types'; import type { NodesState, PendingConnection, Templates } from 'features/nodes/store/types';
import { buildRejectResult, validateConnection } from 'features/nodes/store/util/validateConnection'; import { buildRejectResult, validateConnection } from 'features/nodes/store/util/validateConnection';
import type { Edge, HandleType } from 'reactflow'; import type { Edge, HandleType } from 'reactflow';

View File

@ -1,6 +1,7 @@
import type { PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSelector, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store'; import type { PersistConfig, RootState } from 'app/store/store';
import type { Selector } from 'react-redux';
import { SelectionMode } from 'reactflow'; import { SelectionMode } from 'reactflow';
type WorkflowSettingsState = { type WorkflowSettingsState = {
@ -69,8 +70,6 @@ export const {
selectionModeChanged, selectionModeChanged,
} = workflowSettingsSlice.actions; } = workflowSettingsSlice.actions;
export const selectWorkflowSettingsSlice = (state: RootState) => state.workflowSettings;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateWorkflowSettingsState = (state: any): any => { const migrateWorkflowSettingsState = (state: any): any => {
if (!('_version' in state)) { if (!('_version' in state)) {
@ -85,3 +84,15 @@ export const workflowSettingsPersistConfig: PersistConfig<WorkflowSettingsState>
migrate: migrateWorkflowSettingsState, migrate: migrateWorkflowSettingsState,
persistDenylist: [], persistDenylist: [],
}; };
export const selectWorkflowSettingsSlice = (state: RootState) => state.workflowSettings;
const createWorkflowSettingsSelector = <T>(selector: Selector<WorkflowSettingsState, T>) =>
createSelector(selectWorkflowSettingsSlice, selector);
export const selectShouldSnapToGrid = createWorkflowSettingsSelector((s) => s.shouldSnapToGrid);
export const selectSelectionMode = createWorkflowSettingsSelector((s) => s.selectionMode);
export const selectShouldColorEdges = createWorkflowSettingsSelector((s) => s.shouldColorEdges);
export const selectShouldAnimateEdges = createWorkflowSettingsSelector((s) => s.shouldAnimateEdges);
export const selectShouldShowEdgeLabels = createWorkflowSettingsSelector((s) => s.shouldShowEdgeLabels);
export const selectNodeOpacity = createWorkflowSettingsSelector((s) => s.nodeOpacity);
export const selectShouldShowMinimapPanel = createWorkflowSettingsSelector((s) => s.shouldShowMinimapPanel);
export const selectShouldShouldValidateGraph = createWorkflowSettingsSelector((s) => s.shouldValidateGraph);

View File

@ -1,5 +1,5 @@
import type { PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction, Selector } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSelector, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store'; import type { PersistConfig, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { workflowLoaded } from 'features/nodes/store/actions'; import { workflowLoaded } from 'features/nodes/store/actions';
@ -209,8 +209,6 @@ export const {
workflowSaved, workflowSaved,
} = workflowSlice.actions; } = workflowSlice.actions;
export const selectWorkflowSlice = (state: RootState) => state.workflow;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateWorkflowState = (state: any): any => { const migrateWorkflowState = (state: any): any => {
if (!('_version' in state)) { if (!('_version' in state)) {
@ -225,3 +223,12 @@ export const workflowPersistConfig: PersistConfig<WorkflowState> = {
migrate: migrateWorkflowState, migrate: migrateWorkflowState,
persistDenylist: [], persistDenylist: [],
}; };
export const selectWorkflowSlice = (state: RootState) => state.workflow;
const createWorkflowSelector = <T>(selector: Selector<WorkflowState, T>) =>
createSelector(selectWorkflowSlice, selector);
export const selectWorkflowName = createWorkflowSelector((workflow) => workflow.name);
export const selectWorkflowId = createWorkflowSelector((workflow) => workflow.id);
export const selectWorkflowMode = createWorkflowSelector((workflow) => workflow.mode);
export const selectWorkflowIsTouched = createWorkflowSelector((workflow) => workflow.isTouched);

View File

@ -1,19 +1,14 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setCfgRescaleMultiplier } from 'features/controlLayers/store/paramsSlice'; import { selectCFGRescaleMultiplier, setCfgRescaleMultiplier } from 'features/controlLayers/store/paramsSlice';
import { selectCFGRescaleMultiplierConfig } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const ParamCFGRescaleMultiplier = () => { const ParamCFGRescaleMultiplier = () => {
const cfgRescaleMultiplier = useAppSelector((s) => s.params.cfgRescaleMultiplier); const cfgRescaleMultiplier = useAppSelector(selectCFGRescaleMultiplier);
const initial = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.initial); const config = useAppSelector(selectCFGRescaleMultiplierConfig);
const sliderMin = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.fineStep);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
@ -27,21 +22,21 @@ const ParamCFGRescaleMultiplier = () => {
</InformationalPopover> </InformationalPopover>
<CompositeSlider <CompositeSlider
value={cfgRescaleMultiplier} value={cfgRescaleMultiplier}
defaultValue={initial} defaultValue={config.initial}
min={sliderMin} min={config.sliderMin}
max={sliderMax} max={config.sliderMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={handleChange} onChange={handleChange}
marks marks
/> />
<CompositeNumberInput <CompositeNumberInput
value={cfgRescaleMultiplier} value={cfgRescaleMultiplier}
defaultValue={initial} defaultValue={config.initial}
min={numberInputMin} min={config.numberInputMin}
max={numberInputMax} max={config.numberInputMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={handleChange} onChange={handleChange}
/> />
</FormControl> </FormControl>

View File

@ -1,19 +1,16 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setClipSkip } from 'features/controlLayers/store/paramsSlice'; import { selectCLIPSKip, selectModel, setClipSkip } from 'features/controlLayers/store/paramsSlice';
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants'; import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
import { selectCLIPSkipConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const ParamClipSkip = () => { const ParamClipSkip = () => {
const clipSkip = useAppSelector((s) => s.params.clipSkip); const clipSkip = useAppSelector(selectCLIPSKip);
const initial = useAppSelector((s) => s.config.sd.clipSkip.initial); const config = useAppSelector(selectCLIPSkipConfig);
const sliderMin = useAppSelector((s) => s.config.sd.clipSkip.sliderMin); const model = useAppSelector(selectModel);
const numberInputMin = useAppSelector((s) => s.config.sd.clipSkip.numberInputMin);
const coarseStep = useAppSelector((s) => s.config.sd.clipSkip.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.clipSkip.fineStep);
const model = useAppSelector((s) => s.params.model);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
@ -50,21 +47,21 @@ const ParamClipSkip = () => {
</InformationalPopover> </InformationalPopover>
<CompositeSlider <CompositeSlider
value={clipSkip} value={clipSkip}
defaultValue={initial} defaultValue={config.initial}
min={sliderMin} min={config.sliderMin}
max={max} max={max}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={handleClipSkipChange} onChange={handleClipSkipChange}
marks={sliderMarks} marks={sliderMarks}
/> />
<CompositeNumberInput <CompositeNumberInput
value={clipSkip} value={clipSkip}
defaultValue={initial} defaultValue={config.initial}
min={numberInputMin} min={config.numberInputMin}
max={max} max={max}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={handleClipSkipChange} onChange={handleClipSkipChange}
/> />
</FormControl> </FormControl>

View File

@ -1,20 +1,15 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setCanvasCoherenceEdgeSize } from 'features/controlLayers/store/paramsSlice'; import { selectCanvasCoherenceEdgeSize, setCanvasCoherenceEdgeSize } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasCoherenceEdgeSizeConfig } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const ParamCanvasCoherenceEdgeSize = () => { const ParamCanvasCoherenceEdgeSize = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const canvasCoherenceEdgeSize = useAppSelector((s) => s.params.canvasCoherenceEdgeSize); const canvasCoherenceEdgeSize = useAppSelector(selectCanvasCoherenceEdgeSize);
const initial = useAppSelector((s) => s.config.sd.canvasCoherenceEdgeSize.initial); const config = useAppSelector(selectCanvasCoherenceEdgeSizeConfig);
const sliderMin = useAppSelector((s) => s.config.sd.canvasCoherenceEdgeSize.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.canvasCoherenceEdgeSize.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.canvasCoherenceEdgeSize.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.canvasCoherenceEdgeSize.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.canvasCoherenceEdgeSize.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.canvasCoherenceEdgeSize.fineStep);
const { t } = useTranslation(); const { t } = useTranslation();
@ -31,22 +26,22 @@ const ParamCanvasCoherenceEdgeSize = () => {
<FormLabel>{t('parameters.coherenceEdgeSize')}</FormLabel> <FormLabel>{t('parameters.coherenceEdgeSize')}</FormLabel>
</InformationalPopover> </InformationalPopover>
<CompositeSlider <CompositeSlider
min={sliderMin} min={config.sliderMin}
max={sliderMax} max={config.sliderMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
value={canvasCoherenceEdgeSize} value={canvasCoherenceEdgeSize}
defaultValue={initial} defaultValue={config.initial}
onChange={handleChange} onChange={handleChange}
marks marks
/> />
<CompositeNumberInput <CompositeNumberInput
min={numberInputMin} min={config.numberInputMin}
max={numberInputMax} max={config.numberInputMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
value={canvasCoherenceEdgeSize} value={canvasCoherenceEdgeSize}
defaultValue={initial} defaultValue={config.initial}
onChange={handleChange} onChange={handleChange}
/> />
</FormControl> </FormControl>

View File

@ -1,13 +1,16 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setCanvasCoherenceMinDenoise } from 'features/controlLayers/store/paramsSlice'; import {
selectCanvasCoherenceMinDenoise,
setCanvasCoherenceMinDenoise,
} from 'features/controlLayers/store/paramsSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const ParamCanvasCoherenceMinDenoise = () => { const ParamCanvasCoherenceMinDenoise = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const canvasCoherenceMinDenoise = useAppSelector((s) => s.params.canvasCoherenceMinDenoise); const canvasCoherenceMinDenoise = useAppSelector(selectCanvasCoherenceMinDenoise);
const { t } = useTranslation(); const { t } = useTranslation();
const handleChange = useCallback( const handleChange = useCallback(

View File

@ -2,14 +2,14 @@ import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setCanvasCoherenceMode } from 'features/controlLayers/store/paramsSlice'; import { selectCanvasCoherenceMode, setCanvasCoherenceMode } from 'features/controlLayers/store/paramsSlice';
import { isParameterCanvasCoherenceMode } from 'features/parameters/types/parameterSchemas'; import { isParameterCanvasCoherenceMode } from 'features/parameters/types/parameterSchemas';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const ParamCanvasCoherenceMode = () => { const ParamCanvasCoherenceMode = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const canvasCoherenceMode = useAppSelector((s) => s.params.canvasCoherenceMode); const canvasCoherenceMode = useAppSelector(selectCanvasCoherenceMode);
const { t } = useTranslation(); const { t } = useTranslation();
const options = useMemo<ComboboxOption[]>( const options = useMemo<ComboboxOption[]>(

View File

@ -1,21 +1,16 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setMaskBlur } from 'features/controlLayers/store/paramsSlice'; import { selectMaskBlur, setMaskBlur } from 'features/controlLayers/store/paramsSlice';
import { selectMaskBlurConfig } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const ParamMaskBlur = () => { const ParamMaskBlur = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const maskBlur = useAppSelector((s) => s.params.maskBlur); const maskBlur = useAppSelector(selectMaskBlur);
const initial = useAppSelector((s) => s.config.sd.maskBlur.initial); const config = useAppSelector(selectMaskBlurConfig);
const sliderMin = useAppSelector((s) => s.config.sd.maskBlur.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.maskBlur.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.maskBlur.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.maskBlur.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.maskBlur.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.maskBlur.fineStep);
const handleChange = useCallback( const handleChange = useCallback(
(v: number) => { (v: number) => {
@ -30,23 +25,23 @@ const ParamMaskBlur = () => {
<FormLabel>{t('parameters.maskBlur')}</FormLabel> <FormLabel>{t('parameters.maskBlur')}</FormLabel>
</InformationalPopover> </InformationalPopover>
<CompositeSlider <CompositeSlider
min={sliderMin}
max={sliderMax}
value={maskBlur} value={maskBlur}
defaultValue={initial}
onChange={handleChange} onChange={handleChange}
step={coarseStep} min={config.sliderMin}
fineStep={fineStep} max={config.sliderMax}
defaultValue={config.initial}
step={config.coarseStep}
fineStep={config.fineStep}
marks marks
/> />
<CompositeNumberInput <CompositeNumberInput
min={numberInputMin}
max={numberInputMax}
value={maskBlur} value={maskBlur}
defaultValue={initial}
onChange={handleChange} onChange={handleChange}
step={coarseStep} defaultValue={config.initial}
fineStep={fineStep} min={config.numberInputMin}
max={config.numberInputMax}
step={config.coarseStep}
fineStep={config.fineStep}
/> />
</FormControl> </FormControl>
); );

View File

@ -1,7 +1,11 @@
import { Box, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { Box, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIColorPicker from 'common/components/IAIColorPicker'; import IAIColorPicker from 'common/components/IAIColorPicker';
import { setInfillColorValue } from 'features/controlLayers/store/paramsSlice'; import {
selectInfillColorValue,
selectInfillMethod,
setInfillColorValue,
} from 'features/controlLayers/store/paramsSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import type { RgbaColor } from 'react-colorful'; import type { RgbaColor } from 'react-colorful';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -9,8 +13,8 @@ import { useTranslation } from 'react-i18next';
const ParamInfillColorOptions = () => { const ParamInfillColorOptions = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const infillColor = useAppSelector((s) => s.params.infillColorValue); const infillColor = useAppSelector(selectInfillColorValue);
const infillMethod = useAppSelector((s) => s.params.infillMethod); const infillMethod = useAppSelector(selectInfillMethod);
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -2,7 +2,7 @@ import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setInfillMethod } from 'features/controlLayers/store/paramsSlice'; import { selectInfillMethod, setInfillMethod } from 'features/controlLayers/store/paramsSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useGetAppConfigQuery } from 'services/api/endpoints/appInfo'; import { useGetAppConfigQuery } from 'services/api/endpoints/appInfo';
@ -10,7 +10,7 @@ import { useGetAppConfigQuery } from 'services/api/endpoints/appInfo';
const ParamInfillMethod = () => { const ParamInfillMethod = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const infillMethod = useAppSelector((s) => s.params.infillMethod); const infillMethod = useAppSelector(selectInfillMethod);
const { data: appConfigData } = useGetAppConfigQuery(); const { data: appConfigData } = useGetAppConfigQuery();
const options = useMemo<ComboboxOption[]>( const options = useMemo<ComboboxOption[]>(
() => () =>

View File

@ -1,4 +1,5 @@
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectInfillMethod } from 'features/controlLayers/store/paramsSlice';
import { memo } from 'react'; import { memo } from 'react';
import ParamInfillColorOptions from './ParamInfillColorOptions'; import ParamInfillColorOptions from './ParamInfillColorOptions';
@ -6,7 +7,7 @@ import ParamInfillPatchmatchDownscaleSize from './ParamInfillPatchmatchDownscale
import ParamInfillTilesize from './ParamInfillTilesize'; import ParamInfillTilesize from './ParamInfillTilesize';
const ParamInfillOptions = () => { const ParamInfillOptions = () => {
const infillMethod = useAppSelector((s) => s.params.infillMethod); const infillMethod = useAppSelector(selectInfillMethod);
if (infillMethod === 'tile') { if (infillMethod === 'tile') {
return <ParamInfillTilesize />; return <ParamInfillTilesize />;
} }

View File

@ -1,21 +1,20 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setInfillPatchmatchDownscaleSize } from 'features/controlLayers/store/paramsSlice'; import {
selectInfillMethod,
selectInfillPatchmatchDownscaleSize,
setInfillPatchmatchDownscaleSize,
} from 'features/controlLayers/store/paramsSlice';
import { selectInfillPatchmatchDownscaleSizeConfig } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const ParamInfillPatchmatchDownscaleSize = () => { const ParamInfillPatchmatchDownscaleSize = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const infillMethod = useAppSelector((s) => s.params.infillMethod); const infillMethod = useAppSelector(selectInfillMethod);
const infillPatchmatchDownscaleSize = useAppSelector((s) => s.params.infillPatchmatchDownscaleSize); const infillPatchmatchDownscaleSize = useAppSelector(selectInfillPatchmatchDownscaleSize);
const initial = useAppSelector((s) => s.config.sd.infillPatchmatchDownscaleSize.initial); const config = useAppSelector(selectInfillPatchmatchDownscaleSizeConfig);
const sliderMin = useAppSelector((s) => s.config.sd.infillPatchmatchDownscaleSize.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.infillPatchmatchDownscaleSize.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.infillPatchmatchDownscaleSize.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.infillPatchmatchDownscaleSize.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.infillPatchmatchDownscaleSize.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.infillPatchmatchDownscaleSize.fineStep);
const { t } = useTranslation(); const { t } = useTranslation();
@ -32,23 +31,23 @@ const ParamInfillPatchmatchDownscaleSize = () => {
<FormLabel>{t('parameters.patchmatchDownScaleSize')}</FormLabel> <FormLabel>{t('parameters.patchmatchDownScaleSize')}</FormLabel>
</InformationalPopover> </InformationalPopover>
<CompositeSlider <CompositeSlider
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
value={infillPatchmatchDownscaleSize} value={infillPatchmatchDownscaleSize}
defaultValue={initial}
onChange={handleChange} onChange={handleChange}
marks marks
defaultValue={config.initial}
min={config.sliderMin}
max={config.sliderMax}
step={config.coarseStep}
fineStep={config.fineStep}
/> />
<CompositeNumberInput <CompositeNumberInput
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
value={infillPatchmatchDownscaleSize} value={infillPatchmatchDownscaleSize}
defaultValue={initial}
onChange={handleChange} onChange={handleChange}
defaultValue={config.initial}
min={config.numberInputMin}
max={config.numberInputMax}
step={config.coarseStep}
fineStep={config.fineStep}
/> />
</FormControl> </FormControl>
); );

View File

@ -1,21 +1,15 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setInfillTileSize } from 'features/controlLayers/store/paramsSlice'; import { selectInfillMethod, selectInfillTileSize, setInfillTileSize } from 'features/controlLayers/store/paramsSlice';
import { selectInfillTileSizeConfig } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const ParamInfillTileSize = () => { const ParamInfillTileSize = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const infillTileSize = useAppSelector((s) => s.params.infillTileSize); const infillTileSize = useAppSelector(selectInfillTileSize);
const initial = useAppSelector((s) => s.config.sd.infillTileSize.initial); const config = useAppSelector(selectInfillTileSizeConfig);
const sliderMin = useAppSelector((s) => s.config.sd.infillTileSize.sliderMin); const infillMethod = useAppSelector(selectInfillMethod);
const sliderMax = useAppSelector((s) => s.config.sd.infillTileSize.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.infillTileSize.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.infillTileSize.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.infillTileSize.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.infillTileSize.fineStep);
const infillMethod = useAppSelector((s) => s.params.infillMethod);
const { t } = useTranslation(); const { t } = useTranslation();
@ -30,23 +24,23 @@ const ParamInfillTileSize = () => {
<FormControl isDisabled={infillMethod !== 'tile'}> <FormControl isDisabled={infillMethod !== 'tile'}>
<FormLabel>{t('parameters.tileSize')}</FormLabel> <FormLabel>{t('parameters.tileSize')}</FormLabel>
<CompositeSlider <CompositeSlider
min={sliderMin}
max={sliderMax}
value={infillTileSize} value={infillTileSize}
defaultValue={initial}
onChange={handleChange} onChange={handleChange}
step={coarseStep} defaultValue={config.initial}
fineStep={fineStep} min={config.sliderMin}
max={config.sliderMax}
step={config.coarseStep}
fineStep={config.fineStep}
marks marks
/> />
<CompositeNumberInput <CompositeNumberInput
min={numberInputMin}
max={numberInputMax}
value={infillTileSize} value={infillTileSize}
defaultValue={initial}
onChange={handleChange} onChange={handleChange}
step={coarseStep} defaultValue={config.initial}
fineStep={fineStep} min={config.numberInputMin}
max={config.numberInputMax}
step={config.coarseStep}
fineStep={config.fineStep}
/> />
</FormControl> </FormControl>
); );

View File

@ -1,10 +1,10 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setImg2imgStrength } from 'features/controlLayers/store/paramsSlice'; import { selectImg2imgStrength, setImg2imgStrength } from 'features/controlLayers/store/paramsSlice';
import ImageToImageStrength from 'features/parameters/components/ImageToImage/ImageToImageStrength'; import ImageToImageStrength from 'features/parameters/components/ImageToImage/ImageToImageStrength';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
const ParamImageToImageStrength = () => { const ParamImageToImageStrength = () => {
const img2imgStrength = useAppSelector((s) => s.params.img2imgStrength); const img2imgStrength = useAppSelector(selectImg2imgStrength);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const onChange = useCallback( const onChange = useCallback(

View File

@ -1,22 +1,20 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setCfgScale } from 'features/controlLayers/store/paramsSlice'; import { selectCFGScale, setCfgScale } from 'features/controlLayers/store/paramsSlice';
import { selectCFGScaleConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const ParamCFGScale = () => { const ParamCFGScale = () => {
const cfgScale = useAppSelector((s) => s.params.cfgScale); const cfgScale = useAppSelector(selectCFGScale);
const sliderMin = useAppSelector((s) => s.config.sd.guidance.sliderMin); const config = useAppSelector(selectCFGScaleConfig);
const sliderMax = useAppSelector((s) => s.config.sd.guidance.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.guidance.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.guidance.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.guidance.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.guidance.fineStep);
const initial = useAppSelector((s) => s.config.sd.guidance.initial);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]); const marks = useMemo(
() => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax],
[config.sliderMax, config.sliderMin]
);
const onChange = useCallback((v: number) => dispatch(setCfgScale(v)), [dispatch]); const onChange = useCallback((v: number) => dispatch(setCfgScale(v)), [dispatch]);
return ( return (
@ -26,21 +24,21 @@ const ParamCFGScale = () => {
</InformationalPopover> </InformationalPopover>
<CompositeSlider <CompositeSlider
value={cfgScale} value={cfgScale}
defaultValue={initial} defaultValue={config.initial}
min={sliderMin} min={config.sliderMin}
max={sliderMax} max={config.sliderMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
marks={marks} marks={marks}
/> />
<CompositeNumberInput <CompositeNumberInput
value={cfgScale} value={cfgScale}
defaultValue={initial} defaultValue={config.initial}
min={numberInputMin} min={config.numberInputMin}
max={numberInputMax} max={config.numberInputMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
/> />
</FormControl> </FormControl>

View File

@ -1,21 +1,25 @@
import { Box, Textarea } from '@invoke-ai/ui-library'; import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { negativePromptChanged } from 'features/controlLayers/store/paramsSlice'; import { negativePromptChanged, selectNegativePrompt } from 'features/controlLayers/store/paramsSlice';
import { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel'; import { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
import { ViewModePrompt } from 'features/parameters/components/Prompts/ViewModePrompt'; import { ViewModePrompt } from 'features/parameters/components/Prompts/ViewModePrompt';
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
import { PromptPopover } from 'features/prompt/PromptPopover'; import { PromptPopover } from 'features/prompt/PromptPopover';
import { usePrompt } from 'features/prompt/usePrompt'; import { usePrompt } from 'features/prompt/usePrompt';
import {
selectStylePresetActivePresetId,
selectStylePresetViewMode,
} from 'features/stylePresets/store/stylePresetSlice';
import { memo, useCallback, useRef } from 'react'; import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets'; import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
export const ParamNegativePrompt = memo(() => { export const ParamNegativePrompt = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const prompt = useAppSelector((s) => s.params.negativePrompt); const prompt = useAppSelector(selectNegativePrompt);
const viewMode = useAppSelector((s) => s.stylePreset.viewMode); const viewMode = useAppSelector(selectStylePresetViewMode);
const activeStylePresetId = useAppSelector((s) => s.stylePreset.activeStylePresetId); const activeStylePresetId = useAppSelector(selectStylePresetActivePresetId);
const { activeStylePreset } = useListStylePresetsQuery(undefined, { const { activeStylePreset } = useListStylePresetsQuery(undefined, {
selectFromResult: ({ data }) => { selectFromResult: ({ data }) => {

View File

@ -1,6 +1,6 @@
import { Box, Textarea } from '@invoke-ai/ui-library'; import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { positivePromptChanged } from 'features/controlLayers/store/paramsSlice'; import { positivePromptChanged, selectBase, selectPositivePrompt } from 'features/controlLayers/store/paramsSlice';
import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton'; import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton';
import { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel'; import { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
@ -9,6 +9,10 @@ import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
import { PromptPopover } from 'features/prompt/PromptPopover'; import { PromptPopover } from 'features/prompt/PromptPopover';
import { usePrompt } from 'features/prompt/usePrompt'; import { usePrompt } from 'features/prompt/usePrompt';
import { SDXLConcatButton } from 'features/sdxl/components/SDXLPrompts/SDXLConcatButton'; import { SDXLConcatButton } from 'features/sdxl/components/SDXLPrompts/SDXLConcatButton';
import {
selectStylePresetActivePresetId,
selectStylePresetViewMode,
} from 'features/stylePresets/store/stylePresetSlice';
import { memo, useCallback, useRef } from 'react'; import { memo, useCallback, useRef } from 'react';
import type { HotkeyCallback } from 'react-hotkeys-hook'; import type { HotkeyCallback } from 'react-hotkeys-hook';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
@ -17,10 +21,10 @@ import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
export const ParamPositivePrompt = memo(() => { export const ParamPositivePrompt = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const prompt = useAppSelector((s) => s.params.positivePrompt); const prompt = useAppSelector(selectPositivePrompt);
const baseModel = useAppSelector((s) => s.params.model)?.base; const baseModel = useAppSelector(selectBase);
const viewMode = useAppSelector((s) => s.stylePreset.viewMode); const viewMode = useAppSelector(selectStylePresetViewMode);
const activeStylePresetId = useAppSelector((s) => s.stylePreset.activeStylePresetId); const activeStylePresetId = useAppSelector(selectStylePresetActivePresetId);
const { activeStylePreset } = useListStylePresetsQuery(undefined, { const { activeStylePreset } = useListStylePresetsQuery(undefined, {
selectFromResult: ({ data }) => { selectFromResult: ({ data }) => {

View File

@ -2,7 +2,7 @@ import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setScheduler } from 'features/controlLayers/store/paramsSlice'; import { selectScheduler, setScheduler } from 'features/controlLayers/store/paramsSlice';
import { SCHEDULER_OPTIONS } from 'features/parameters/types/constants'; import { SCHEDULER_OPTIONS } from 'features/parameters/types/constants';
import { isParameterScheduler } from 'features/parameters/types/parameterSchemas'; import { isParameterScheduler } from 'features/parameters/types/parameterSchemas';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
const ParamScheduler = () => { const ParamScheduler = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const scheduler = useAppSelector((s) => s.params.scheduler); const scheduler = useAppSelector(selectScheduler);
const onChange = useCallback<ComboboxOnChange>( const onChange = useCallback<ComboboxOnChange>(
(v) => { (v) => {

View File

@ -1,22 +1,20 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setSteps } from 'features/controlLayers/store/paramsSlice'; import { selectSteps, setSteps } from 'features/controlLayers/store/paramsSlice';
import { selectStepsConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const ParamSteps = () => { const ParamSteps = () => {
const steps = useAppSelector((s) => s.params.steps); const steps = useAppSelector(selectSteps);
const initial = useAppSelector((s) => s.config.sd.steps.initial); const config = useAppSelector(selectStepsConfig);
const sliderMin = useAppSelector((s) => s.config.sd.steps.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.steps.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.steps.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.steps.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.steps.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.steps.fineStep);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]); const marks = useMemo(
() => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax],
[config.sliderMax, config.sliderMin]
);
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
dispatch(setSteps(v)); dispatch(setSteps(v));
@ -31,21 +29,21 @@ const ParamSteps = () => {
</InformationalPopover> </InformationalPopover>
<CompositeSlider <CompositeSlider
value={steps} value={steps}
defaultValue={initial} defaultValue={config.initial}
min={sliderMin} min={config.sliderMin}
max={sliderMax} max={config.sliderMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
marks={marks} marks={marks}
/> />
<CompositeNumberInput <CompositeNumberInput
value={steps} value={steps}
defaultValue={initial} defaultValue={config.initial}
min={numberInputMin} min={config.numberInputMin}
max={numberInputMax} max={config.numberInputMax}
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
onChange={onChange} onChange={onChange}
/> />
</FormControl> </FormControl>

View File

@ -1,6 +1,7 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { selectImg2imgStrengthConfig } from 'features/system/store/configSlice';
import { memo } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -12,13 +13,7 @@ type Props = {
}; };
const ImageToImageStrength = ({ value, onChange }: Props) => { const ImageToImageStrength = ({ value, onChange }: Props) => {
const initial = useAppSelector((s) => s.config.sd.img2imgStrength.initial); const config = useAppSelector(selectImg2imgStrengthConfig);
const sliderMin = useAppSelector((s) => s.config.sd.img2imgStrength.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.img2imgStrength.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.img2imgStrength.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.img2imgStrength.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.img2imgStrength.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.img2imgStrength.fineStep);
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@ -27,23 +22,23 @@ const ImageToImageStrength = ({ value, onChange }: Props) => {
<FormLabel>{`${t('parameters.denoisingStrength')}`}</FormLabel> <FormLabel>{`${t('parameters.denoisingStrength')}`}</FormLabel>
</InformationalPopover> </InformationalPopover>
<CompositeSlider <CompositeSlider
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
min={sliderMin} min={config.sliderMin}
max={sliderMax} max={config.sliderMax}
defaultValue={config.initial}
onChange={onChange} onChange={onChange}
value={value} value={value}
defaultValue={initial}
marks={marks} marks={marks}
/> />
<CompositeNumberInput <CompositeNumberInput
step={coarseStep} step={config.coarseStep}
fineStep={fineStep} fineStep={config.fineStep}
min={numberInputMin} min={config.numberInputMin}
max={numberInputMax} max={config.numberInputMax}
defaultValue={config.initial}
onChange={onChange} onChange={onChange}
value={value} value={value}
defaultValue={initial}
/> />
</FormControl> </FormControl>
); );

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