From ba8bed68708b99b65184bfcb7dea149228318136 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sun, 19 May 2024 23:44:07 +1000 Subject: [PATCH] fix(ui): edge case resulting in no node templates when loading workflow, causing failure Depending on the user behaviour and network conditions, it's possible that we could try to load a workflow before the invocation templates are available. Fix is simple: - Use the RTKQ query hook for openAPI schema in App.tsx - Disable the load workflow buttons until w have templates parsed --- invokeai/frontend/web/src/app/components/App.tsx | 2 ++ .../ImageContextMenu/SingleSelectionMenuItems.tsx | 5 ++++- .../gallery/components/ImageViewer/CurrentImageButtons.tsx | 7 +++++-- .../WorkflowLibraryMenu/LoadWorkflowFromGraphMenuItem.tsx | 6 +++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index 30d8f41200..1ff093f348 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -21,6 +21,7 @@ import i18n from 'i18n'; import { size } from 'lodash-es'; import { memo, useCallback, useEffect } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; +import { useGetOpenAPISchemaQuery } from 'services/api/endpoints/appInfo'; import AppErrorBoundaryFallback from './AppErrorBoundaryFallback'; import PreselectedImage from './PreselectedImage'; @@ -46,6 +47,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => { useSocketIO(); useGlobalModifiersInit(); useGlobalHotkeys(); + useGetOpenAPISchemaQuery(); const { dropzone, isHandlingUpload, setIsHandlingUpload } = useFullscreenDropzone(); 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 a25f6d8c0e..f5063ea717 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx @@ -11,10 +11,12 @@ import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice'; import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice'; import { useImageActions } from 'features/gallery/hooks/useImageActions'; import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/actions'; +import { $templates } from 'features/nodes/store/nodesSlice'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { setActiveTab } from 'features/ui/store/uiSlice'; import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow'; +import { size } from 'lodash-es'; import { memo, useCallback } from 'react'; import { flushSync } from 'react-dom'; import { useTranslation } from 'react-i18next'; @@ -48,6 +50,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { const isCanvasEnabled = useFeatureStatus('canvas'); const customStarUi = useStore($customStarUI); const { downloadImage } = useDownloadImage(); + const templates = useStore($templates); const { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata } = useImageActions(imageDTO?.image_name); @@ -133,7 +136,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { : } onClickCapture={handleLoadWorkflow} - isDisabled={!imageDTO.has_workflow} + isDisabled={!imageDTO.has_workflow || !size(templates)} > {t('nodes.loadWorkflow')} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx index ada9c35d28..d500d692fe 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx @@ -1,4 +1,5 @@ import { ButtonGroup, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library'; +import { useStore } from '@nanostores/react'; import { createSelector } from '@reduxjs/toolkit'; import { skipToken } from '@reduxjs/toolkit/query'; import { upscaleRequested } from 'app/store/middleware/listenerMiddleware/listeners/upscaleRequested'; @@ -12,12 +13,14 @@ 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 { $templates } from 'features/nodes/store/nodesSlice'; import ParamUpscalePopover from 'features/parameters/components/Upscale/ParamUpscaleSettings'; import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { selectSystemSlice } from 'features/system/store/systemSlice'; import { setActiveTab } from 'features/ui/store/uiSlice'; import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow'; +import { size } from 'lodash-es'; import { memo, useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; @@ -48,7 +51,7 @@ const CurrentImageButtons = () => { const lastSelectedImage = useAppSelector(selectLastSelectedImage); const selection = useAppSelector((s) => s.gallery.selection); const shouldDisableToolbarButtons = useAppSelector(selectShouldDisableToolbarButtons); - + const templates = useStore($templates); const isUpscalingEnabled = useFeatureStatus('upscaling'); const isQueueMutationInProgress = useIsQueueMutationInProgress(); const { t } = useTranslation(); @@ -143,7 +146,7 @@ const CurrentImageButtons = () => { icon={} tooltip={`${t('nodes.loadWorkflow')} (W)`} aria-label={`${t('nodes.loadWorkflow')} (W)`} - isDisabled={!imageDTO?.has_workflow} + isDisabled={!imageDTO?.has_workflow || !size(templates)} onClick={handleLoadWorkflow} isLoading={getAndLoadEmbeddedWorkflowResult.isLoading} /> diff --git a/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryMenu/LoadWorkflowFromGraphMenuItem.tsx b/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryMenu/LoadWorkflowFromGraphMenuItem.tsx index 8f3cb0c6f6..8006ca937f 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryMenu/LoadWorkflowFromGraphMenuItem.tsx +++ b/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryMenu/LoadWorkflowFromGraphMenuItem.tsx @@ -1,15 +1,19 @@ import { MenuItem } from '@invoke-ai/ui-library'; +import { useStore } from '@nanostores/react'; +import { $templates } from 'features/nodes/store/nodesSlice'; import { useLoadWorkflowFromGraphModal } from 'features/workflowLibrary/components/LoadWorkflowFromGraphModal/LoadWorkflowFromGraphModal'; +import { size } from 'lodash-es'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiFlaskBold } from 'react-icons/pi'; const LoadWorkflowFromGraphMenuItem = () => { const { t } = useTranslation(); + const templates = useStore($templates); const { onOpen } = useLoadWorkflowFromGraphModal(); return ( - } onClick={onOpen}> + } onClick={onOpen} isDisabled={!size(templates)}> {t('workflows.loadFromGraph')} );