From f04462973b091f02ef15046cecfaf266de7ab0bb Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 18 Oct 2023 21:23:09 +1100 Subject: [PATCH] feat(ui): create debounced metadata/workflow query hooks Also added config options for metadata and workflow debounce times (`metadataFetchDebounce` & `workflowFetchDebounce`). Falls back to 0 if not provided. In OSS, because we have no major latency concerns, the debounce is 0. But in other environments, it may be desirable to set this to something like 300ms. --- .../frontend/web/src/app/types/invokeai.ts | 2 ++ .../CurrentImage/CurrentImageButtons.tsx | 22 ++++++------------- .../SingleSelectionMenuItems.tsx | 19 ++++++---------- .../ImageMetadataViewer.tsx | 18 ++++----------- .../src/services/api/endpoints/workflows.ts | 1 - .../api/hooks/useDebouncedMetadata.ts | 21 ++++++++++++++++++ .../api/hooks/useDebouncedWorkflow.ts | 21 ++++++++++++++++++ 7 files changed, 62 insertions(+), 42 deletions(-) create mode 100644 invokeai/frontend/web/src/services/api/hooks/useDebouncedMetadata.ts create mode 100644 invokeai/frontend/web/src/services/api/hooks/useDebouncedWorkflow.ts diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 39e4ffd27a..0fe7a36052 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -59,6 +59,8 @@ export type AppConfig = { nodesAllowlist: string[] | undefined; nodesDenylist: string[] | undefined; maxUpscalePixels?: number; + metadataFetchDebounce?: number; + workflowFetchDebounce?: number; sd: { defaultModel?: string; disabledControlNetModels: string[]; 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 4c0aa5e0e8..36a251952c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx @@ -38,15 +38,12 @@ import { FaSeedling, } from 'react-icons/fa'; import { FaCircleNodes, FaEllipsis } from 'react-icons/fa6'; -import { - useGetImageDTOQuery, - useGetImageMetadataQuery, -} from 'services/api/endpoints/images'; +import { useGetImageDTOQuery } from 'services/api/endpoints/images'; +import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata'; +import { useDebouncedWorkflow } from 'services/api/hooks/useDebouncedWorkflow'; import { menuListMotionProps } from 'theme/components/menu'; -import { useDebounce } from 'use-debounce'; import { sentImageToImg2Img } from '../../store/actions'; import SingleSelectionMenuItems from '../ImageContextMenu/SingleSelectionMenuItems'; -import { useGetWorkflowQuery } from 'services/api/endpoints/workflows'; const currentImageButtonsSelector = createSelector( [stateSelector, activeTabNameSelector], @@ -105,17 +102,12 @@ const CurrentImageButtons = () => { lastSelectedImage?.image_name ?? skipToken ); - const [debouncedImageName] = useDebounce(lastSelectedImage?.image_name, 300); - const [debouncedWorkflowId] = useDebounce( - lastSelectedImage?.workflow_id, - 300 + const { metadata, isLoading: isLoadingMetadata } = useDebouncedMetadata( + lastSelectedImage?.image_name ); - const { data: metadata, isLoading: isLoadingMetadata } = - useGetImageMetadataQuery(debouncedImageName ?? skipToken); - - const { data: workflow, isLoading: isLoadingWorkflow } = useGetWorkflowQuery( - debouncedWorkflowId ?? skipToken + const { workflow, isLoading: isLoadingWorkflow } = useDebouncedWorkflow( + lastSelectedImage?.workflow_id ); const handleLoadWorkflow = useCallback(() => { 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 38de235e38..ed12eb5ff4 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,5 @@ import { Flex, MenuItem, Spinner } from '@chakra-ui/react'; import { useStore } from '@nanostores/react'; -import { skipToken } from '@reduxjs/toolkit/dist/query'; import { useAppToaster } from 'app/components/Toaster'; import { $customStarUI } from 'app/store/nanostores/customStarUI'; import { useAppDispatch } from 'app/store/storeHooks'; @@ -33,13 +32,12 @@ import { import { FaCircleNodes } from 'react-icons/fa6'; import { MdStar, MdStarBorder } from 'react-icons/md'; import { - useGetImageMetadataQuery, useStarImagesMutation, useUnstarImagesMutation, } from 'services/api/endpoints/images'; -import { useGetWorkflowQuery } from 'services/api/endpoints/workflows'; +import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata'; +import { useDebouncedWorkflow } from 'services/api/hooks/useDebouncedWorkflow'; import { ImageDTO } from 'services/api/types'; -import { useDebounce } from 'use-debounce'; import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions'; type SingleSelectionMenuItemsProps = { @@ -57,14 +55,11 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled; const customStarUi = useStore($customStarUI); - const [debouncedImageName] = useDebounce(imageDTO?.image_name, 300); - const [debouncedWorkflowId] = useDebounce(imageDTO?.workflow_id, 300); - - const { data: metadata, isLoading: isLoadingMetadata } = - useGetImageMetadataQuery(debouncedImageName ?? skipToken); - - const { data: workflow, isLoading: isLoadingWorkflow } = useGetWorkflowQuery( - debouncedWorkflowId ?? skipToken + const { metadata, isLoading: isLoadingMetadata } = useDebouncedMetadata( + imageDTO?.image_name + ); + const { workflow, isLoading: isLoadingWorkflow } = useDebouncedWorkflow( + imageDTO?.workflow_id ); const [starImages] = useStarImagesMutation(); 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 f6820b9d20..29637e4a3c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx @@ -9,15 +9,13 @@ import { Tabs, Text, } from '@chakra-ui/react'; -import { skipToken } from '@reduxjs/toolkit/dist/query'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; -import { useGetImageMetadataQuery } from 'services/api/endpoints/images'; -import { useGetWorkflowQuery } from 'services/api/endpoints/workflows'; +import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata'; +import { useDebouncedWorkflow } from 'services/api/hooks/useDebouncedWorkflow'; import { ImageDTO } from 'services/api/types'; -import { useDebounce } from 'use-debounce'; import DataViewer from './DataViewer'; import ImageMetadataActions from './ImageMetadataActions'; @@ -33,16 +31,8 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => { // }); const { t } = useTranslation(); - const [debouncedImageName] = useDebounce(image.image_name, 300); - const [debouncedWorkflowId] = useDebounce(image.workflow_id, 300); - - const { data: metadata } = useGetImageMetadataQuery( - debouncedImageName ?? skipToken - ); - - const { data: workflow } = useGetWorkflowQuery( - debouncedWorkflowId ?? skipToken - ); + const { metadata } = useDebouncedMetadata(image.image_name); + const { workflow } = useDebouncedWorkflow(image.workflow_id); return ( ({ getWorkflow: build.query({ query: (workflow_id) => `workflows/i/${workflow_id}`, - keepUnusedDataFor: 86400, // 24 hours providesTags: (result, error, workflow_id) => [ { type: 'Workflow', id: workflow_id }, ], diff --git a/invokeai/frontend/web/src/services/api/hooks/useDebouncedMetadata.ts b/invokeai/frontend/web/src/services/api/hooks/useDebouncedMetadata.ts new file mode 100644 index 0000000000..023b3c140c --- /dev/null +++ b/invokeai/frontend/web/src/services/api/hooks/useDebouncedMetadata.ts @@ -0,0 +1,21 @@ +import { skipToken } from '@reduxjs/toolkit/query'; +import { useDebounce } from 'use-debounce'; +import { useGetImageMetadataQuery } from '../endpoints/images'; +import { useAppSelector } from 'app/store/storeHooks'; + +export const useDebouncedMetadata = (imageName?: string | null) => { + const metadataFetchDebounce = useAppSelector( + (state) => state.config.metadataFetchDebounce + ); + + const [debouncedImageName] = useDebounce( + imageName, + metadataFetchDebounce ?? 0 + ); + + const { data: metadata, isLoading } = useGetImageMetadataQuery( + debouncedImageName ?? skipToken + ); + + return { metadata, isLoading }; +}; diff --git a/invokeai/frontend/web/src/services/api/hooks/useDebouncedWorkflow.ts b/invokeai/frontend/web/src/services/api/hooks/useDebouncedWorkflow.ts new file mode 100644 index 0000000000..2731597b2c --- /dev/null +++ b/invokeai/frontend/web/src/services/api/hooks/useDebouncedWorkflow.ts @@ -0,0 +1,21 @@ +import { skipToken } from '@reduxjs/toolkit/query'; +import { useAppSelector } from 'app/store/storeHooks'; +import { useDebounce } from 'use-debounce'; +import { useGetWorkflowQuery } from '../endpoints/workflows'; + +export const useDebouncedWorkflow = (workflowId?: string | null) => { + const workflowFetchDebounce = useAppSelector( + (state) => state.config.workflowFetchDebounce + ); + + const [debouncedWorkflowID] = useDebounce( + workflowId, + workflowFetchDebounce ?? 0 + ); + + const { data: workflow, isLoading } = useGetWorkflowQuery( + debouncedWorkflowID ?? skipToken + ); + + return { workflow, isLoading }; +};