feat(ui): migrate all metadata recall logic to new system

This commit is contained in:
psychedelicious 2024-02-26 11:49:53 +11:00 committed by Brandon Rising
parent 0cb2579109
commit 6a2ae5caa4
10 changed files with 177 additions and 1142 deletions

View File

@ -0,0 +1,6 @@
/**
* Get the keys of an object. This is a wrapper around `Object.keys` that types the result as an array of the keys of the object.
* @param obj The object to get the keys of.
* @returns The keys of the object.
*/
export const objectKeys = <T extends Record<string, unknown>>(obj: T) => Object.keys(obj) as Array<keyof T>;

View File

@ -7,11 +7,12 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { DeleteImageButton } from 'features/deleteImageModal/components/DeleteImageButton';
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
import SingleSelectionMenuItems from 'features/gallery/components/ImageContextMenu/SingleSelectionMenuItems';
import { useImageActions } from 'features/gallery/hooks/useImageActions';
import { sentImageToImg2Img } from 'features/gallery/store/actions';
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
import { parseAndRecallImageDimensions } from 'features/metadata/util/handlers';
import ParamUpscalePopover from 'features/parameters/components/Upscale/ParamUpscaleSettings';
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
import { initialImageSelected } from 'features/parameters/store/actions';
import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
@ -33,7 +34,6 @@ import {
PiRulerBold,
} from 'react-icons/pi';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
const selectShouldDisableToolbarButtons = createSelector(
selectSystemSlice,
@ -58,11 +58,10 @@ const CurrentImageButtons = () => {
const toaster = useAppToaster();
const { t } = useTranslation();
const { recallBothPrompts, recallSeed, recallWidthAndHeight, recallAllParameters } = useRecallParameters();
const { currentData: imageDTO } = useGetImageDTOQuery(lastSelectedImage?.image_name ?? skipToken);
const { metadata, isLoading: isLoadingMetadata } = useDebouncedMetadata(lastSelectedImage?.image_name);
const { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata } =
useImageActions(lastSelectedImage?.image_name);
const { getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult } = useGetAndLoadEmbeddedWorkflow({});
@ -74,51 +73,16 @@ const CurrentImageButtons = () => {
}, [getAndLoadEmbeddedWorkflow, lastSelectedImage]);
useHotkeys('w', handleLoadWorkflow, [lastSelectedImage]);
const handleClickUseAllParameters = useCallback(() => {
recallAllParameters(metadata);
}, [metadata, recallAllParameters]);
useHotkeys('a', handleClickUseAllParameters, [metadata]);
const handleUseSeed = useCallback(() => {
recallSeed(metadata?.seed);
}, [metadata?.seed, recallSeed]);
useHotkeys('s', handleUseSeed, [metadata]);
const handleUsePrompt = useCallback(() => {
recallBothPrompts(
metadata?.positive_prompt,
metadata?.negative_prompt,
metadata?.positive_style_prompt,
metadata?.negative_style_prompt
);
}, [
metadata?.negative_prompt,
metadata?.positive_prompt,
metadata?.positive_style_prompt,
metadata?.negative_style_prompt,
recallBothPrompts,
]);
useHotkeys('p', handleUsePrompt, [metadata]);
const handleRemixImage = useCallback(() => {
// Recalls all metadata parameters except seed
recallAllParameters({
...metadata,
seed: undefined,
});
}, [metadata, recallAllParameters]);
useHotkeys('r', handleRemixImage, [metadata]);
useHotkeys('a', recallAll, [recallAll]);
useHotkeys('s', recallSeed, [recallSeed]);
useHotkeys('p', recallPrompts, [recallPrompts]);
useHotkeys('r', remix, [remix]);
const handleUseSize = useCallback(() => {
recallWidthAndHeight(metadata?.width, metadata?.height);
}, [metadata?.width, metadata?.height, recallWidthAndHeight]);
parseAndRecallImageDimensions(lastSelectedImage);
}, [lastSelectedImage]);
useHotkeys('d', handleUseSize, [metadata]);
useHotkeys('d', handleUseSize, [handleUseSize]);
const handleSendToImageToImage = useCallback(() => {
dispatch(sentImageToImg2Img());
@ -216,36 +180,30 @@ const CurrentImageButtons = () => {
icon={<PiArrowsCounterClockwiseBold />}
tooltip={`${t('parameters.remixImage')} (R)`}
aria-label={`${t('parameters.remixImage')} (R)`}
isDisabled={!metadata?.positive_prompt}
onClick={handleRemixImage}
isDisabled={!hasMetadata}
onClick={remix}
/>
<IconButton
isLoading={isLoadingMetadata}
icon={<PiQuotesBold />}
tooltip={`${t('parameters.usePrompt')} (P)`}
aria-label={`${t('parameters.usePrompt')} (P)`}
isDisabled={!metadata?.positive_prompt}
onClick={handleUsePrompt}
isDisabled={!hasPrompts}
onClick={recallPrompts}
/>
<IconButton
isLoading={isLoadingMetadata}
icon={<PiPlantBold />}
tooltip={`${t('parameters.useSeed')} (S)`}
aria-label={`${t('parameters.useSeed')} (S)`}
isDisabled={metadata?.seed === null || metadata?.seed === undefined}
onClick={handleUseSeed}
isDisabled={!hasSeed}
onClick={recallSeed}
/>
<IconButton
isLoading={isLoadingMetadata}
icon={<PiRulerBold />}
tooltip={`${t('parameters.useSize')} (D)`}
aria-label={`${t('parameters.useSize')} (D)`}
isDisabled={
metadata?.height === null ||
metadata?.height === undefined ||
metadata?.width === null ||
metadata?.width === undefined
}
onClick={handleUseSize}
/>
<IconButton
@ -253,8 +211,8 @@ const CurrentImageButtons = () => {
icon={<PiAsteriskBold />}
tooltip={`${t('parameters.useAll')} (A)`}
aria-label={`${t('parameters.useAll')} (A)`}
isDisabled={!metadata}
onClick={handleClickUseAllParameters}
isDisabled={!hasMetadata}
onClick={recallAll}
/>
</ButtonGroup>

View File

@ -8,8 +8,8 @@ import { useDownloadImage } from 'common/hooks/useDownloadImage';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice';
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
import { useImageActions } from 'features/gallery/hooks/useImageActions';
import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/actions';
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
import { initialImageSelected } from 'features/parameters/store/actions';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
@ -33,7 +33,6 @@ import {
PiTrashSimpleBold,
} from 'react-icons/pi';
import { useStarImagesMutation, useUnstarImagesMutation } from 'services/api/endpoints/images';
import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
import type { ImageDTO } from 'services/api/types';
type SingleSelectionMenuItemsProps = {
@ -49,7 +48,9 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
const customStarUi = useStore($customStarUI);
const { downloadImage } = useDownloadImage();
const { metadata, isLoading: isLoadingMetadata } = useDebouncedMetadata(imageDTO?.image_name);
const { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata } =
useImageActions(imageDTO?.image_name);
const { getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult } = useGetAndLoadEmbeddedWorkflow({});
@ -69,28 +70,6 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
dispatch(imagesToDeleteSelected([imageDTO]));
}, [dispatch, imageDTO]);
const { recallBothPrompts, recallSeed, recallAllParameters } = useRecallParameters();
// Recall parameters handlers
const handleRecallPrompt = useCallback(() => {
recallBothPrompts(
metadata?.positive_prompt,
metadata?.negative_prompt,
metadata?.positive_style_prompt,
metadata?.negative_style_prompt
);
}, [
metadata?.negative_prompt,
metadata?.positive_prompt,
metadata?.positive_style_prompt,
metadata?.negative_style_prompt,
recallBothPrompts,
]);
const handleRecallSeed = useCallback(() => {
recallSeed(metadata?.seed);
}, [metadata?.seed, recallSeed]);
const handleSendToImageToImage = useCallback(() => {
dispatch(sentImageToImg2Img());
dispatch(initialImageSelected(imageDTO));
@ -111,18 +90,6 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
});
}, [dispatch, imageDTO, t, toaster, optimalDimension]);
const handleUseAllParameters = useCallback(() => {
recallAllParameters(metadata);
}, [metadata, recallAllParameters]);
const handleRemixImage = useCallback(() => {
// Recalls all metadata parameters except seed
recallAllParameters({
...metadata,
seed: undefined,
});
}, [metadata, recallAllParameters]);
const handleChangeBoard = useCallback(() => {
dispatch(imagesToChangeSelected([imageDTO]));
dispatch(isModalOpenChanged(true));
@ -171,33 +138,29 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
</MenuItem>
<MenuItem
icon={isLoadingMetadata ? <SpinnerIcon /> : <PiArrowsCounterClockwiseBold />}
onClickCapture={handleRemixImage}
isDisabled={
isLoadingMetadata || (metadata?.positive_prompt === undefined && metadata?.negative_prompt === undefined)
}
onClickCapture={remix}
isDisabled={isLoadingMetadata || !hasMetadata}
>
{t('parameters.remixImage')}
</MenuItem>
<MenuItem
icon={isLoadingMetadata ? <SpinnerIcon /> : <PiQuotesBold />}
onClickCapture={handleRecallPrompt}
isDisabled={
isLoadingMetadata || (metadata?.positive_prompt === undefined && metadata?.negative_prompt === undefined)
}
onClickCapture={recallPrompts}
isDisabled={isLoadingMetadata || !hasPrompts}
>
{t('parameters.usePrompt')}
</MenuItem>
<MenuItem
icon={isLoadingMetadata ? <SpinnerIcon /> : <PiPlantBold />}
onClickCapture={handleRecallSeed}
isDisabled={isLoadingMetadata || metadata?.seed === undefined}
onClickCapture={recallSeed}
isDisabled={isLoadingMetadata || !hasSeed}
>
{t('parameters.useSeed')}
</MenuItem>
<MenuItem
icon={isLoadingMetadata ? <SpinnerIcon /> : <PiAsteriskBold />}
onClickCapture={handleUseAllParameters}
isDisabled={isLoadingMetadata || !metadata}
onClickCapture={recallAll}
isDisabled={isLoadingMetadata || !hasMetadata}
>
{t('parameters.useAll')}
</MenuItem>

View File

@ -1,109 +0,0 @@
import { ExternalLink, Flex, IconButton, Text, Tooltip } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
import { get } from 'lodash-es';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowBendUpLeftBold } from 'react-icons/pi';
import { useGetModelConfigQuery } from 'services/api/endpoints/models';
type MetadataItemProps = {
isLink?: boolean;
label: string;
metadata: unknown;
propertyName: string;
onRecall?: (value: unknown) => void;
labelPosition?: string;
};
/**
* Component to display an individual metadata item or parameter.
*/
const ImageMetadataItem = ({
label,
metadata,
propertyName,
onRecall: _onRecall,
isLink,
labelPosition,
}: MetadataItemProps) => {
const { t } = useTranslation();
const value = useMemo(() => get(metadata, propertyName), [metadata, propertyName]);
const onRecall = useCallback(() => {
if (!_onRecall) {
return;
}
_onRecall(value);
}, [_onRecall, value]);
if (!value) {
return null;
}
return (
<Flex gap={2}>
{_onRecall && (
<Tooltip label={t('metadata.recallParameter', { parameter: label })}>
<IconButton
aria-label={t('metadata.recallParameter', { parameter: label })}
icon={<PiArrowBendUpLeftBold />}
size="xs"
variant="ghost"
fontSize={20}
onClick={onRecall}
/>
</Tooltip>
)}
<Flex direction={labelPosition ? 'column' : 'row'}>
<Text fontWeight="semibold" whiteSpace="pre-wrap" pr={2}>
{label}:
</Text>
{isLink ? (
<ExternalLink href={value.toString()} label={value.toString()} />
) : (
<Text overflowY="scroll" wordBreak="break-all">
{value.toString()}
</Text>
)}
</Flex>
</Flex>
);
};
export default memo(ImageMetadataItem);
type VAEMetadataItemProps = {
label: string;
modelKey?: string;
onClick: () => void;
};
export const VAEMetadataItem = memo(({ label, modelKey, onClick }: VAEMetadataItemProps) => {
const { data: modelConfig } = useGetModelConfigQuery(modelKey ?? skipToken);
return (
<ImageMetadataItem label={label} value={modelKey ? modelConfig?.name ?? modelKey : 'Default'} onClick={onClick} />
);
});
VAEMetadataItem.displayName = 'VAEMetadataItem';
type ModelMetadataItemProps = {
label: string;
modelKey?: string;
extra?: string;
onClick: () => void;
};
export const ModelMetadataItem = memo(({ label, modelKey, extra, onClick }: ModelMetadataItemProps) => {
const { data: modelConfig } = useGetModelConfigQuery(modelKey ?? skipToken);
const value = useMemo(() => {
if (modelConfig) {
return `${modelConfig.name}${extra ?? ''}`;
}
return `${modelKey}${extra ?? ''}`;
}, [extra, modelConfig, modelKey]);
return <ImageMetadataItem label={label} value={value} onClick={onClick} />;
});
ModelMetadataItem.displayName = 'ModelMetadataItem';

View File

@ -0,0 +1,62 @@
import { handlers, parseAndRecallAllMetadata, parseAndRecallPrompts } from 'features/metadata/util/handlers';
import { useCallback, useEffect, useState } from 'react';
import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
export const useImageActions = (image_name?: string) => {
const { metadata, isLoading: isLoadingMetadata } = useDebouncedMetadata(image_name);
const [hasMetadata, setHasMetadata] = useState(false);
const [hasSeed, setHasSeed] = useState(false);
const [hasPrompts, setHasPrompts] = useState(false);
useEffect(() => {
const parseMetadata = async () => {
if (metadata) {
setHasMetadata(true);
try {
await handlers.seed.parse(metadata);
setHasSeed(true);
} catch {
setHasSeed(false);
}
const promptParseResults = await Promise.allSettled([
handlers.positivePrompt.parse(metadata),
handlers.negativePrompt.parse(metadata),
handlers.sdxlPositiveStylePrompt.parse(metadata),
handlers.sdxlNegativeStylePrompt.parse(metadata),
]);
if (promptParseResults.some((result) => result.status === 'fulfilled')) {
setHasPrompts(true);
} else {
setHasPrompts(false);
}
} else {
setHasMetadata(false);
setHasSeed(false);
setHasPrompts(false);
}
};
parseMetadata();
}, [metadata]);
const recallAll = useCallback(() => {
parseAndRecallAllMetadata(metadata);
}, [metadata]);
const remix = useCallback(() => {
// Recalls all metadata parameters except seed
parseAndRecallAllMetadata(metadata, ['seed']);
}, [metadata]);
const recallSeed = useCallback(() => {
handlers.seed.parse(metadata).then((seed) => {
handlers.seed.recall && handlers.seed.recall(seed);
});
}, [metadata]);
const recallPrompts = useCallback(() => {
parseAndRecallPrompts(metadata);
}, [metadata]);
return { recallAll, remix, recallSeed, recallPrompts , hasMetadata, hasSeed, hasPrompts, isLoadingMetadata };
};

View File

@ -1,3 +1,4 @@
import { objectKeys } from 'common/util/objectKeys';
import { toast } from 'common/util/toast';
import type { ControlAdapterConfig } from 'features/controlAdapters/store/types';
import type { LoRA } from 'features/lora/store/loraSlice';
@ -312,3 +313,75 @@ export const handlers = {
renderItemValue: renderControlAdapterValue,
}),
} as const;
export const parseAndRecallPrompts = async (metadata: unknown) => {
const results = await Promise.allSettled([
handlers.positivePrompt.parse(metadata).then((positivePrompt) => {
if (!handlers.positivePrompt.recall) {
return;
}
handlers.positivePrompt?.recall(positivePrompt);
}),
handlers.negativePrompt.parse(metadata).then((negativePrompt) => {
if (!handlers.negativePrompt.recall) {
return;
}
handlers.negativePrompt?.recall(negativePrompt);
}),
handlers.sdxlPositiveStylePrompt.parse(metadata).then((sdxlPositiveStylePrompt) => {
if (!handlers.sdxlPositiveStylePrompt.recall) {
return;
}
handlers.sdxlPositiveStylePrompt?.recall(sdxlPositiveStylePrompt);
}),
handlers.sdxlNegativeStylePrompt.parse(metadata).then((sdxlNegativeStylePrompt) => {
if (!handlers.sdxlNegativeStylePrompt.recall) {
return;
}
handlers.sdxlNegativeStylePrompt?.recall(sdxlNegativeStylePrompt);
}),
]);
if (results.some((result) => result.status === 'fulfilled')) {
parameterSetToast(t('metadata.allPrompts'));
}
};
export const parseAndRecallImageDimensions = async (metadata: unknown) => {
const results = await Promise.allSettled([
handlers.width.parse(metadata).then((width) => {
if (!handlers.width.recall) {
return;
}
handlers.width?.recall(width);
}),
handlers.height.parse(metadata).then((height) => {
if (!handlers.height.recall) {
return;
}
handlers.height?.recall(height);
}),
]);
if (results.some((result) => result.status === 'fulfilled')) {
parameterSetToast(t('metadata.imageDimensions'));
}
};
export const parseAndRecallAllMetadata = async (metadata: unknown, skip: (keyof typeof handlers)[] = []) => {
const results = await Promise.allSettled(
objectKeys(handlers)
.filter((key) => !skip.includes(key))
.map((key) => {
const { parse, recall } = handlers[key];
return parse(metadata).then((value) => {
if (!recall) {
return;
}
/* @ts-expect-error The return type of parse and the input type of recall are guaranteed to be compatible. */
recall(value);
});
})
);
if (results.some((result) => result.status === 'fulfilled')) {
parameterSetToast(t('toast.parametersSet'));
}
};

View File

@ -203,62 +203,6 @@ const recallIPAdapters: MetadataRecallFunc<IPAdapterConfig[]> = (ipAdapters) =>
});
};
export const recallPrompts = (_metadata: unknown) => {
// recallPositivePrompt(metadata);
// recallNegativePrompt(metadata);
// recallSDXLPositiveStylePrompt(metadata);
// recallSDXLNegativeStylePrompt(metadata);
// parameterNotSetToast(t('metadata.allPrompts'));
// parameterSetToast(t('metadata.allPrompts'));
};
export const recallWidthAndHeight = (_metadata: unknown) => {
// recallWidth(metadata);
// recallHeight(metadata);
};
export const recallAll = async (_metadata: unknown) => {
// if (!metadata) {
// allParameterNotSetToast();
// return;
// }
// // Update the main model first, as other parameters may depend on it.
// await recallModel(metadata);
// await Promise.allSettled([
// // Core parameters
// recallCFGScale(metadata),
// recallCFGRescaleMultiplier(metadata),
// recallPositivePrompt(metadata),
// recallNegativePrompt(metadata),
// recallScheduler(metadata),
// recallSeed(metadata),
// recallSteps(metadata),
// recallWidth(metadata),
// recallHeight(metadata),
// recallStrength(metadata),
// recallHRFEnabled(metadata),
// recallHRFMethod(metadata),
// recallHRFStrength(metadata),
// // SDXL parameters
// recallSDXLPositiveStylePrompt(metadata),
// recallSDXLNegativeStylePrompt(metadata),
// recallRefinerSteps(metadata),
// recallRefinerCFGScale(metadata),
// recallRefinerScheduler(metadata),
// recallRefinerPositiveAestheticScore(metadata),
// recallRefinerNegativeAestheticScore(metadata),
// recallRefinerStart(metadata),
// // Models
// recallVAE(metadata),
// recallRefinerModel(metadata),
// recallAllLoRAs(metadata),
// recallControlNets(metadata),
// recallT2IAdapters(metadata),
// recallIPAdapters(metadata),
// ]);
// allParameterSetToast();
};
export const recallers = {
positivePrompt: recallPositivePrompt,
negativePrompt: recallNegativePrompt,

View File

@ -2,7 +2,7 @@ import { Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
import { parseAndRecallImageDimensions } from 'features/metadata/util/handlers';
import { clearInitialImage, selectGenerationSlice } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
@ -19,7 +19,6 @@ const postUploadAction: PostUploadAction = {
};
const InitialImageDisplay = () => {
const { recallWidthAndHeight } = useRecallParameters();
const { t } = useTranslation();
const initialImage = useAppSelector(selectInitialImage);
const dispatch = useAppDispatch();
@ -34,9 +33,9 @@ const InitialImageDisplay = () => {
const handleUseSizeInitialImage = useCallback(() => {
if (initialImage) {
recallWidthAndHeight(initialImage.width, initialImage.height);
parseAndRecallImageDimensions(initialImage);
}
}, [initialImage, recallWidthAndHeight]);
}, [initialImage]);
useHotkeys('shift+d', handleUseSizeInitialImage, [initialImage]);

View File

@ -2,6 +2,7 @@ import { skipToken } from '@reduxjs/toolkit/query';
import { useAppToaster } from 'app/components/Toaster';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { parseAndRecallAllMetadata } from 'features/metadata/util/handlers';
import { initialImageSelected } from 'features/parameters/store/actions';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { setActiveTab } from 'features/ui/store/uiSlice';
@ -9,14 +10,12 @@ import { t } from 'i18next';
import { useCallback, useEffect } from 'react';
import { useGetImageDTOQuery, useGetImageMetadataQuery } from 'services/api/endpoints/images';
import { useRecallParameters } from './useRecallParameters';
export const usePreselectedImage = (selectedImage?: {
imageName: string;
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
}) => {
const dispatch = useAppDispatch();
const { recallAllParameters } = useRecallParameters();
const optimalDimension = useAppSelector(selectOptimalDimension);
const toaster = useAppToaster();
@ -45,10 +44,8 @@ export const usePreselectedImage = (selectedImage?: {
const handleUseAllMetadata = useCallback(() => {
if (selectedImageMetadata) {
recallAllParameters(selectedImageMetadata);
parseAndRecallAllMetadata(selectedImageMetadata);
}
// disabled because `recallAllParameters` changes the model, but its dep to prepare LoRAs has model as a dep. this introduces circular logic that causes infinite re-renders
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedImageMetadata]);
useEffect(() => {

View File

@ -1,858 +0,0 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { getStore } from 'app/store/nanostores/store';
import { useAppDispatch } from 'app/store/storeHooks';
import { toast } from 'common/util/toast';
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import { controlAdapterRecalled, controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice';
import type { ControlNetConfig, IPAdapterConfig, T2IAdapterConfig } from 'features/controlAdapters/store/types';
import {
initialControlNet,
initialIPAdapter,
initialT2IAdapter,
} from 'features/controlAdapters/util/buildControlAdapter';
import { setHrfEnabled, setHrfMethod, setHrfStrength } from 'features/hrf/store/hrfSlice';
import type { LoRA } from 'features/lora/store/loraSlice';
import { defaultLoRAConfig, loraRecalled, lorasCleared } from 'features/lora/store/loraSlice';
import type { ModelIdentifierWithBase } from 'features/nodes/types/common';
import {
zControlField,
zIPAdapterField,
zModelIdentifierWithBase,
zT2IAdapterField,
} from 'features/nodes/types/common';
import { initialImageSelected, modelSelected } from 'features/parameters/store/actions';
import {
heightRecalled,
selectGenerationSlice,
setCfgRescaleMultiplier,
setCfgScale,
setImg2imgStrength,
setNegativePrompt,
setPositivePrompt,
setScheduler,
setSeed,
setSteps,
vaeSelected,
widthRecalled,
} from 'features/parameters/store/generationSlice';
import {
isParameterCFGRescaleMultiplier,
isParameterCFGScale,
isParameterHeight,
isParameterHRFEnabled,
isParameterHRFMethod,
isParameterLoRAWeight,
isParameterNegativePrompt,
isParameterNegativeStylePromptSDXL,
isParameterPositivePrompt,
isParameterPositiveStylePromptSDXL,
isParameterScheduler,
isParameterSDXLRefinerNegativeAestheticScore,
isParameterSDXLRefinerPositiveAestheticScore,
isParameterSDXLRefinerStart,
isParameterSeed,
isParameterSteps,
isParameterStrength,
isParameterWidth,
} from 'features/parameters/types/parameterSchemas';
import {
fetchControlNetModel,
fetchIPAdapterModel,
fetchLoRAModel,
fetchMainModelConfig,
fetchRefinerModelConfig,
fetchT2IAdapterModel,
fetchVAEModelConfig,
getModelKey,
raiseIfBaseIncompatible,
} from 'features/parameters/util/modelFetchingHelpers';
import {
refinerModelChanged,
setNegativeStylePromptSDXL,
setPositiveStylePromptSDXL,
setRefinerCFGScale,
setRefinerNegativeAestheticScore,
setRefinerPositiveAestheticScore,
setRefinerScheduler,
setRefinerStart,
setRefinerSteps,
} from 'features/sdxl/store/sdxlSlice';
import { t } from 'i18next';
import { get, isArray, isNil } from 'lodash-es';
import { useCallback } from 'react';
import type { BaseModelType, ImageDTO } from 'services/api/types';
import { v4 as uuidv4 } from 'uuid';
const selectModel = createMemoizedSelector(selectGenerationSlice, (generation) => generation.model);
/**
* A function that recalls from metadata from the full metadata object.
*/
type MetadataRecallFunc = (metadata: unknown, withToast?: boolean) => void;
/**
* A function that recalls metadata from a specific metadata item.
*/
type MetadataItemRecallFunc = (metadataItem: unknown, withToast?: boolean) => void;
/**
* Raised when metadata recall fails.
*/
export class MetadataRecallError extends Error {
/**
* Create MetadataRecallError
* @param {String} message
*/
constructor(message: string) {
super(message);
this.name = this.constructor.name;
}
}
export class InvalidMetadataPropertyType extends MetadataRecallError {}
const getProperty = <T = unknown>(
obj: unknown,
property: string,
typeGuard: (val: unknown) => val is T = (val: unknown): val is T => true
): T => {
const val = get(obj, property) as unknown;
if (typeGuard(val)) {
return val;
}
throw new InvalidMetadataPropertyType(`Property ${property} is not of expected type`);
};
const getCurrentBase = () => selectModel(getStore().getState())?.base;
const parameterSetToast = (parameter: string, description?: string) => {
toast({
title: t('toast.parameterSet', { parameter }),
description,
status: 'info',
duration: 2500,
isClosable: true,
});
};
const parameterNotSetToast = (parameter: string, description?: string) => {
toast({
title: t('toast.parameterNotSet', { parameter }),
description,
status: 'warning',
duration: 2500,
isClosable: true,
});
};
const allParameterSetToast = (description?: string) => {
toast({
title: t('toast.parametersSet'),
status: 'info',
description,
duration: 2500,
isClosable: true,
});
};
const allParameterNotSetToast = (description?: string) => {
toast({
title: t('toast.parametersNotSet'),
status: 'warning',
description,
duration: 2500,
isClosable: true,
});
};
const recall = (callback: () => void, parameter: string, withToast = true) => {
try {
callback();
withToast && parameterSetToast(parameter);
} catch (e) {
withToast && parameterNotSetToast(parameter, (e as Error).message);
}
};
const recallAsync = async (callback: () => Promise<void>, parameter: string, withToast = true) => {
try {
await callback();
withToast && parameterSetToast(parameter);
} catch (e) {
withToast && parameterNotSetToast(parameter, (e as Error).message);
}
};
export const recallPositivePrompt: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const positive_prompt = getProperty(metadata, 'positive_prompt', isParameterPositivePrompt);
getStore().dispatch(setPositivePrompt(positive_prompt));
},
t('metadata.positivePrompt'),
withToast
);
};
export const recallNegativePrompt: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const negative_prompt = getProperty(metadata, 'negative_prompt', isParameterNegativePrompt);
getStore().dispatch(setNegativePrompt(negative_prompt));
},
t('metadata.negativePrompt'),
withToast
);
};
export const recallSDXLPositiveStylePrompt: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const positive_style_prompt = getProperty(metadata, 'positive_style_prompt', isParameterPositiveStylePromptSDXL);
getStore().dispatch(setPositiveStylePromptSDXL(positive_style_prompt));
},
t('sdxl.posStylePrompt'),
withToast
);
};
export const recallSDXLNegativeStylePrompt: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const negative_style_prompt = getProperty(metadata, 'negative_style_prompt', isParameterNegativeStylePromptSDXL);
getStore().dispatch(setNegativeStylePromptSDXL(negative_style_prompt));
},
t('sdxl.negStylePrompt'),
withToast
);
};
export const recallSeed: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const seed = getProperty(metadata, 'seed', isParameterSeed);
getStore().dispatch(setSeed(seed));
},
t('metadata.seed'),
withToast
);
};
export const recallCFGScale: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const cfg_scale = getProperty(metadata, 'cfg_scale', isParameterCFGScale);
getStore().dispatch(setCfgScale(cfg_scale));
},
t('metadata.cfgScale'),
withToast
);
};
export const recallCFGRescaleMultiplier: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const cfg_rescale_multiplier = getProperty(metadata, 'cfg_rescale_multiplier', isParameterCFGRescaleMultiplier);
getStore().dispatch(setCfgRescaleMultiplier(cfg_rescale_multiplier));
},
t('metadata.cfgRescaleMultiplier'),
withToast
);
};
export const recallScheduler: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const scheduler = getProperty(metadata, 'scheduler', isParameterScheduler);
getStore().dispatch(setScheduler(scheduler));
},
t('metadata.scheduler'),
withToast
);
};
export const recallWidth: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const width = getProperty(metadata, 'width', isParameterWidth);
getStore().dispatch(widthRecalled(width));
},
t('metadata.width'),
withToast
);
};
export const recallHeight: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const height = getProperty(metadata, 'height', isParameterHeight);
getStore().dispatch(heightRecalled(height));
},
t('metadata.height'),
withToast
);
};
export const recallSteps: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const steps = getProperty(metadata, 'steps', isParameterSteps);
getStore().dispatch(setSteps(steps));
},
t('metadata.steps'),
withToast
);
};
export const recallStrength: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const strength = getProperty(metadata, 'strength', isParameterStrength);
getStore().dispatch(setImg2imgStrength(strength));
},
t('metadata.strength'),
withToast
);
};
export const recallHRFEnabled: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const hrf_enabled = getProperty(metadata, 'hrf_enabled', isParameterHRFEnabled);
getStore().dispatch(setHrfEnabled(hrf_enabled));
},
t('hrf.metadata.enabled'),
withToast
);
};
export const recallHRFStrength: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const hrf_strength = getProperty(metadata, 'hrf_strength', isParameterStrength);
getStore().dispatch(setHrfStrength(hrf_strength));
},
t('hrf.metadata.strength'),
withToast
);
};
export const recallHRFMethod: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const hrf_method = getProperty(metadata, 'hrf_method', isParameterHRFMethod);
getStore().dispatch(setHrfMethod(hrf_method));
},
t('hrf.metadata.method'),
withToast
);
};
export const recallRefinerSteps: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const refiner_steps = getProperty(metadata, 'refiner_steps', isParameterSteps);
getStore().dispatch(setRefinerSteps(refiner_steps));
},
t('sdxl.steps'),
withToast
);
};
export const recallRefinerCFGScale: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const refiner_cfg_scale = getProperty(metadata, 'refiner_cfg_scale', isParameterCFGScale);
getStore().dispatch(setRefinerCFGScale(refiner_cfg_scale));
},
t('sdxl.cfgScale'),
withToast
);
};
export const recallRefinerScheduler: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const refiner_scheduler = getProperty(metadata, 'refiner_scheduler', isParameterScheduler);
getStore().dispatch(setRefinerScheduler(refiner_scheduler));
},
t('sdxl.cfgScale'),
withToast
);
};
export const recallRefinerPositiveAestheticScore: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const refiner_positive_aesthetic_score = getProperty(
metadata,
'refiner_positive_aesthetic_score',
isParameterSDXLRefinerPositiveAestheticScore
);
getStore().dispatch(setRefinerPositiveAestheticScore(refiner_positive_aesthetic_score));
},
t('sdxl.posAestheticScore'),
withToast
);
};
export const recallRefinerNegativeAestheticScore: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const refiner_negative_aesthetic_score = getProperty(
metadata,
'refiner_negative_aesthetic_score',
isParameterSDXLRefinerNegativeAestheticScore
);
getStore().dispatch(setRefinerNegativeAestheticScore(refiner_negative_aesthetic_score));
},
t('sdxl.negAestheticScore'),
withToast
);
};
export const recallRefinerStart: MetadataRecallFunc = (metadata: unknown, withToast = true) => {
recall(
() => {
const refiner_start = getProperty(metadata, 'refiner_start', isParameterSDXLRefinerStart);
getStore().dispatch(setRefinerStart(refiner_start));
},
t('sdxl.refinerStart'),
withToast
);
};
export const prepareMainModelMetadataItem = async (model: unknown): Promise<ModelIdentifierWithBase> => {
const key = await getModelKey(model, 'main');
const mainModel = await fetchMainModelConfig(key);
return zModelIdentifierWithBase.parse(mainModel);
};
const recallModelAsync: MetadataRecallFunc = async (metadata: unknown, withToast = true) => {
await recallAsync(
async () => {
const modelMetadataItem = getProperty(metadata, 'model');
const model = await prepareMainModelMetadataItem(modelMetadataItem);
getStore().dispatch(modelSelected(model));
},
t('metadata.model'),
withToast
);
};
export const prepareRefinerMetadataItem = async (
model: unknown,
currentBase: BaseModelType | undefined
): Promise<ModelIdentifierWithBase> => {
const key = await getModelKey(model, 'main');
const refinerModel = await fetchRefinerModelConfig(key);
raiseIfBaseIncompatible('sdxl-refiner', currentBase, 'Refiner incompatible with currently-selected model');
return zModelIdentifierWithBase.parse(refinerModel);
};
const recallRefinerModelAsync: MetadataRecallFunc = async (metadata: unknown, withToast = true) => {
await recallAsync(
async () => {
const refinerMetadataItem = getProperty(metadata, 'refiner_model');
const refiner = await prepareRefinerMetadataItem(refinerMetadataItem, getCurrentBase());
getStore().dispatch(refinerModelChanged(refiner));
},
t('sdxl.refinerModel'),
withToast
);
};
export const prepareVAEMetadataItem = async (
vae: unknown,
currentBase: BaseModelType | undefined
): Promise<ModelIdentifierWithBase> => {
const key = await getModelKey(vae, 'vae');
const vaeModel = await fetchVAEModelConfig(key);
raiseIfBaseIncompatible(vaeModel.base, currentBase, 'VAE incompatible with currently-selected model');
return zModelIdentifierWithBase.parse(vaeModel);
};
const recallVAEAsync: MetadataRecallFunc = async (metadata: unknown, withToast = true) => {
await recallAsync(
async () => {
const currentBase = getCurrentBase();
const vaeMetadataItem = getProperty(metadata, 'vae');
if (isNil(vaeMetadataItem)) {
getStore().dispatch(vaeSelected(null));
} else {
const vae = await prepareVAEMetadataItem(vaeMetadataItem, currentBase);
getStore().dispatch(vaeSelected(vae));
}
},
t('metadata.vae'),
withToast
);
};
export const prepareLoRAMetadataItem = async (
loraMetadataItem: unknown,
currentBase: BaseModelType | undefined
): Promise<LoRA> => {
const lora = getProperty(loraMetadataItem, 'lora');
const weight = getProperty(loraMetadataItem, 'weight');
const key = await getModelKey(lora, 'lora');
const loraModel = await fetchLoRAModel(key);
raiseIfBaseIncompatible(loraModel.base, currentBase, 'LoRA incompatible with currently-selected model');
return {
key: loraModel.key,
base: loraModel.base,
weight: isParameterLoRAWeight(weight) ? weight : defaultLoRAConfig.weight,
isEnabled: true,
};
};
const recallLoRAAsync: MetadataItemRecallFunc = async (metadataItem: unknown, withToast = true) => {
await recallAsync(
async () => {
const currentBase = getCurrentBase();
const lora = await prepareLoRAMetadataItem(metadataItem, currentBase);
getStore().dispatch(loraRecalled(lora));
},
t('models.lora'),
withToast
);
};
export const prepareControlNetMetadataItem = async (
metadataItem: unknown,
currentBase: BaseModelType | undefined
): Promise<ControlNetConfig> => {
const control_model = getProperty(metadataItem, 'control_model');
const key = await getModelKey(control_model, 'controlnet');
const controlNetModel = await fetchControlNetModel(key);
raiseIfBaseIncompatible(controlNetModel.base, currentBase, 'ControlNet incompatible with currently-selected model');
const image = zControlField.shape.image.nullish().catch(null).parse(getProperty(metadataItem, 'image'));
const control_weight = zControlField.shape.control_weight
.nullish()
.catch(null)
.parse(getProperty(metadataItem, 'control_weight'));
const begin_step_percent = zControlField.shape.begin_step_percent
.nullish()
.catch(null)
.parse(getProperty(metadataItem, 'begin_step_percent'));
const end_step_percent = zControlField.shape.end_step_percent
.nullish()
.catch(null)
.parse(getProperty(metadataItem, 'end_step_percent'));
const control_mode = zControlField.shape.control_mode
.nullish()
.catch(null)
.parse(getProperty(metadataItem, 'control_mode'));
const resize_mode = zControlField.shape.resize_mode
.nullish()
.catch(null)
.parse(getProperty(metadataItem, 'resize_mode'));
// We don't save the original image that was processed into a control image, only the processed image
const processorType = 'none';
const processorNode = CONTROLNET_PROCESSORS.none.default;
const controlnet: ControlNetConfig = {
type: 'controlnet',
isEnabled: true,
model: zModelIdentifierWithBase.parse(controlNetModel),
weight: typeof control_weight === 'number' ? control_weight : initialControlNet.weight,
beginStepPct: begin_step_percent ?? initialControlNet.beginStepPct,
endStepPct: end_step_percent ?? initialControlNet.endStepPct,
controlMode: control_mode ?? initialControlNet.controlMode,
resizeMode: resize_mode ?? initialControlNet.resizeMode,
controlImage: image?.image_name ?? null,
processedControlImage: image?.image_name ?? null,
processorType,
processorNode,
shouldAutoConfig: true,
id: uuidv4(),
};
return controlnet;
};
const recallControlNetAsync: MetadataItemRecallFunc = async (metadataItem: unknown, withToast = true) => {
await recallAsync(
async () => {
const currentBase = getCurrentBase();
const controlNetConfig = await prepareControlNetMetadataItem(metadataItem, currentBase);
getStore().dispatch(controlAdapterRecalled(controlNetConfig));
},
t('common.controlNet'),
withToast
);
};
export const prepareT2IAdapterMetadataItem = async (
metadataItem: unknown,
currentBase: BaseModelType | undefined
): Promise<T2IAdapterConfig> => {
const t2i_adapter_model = getProperty(metadataItem, 't2i_adapter_model');
const key = await getModelKey(t2i_adapter_model, 't2i_adapter');
const t2iAdapterModel = await fetchT2IAdapterModel(key);
raiseIfBaseIncompatible(t2iAdapterModel.base, currentBase, 'T2I Adapter incompatible with currently-selected model');
const image = zT2IAdapterField.shape.image.nullish().catch(null).parse(getProperty(metadataItem, 'image'));
const weight = zT2IAdapterField.shape.weight.nullish().catch(null).parse(getProperty(metadataItem, 'weight'));
const begin_step_percent = zT2IAdapterField.shape.begin_step_percent
.nullish()
.catch(null)
.parse(getProperty(metadataItem, 'begin_step_percent'));
const end_step_percent = zT2IAdapterField.shape.end_step_percent
.nullish()
.catch(null)
.parse(getProperty(metadataItem, 'end_step_percent'));
const resize_mode = zT2IAdapterField.shape.resize_mode
.nullish()
.catch(null)
.parse(getProperty(metadataItem, 'resize_mode'));
// We don't save the original image that was processed into a control image, only the processed image
const processorType = 'none';
const processorNode = CONTROLNET_PROCESSORS.none.default;
const t2iAdapter: T2IAdapterConfig = {
type: 't2i_adapter',
isEnabled: true,
model: zModelIdentifierWithBase.parse(t2iAdapterModel),
weight: typeof weight === 'number' ? weight : initialT2IAdapter.weight,
beginStepPct: begin_step_percent ?? initialT2IAdapter.beginStepPct,
endStepPct: end_step_percent ?? initialT2IAdapter.endStepPct,
resizeMode: resize_mode ?? initialT2IAdapter.resizeMode,
controlImage: image?.image_name ?? null,
processedControlImage: image?.image_name ?? null,
processorType,
processorNode,
shouldAutoConfig: true,
id: uuidv4(),
};
return t2iAdapter;
};
const recallT2IAdapterAsync: MetadataItemRecallFunc = async (metadataItem: unknown, withToast = true) => {
await recallAsync(
async () => {
const currentBase = getCurrentBase();
const t2iAdapterConfig = await prepareT2IAdapterMetadataItem(metadataItem, currentBase);
getStore().dispatch(controlAdapterRecalled(t2iAdapterConfig));
},
t('common.t2iAdapter'),
withToast
);
};
export const prepareIPAdapterMetadataItem = async (
metadataItem: unknown,
currentBase: BaseModelType | undefined
): Promise<IPAdapterConfig> => {
const ip_adapter_model = getProperty(metadataItem, 'ip_adapter_model');
const key = await getModelKey(ip_adapter_model, 'ip_adapter');
const ipAdapterModel = await fetchIPAdapterModel(key);
raiseIfBaseIncompatible(ipAdapterModel.base, currentBase, 'T2I Adapter incompatible with currently-selected model');
const image = zIPAdapterField.shape.image.nullish().catch(null).parse(getProperty(metadataItem, 'image'));
const weight = zIPAdapterField.shape.weight.nullish().catch(null).parse(getProperty(metadataItem, 'weight'));
const begin_step_percent = zIPAdapterField.shape.begin_step_percent
.nullish()
.catch(null)
.parse(getProperty(metadataItem, 'begin_step_percent'));
const end_step_percent = zIPAdapterField.shape.end_step_percent
.nullish()
.catch(null)
.parse(getProperty(metadataItem, 'end_step_percent'));
const ipAdapter: IPAdapterConfig = {
id: uuidv4(),
type: 'ip_adapter',
isEnabled: true,
model: zModelIdentifierWithBase.parse(ipAdapterModel),
controlImage: image?.image_name ?? null,
weight: weight ?? initialIPAdapter.weight,
beginStepPct: begin_step_percent ?? initialIPAdapter.beginStepPct,
endStepPct: end_step_percent ?? initialIPAdapter.endStepPct,
};
return ipAdapter;
};
const recallIPAdapterAsync: MetadataItemRecallFunc = async (metadataItem: unknown, withToast) => {
await recallAsync(
async () => {
const currentBase = getCurrentBase();
const ipAdapterConfig = await prepareIPAdapterMetadataItem(metadataItem, currentBase);
getStore().dispatch(controlAdapterRecalled(ipAdapterConfig));
},
t('common.ipAdapter'),
withToast
);
};
export const useRecallParameters = () => {
const dispatch = useAppDispatch();
const recallBothPrompts = useCallback<MetadataRecallFunc>(
(metadata: unknown) => {
const positive_prompt = getProperty(metadata, 'positive_prompt');
const negative_prompt = getProperty(metadata, 'negative_prompt');
const positive_style_prompt = getProperty(metadata, 'positive_style_prompt');
const negative_style_prompt = getProperty(metadata, 'negative_style_prompt');
if (
isParameterPositivePrompt(positive_prompt) ||
isParameterNegativePrompt(negative_prompt) ||
isParameterPositiveStylePromptSDXL(positive_style_prompt) ||
isParameterNegativeStylePromptSDXL(negative_style_prompt)
) {
if (isParameterPositivePrompt(positive_prompt)) {
dispatch(setPositivePrompt(positive_prompt));
}
if (isParameterNegativePrompt(negative_prompt)) {
dispatch(setNegativePrompt(negative_prompt));
}
if (isParameterPositiveStylePromptSDXL(positive_style_prompt)) {
dispatch(setPositiveStylePromptSDXL(positive_style_prompt));
}
if (isParameterPositiveStylePromptSDXL(negative_style_prompt)) {
dispatch(setNegativeStylePromptSDXL(negative_style_prompt));
}
parameterSetToast(t('metadata.allPrompts'));
return;
}
parameterNotSetToast(t('metadata.allPrompts'));
},
[dispatch]
);
const recallWidthAndHeight = useCallback<MetadataRecallFunc>(
(metadata: unknown) => {
const width = getProperty(metadata, 'width');
const height = getProperty(metadata, 'height');
if (!isParameterWidth(width)) {
allParameterNotSetToast();
return;
}
if (!isParameterHeight(height)) {
allParameterNotSetToast();
return;
}
dispatch(heightRecalled(height));
dispatch(widthRecalled(width));
allParameterSetToast();
},
[dispatch]
);
const sendToImageToImage = useCallback(
(image: ImageDTO) => {
dispatch(initialImageSelected(image));
},
[dispatch]
);
const recallAllParameters = useCallback<MetadataRecallFunc>(
async (metadata: unknown) => {
if (!metadata) {
allParameterNotSetToast();
return;
}
await recallModelAsync(metadata, false);
recallCFGScale(metadata, false);
recallCFGRescaleMultiplier(metadata, false);
recallPositivePrompt(metadata, false);
recallNegativePrompt(metadata, false);
recallScheduler(metadata, false);
recallSeed(metadata, false);
recallSteps(metadata, false);
recallWidth(metadata, false);
recallHeight(metadata, false);
recallStrength(metadata, false);
recallHRFEnabled(metadata, false);
recallHRFMethod(metadata, false);
recallHRFStrength(metadata, false);
// SDXL
recallSDXLPositiveStylePrompt(metadata, false);
recallSDXLNegativeStylePrompt(metadata, false);
recallRefinerSteps(metadata, false);
recallRefinerCFGScale(metadata, false);
recallRefinerScheduler(metadata, false);
recallRefinerPositiveAestheticScore(metadata, false);
recallRefinerNegativeAestheticScore(metadata, false);
recallRefinerStart(metadata, false);
await recallVAEAsync(metadata, false);
await recallRefinerModelAsync(metadata, false);
dispatch(lorasCleared());
const loraMetadataArray = getProperty(metadata, 'loras');
if (isArray(loraMetadataArray)) {
loraMetadataArray.forEach(async (loraMetadataItem) => {
await recallLoRAAsync(loraMetadataItem, false);
});
}
dispatch(controlAdaptersReset());
const controlNetMetadataArray = getProperty(metadata, 'controlnets');
if (isArray(controlNetMetadataArray)) {
controlNetMetadataArray.forEach(async (controlNetMetadataItem) => {
await recallControlNetAsync(controlNetMetadataItem, false);
});
}
const ipAdapterMetadataArray = getProperty(metadata, 'ipAdapters');
if (isArray(ipAdapterMetadataArray)) {
ipAdapterMetadataArray.forEach(async (ipAdapterMetadataItem) => {
await recallIPAdapterAsync(ipAdapterMetadataItem, false);
});
}
const t2iAdapterMetadataArray = getProperty(metadata, 't2iAdapters');
if (isArray(t2iAdapterMetadataArray)) {
t2iAdapterMetadataArray.forEach(async (t2iAdapterMetadataItem) => {
await recallT2IAdapterAsync(t2iAdapterMetadataItem, false);
});
}
allParameterSetToast();
},
[dispatch]
);
return {
recallBothPrompts,
recallPositivePrompt,
recallNegativePrompt,
recallSDXLPositiveStylePrompt,
recallSDXLNegativeStylePrompt,
recallSeed,
recallCFGScale,
recallCFGRescaleMultiplier,
recallModel: recallModelAsync,
recallScheduler,
recallVaeModel: recallVAEAsync,
recallSteps,
recallWidth,
recallHeight,
recallWidthAndHeight,
recallStrength,
recallHRFEnabled,
recallHRFStrength,
recallHRFMethod,
recallLoRA: recallLoRAAsync,
recallControlNet: recallControlNetAsync,
recallIPAdapter: recallIPAdapterAsync,
recallT2IAdapter: recallT2IAdapterAsync,
recallAllParameters,
recallRefinerModel: recallRefinerModelAsync,
sendToImageToImage,
};
};