mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): optimized workflow building
- Store workflow in nanostore as singleton instead of building for each consumer - Debounce the build (already was indirectly debounced) - When the workflow is needed, imperatively grab it from the nanostores, instead of letting react handle it via reactivity
This commit is contained in:
parent
7eb79266c4
commit
7e2eeec1f3
@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger';
|
||||
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
||||
import { $mouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
|
||||
import { useWorkflowWatcher } from 'features/nodes/hooks/useWorkflowWatcher';
|
||||
import {
|
||||
connectionEnded,
|
||||
connectionMade,
|
||||
@ -80,7 +81,7 @@ export const Flow = memo(() => {
|
||||
const flowWrapper = useRef<HTMLDivElement>(null);
|
||||
const cursorPosition = useRef<XYPosition | null>(null);
|
||||
const isValidConnection = useIsValidConnection();
|
||||
|
||||
useWorkflowWatcher();
|
||||
const [borderRadius] = useToken('radii', ['base']);
|
||||
|
||||
const flowStyles = useMemo<CSSProperties>(
|
||||
|
@ -1,16 +1,17 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
|
||||
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||
import { $builtWorkflow } from 'features/nodes/hooks/useWorkflowWatcher';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const WorkflowJSONTab = () => {
|
||||
const workflow = useWorkflow();
|
||||
const workflow = useStore($builtWorkflow);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" alignItems="flex-start" gap={2} h="full">
|
||||
<DataViewer data={workflow} label={t('nodes.workflow')} />
|
||||
<DataViewer data={workflow ?? {}} label={t('nodes.workflow')} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -1,19 +0,0 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { buildWorkflow } from 'features/nodes/util/workflow/buildWorkflow';
|
||||
import { useMemo } from 'react';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
|
||||
export const useWorkflow = () => {
|
||||
const nodes_ = useAppSelector((state) => state.nodes.nodes);
|
||||
const edges_ = useAppSelector((state) => state.nodes.edges);
|
||||
const workflow_ = useAppSelector((state) => state.workflow);
|
||||
const [nodes] = useDebounce(nodes_, 300);
|
||||
const [edges] = useDebounce(edges_, 300);
|
||||
const [workflow] = useDebounce(workflow_, 300);
|
||||
const builtWorkflow = useMemo(
|
||||
() => buildWorkflow({ nodes, edges, workflow }),
|
||||
[nodes, edges, workflow]
|
||||
);
|
||||
|
||||
return builtWorkflow;
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import type { WorkflowV2 } from 'features/nodes/types/workflow';
|
||||
import type { BuildWorkflowArg } from 'features/nodes/util/workflow/buildWorkflow';
|
||||
import { buildWorkflow } from 'features/nodes/util/workflow/buildWorkflow';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { atom } from 'nanostores';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const $builtWorkflow = atom<WorkflowV2 | null>(null);
|
||||
|
||||
const debouncedBuildWorkflow = debounce((arg: BuildWorkflowArg) => {
|
||||
$builtWorkflow.set(buildWorkflow(arg));
|
||||
}, 300);
|
||||
|
||||
export const useWorkflowWatcher = () => {
|
||||
const buildWorkflowArg = useAppSelector(({ nodes, workflow }) => ({
|
||||
nodes: nodes.nodes,
|
||||
edges: nodes.edges,
|
||||
workflow,
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
debouncedBuildWorkflow(buildWorkflowArg);
|
||||
}, [buildWorkflowArg]);
|
||||
};
|
@ -2,19 +2,23 @@ import { logger } from 'app/logging/logger';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import type { NodesState, WorkflowsState } from 'features/nodes/store/types';
|
||||
import { isInvocationNode, isNotesNode } from 'features/nodes/types/invocation';
|
||||
import type { WorkflowV2 } from 'features/nodes/types/workflow';
|
||||
import { zWorkflowEdge, zWorkflowNode } from 'features/nodes/types/workflow';
|
||||
import i18n from 'i18next';
|
||||
import type {
|
||||
WorkflowV2} from 'features/nodes/types/workflow';
|
||||
import {
|
||||
zWorkflowEdge,
|
||||
zWorkflowNode,
|
||||
} from 'features/nodes/types/workflow';
|
||||
import i18n from 'i18n';
|
||||
import { cloneDeep, omit } from 'lodash-es';
|
||||
import { fromZodError } from 'zod-validation-error';
|
||||
|
||||
type BuildWorkflowArg = {
|
||||
export type BuildWorkflowArg = {
|
||||
nodes: NodesState['nodes'];
|
||||
edges: NodesState['edges'];
|
||||
workflow: WorkflowsState;
|
||||
};
|
||||
|
||||
type BuildWorkflowFunction = (arg: BuildWorkflowArg) => WorkflowV2;
|
||||
export type BuildWorkflowFunction = (arg: BuildWorkflowArg) => WorkflowV2;
|
||||
|
||||
export const buildWorkflow: BuildWorkflowFunction = ({
|
||||
nodes,
|
||||
|
@ -1,17 +1,19 @@
|
||||
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||
import { useCallback } from 'react';
|
||||
import { $builtWorkflow } from 'features/nodes/hooks/useWorkflowWatcher';
|
||||
|
||||
const downloadWorkflow = () => {
|
||||
const workflow = $builtWorkflow.get();
|
||||
if (!workflow) {
|
||||
return;
|
||||
}
|
||||
const blob = new Blob([JSON.stringify(workflow, null, 2)]);
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `${workflow.name || 'My Workflow'}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
};
|
||||
|
||||
export const useDownloadWorkflow = () => {
|
||||
const workflow = useWorkflow();
|
||||
const downloadWorkflow = useCallback(() => {
|
||||
const blob = new Blob([JSON.stringify(workflow, null, 2)]);
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `${workflow.name || 'My Workflow'}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
}, [workflow]);
|
||||
|
||||
return downloadWorkflow;
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { ToastId } from '@chakra-ui/react';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||
import { $builtWorkflow } from 'features/nodes/hooks/useWorkflowWatcher';
|
||||
import {
|
||||
workflowIDChanged,
|
||||
workflowSaved,
|
||||
@ -30,12 +30,15 @@ const isWorkflowWithID = (
|
||||
export const useSaveLibraryWorkflow: UseSaveLibraryWorkflow = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const workflow = useWorkflow();
|
||||
const [updateWorkflow, updateWorkflowResult] = useUpdateWorkflowMutation();
|
||||
const [createWorkflow, createWorkflowResult] = useCreateWorkflowMutation();
|
||||
const toast = useToast();
|
||||
const toastRef = useRef<ToastId | undefined>();
|
||||
const saveWorkflow = useCallback(async () => {
|
||||
const workflow = $builtWorkflow.get();
|
||||
if (!workflow) {
|
||||
return;
|
||||
}
|
||||
toastRef.current = toast({
|
||||
title: t('workflows.savingWorkflow'),
|
||||
status: 'loading',
|
||||
@ -64,7 +67,7 @@ export const useSaveLibraryWorkflow: UseSaveLibraryWorkflow = () => {
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
}, [workflow, updateWorkflow, dispatch, toast, t, createWorkflow]);
|
||||
}, [updateWorkflow, dispatch, toast, t, createWorkflow]);
|
||||
return {
|
||||
saveWorkflow,
|
||||
isLoading: updateWorkflowResult.isLoading || createWorkflowResult.isLoading,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { ToastId } from '@chakra-ui/react';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||
import { $builtWorkflow } from 'features/nodes/hooks/useWorkflowWatcher';
|
||||
import {
|
||||
workflowIDChanged,
|
||||
workflowNameChanged,
|
||||
@ -28,12 +28,15 @@ type UseSaveWorkflowAs = () => UseSaveWorkflowAsReturn;
|
||||
export const useSaveWorkflowAs: UseSaveWorkflowAs = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const workflow = useWorkflow();
|
||||
const [createWorkflow, createWorkflowResult] = useCreateWorkflowMutation();
|
||||
const toast = useToast();
|
||||
const toastRef = useRef<ToastId | undefined>();
|
||||
const saveWorkflowAs = useCallback(
|
||||
async ({ name: newName, onSuccess, onError }: SaveWorkflowAsArg) => {
|
||||
const workflow = $builtWorkflow.get();
|
||||
if (!workflow) {
|
||||
return;
|
||||
}
|
||||
toastRef.current = toast({
|
||||
title: t('workflows.savingWorkflow'),
|
||||
status: 'loading',
|
||||
@ -64,7 +67,7 @@ export const useSaveWorkflowAs: UseSaveWorkflowAs = () => {
|
||||
});
|
||||
}
|
||||
},
|
||||
[toast, workflow, createWorkflow, dispatch, t]
|
||||
[toast, createWorkflow, dispatch, t]
|
||||
);
|
||||
return {
|
||||
saveWorkflowAs,
|
||||
|
Loading…
Reference in New Issue
Block a user