From a29fb18c0b6033d8487da71766c2d093ef55c295 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 12 Mar 2025 18:23:36 +1000 Subject: [PATCH] feat(ui): standardize and clean up workflow loading hooks and logic --- .../web/src/app/hooks/useStudioInitAction.ts | 13 ++-- .../ImageMenuItemLoadWorkflow.tsx | 4 +- .../features/gallery/hooks/useImageActions.ts | 4 +- .../LoadWorkflowConfirmationAlertDialog.tsx | 13 ++-- .../LoadWorkflowFromGraphModal.tsx | 18 +++--- .../components/UploadWorkflowButton.tsx | 25 ++++---- .../UploadWorkflowMenuItem.tsx | 26 ++++---- .../hooks/useGetAndLoadEmbeddedWorkflow.ts | 44 ------------- .../hooks/useGetAndLoadLibraryWorkflow.ts | 47 -------------- .../hooks/useLoadWorkflowFromFile.tsx | 50 +++++++-------- .../hooks/useLoadWorkflowFromImage.ts | 62 +++++++++++++++++++ .../hooks/useLoadWorkflowFromLibrary.ts | 48 ++++++++++++++ ...kflow.ts => useValidateAndLoadWorkflow.ts} | 47 ++++++-------- 13 files changed, 204 insertions(+), 197 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow.ts delete mode 100644 invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts create mode 100644 invokeai/frontend/web/src/features/workflowLibrary/hooks/useLoadWorkflowFromImage.ts create mode 100644 invokeai/frontend/web/src/features/workflowLibrary/hooks/useLoadWorkflowFromLibrary.ts rename invokeai/frontend/web/src/features/workflowLibrary/hooks/{useLoadWorkflow.ts => useValidateAndLoadWorkflow.ts} (71%) diff --git a/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts b/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts index 8af256ad38..6cc83854d4 100644 --- a/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts +++ b/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts @@ -15,7 +15,7 @@ import { $isWorkflowLibraryModalOpen } from 'features/nodes/store/workflowLibrar import { $isStylePresetsMenuOpen, activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice'; import { toast } from 'features/toast/toast'; import { activeTabCanvasRightPanelChanged, setActiveTab } from 'features/ui/store/uiSlice'; -import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow'; +import { useLoadWorkflowFromLibrary } from 'features/workflowLibrary/hooks/useLoadWorkflowFromLibrary'; import { atom } from 'nanostores'; import { useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; @@ -57,7 +57,7 @@ export const useStudioInitAction = (action?: StudioInitAction) => { const { t } = useTranslation(); const didParseOpenAPISchema = useStore($hasTemplates); const store = useAppStore(); - const { getAndLoadWorkflow } = useGetAndLoadLibraryWorkflow(); + const loadWorkflowFromLibrary = useLoadWorkflowFromLibrary(); const handleSendToCanvas = useCallback( async (imageName: string) => { @@ -113,10 +113,13 @@ export const useStudioInitAction = (action?: StudioInitAction) => { const handleLoadWorkflow = useCallback( async (workflowId: string) => { // This shows a toast - await getAndLoadWorkflow(workflowId); - store.dispatch(setActiveTab('workflows')); + await loadWorkflowFromLibrary(workflowId, { + onSuccess: () => { + store.dispatch(setActiveTab('workflows')); + }, + }); }, - [getAndLoadWorkflow, store] + [loadWorkflowFromLibrary, store] ); const handleSelectStylePreset = useCallback( diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemLoadWorkflow.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemLoadWorkflow.tsx index 1aab80b21d..f39206811e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemLoadWorkflow.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemLoadWorkflow.tsx @@ -3,7 +3,7 @@ import { useStore } from '@nanostores/react'; import { SpinnerIcon } from 'features/gallery/components/ImageContextMenu/SpinnerIcon'; import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext'; import { $hasTemplates } from 'features/nodes/store/nodesSlice'; -import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow'; +import { useLoadWorkflowFromImage } from 'features/workflowLibrary/hooks/useLoadWorkflowFromImage'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiFlowArrowBold } from 'react-icons/pi'; @@ -11,7 +11,7 @@ import { PiFlowArrowBold } from 'react-icons/pi'; export const ImageMenuItemLoadWorkflow = memo(() => { const { t } = useTranslation(); const imageDTO = useImageDTOContext(); - const [getAndLoadEmbeddedWorkflow, { isLoading }] = useGetAndLoadEmbeddedWorkflow(); + const [getAndLoadEmbeddedWorkflow, { isLoading }] = useLoadWorkflowFromImage(); const hasTemplates = useStore($hasTemplates); const onClick = useCallback(() => { diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useImageActions.ts b/invokeai/frontend/web/src/features/gallery/hooks/useImageActions.ts index c02b72575f..2f90766512 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useImageActions.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useImageActions.ts @@ -17,7 +17,7 @@ import { } from 'features/stylePresets/store/stylePresetSlice'; import { toast } from 'features/toast/toast'; import { selectActiveTab } from 'features/ui/store/uiSelectors'; -import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow'; +import { useLoadWorkflowFromImage } from 'features/workflowLibrary/hooks/useLoadWorkflowFromImage'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata'; @@ -147,7 +147,7 @@ export const useImageActions = (imageDTO: ImageDTO) => { }); }, [metadata, imageDTO]); - const [getAndLoadEmbeddedWorkflow] = useGetAndLoadEmbeddedWorkflow(); + const [getAndLoadEmbeddedWorkflow] = useLoadWorkflowFromImage(); const loadWorkflow = useCallback(() => { if (!imageDTO.has_workflow || !hasTemplates) { diff --git a/invokeai/frontend/web/src/features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog.tsx b/invokeai/frontend/web/src/features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog.tsx index 9deda79605..7761adf767 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog.tsx +++ b/invokeai/frontend/web/src/features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog.tsx @@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAssertSingleton } from 'common/hooks/useAssertSingleton'; import { useWorkflowLibraryModal } from 'features/nodes/store/workflowLibraryModal'; import { selectWorkflowIsTouched, workflowModeChanged } from 'features/nodes/store/workflowSlice'; -import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow'; +import { useLoadWorkflowFromLibrary } from 'features/workflowLibrary/hooks/useLoadWorkflowFromLibrary'; import { atom } from 'nanostores'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -15,7 +15,7 @@ const cleanup = () => $workflowToLoad.set(null); export const useLoadWorkflow = () => { const dispatch = useAppDispatch(); const workflowLibraryModal = useWorkflowLibraryModal(); - const { getAndLoadWorkflow } = useGetAndLoadLibraryWorkflow(); + const loadWorkflowFromLibrary = useLoadWorkflowFromLibrary(); const isTouched = useAppSelector(selectWorkflowIsTouched); @@ -25,11 +25,14 @@ export const useLoadWorkflow = () => { return; } const { workflowId, mode } = workflow; - await getAndLoadWorkflow(workflowId); - dispatch(workflowModeChanged(mode)); + await loadWorkflowFromLibrary(workflowId, { + onSuccess: () => { + dispatch(workflowModeChanged(mode)); + }, + }); cleanup(); workflowLibraryModal.close(); - }, [dispatch, getAndLoadWorkflow, workflowLibraryModal]); + }, [dispatch, loadWorkflowFromLibrary, workflowLibraryModal]); const loadWithDialog = useCallback( (workflowId: string, mode: 'view' | 'edit') => { diff --git a/invokeai/frontend/web/src/features/workflowLibrary/components/LoadWorkflowFromGraphModal/LoadWorkflowFromGraphModal.tsx b/invokeai/frontend/web/src/features/workflowLibrary/components/LoadWorkflowFromGraphModal/LoadWorkflowFromGraphModal.tsx index d759eff611..f12d56ae61 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/components/LoadWorkflowFromGraphModal/LoadWorkflowFromGraphModal.tsx +++ b/invokeai/frontend/web/src/features/workflowLibrary/components/LoadWorkflowFromGraphModal/LoadWorkflowFromGraphModal.tsx @@ -15,7 +15,7 @@ import { } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { graphToWorkflow } from 'features/nodes/util/workflow/graphToWorkflow'; -import { useLoadWorkflow } from 'features/workflowLibrary/hooks/useLoadWorkflow'; +import { useValidateAndLoadWorkflow } from 'features/workflowLibrary/hooks/useValidateAndLoadWorkflow'; import { atom } from 'nanostores'; import type { ChangeEvent } from 'react'; import { useCallback, useState } from 'react'; @@ -37,16 +37,16 @@ export const useLoadWorkflowFromGraphModal = () => { export const LoadWorkflowFromGraphModal = () => { const { t } = useTranslation(); - const _loadWorkflow = useLoadWorkflow(); + const validateAndLoadWorkflow = useValidateAndLoadWorkflow(); const { isOpen, onClose } = useLoadWorkflowFromGraphModal(); const [graphRaw, setGraphRaw] = useState(''); - const [workflowRaw, setWorkflowRaw] = useState(''); + const [unvalidatedWorkflow, setUnvalidatedWorkflow] = useState(''); const [shouldAutoLayout, setShouldAutoLayout] = useState(true); const onChangeGraphRaw = useCallback((e: ChangeEvent) => { setGraphRaw(e.target.value); }, []); const onChangeWorkflowRaw = useCallback((e: ChangeEvent) => { - setWorkflowRaw(e.target.value); + setUnvalidatedWorkflow(e.target.value); }, []); const onChangeShouldAutoLayout = useCallback((e: ChangeEvent) => { setShouldAutoLayout(e.target.checked); @@ -54,12 +54,12 @@ export const LoadWorkflowFromGraphModal = () => { const parse = useCallback(() => { const graph = JSON.parse(graphRaw); const workflow = graphToWorkflow(graph, shouldAutoLayout); - setWorkflowRaw(JSON.stringify(workflow, null, 2)); + setUnvalidatedWorkflow(JSON.stringify(workflow, null, 2)); }, [graphRaw, shouldAutoLayout]); - const loadWorkflow = useCallback(() => { - _loadWorkflow({ workflow: workflowRaw, graph: null }); + const loadWorkflow = useCallback(async () => { + await validateAndLoadWorkflow(unvalidatedWorkflow); onClose(); - }, [_loadWorkflow, onClose, workflowRaw]); + }, [validateAndLoadWorkflow, onClose, unvalidatedWorkflow]); return ( @@ -95,7 +95,7 @@ export const LoadWorkflowFromGraphModal = () => { {t('nodes.workflow')}