diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index a39ed2ca7b2..8b247327c70 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -45,6 +45,7 @@ export type AppConfig = { * Whether or not we should update image urls when image loading errors */ shouldUpdateImagesOnConnect: boolean; + shouldFetchMetadataFromApi: boolean; disabledTabs: InvokeTabName[]; disabledFeatures: AppFeature[]; disabledSDFeatures: SDFeature[]; 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 846cf5a6f00..e5f0bc54f59 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx @@ -27,7 +27,7 @@ import { setShouldShowImageDetails, setShouldShowProgressInViewer, } from 'features/ui/store/uiSlice'; -import { memo, useCallback } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { @@ -49,7 +49,7 @@ import SingleSelectionMenuItems from '../ImageContextMenu/SingleSelectionMenuIte const currentImageButtonsSelector = createSelector( [stateSelector, activeTabNameSelector], - ({ gallery, system, ui }, activeTabName) => { + ({ gallery, system, ui, config }, activeTabName) => { const { isProcessing, isConnected, shouldConfirmOnDelete, progressImage } = system; @@ -59,6 +59,8 @@ const currentImageButtonsSelector = createSelector( shouldShowProgressInViewer, } = ui; + const { shouldFetchMetadataFromApi } = config; + const lastSelectedImage = gallery.selection[gallery.selection.length - 1]; return { @@ -72,6 +74,7 @@ const currentImageButtonsSelector = createSelector( shouldHidePreview, shouldShowProgressInViewer, lastSelectedImage, + shouldFetchMetadataFromApi, }; }, { @@ -92,6 +95,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { shouldShowImageDetails, lastSelectedImage, shouldShowProgressInViewer, + shouldFetchMetadataFromApi, } = useAppSelector(currentImageButtonsSelector); const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled; @@ -106,8 +110,16 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { lastSelectedImage?.image_name ?? skipToken ); + const getMetadataArg = useMemo(() => { + if (lastSelectedImage) { + return { image: lastSelectedImage, shouldFetchMetadataFromApi }; + } else { + return skipToken; + } + }, [lastSelectedImage, shouldFetchMetadataFromApi]); + const { metadata, workflow, isLoading } = useGetImageMetadataFromFileQuery( - lastSelectedImage ?? skipToken, + getMetadataArg, { selectFromResult: (res) => ({ isLoading: res.isFetching, 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 90272a3a860..46c84e85ce7 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx @@ -1,6 +1,6 @@ import { Flex, MenuItem, Spinner } from '@chakra-ui/react'; import { useAppToaster } from 'app/components/Toaster'; -import { useAppDispatch } from 'app/store/storeHooks'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { imagesToChangeSelected, @@ -34,6 +34,7 @@ import { import { ImageDTO } from 'services/api/types'; import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions'; import { workflowLoadRequested } from 'features/nodes/store/actions'; +import { configSelector } from '../../../system/store/configSelectors'; type SingleSelectionMenuItemsProps = { imageDTO: ImageDTO; @@ -48,9 +49,10 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { const toaster = useAppToaster(); const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled; + const { shouldFetchMetadataFromApi } = useAppSelector(configSelector); const { metadata, workflow, isLoading } = useGetImageMetadataFromFileQuery( - imageDTO, + { image: imageDTO, shouldFetchMetadataFromApi }, { selectFromResult: (res) => ({ isLoading: res.isFetching, diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx index ca3eab2dba8..bec51256574 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx @@ -15,6 +15,8 @@ import { useGetImageMetadataFromFileQuery } from 'services/api/endpoints/images' import { ImageDTO } from 'services/api/types'; import DataViewer from './DataViewer'; import ImageMetadataActions from './ImageMetadataActions'; +import { useAppSelector } from '../../../../app/store/storeHooks'; +import { configSelector } from '../../../system/store/configSelectors'; type ImageMetadataViewerProps = { image: ImageDTO; @@ -27,12 +29,17 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => { // dispatch(setShouldShowImageDetails(false)); // }); - const { metadata, workflow } = useGetImageMetadataFromFileQuery(image, { - selectFromResult: (res) => ({ - metadata: res?.currentData?.metadata, - workflow: res?.currentData?.workflow, - }), - }); + const { shouldFetchMetadataFromApi } = useAppSelector(configSelector); + + const { metadata, workflow } = useGetImageMetadataFromFileQuery( + { image, shouldFetchMetadataFromApi }, + { + selectFromResult: (res) => ({ + metadata: res?.currentData?.metadata, + workflow: res?.currentData?.workflow, + }), + } + ); return ( ({ @@ -117,8 +120,16 @@ export const imagesApi = api.injectEndpoints({ ], keepUnusedDataFor: 86400, // 24 hours }), - getImageMetadataFromFile: build.query({ - queryFn: async (args: ImageDTO, api, extraOptions) => { + getImageMetadataFromFile: build.query< + ImageMetadataAndWorkflow, + { image: ImageDTO; shouldFetchMetadataFromApi: boolean } + >({ + queryFn: async ( + args: { image: ImageDTO; shouldFetchMetadataFromApi: boolean }, + api, + extraOptions, + fetchWithBaseQuery + ) => { const authToken = $authToken.get(); const projectId = $projectId.get(); const customBaseQuery = fetchBaseQuery({ @@ -139,17 +150,35 @@ export const imagesApi = api.injectEndpoints({ }); const response = await customBaseQuery( - args.image_url, + args.image.image_url, api, extraOptions ); - const data = await getMetadataAndWorkflowFromImageBlob( + const blobData = await getMetadataAndWorkflowFromImageBlob( response.data as Blob ); - return { data }; + + let metadata = blobData.metadata; + + if (args.shouldFetchMetadataFromApi) { + const metadataResponse = await fetchWithBaseQuery( + `images/i/${args.image.image_name}/metadata` + ); + if (metadataResponse.data) { + const metadataResult = zCoreMetadata.safeParse( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (metadataResponse.data as any)?.metadata + ); + if (metadataResult.success) { + metadata = metadataResult.data; + } + } + } + + return { data: { ...blobData, metadata } }; }, - providesTags: (result, error, image_dto) => [ - { type: 'ImageMetadataFromFile', id: image_dto.image_name }, + providesTags: (result, error, { image }) => [ + { type: 'ImageMetadataFromFile', id: image.image_name }, ], keepUnusedDataFor: 86400, // 24 hours }),