From 8faefa89fe56168346b07ecabd5d22ac9df2d967 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:49:53 +1100 Subject: [PATCH] feat(ui): migrate all metadata recall logic to new system --- .../web/src/common/util/objectKeys.ts | 6 + .../CurrentImage/CurrentImageButtons.tsx | 80 +- .../SingleSelectionMenuItems.tsx | 61 +- .../ImageMetadataViewer/ImageMetadataItem.tsx | 109 --- .../features/gallery/hooks/useImageActions.ts | 62 ++ .../src/features/metadata/util/handlers.ts | 73 ++ .../src/features/metadata/util/recallers.ts | 56 -- .../ImageToImage/InitialImageDisplay.tsx | 7 +- .../parameters/hooks/usePreselectedImage.ts | 7 +- .../parameters/hooks/useRecallParameters.ts | 858 ------------------ 10 files changed, 177 insertions(+), 1142 deletions(-) create mode 100644 invokeai/frontend/web/src/common/util/objectKeys.ts delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataItem.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/hooks/useImageActions.ts delete mode 100644 invokeai/frontend/web/src/features/parameters/hooks/useRecallParameters.ts diff --git a/invokeai/frontend/web/src/common/util/objectKeys.ts b/invokeai/frontend/web/src/common/util/objectKeys.ts new file mode 100644 index 0000000000..bea0905c7f --- /dev/null +++ b/invokeai/frontend/web/src/common/util/objectKeys.ts @@ -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 = >(obj: T) => Object.keys(obj) as Array; diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx index e25324b9ea..896b6efef7 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx @@ -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={} tooltip={`${t('parameters.remixImage')} (R)`} aria-label={`${t('parameters.remixImage')} (R)`} - isDisabled={!metadata?.positive_prompt} - onClick={handleRemixImage} + isDisabled={!hasMetadata} + onClick={remix} /> } tooltip={`${t('parameters.usePrompt')} (P)`} aria-label={`${t('parameters.usePrompt')} (P)`} - isDisabled={!metadata?.positive_prompt} - onClick={handleUsePrompt} + isDisabled={!hasPrompts} + onClick={recallPrompts} /> } tooltip={`${t('parameters.useSeed')} (S)`} aria-label={`${t('parameters.useSeed')} (S)`} - isDisabled={metadata?.seed === null || metadata?.seed === undefined} - onClick={handleUseSeed} + isDisabled={!hasSeed} + onClick={recallSeed} /> } 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} /> { icon={} tooltip={`${t('parameters.useAll')} (A)`} aria-label={`${t('parameters.useAll')} (A)`} - isDisabled={!metadata} - onClick={handleClickUseAllParameters} + isDisabled={!hasMetadata} + onClick={recallAll} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx index f4d9c7a840..6f49f4afcc 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx @@ -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) => { : } - onClickCapture={handleRemixImage} - isDisabled={ - isLoadingMetadata || (metadata?.positive_prompt === undefined && metadata?.negative_prompt === undefined) - } + onClickCapture={remix} + isDisabled={isLoadingMetadata || !hasMetadata} > {t('parameters.remixImage')} : } - onClickCapture={handleRecallPrompt} - isDisabled={ - isLoadingMetadata || (metadata?.positive_prompt === undefined && metadata?.negative_prompt === undefined) - } + onClickCapture={recallPrompts} + isDisabled={isLoadingMetadata || !hasPrompts} > {t('parameters.usePrompt')} : } - onClickCapture={handleRecallSeed} - isDisabled={isLoadingMetadata || metadata?.seed === undefined} + onClickCapture={recallSeed} + isDisabled={isLoadingMetadata || !hasSeed} > {t('parameters.useSeed')} : } - onClickCapture={handleUseAllParameters} - isDisabled={isLoadingMetadata || !metadata} + onClickCapture={recallAll} + isDisabled={isLoadingMetadata || !hasMetadata} > {t('parameters.useAll')} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataItem.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataItem.tsx deleted file mode 100644 index 146c1437d1..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataItem.tsx +++ /dev/null @@ -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 ( - - {_onRecall && ( - - } - size="xs" - variant="ghost" - fontSize={20} - onClick={onRecall} - /> - - )} - - - {label}: - - {isLink ? ( - - ) : ( - - {value.toString()} - - )} - - - ); -}; - -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 ( - - ); -}); - -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 ; -}); - -ModelMetadataItem.displayName = 'ModelMetadataItem'; diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useImageActions.ts b/invokeai/frontend/web/src/features/gallery/hooks/useImageActions.ts new file mode 100644 index 0000000000..a61b2096da --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/hooks/useImageActions.ts @@ -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 }; +}; diff --git a/invokeai/frontend/web/src/features/metadata/util/handlers.ts b/invokeai/frontend/web/src/features/metadata/util/handlers.ts index 2c6e9d7c63..49d85f4eb3 100644 --- a/invokeai/frontend/web/src/features/metadata/util/handlers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/handlers.ts @@ -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')); + } +}; diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index e33589d5ce..5e156dc035 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -203,62 +203,6 @@ const recallIPAdapters: MetadataRecallFunc = (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, diff --git a/invokeai/frontend/web/src/features/parameters/components/ImageToImage/InitialImageDisplay.tsx b/invokeai/frontend/web/src/features/parameters/components/ImageToImage/InitialImageDisplay.tsx index 110f9bb2a2..68e981b7f5 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ImageToImage/InitialImageDisplay.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ImageToImage/InitialImageDisplay.tsx @@ -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]); diff --git a/invokeai/frontend/web/src/features/parameters/hooks/usePreselectedImage.ts b/invokeai/frontend/web/src/features/parameters/hooks/usePreselectedImage.ts index 4ec1a50dab..ab0fdca0af 100644 --- a/invokeai/frontend/web/src/features/parameters/hooks/usePreselectedImage.ts +++ b/invokeai/frontend/web/src/features/parameters/hooks/usePreselectedImage.ts @@ -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(() => { diff --git a/invokeai/frontend/web/src/features/parameters/hooks/useRecallParameters.ts b/invokeai/frontend/web/src/features/parameters/hooks/useRecallParameters.ts deleted file mode 100644 index 8f9a76feca..0000000000 --- a/invokeai/frontend/web/src/features/parameters/hooks/useRecallParameters.ts +++ /dev/null @@ -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 = ( - 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, 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 => { - 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 => { - 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 => { - 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 => { - 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 => { - 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 => { - 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 => { - 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( - (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( - (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( - 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, - }; -};