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 { activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice';
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 { setActiveTab } from 'features/ui/store/uiSlice';
import type { TabName } from 'features/ui/store/uiTypes';
@ -53,7 +53,7 @@ const App = ({
selectedStylePresetId,
destination,
}: Props) => {
const language = useAppSelector(languageSelector);
const language = useAppSelector(selectLanguage);
const logger = useLogger('system');
const dispatch = useAppDispatch();
const clearStorage = useClearStorage();

View File

@ -1,21 +1,20 @@
import { createSelector } from '@reduxjs/toolkit';
import { createLogWriter } from '@roarr/browser-log-writer';
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 { ROARR, Roarr } from 'roarr';
import type { LogNamespace } 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) => {
const logLevel = useAppSelector(selectLogLevel);
const logNamespaces = useAppSelector(selectLogNamespaces);
const logIsEnabled = useAppSelector(selectLogIsEnabled);
const logLevel = useAppSelector(selectSystemLogLevel);
const logNamespaces = useAppSelector(selectSystemLogNamespaces);
const logIsEnabled = useAppSelector(selectSystemLogIsEnabled);
// The provided Roarr browser log writer uses localStorage to config logging to console
useEffect(() => {

View File

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

View File

@ -1,7 +1,8 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { selectCanvasSlice } from 'features/controlLayers/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';
export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => {

View File

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

View File

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

View File

@ -32,7 +32,10 @@ type Props = {
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) => {
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 { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
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 { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
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 { LoRA } from 'features/controlLayers/store/types';
import { zModelIdentifierField } from 'features/nodes/types/common';
@ -65,8 +65,6 @@ export const lorasSlice = createSlice({
export const { loraAdded, loraRecalled, loraDeleted, loraWeightChanged, loraIsEnabledChanged, loraAllDeleted } =
lorasSlice.actions;
export const selectLoRAsSlice = (state: RootState) => state.loras;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrate = (state: any): any => {
return state;
@ -78,3 +76,6 @@ export const lorasPersistConfig: PersistConfig<LoRAsState> = {
migrate,
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 { RgbaColor } from 'features/controlLayers/store/types';
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
@ -270,9 +271,6 @@ export const {
modelChanged,
} = 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 */
const migrate = (state: any): any => {
return state;
@ -284,3 +282,55 @@ export const paramsPersistConfig: PersistConfig<ParamsState> = {
migrate,
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,
} from 'features/deleteImageModal/store/slice';
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 { some } from 'lodash-es';
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 type { CanvasState } from 'features/controlLayers/store/types';
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 { isImageFieldInputInstance } from 'features/nodes/types/field';
import { isInvocationNode } from 'features/nodes/types/invocation';

View File

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

View File

@ -1,11 +1,15 @@
import type { ChakraProps } 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 { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
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 { useTranslation } from 'react-i18next';
import { PiWarningCircleBold } from 'react-icons/pi';
@ -14,17 +18,12 @@ const listItemStyles: ChakraProps['sx'] = {
'&::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 { t } = useTranslation();
const parsingError = useAppSelector(selectParsingError);
const isError = useAppSelector(selectIsError);
const isLoading = useAppSelector(selectIsLoading);
const prompts = useAppSelector(selectPrompts);
const parsingError = useAppSelector(selectDynamicPromptsParsingError);
const isError = useAppSelector(selectDynamicPromptsIsError);
const isLoading = useAppSelector(selectDynamicPromptsIsLoading);
const prompts = useAppSelector(selectDynamicPromptsPrompts);
const label = useMemo(() => {
let _label = `${t('dynamicPrompts.promptsPreview')} (${prompts.length})`;

View File

@ -1,22 +1,19 @@
import type { ComboboxOnChange, ComboboxOption } 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 { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import {
isSeedBehaviour,
seedBehaviourChanged,
selectDynamicPromptsSlice,
selectDynamicPromptsSeedBehaviour,
} from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
const selectSeedBehaviour = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.seedBehaviour);
const ParamDynamicPromptsSeedBehaviour = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const seedBehaviour = useAppSelector(selectSeedBehaviour);
const seedBehaviour = useAppSelector(selectDynamicPromptsSeedBehaviour);
const options = useMemo<ComboboxOption[]>(() => {
return [

View File

@ -1,9 +1,11 @@
import type { SystemStyleObject } 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 { 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 { useTranslation } from 'react-i18next';
import { BsBracesAsterisk } from 'react-icons/bs';
@ -12,15 +14,10 @@ const loadingStyles: SystemStyleObject = {
svg: { animation: spinAnimation },
};
const selectIsError = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) =>
Boolean(dynamicPrompts.isError || dynamicPrompts.parsingError)
);
const selectIsLoading = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.isLoading);
export const ShowDynamicPromptsPreviewButton = memo(() => {
const { t } = useTranslation();
const isLoading = useAppSelector(selectIsLoading);
const isError = useAppSelector(selectIsError);
const isLoading = useAppSelector(selectDynamicPromptsIsLoading);
const isError = useAppSelector(selectDynamicPromptsIsError);
const { isOpen, onOpen } = useDynamicPromptsModal();
return (

View File

@ -1,5 +1,5 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } 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 { z } from 'zod';
@ -72,8 +72,6 @@ export const {
seedBehaviourChanged,
} = dynamicPromptsSlice.actions;
export const selectDynamicPromptsSlice = (state: RootState) => state.dynamicPrompts;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateDynamicPromptsState = (state: any): any => {
if (!('_version' in state)) {
@ -88,3 +86,23 @@ export const dynamicPromptsPersistConfig: PersistConfig<DynamicPromptsState> = {
migrate: migrateDynamicPromptsState,
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;
};
export const BoardsList = ({ isPrivate, setBoardToDelete }: Props) => {
const { t } = useTranslation();
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 { getImageUsage } from 'features/deleteImageModal/store/selectors';
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 { memo, useCallback, useMemo, useRef } from 'react';
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 { handlers, parseAndRecallAllMetadata, parseAndRecallPrompts } from 'features/metadata/util/handlers';
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 { selectActiveTab } from 'features/ui/store/uiSelectors';
import { useCallback, useEffect, useState } from 'react';
@ -13,7 +16,7 @@ import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
export const useImageActions = (image_name?: string) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const activeStylePresetId = useAppSelector(selectActivePresetId);
const activeStylePresetId = useAppSelector(selectStylePresetActivePresetId);
const activeTabName = useAppSelector(selectActiveTab);
const { metadata, isLoading: isLoadingMetadata } = useDebouncedMetadata(image_name);
const [hasMetadata, setHasMetadata] = useState(false);

View File

@ -1,5 +1,5 @@
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 { ModelType } from 'services/api/types';
@ -50,8 +50,6 @@ export const modelManagerV2Slice = createSlice({
export const { setSelectedModelKey, setSearchTerm, setFilteredModelType, setSelectedModelMode, setScanPath } =
modelManagerV2Slice.actions;
export const selectModelManagerV2Slice = (state: RootState) => state.modelmanagerV2;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateModelManagerState = (state: any): any => {
if (!('_version' in state)) {
@ -66,3 +64,13 @@ export const modelManagerV2PersistConfig: PersistConfig<ModelManagerState> = {
migrate: migrateModelManagerState,
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 { 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 { memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -8,8 +8,10 @@ import { useLazyScanFolderQuery } from 'services/api/endpoints/models';
import { ScanModelsResults } from './ScanFolderResults';
const selectScanPath = createModelManagerSelector((mm) => mm.scanPath);
export const ScanModelsForm = memo(() => {
const scanPath = useAppSelector((state) => state.modelmanagerV2.scanPath);
const scanPath = useAppSelector(selectScanPath);
const dispatch = useAppDispatch();
const [errorMessage, setErrorMessage] = useState('');
const { t } = useTranslation();

View File

@ -1,7 +1,11 @@
import { Flex, Text } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
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 { useTranslation } from 'react-i18next';
import {
@ -23,8 +27,8 @@ import { FetchingModelsLoader } from './FetchingModelsLoader';
import { ModelListWrapper } from './ModelListWrapper';
const ModelList = () => {
const filteredModelType = useAppSelector((s) => s.modelmanagerV2.filteredModelType);
const searchTerm = useAppSelector((s) => s.modelmanagerV2.searchTerm);
const filteredModelType = useAppSelector(selectFilteredModelType);
const searchTerm = useAppSelector(selectSearchTerm);
const { t } = useTranslation();
const [mainModels, { isLoading: isLoadingMainModels }] = useMainModels();

View File

@ -1,6 +1,6 @@
import { Flex, IconButton, Input, InputGroup, InputRightElement, Spacer } from '@invoke-ai/ui-library';
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 type { ChangeEventHandler } from 'react';
import { memo, useCallback } from 'react';
@ -10,7 +10,7 @@ import { ModelTypeFilter } from './ModelTypeFilter';
export const ModelListNavigation = memo(() => {
const dispatch = useAppDispatch();
const searchTerm = useAppSelector((s) => s.modelmanagerV2.searchTerm);
const searchTerm = useAppSelector(selectSearchTerm);
const handleSearch: ChangeEventHandler<HTMLInputElement> = useCallback(
(event) => {

View File

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

View File

@ -1,12 +1,13 @@
import { Box } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { selectSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice';
import { memo } from 'react';
import { InstallModels } from './InstallModels';
import { Model } from './ModelPanel/Model';
export const ModelPane = memo(() => {
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const selectedModelKey = useAppSelector(selectSelectedModelKey);
return (
<Box layerStyle="first" p={4} borderRadius="base" w="50%" h="full">
{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 { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
import { selectCFGRescaleMultiplierConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } 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>) => {
const { field } = useController(props);
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 config = useAppSelector(selectCFGRescaleMultiplierConfig);
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) => {
@ -54,20 +53,20 @@ export const DefaultCfgRescaleMultiplier = memo((props: UseControllerProps<MainM
<Flex w="full" gap={4}>
<CompositeSlider
value={value}
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
min={config.sliderMin}
max={config.sliderMax}
step={config.coarseStep}
fineStep={config.fineStep}
onChange={onChange}
marks={marks}
isDisabled={isDisabled}
/>
<CompositeNumberInput
value={value}
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
min={config.numberInputMin}
max={config.numberInputMax}
step={config.coarseStep}
fineStep={config.fineStep}
onChange={onChange}
isDisabled={isDisabled}
/>

View File

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

View File

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

View File

@ -2,6 +2,7 @@ import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } f
import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
import { selectStepsConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } 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>) => {
const { field } = useController(props);
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 config = useAppSelector(selectStepsConfig);
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) => {
@ -54,20 +53,20 @@ export const DefaultSteps = memo((props: UseControllerProps<MainModelDefaultSett
<Flex w="full" gap={4}>
<CompositeSlider
value={value}
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
min={config.sliderMin}
max={config.sliderMax}
step={config.coarseStep}
fineStep={config.fineStep}
onChange={onChange}
marks={marks}
isDisabled={isDisabled}
/>
<CompositeNumberInput
value={value}
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
min={config.numberInputMin}
max={config.numberInputMax}
step={config.coarseStep}
fineStep={config.fineStep}
onChange={onChange}
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 { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { selectSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form';
@ -18,7 +19,7 @@ type DefaultVaeType = MainModelDefaultSettingsFormData['vae'];
export const DefaultVae = memo((props: UseControllerProps<MainModelDefaultSettingsFormData>) => {
const { t } = useTranslation();
const { field } = useController(props);
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const selectedModelKey = useAppSelector(selectSelectedModelKey);
const { data: modelData } = useGetModelConfigQuery(selectedModelKey ?? skipToken);
const [vaeModels] = useVAEModels();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { $pendingConnection } from 'features/nodes/store/nodesSlice';
import { selectShouldAnimateEdges, selectShouldColorEdges } from 'features/nodes/store/workflowSettingsSlice';
import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react';
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 pendingConnection = useStore($pendingConnection);
const shouldColorEdges = useAppSelector((state) => state.workflowSettings.shouldColorEdges);
const shouldAnimateEdges = useAppSelector((state) => state.workflowSettings.shouldAnimateEdges);
const shouldColorEdges = useAppSelector(selectShouldColorEdges);
const shouldAnimateEdges = useAppSelector(selectShouldAnimateEdges);
const stroke = useMemo(() => {
if (shouldColorEdges && pendingConnection) {
return getFieldColor(pendingConnection.fieldTemplate.type);

View File

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

View File

@ -1,7 +1,7 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
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 { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
import { isInvocationNode } from 'features/nodes/types/invocation';

View File

@ -1,7 +1,9 @@
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import InvocationNode from 'features/nodes/components/flow/nodes/Invocation/InvocationNode';
import { $templates } from 'features/nodes/store/nodesSlice';
import { selectNodes } from 'features/nodes/store/selectors';
import type { InvocationNodeData } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react';
import type { NodeProps } from 'reactflow';
@ -13,7 +15,11 @@ const InvocationNodeWrapper = (props: NodeProps<InvocationNodeData>) => {
const { id: nodeId, type, isOpen, label } = data;
const templates = useStore($templates);
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) {
return null;

View File

@ -2,8 +2,8 @@ import { Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectInvocationNode } from 'features/nodes/store/selectors';
import { $templates } from 'features/nodes/store/nodesSlice';
import { selectInvocationNode, selectNodesSlice } from 'features/nodes/store/selectors';
import type { PropsWithChildren } from 'react';
import { memo, useMemo } from 'react';
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 { fieldT5EncoderValueChanged } from 'features/nodes/store/nodesSlice';
import type { T5EncoderModelFieldInputInstance, T5EncoderModelFieldInputTemplate } from 'features/nodes/types/field';
import { selectIsModelsTabDisabled } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useT5EncoderModels } from 'services/api/hooks/modelsByType';
@ -15,7 +16,7 @@ type Props = FieldComponentProps<T5EncoderModelFieldInputInstance, T5EncoderMode
const T5EncoderModelFieldInputComponent = (props: Props) => {
const { nodeId, field } = props;
const { t } = useTranslation();
const disabledTabs = useAppSelector((s) => s.config.disabledTabs);
const isModelsTabDisabled = useAppSelector(selectIsModelsTabDisabled);
const dispatch = useAppDispatch();
const [modelConfigs, { isLoading }] = useT5EncoderModels();
const _onChange = useCallback(
@ -42,7 +43,7 @@ const T5EncoderModelFieldInputComponent = (props: Props) => {
return (
<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}>
<Combobox
value={value}

View File

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

View File

@ -1,12 +1,12 @@
import { CompositeSlider, Flex } from '@invoke-ai/ui-library';
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 { useTranslation } from 'react-i18next';
const NodeOpacitySlider = () => {
const dispatch = useAppDispatch();
const nodeOpacity = useAppSelector((s) => s.workflowSettings.nodeOpacity);
const nodeOpacity = useAppSelector(selectNodeOpacity);
const { t } = useTranslation();
const handleChange = useCallback(

View File

@ -1,6 +1,9 @@
import { ButtonGroup, IconButton } from '@invoke-ai/ui-library';
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 { useTranslation } from 'react-i18next';
import {
@ -15,7 +18,7 @@ const ViewportControls = () => {
const { t } = useTranslation();
const { zoomIn, zoomOut, fitView } = useReactFlow();
const dispatch = useAppDispatch();
const shouldShowMinimapPanel = useAppSelector((s) => s.workflowSettings.shouldShowMinimapPanel);
const shouldShowMinimapPanel = useAppSelector(selectShouldShowMinimapPanel);
const handleClickedZoomIn = useCallback(() => {
zoomIn({ duration: 300 });

View File

@ -1,6 +1,7 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { chakra, Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { selectShouldShowMinimapPanel } from 'features/nodes/store/workflowSettingsSlice';
import { memo } from 'react';
import { MiniMap } from 'reactflow';
@ -16,7 +17,7 @@ const minimapStyles: SystemStyleObject = {
};
const MinimapPanel = () => {
const shouldShowMinimapPanel = useAppSelector((s) => s.workflowSettings.shouldShowMinimapPanel);
const shouldShowMinimapPanel = useAppSelector(selectShouldShowMinimapPanel);
return (
<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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
import { selectWorkflowIsTouched } from 'features/nodes/store/workflowSlice';
import { toast } from 'features/toast/toast';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@ -10,7 +11,7 @@ const ClearFlowButton = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
const isTouched = useAppSelector((s) => s.workflow.isTouched);
const isTouched = useAppSelector(selectWorkflowIsTouched);
const handleNewWorkflow = useCallback(() => {
dispatch(nodeEditorReset());

View File

@ -1,6 +1,7 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { $builtWorkflow } from 'features/nodes/hooks/useWorkflowWatcher';
import { selectWorkflowIsTouched } from 'features/nodes/store/workflowSlice';
import { useSaveWorkflowAsDialog } from 'features/workflowLibrary/components/SaveWorkflowAsDialog/useSaveWorkflowAsDialog';
import { isWorkflowWithID, useSaveLibraryWorkflow } from 'features/workflowLibrary/hooks/useSaveWorkflow';
import { memo, useCallback } from 'react';
@ -9,7 +10,7 @@ import { PiFloppyDiskBold } from 'react-icons/pi';
const SaveWorkflowButton = () => {
const { t } = useTranslation();
const isTouched = useAppSelector((s) => s.workflow.isTouched);
const isTouched = useAppSelector(selectWorkflowIsTouched);
const { onOpen } = useSaveWorkflowAsDialog();
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 UpdateNodesButton from 'features/nodes/components/flow/panels/TopPanel/UpdateNodesButton';
import { WorkflowName } from 'features/nodes/components/sidePanel/WorkflowName';
import { selectWorkflowName } from 'features/nodes/store/workflowSlice';
import WorkflowLibraryMenu from 'features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu';
import { memo } from 'react';
const TopCenterPanel = () => {
const name = useAppSelector((s) => s.workflow.name);
const name = useAppSelector(selectWorkflowName);
return (
<Flex gap={2} top={0} left={0} right={0} position="absolute" alignItems="flex-start" pointerEvents="none">
<Flex gap="2">

View File

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

View File

@ -1,13 +1,13 @@
import { Flex, IconButton } from '@invoke-ai/ui-library';
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 { useTranslation } from 'react-i18next';
import { PiEyeBold, PiPencilBold } from 'react-icons/pi';
export const ModeToggle = () => {
const dispatch = useAppDispatch();
const mode = useAppSelector((s) => s.workflow.mode);
const mode = useAppSelector(selectWorkflowMode);
const { t } = useTranslation();
const onClickEdit = useCallback(() => {

View File

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

View File

@ -1,12 +1,13 @@
import { Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
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 { ModeToggle } from './ModeToggle';
export const WorkflowMenu = () => {
const mode = useAppSelector((s) => s.workflow.mode);
const mode = useAppSelector(selectWorkflowMode);
return (
<Flex gap="2" alignItems="center">

View File

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

View File

@ -2,8 +2,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode } from 'features/nodes/store/selectors';
import { selectLastSelectedNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { memo } from 'react';
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 NotesTextarea from 'features/nodes/components/flow/nodes/Invocation/NotesTextarea';
import { useNodeNeedsUpdate } from 'features/nodes/hooks/useNodeNeedsUpdate';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode } from 'features/nodes/store/selectors';
import { $templates } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react';
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 DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { useExecutionState } from 'features/nodes/hooks/useExecutionState';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode } from 'features/nodes/store/selectors';
import { $templates } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';

View File

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

View File

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

View File

@ -1,7 +1,8 @@
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
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 { useMemo } from 'react';

View File

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

View File

@ -1,7 +1,6 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectNodeData } from 'features/nodes/store/selectors';
import { selectNodeData, selectNodesSlice } from 'features/nodes/store/selectors';
import { useMemo } from 'react';
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 { useAppSelector } from 'app/store/storeHooks';
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 { NodeExecutionState } 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 { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectFieldInputInstance } from 'features/nodes/store/selectors';
import { selectFieldInputInstance, selectNodesSlice } from 'features/nodes/store/selectors';
import type { FieldInputInstance } from 'features/nodes/types/field';
import { useMemo } from 'react';

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
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 { useMemo } from 'react';

View File

@ -1,7 +1,8 @@
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
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 { getNeedsUpdate } from 'features/nodes/util/node/nodeUpdate';
import { useMemo } from 'react';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,8 @@
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
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 { useMemo } from 'react';

View File

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

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
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 type { WorkflowV3 } from 'features/nodes/types/workflow';
import type { BuildWorkflowArg } from 'features/nodes/util/workflow/buildWorkflow';

View File

@ -1,6 +1,6 @@
import type { PayloadAction, UnknownAction } 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 { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type {
@ -452,8 +452,6 @@ export const openAddNodePopover = () => {
$isAddNodePopoverOpen.set(true);
};
export const selectNodesSlice = (state: RootState) => state.nodes.present;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateNodesState = (state: any): any => {
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 { FieldInputInstance } from 'features/nodes/types/field';
import type { InvocationNode, InvocationNodeData } from 'features/nodes/types/invocation';
@ -36,3 +39,17 @@ export const selectLastSelectedNode = (nodesSlice: NodesState) => {
}
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 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 { buildRejectResult, validateConnection } from 'features/nodes/store/util/validateConnection';
import type { Edge, HandleType } from 'reactflow';

View File

@ -1,6 +1,7 @@
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 { Selector } from 'react-redux';
import { SelectionMode } from 'reactflow';
type WorkflowSettingsState = {
@ -69,8 +70,6 @@ export const {
selectionModeChanged,
} = workflowSettingsSlice.actions;
export const selectWorkflowSettingsSlice = (state: RootState) => state.workflowSettings;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateWorkflowSettingsState = (state: any): any => {
if (!('_version' in state)) {
@ -85,3 +84,15 @@ export const workflowSettingsPersistConfig: PersistConfig<WorkflowSettingsState>
migrate: migrateWorkflowSettingsState,
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 { createSlice } 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 { deepClone } from 'common/util/deepClone';
import { workflowLoaded } from 'features/nodes/store/actions';
@ -209,8 +209,6 @@ export const {
workflowSaved,
} = workflowSlice.actions;
export const selectWorkflowSlice = (state: RootState) => state.workflow;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateWorkflowState = (state: any): any => {
if (!('_version' in state)) {
@ -225,3 +223,12 @@ export const workflowPersistConfig: PersistConfig<WorkflowState> = {
migrate: migrateWorkflowState,
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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
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 { useTranslation } from 'react-i18next';
const ParamCFGRescaleMultiplier = () => {
const cfgRescaleMultiplier = useAppSelector((s) => s.params.cfgRescaleMultiplier);
const initial = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.initial);
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 cfgRescaleMultiplier = useAppSelector(selectCFGRescaleMultiplier);
const config = useAppSelector(selectCFGRescaleMultiplierConfig);
const dispatch = useAppDispatch();
const { t } = useTranslation();
@ -27,21 +22,21 @@ const ParamCFGRescaleMultiplier = () => {
</InformationalPopover>
<CompositeSlider
value={cfgRescaleMultiplier}
defaultValue={initial}
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
defaultValue={config.initial}
min={config.sliderMin}
max={config.sliderMax}
step={config.coarseStep}
fineStep={config.fineStep}
onChange={handleChange}
marks
/>
<CompositeNumberInput
value={cfgRescaleMultiplier}
defaultValue={initial}
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
defaultValue={config.initial}
min={config.numberInputMin}
max={config.numberInputMax}
step={config.coarseStep}
fineStep={config.fineStep}
onChange={handleChange}
/>
</FormControl>

View File

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

View File

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

View File

@ -1,13 +1,16 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
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 { useTranslation } from 'react-i18next';
const ParamCanvasCoherenceMinDenoise = () => {
const dispatch = useAppDispatch();
const canvasCoherenceMinDenoise = useAppSelector((s) => s.params.canvasCoherenceMinDenoise);
const canvasCoherenceMinDenoise = useAppSelector(selectCanvasCoherenceMinDenoise);
const { t } = useTranslation();
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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
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 { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
const ParamCanvasCoherenceMode = () => {
const dispatch = useAppDispatch();
const canvasCoherenceMode = useAppSelector((s) => s.params.canvasCoherenceMode);
const canvasCoherenceMode = useAppSelector(selectCanvasCoherenceMode);
const { t } = useTranslation();
const options = useMemo<ComboboxOption[]>(

View File

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

View File

@ -1,7 +1,11 @@
import { Box, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
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 type { RgbaColor } from 'react-colorful';
import { useTranslation } from 'react-i18next';
@ -9,8 +13,8 @@ import { useTranslation } from 'react-i18next';
const ParamInfillColorOptions = () => {
const dispatch = useAppDispatch();
const infillColor = useAppSelector((s) => s.params.infillColorValue);
const infillMethod = useAppSelector((s) => s.params.infillMethod);
const infillColor = useAppSelector(selectInfillColorValue);
const infillMethod = useAppSelector(selectInfillMethod);
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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
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 { useTranslation } from 'react-i18next';
import { useGetAppConfigQuery } from 'services/api/endpoints/appInfo';
@ -10,7 +10,7 @@ import { useGetAppConfigQuery } from 'services/api/endpoints/appInfo';
const ParamInfillMethod = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const infillMethod = useAppSelector((s) => s.params.infillMethod);
const infillMethod = useAppSelector(selectInfillMethod);
const { data: appConfigData } = useGetAppConfigQuery();
const options = useMemo<ComboboxOption[]>(
() =>

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
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 { memo, useCallback } from 'react';
const ParamImageToImageStrength = () => {
const img2imgStrength = useAppSelector((s) => s.params.img2imgStrength);
const img2imgStrength = useAppSelector(selectImg2imgStrength);
const dispatch = useAppDispatch();
const onChange = useCallback(

View File

@ -1,22 +1,20 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
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 { useTranslation } from 'react-i18next';
const ParamCFGScale = () => {
const cfgScale = useAppSelector((s) => s.params.cfgScale);
const sliderMin = useAppSelector((s) => s.config.sd.guidance.sliderMin);
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 cfgScale = useAppSelector(selectCFGScale);
const config = useAppSelector(selectCFGScaleConfig);
const dispatch = useAppDispatch();
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]);
return (
@ -26,21 +24,21 @@ const ParamCFGScale = () => {
</InformationalPopover>
<CompositeSlider
value={cfgScale}
defaultValue={initial}
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
defaultValue={config.initial}
min={config.sliderMin}
max={config.sliderMax}
step={config.coarseStep}
fineStep={config.fineStep}
onChange={onChange}
marks={marks}
/>
<CompositeNumberInput
value={cfgScale}
defaultValue={initial}
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
defaultValue={config.initial}
min={config.numberInputMin}
max={config.numberInputMax}
step={config.coarseStep}
fineStep={config.fineStep}
onChange={onChange}
/>
</FormControl>

View File

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

View File

@ -1,6 +1,6 @@
import { Box, Textarea } from '@invoke-ai/ui-library';
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 { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel';
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 { usePrompt } from 'features/prompt/usePrompt';
import { SDXLConcatButton } from 'features/sdxl/components/SDXLPrompts/SDXLConcatButton';
import {
selectStylePresetActivePresetId,
selectStylePresetViewMode,
} from 'features/stylePresets/store/stylePresetSlice';
import { memo, useCallback, useRef } from 'react';
import type { HotkeyCallback } 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(() => {
const dispatch = useAppDispatch();
const prompt = useAppSelector((s) => s.params.positivePrompt);
const baseModel = useAppSelector((s) => s.params.model)?.base;
const viewMode = useAppSelector((s) => s.stylePreset.viewMode);
const activeStylePresetId = useAppSelector((s) => s.stylePreset.activeStylePresetId);
const prompt = useAppSelector(selectPositivePrompt);
const baseModel = useAppSelector(selectBase);
const viewMode = useAppSelector(selectStylePresetViewMode);
const activeStylePresetId = useAppSelector(selectStylePresetActivePresetId);
const { activeStylePreset } = useListStylePresetsQuery(undefined, {
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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
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 { isParameterScheduler } from 'features/parameters/types/parameterSchemas';
import { memo, useCallback, useMemo } from 'react';
@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
const ParamScheduler = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const scheduler = useAppSelector((s) => s.params.scheduler);
const scheduler = useAppSelector(selectScheduler);
const onChange = useCallback<ComboboxOnChange>(
(v) => {

View File

@ -1,22 +1,20 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
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 { useTranslation } from 'react-i18next';
const ParamSteps = () => {
const steps = useAppSelector((s) => s.params.steps);
const initial = useAppSelector((s) => s.config.sd.steps.initial);
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 steps = useAppSelector(selectSteps);
const config = useAppSelector(selectStepsConfig);
const dispatch = useAppDispatch();
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(setSteps(v));
@ -31,21 +29,21 @@ const ParamSteps = () => {
</InformationalPopover>
<CompositeSlider
value={steps}
defaultValue={initial}
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
defaultValue={config.initial}
min={config.sliderMin}
max={config.sliderMax}
step={config.coarseStep}
fineStep={config.fineStep}
onChange={onChange}
marks={marks}
/>
<CompositeNumberInput
value={steps}
defaultValue={initial}
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
defaultValue={config.initial}
min={config.numberInputMin}
max={config.numberInputMax}
step={config.coarseStep}
fineStep={config.fineStep}
onChange={onChange}
/>
</FormControl>

View File

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

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