mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): migrate all metadata recall logic to new system
This commit is contained in:
parent
0cb2579109
commit
6a2ae5caa4
6
invokeai/frontend/web/src/common/util/objectKeys.ts
Normal file
6
invokeai/frontend/web/src/common/util/objectKeys.ts
Normal 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>;
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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';
|
@ -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 };
|
||||
};
|
@ -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'));
|
||||
}
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -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(() => {
|
||||
|
@ -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,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user