diff --git a/projects/app/src/pages/app/detail/components/Plugin/Header.tsx b/projects/app/src/pages/app/detail/components/Plugin/Header.tsx
index fe2dd9dc2..70db4ab7d 100644
--- a/projects/app/src/pages/app/detail/components/Plugin/Header.tsx
+++ b/projects/app/src/pages/app/detail/components/Plugin/Header.tsx
@@ -29,6 +29,11 @@ import { useDebounceEffect } from 'ahooks';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SaveButton from '../Workflow/components/SaveButton';
import PublishHistories from '../PublishHistoriesSlider';
+import {
+ WorkflowActionContext,
+ WorkflowInitContext
+} from '../WorkflowComponents/context/workflowInitContext';
+import { WorkflowEventContext } from '../WorkflowComponents/context/workflowEventContext';
const Header = () => {
const { t } = useTranslation();
@@ -44,20 +49,24 @@ const Header = () => {
onClose: onCloseBackConfirm
} = useDisclosure();
+ const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const {
flowData2StoreData,
flowData2StoreDataAndCheck,
setWorkflowTestData,
- setShowHistoryModal,
- showHistoryModal,
- nodes,
- edges,
past,
future,
setPast,
onSwitchTmpVersion,
onSwitchCloudVersion
} = useContextSelector(WorkflowContext, (v) => v);
+ const showHistoryModal = useContextSelector(WorkflowEventContext, (v) => v.showHistoryModal);
+ const setShowHistoryModal = useContextSelector(
+ WorkflowEventContext,
+ (v) => v.setShowHistoryModal
+ );
+
const { lastAppListRouteType } = useSystemStore();
const [isPublished, setIsPublished] = useState(false);
diff --git a/projects/app/src/pages/app/detail/components/Workflow/Header.tsx b/projects/app/src/pages/app/detail/components/Workflow/Header.tsx
index 58f49e338..640ee0063 100644
--- a/projects/app/src/pages/app/detail/components/Workflow/Header.tsx
+++ b/projects/app/src/pages/app/detail/components/Workflow/Header.tsx
@@ -29,6 +29,11 @@ import { useDebounceEffect } from 'ahooks';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SaveButton from './components/SaveButton';
import PublishHistories from '../PublishHistoriesSlider';
+import {
+ WorkflowActionContext,
+ WorkflowInitContext
+} from '../WorkflowComponents/context/workflowInitContext';
+import { WorkflowEventContext } from '../WorkflowComponents/context/workflowEventContext';
const Header = () => {
const { t } = useTranslation();
@@ -48,20 +53,23 @@ const Header = () => {
onClose: onCloseBackConfirm
} = useDisclosure();
+ const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const {
flowData2StoreData,
flowData2StoreDataAndCheck,
setWorkflowTestData,
- setShowHistoryModal,
- showHistoryModal,
- nodes,
- edges,
past,
future,
setPast,
onSwitchTmpVersion,
onSwitchCloudVersion
} = useContextSelector(WorkflowContext, (v) => v);
+ const showHistoryModal = useContextSelector(WorkflowEventContext, (v) => v.showHistoryModal);
+ const setShowHistoryModal = useContextSelector(
+ WorkflowEventContext,
+ (v) => v.setShowHistoryModal
+ );
const { lastAppListRouteType } = useSystemStore();
diff --git a/projects/app/src/pages/app/detail/components/Workflow/index.tsx b/projects/app/src/pages/app/detail/components/Workflow/index.tsx
index 5ebbe3507..07812123e 100644
--- a/projects/app/src/pages/app/detail/components/Workflow/index.tsx
+++ b/projects/app/src/pages/app/detail/components/Workflow/index.tsx
@@ -14,7 +14,7 @@ import { cloneDeep } from 'lodash';
import { useTranslation } from 'next-i18next';
import Flow from '../WorkflowComponents/Flow';
-import { ReactFlowProvider } from 'reactflow';
+import { ReactFlowCustomProvider } from '../WorkflowComponents/context/index';
const Logs = dynamic(() => import('../Logs/index'));
const PublishChannel = dynamic(() => import('../Publish'));
@@ -67,11 +67,9 @@ const WorkflowEdit = () => {
const Render = () => {
return (
-
-
-
-
-
+
+
+
);
};
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx
index 8eec02669..967ec14e6 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx
@@ -50,6 +50,7 @@ import { useUserStore } from '@/web/support/user/useUserStore';
import { LoopStartNode } from '@fastgpt/global/core/workflow/template/system/loop/loopStart';
import { LoopEndNode } from '@fastgpt/global/core/workflow/template/system/loop/loopEnd';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
+import { WorkflowActionContext } from '../context/workflowInitContext';
type ModuleTemplateListProps = {
isOpen: boolean;
@@ -79,10 +80,10 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
const [parentId, setParentId] = useState('');
const [searchKey, setSearchKey] = useState('');
const { feConfigs } = useSystemStore();
- const { basicNodeTemplates, hasToolNode, nodeList, appId } = useContextSelector(
- WorkflowContext,
- (v) => v
- );
+ const basicNodeTemplates = useContextSelector(WorkflowContext, (v) => v.basicNodeTemplates);
+ const hasToolNode = useContextSelector(WorkflowContext, (v) => v.hasToolNode);
+ const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
+ const appId = useContextSelector(WorkflowContext, (v) => v.appId);
const { data: members = [] } = useRequest2(loadAndGetTeamMembers, {
manual: !feConfigs.isPlus
@@ -217,105 +218,120 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
}
);
- const Render = useMemo(() => {
- return (
- <>
-
-
- {/* Header */}
-
- {/* Tabs */}
-
-
- {
- loadNodeTemplates({
- type: e as TemplateTypeEnum,
- parentId: ''
- });
- }}
- />
-
- {/* close icon */}
- }
- borderColor={'myGray.300'}
- variant={'grayBase'}
- aria-label={''}
- onClick={onClose}
+ return (
+ <>
+
+
+ {/* Header */}
+
+ {/* Tabs */}
+
+
+ {
+ loadNodeTemplates({
+ type: e as TemplateTypeEnum,
+ parentId: ''
+ });
+ }}
/>
-
- {/* Search */}
- {(templateType === TemplateTypeEnum.teamPlugin ||
- templateType === TemplateTypeEnum.systemPlugin) && (
-
-
-
-
-
- setSearchKey(e.target.value)}
- />
-
-
- {templateType === TemplateTypeEnum.teamPlugin && (
+
+ {/* close icon */}
+ }
+ borderColor={'myGray.300'}
+ variant={'grayBase'}
+ aria-label={''}
+ onClick={onClose}
+ />
+
+ {/* Search */}
+ {(templateType === TemplateTypeEnum.teamPlugin ||
+ templateType === TemplateTypeEnum.systemPlugin) && (
+
+
+
+
+
+ setSearchKey(e.target.value)}
+ />
+
+
+ {templateType === TemplateTypeEnum.teamPlugin && (
+ router.push('/app/list')}
+ gap={1}
+ >
+ {t('common:create')}
+
+
+ )}
+ {templateType === TemplateTypeEnum.systemPlugin &&
+ feConfigs.systemPluginCourseUrl && (
{
color: 'primary.600'
}}
fontSize={'sm'}
- onClick={() => router.push('/app/list')}
+ onClick={() => window.open(feConfigs.systemPluginCourseUrl)}
gap={1}
>
- {t('common:create')}
+ {t('common:plugin.contribute')}
)}
- {templateType === TemplateTypeEnum.systemPlugin &&
- feConfigs.systemPluginCourseUrl && (
- window.open(feConfigs.systemPluginCourseUrl)}
- gap={1}
- >
- {t('common:plugin.contribute')}
-
-
- )}
+
+ )}
+ {/* paths */}
+ {(templateType === TemplateTypeEnum.teamPlugin ||
+ templateType === TemplateTypeEnum.systemPlugin) &&
+ !searchKey &&
+ parentId && (
+
+
)}
- {/* paths */}
- {(templateType === TemplateTypeEnum.teamPlugin ||
- templateType === TemplateTypeEnum.systemPlugin) &&
- !searchKey &&
- parentId && (
-
-
-
- )}
-
-
-
- >
- );
- }, [
- isOpen,
- onClose,
- isLoading,
- t,
- templateType,
- feConfigs.systemPluginCourseUrl,
- searchKey,
- parentId,
- paths,
- onUpdateParentId,
- templates,
- loadNodeTemplates,
- router
- ]);
-
- return Render;
+
+
+
+ >
+ );
};
export default React.memo(NodeTemplatesModal);
@@ -403,9 +386,11 @@ const RenderList = React.memo(function RenderList({
const isSystemPlugin = type === TemplateTypeEnum.systemPlugin;
const { screenToFlowPosition } = useReactFlow();
- const { toast } = useToast();
- const { reactFlowWrapper, setNodes, nodeList } = useContextSelector(WorkflowContext, (v) => v);
const { computedNewNodeName } = useWorkflowUtils();
+ const { toast } = useToast();
+
+ const setNodes = useContextSelector(WorkflowActionContext, (v) => v.setNodes);
+ const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const formatTemplates = useMemo(() => {
const copy: NodeTemplateListType = cloneDeep(workflowNodeTemplateList);
@@ -426,8 +411,6 @@ const RenderList = React.memo(function RenderList({
template: NodeTemplateListItemType;
position: XYPosition;
}) => {
- if (!reactFlowWrapper?.current) return;
-
// Load template node
const templateNode = await (async () => {
try {
@@ -539,16 +522,7 @@ const RenderList = React.memo(function RenderList({
return newState;
});
},
- [
- reactFlowWrapper,
- screenToFlowPosition,
- nodeList,
- computedNewNodeName,
- t,
- setNodes,
- setLoading,
- toast
- ]
+ [screenToFlowPosition, nodeList, computedNewNodeName, t, setNodes, setLoading, toast]
);
const gridStyle = useMemo(() => {
@@ -571,133 +545,118 @@ const RenderList = React.memo(function RenderList({
};
}, [type]);
- const Render = useMemo(() => {
- return templates.length === 0 ? (
-
- ) : (
-
-
- {formatTemplates.map((item, i) => (
-
- {item.label && formatTemplates.length > 1 && (
-
-
- {t(item.label as any)}
-
-
- )}
+ return templates.length === 0 ? (
+
+ ) : (
+
+
+ {formatTemplates.map((item, i) => (
+
+ {item.label && formatTemplates.length > 1 && (
+
+
+ {t(item.label as any)}
+
+
+ )}
-
- {item.list.map((template) => (
-
-
-
-
- {t(template.name as any)}
-
-
-
- {t(template.intro as any) || t('common:core.workflow.Not intro')}
-
- {isSystemPlugin && }
-
- }
- >
- {
- if (e.clientX < sliderWidth) return;
- onAddNode({
- template,
- position: { x: e.clientX, y: e.clientY }
- });
- }}
- onClick={(e) => {
- if (template.isFolder) {
- return setParentId(template.id);
- }
- if (isPc) {
- return onAddNode({
- template,
- position: { x: sliderWidth * 1.5, y: 200 }
- });
- }
- onAddNode({
- template,
- position: { x: e.clientX, y: e.clientY }
- });
- onClose();
- }}
- >
-
-
-
+
+ {item.list.map((template) => (
+
+
+
+
{t(template.name as any)}
- {gridStyle.authorInName && template.author !== undefined && (
-
- {`by ${template.author || feConfigs.systemTitle}`}
-
- )}
+
+
+ {t(template.intro as any) || t('common:core.workflow.Not intro')}
-
- {gridStyle.authorInRight && template.authorAvatar && template.author && (
-
-
-
- {template.author}
-
-
+ {isSystemPlugin && }
+
+ }
+ >
+ {
+ if (e.clientX < sliderWidth) return;
+ onAddNode({
+ template,
+ position: { x: e.clientX, y: e.clientY }
+ });
+ }}
+ onClick={(e) => {
+ if (template.isFolder) {
+ return setParentId(template.id);
+ }
+ if (isPc) {
+ return onAddNode({
+ template,
+ position: { x: sliderWidth * 1.5, y: 200 }
+ });
+ }
+ onAddNode({
+ template,
+ position: { x: e.clientX, y: e.clientY }
+ });
+ onClose();
+ }}
+ >
+
+
+
+ {t(template.name as any)}
+
+ {gridStyle.authorInName && template.author !== undefined && (
+
+ {`by ${template.author || feConfigs.systemTitle}`}
+
)}
-
-
- ))}
-
-
- ))}
-
-
- );
- }, [
- feConfigs.systemTitle,
- formatTemplates,
- gridStyle,
- isPc,
- isSystemPlugin,
- onAddNode,
- onClose,
- setParentId,
- t,
- templates.length
- ]);
+
- return Render;
+ {gridStyle.authorInRight && template.authorAvatar && template.author && (
+
+
+
+ {template.author}
+
+
+ )}
+
+
+ ))}
+
+
+ ))}
+
+
+ );
});
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx
index 5bc19587c..1d0985403 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx
@@ -6,12 +6,15 @@ import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/w
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { useThrottleEffect } from 'ahooks';
+import { WorkflowActionContext, WorkflowInitContext } from '../../context/workflowInitContext';
+import { WorkflowEventContext } from '../../context/workflowEventContext';
const ButtonEdge = (props: EdgeProps) => {
- const { nodes, nodeList, onEdgesChange, workflowDebugData, hoverEdgeId } = useContextSelector(
- WorkflowContext,
- (v) => v
- );
+ const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
+ const onEdgesChange = useContextSelector(WorkflowActionContext, (v) => v.onEdgesChange);
+ const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
+ const workflowDebugData = useContextSelector(WorkflowContext, (v) => v.workflowDebugData);
+ const hoverEdgeId = useContextSelector(WorkflowEventContext, (v) => v.hoverEdgeId);
const {
id,
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ContextMenu.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ContextMenu.tsx
index 12a352c6a..125c49918 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ContextMenu.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ContextMenu.tsx
@@ -7,76 +7,76 @@ import { CommentNode } from '@fastgpt/global/core/workflow/template/system/comme
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { useReactFlow } from 'reactflow';
+import { WorkflowActionContext } from '../../context/workflowInitContext';
+import { WorkflowEventContext } from '../../context/workflowEventContext';
-type ContextMenuProps = {
- top: number;
- left: number;
-};
-
-const ContextMenu = ({ top, left }: ContextMenuProps) => {
+const ContextMenu = () => {
const { t } = useTranslation();
- const setNodes = useContextSelector(WorkflowContext, (ctx) => ctx.setNodes);
- const setMenu = useContextSelector(WorkflowContext, (ctx) => ctx.setMenu);
+ const setNodes = useContextSelector(WorkflowActionContext, (v) => v.setNodes);
+ const menu = useContextSelector(WorkflowEventContext, (v) => v.menu);
+ const setMenu = useContextSelector(WorkflowEventContext, (ctx) => ctx.setMenu);
const { screenToFlowPosition } = useReactFlow();
const newNode = nodeTemplate2FlowNode({
template: CommentNode,
- position: screenToFlowPosition({ x: left, y: top }),
+ position: screenToFlowPosition({ x: menu?.left ?? 0, y: menu?.top ?? 0 }),
t
});
return (
-
-
- {
- setMenu(null);
- setNodes((state) => {
- const newState = state
- .map((node) => ({
- ...node,
- selected: false
- }))
- // @ts-ignore
- .concat(newNode);
- return newState;
- });
- }}
- zIndex={1}
- >
-
-
- {t('workflow:context_menu.add_comment')}
-
-
-
+ !!menu && (
+
+
+ {
+ setMenu(null);
+ setNodes((state) => {
+ const newState = state
+ .map((node) => ({
+ ...node,
+ selected: false
+ }))
+ // @ts-ignore
+ .concat(newNode);
+ return newState;
+ });
+ }}
+ zIndex={1}
+ >
+
+
+ {t('workflow:context_menu.add_comment')}
+
+
+
+ )
);
};
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/FlowController.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/FlowController.tsx
index 8c192a59a..9fac06035 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/FlowController.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/FlowController.tsx
@@ -15,8 +15,9 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import styles from './index.module.scss';
-import { maxZoom, minZoom } from '../index';
+import { maxZoom, minZoom } from '../../constants';
import { useKeyPress } from 'ahooks';
+import { WorkflowEventContext } from '../../context/workflowEventContext';
const buttonStyle = {
border: 'none',
@@ -27,16 +28,20 @@ const buttonStyle = {
const FlowController = React.memo(function FlowController() {
const { fitView, zoomIn, zoomOut } = useReactFlow();
const { zoom } = useViewport();
- const {
- undo,
- redo,
- canRedo,
- canUndo,
- workflowControlMode,
- setWorkflowControlMode,
- mouseInCanvas,
- nodeList
- } = useContextSelector(WorkflowContext, (v) => v);
+ const undo = useContextSelector(WorkflowContext, (v) => v.undo);
+ const redo = useContextSelector(WorkflowContext, (v) => v.redo);
+ const canRedo = useContextSelector(WorkflowContext, (v) => v.canRedo);
+ const canUndo = useContextSelector(WorkflowContext, (v) => v.canUndo);
+ const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
+ const workflowControlMode = useContextSelector(
+ WorkflowEventContext,
+ (v) => v.workflowControlMode
+ );
+ const setWorkflowControlMode = useContextSelector(
+ WorkflowEventContext,
+ (v) => v.setWorkflowControlMode
+ );
+ const mouseInCanvas = useContextSelector(WorkflowEventContext, (v) => v.mouseInCanvas);
const { t } = useTranslation();
const isMac = !window ? false : window.navigator.userAgent.toLocaleLowerCase().includes('mac');
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/IOTitle.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/IOTitle.tsx
index 8601cd7a2..77c164f01 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/IOTitle.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/IOTitle.tsx
@@ -1,6 +1,5 @@
import React from 'react';
import { Box, StackProps, HStack } from '@chakra-ui/react';
-import MyIcon from '@fastgpt/web/components/common/Icon';
const IOTitle = ({ text, ...props }: { text?: 'Input' | 'Output' | string } & StackProps) => {
return (
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx
index 31496601b..64b8dd53b 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx
@@ -1,7 +1,7 @@
import { storeNodes2RuntimeNodes } from '@fastgpt/global/core/workflow/runtime/utils';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
-import { useCallback, useState, useMemo, useEffect } from 'react';
+import { useCallback, useState, useMemo } from 'react';
import { checkWorkflowNodeAndConnection } from '@/web/core/workflow/utils';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
@@ -27,13 +27,14 @@ import {
} from '@fastgpt/global/core/workflow/constants';
import { checkInputIsReference } from '@fastgpt/global/core/workflow/utils';
import { useContextSelector } from 'use-context-selector';
-import { WorkflowContext, getWorkflowStore } from '../../context';
+import { WorkflowContext } from '../../context';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { AppContext } from '../../../context';
import { VariableInputItem } from '@/components/core/chat/ChatContainer/ChatBox/components/VariableInput';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import MyTextarea from '@/components/common/Textarea/MyTextarea';
+import { WorkflowActionContext } from '../../context/workflowInitContext';
const MyRightDrawer = dynamic(
() => import('@fastgpt/web/components/common/MyDrawer/MyRightDrawer')
@@ -49,9 +50,10 @@ export const useDebug = () => {
const { t } = useTranslation();
const { toast } = useToast();
- const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
+ const setNodes = useContextSelector(WorkflowActionContext, (v) => v.setNodes);
+ const getNodes = useContextSelector(WorkflowActionContext, (v) => v.getNodes);
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
- const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const onStartNodeDebug = useContextSelector(WorkflowContext, (v) => v.onStartNodeDebug);
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
@@ -76,7 +78,7 @@ export const useDebug = () => {
const [runtimeEdges, setRuntimeEdges] = useState();
const flowData2StoreDataAndCheck = useCallback(async () => {
- const { nodes } = await getWorkflowStore();
+ const nodes = getNodes();
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
if (!checkResults) {
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useKeyboard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useKeyboard.tsx
index 824814d4d..2a20c2d25 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useKeyboard.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useKeyboard.tsx
@@ -5,14 +5,18 @@ import { useTranslation } from 'next-i18next';
import { Node, useKeyPress } from 'reactflow';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { useContextSelector } from 'use-context-selector';
-import { WorkflowContext, getWorkflowStore } from '../../context';
import { useWorkflowUtils } from './useUtils';
import { useKeyPress as useKeyPressEffect } from 'ahooks';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
+import { WorkflowActionContext } from '../../context/workflowInitContext';
+import { WorkflowEventContext } from '../../context/workflowEventContext';
export const useKeyboard = () => {
const { t } = useTranslation();
- const { setNodes, mouseInCanvas } = useContextSelector(WorkflowContext, (v) => v);
+ const getNodes = useContextSelector(WorkflowActionContext, (v) => v.getNodes);
+ const setNodes = useContextSelector(WorkflowActionContext, (v) => v.setNodes);
+ const mouseInCanvas = useContextSelector(WorkflowEventContext, (v) => v.mouseInCanvas);
+
const { copyData } = useCopyData();
const { computedNewNodeName } = useWorkflowUtils();
@@ -33,14 +37,14 @@ export const useKeyboard = () => {
const onCopy = useCallback(async () => {
if (hasInputtingElement()) return;
- const { nodes } = await getWorkflowStore();
+ const nodes = getNodes();
const selectedNodes = nodes.filter(
(node) => node.selected && !node.data?.isError && node.data?.unique !== true
);
if (selectedNodes.length === 0) return;
copyData(JSON.stringify(selectedNodes), t('common:core.workflow.Copy node'));
- }, [copyData, hasInputtingElement, t]);
+ }, [copyData, getNodes, hasInputtingElement, t]);
const onParse = useCallback(async () => {
if (hasInputtingElement()) return;
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx
index bd90b7f1b..f2383dda9 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx
@@ -25,12 +25,16 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { THelperLine } from '@fastgpt/global/core/workflow/type';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
-import { useMemoizedFn } from 'ahooks';
+import { useDebounceEffect, useMemoizedFn } from 'ahooks';
import {
Input_Template_Node_Height,
Input_Template_Node_Width
} from '@fastgpt/global/core/workflow/template/input';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
+import { WorkflowActionContext, WorkflowInitContext } from '../../context/workflowInitContext';
+import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
+import { AppContext } from '../../../context';
+import { WorkflowEventContext } from '../../context/workflowEventContext';
/*
Compute helper lines for snapping nodes to each other
@@ -271,21 +275,22 @@ export const useWorkflow = () => {
const { toast } = useToast();
const { t } = useTranslation();
- const { isDowningCtrl } = useKeyboard();
- const {
- setConnectingEdge,
- edges,
- nodes,
- nodeList,
- onNodesChange,
- setEdges,
- onChangeNode,
- onEdgesChange,
- setHoverEdgeId,
- setMenu
- } = useContextSelector(WorkflowContext, (v) => v);
+ const appDetail = useContextSelector(AppContext, (e) => e.appDetail);
+
+ const nodes = useContextSelector(WorkflowInitContext, (state) => state.nodes);
+ const onNodesChange = useContextSelector(WorkflowActionContext, (state) => state.onNodesChange);
+ const edges = useContextSelector(WorkflowActionContext, (state) => state.edges);
+ const setEdges = useContextSelector(WorkflowActionContext, (v) => v.setEdges);
+ const onEdgesChange = useContextSelector(WorkflowActionContext, (v) => v.onEdgesChange);
+ const { setConnectingEdge, nodeList, onChangeNode, pushPastSnapshot } = useContextSelector(
+ WorkflowContext,
+ (v) => v
+ );
+ const setHoverEdgeId = useContextSelector(WorkflowEventContext, (v) => v.setHoverEdgeId);
+ const setMenu = useContextSelector(WorkflowEventContext, (v) => v.setMenu);
const { getIntersectingNodes } = useReactFlow();
+ const { isDowningCtrl } = useKeyboard();
// Loop node size and position
const resetParentNodeSizeAndPosition = useMemoizedFn((rect: Rect, parentId: string) => {
@@ -330,41 +335,43 @@ export const useWorkflow = () => {
const [helperLineVertical, setHelperLineVertical] = useState();
const checkNodeHelpLine = useMemoizedFn((change: NodeChange, nodes: Node[]) => {
- const positionChange = change.type === 'position' && change.dragging ? change : undefined;
+ requestAnimationFrame(() => {
+ const positionChange = change.type === 'position' && change.dragging ? change : undefined;
- if (positionChange?.position) {
- // 只判断,3000px 内的 nodes,并按从近到远的顺序排序
- const filterNodes = nodes
- .filter((node) => {
- if (!positionChange.position) return false;
+ if (positionChange?.position) {
+ // 只判断,3000px 内的 nodes,并按从近到远的顺序排序
+ const filterNodes = nodes
+ .filter((node) => {
+ if (!positionChange.position) return false;
- return (
- Math.abs(node.position.x - positionChange.position.x) <= 3000 &&
- Math.abs(node.position.y - positionChange.position.y) <= 3000
- );
- })
- .sort((a, b) => {
- if (!positionChange.position) return 0;
- return (
- Math.abs(a.position.x - positionChange.position.x) +
- Math.abs(a.position.y - positionChange.position.y) -
- Math.abs(b.position.x - positionChange.position.x) -
- Math.abs(b.position.y - positionChange.position.y)
- );
- })
- .slice(0, 15);
+ return (
+ Math.abs(node.position.x - positionChange.position.x) <= 3000 &&
+ Math.abs(node.position.y - positionChange.position.y) <= 3000
+ );
+ })
+ .sort((a, b) => {
+ if (!positionChange.position) return 0;
+ return (
+ Math.abs(a.position.x - positionChange.position.x) +
+ Math.abs(a.position.y - positionChange.position.y) -
+ Math.abs(b.position.x - positionChange.position.x) -
+ Math.abs(b.position.y - positionChange.position.y)
+ );
+ })
+ .slice(0, 15);
- const helperLines = computeHelperLines(positionChange, filterNodes);
+ const helperLines = computeHelperLines(positionChange, filterNodes);
- positionChange.position.x = helperLines.snapPosition.x ?? positionChange.position.x;
- positionChange.position.y = helperLines.snapPosition.y ?? positionChange.position.y;
+ positionChange.position.x = helperLines.snapPosition.x ?? positionChange.position.x;
+ positionChange.position.y = helperLines.snapPosition.y ?? positionChange.position.y;
- setHelperLineHorizontal(helperLines.horizontal);
- setHelperLineVertical(helperLines.vertical);
- } else {
- setHelperLineHorizontal(undefined);
- setHelperLineVertical(undefined);
- }
+ setHelperLineHorizontal(helperLines.horizontal);
+ setHelperLineVertical(helperLines.vertical);
+ } else {
+ setHelperLineHorizontal(undefined);
+ setHelperLineVertical(undefined);
+ }
+ });
});
// Check if a node is placed on top of a loop node
@@ -642,6 +649,23 @@ export const useWorkflow = () => {
setMenu(null);
}, [setMenu]);
+ // Watch
+ // Auto save snapshot
+ useDebounceEffect(
+ () => {
+ if (nodes.length === 0 || !appDetail.chatConfig) return;
+
+ pushPastSnapshot({
+ pastNodes: nodes,
+ pastEdges: edges,
+ customTitle: formatTime2YMDHMS(new Date()),
+ chatConfig: appDetail.chatConfig
+ });
+ },
+ [nodes, edges, appDetail.chatConfig],
+ { wait: 500 }
+ );
+
return {
handleNodesChange,
handleEdgeChange,
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx
index c29c25473..200085f6b 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useRef } from 'react';
import ReactFlow, { NodeProps, ReactFlowProvider, SelectionMode } from 'reactflow';
import { Box, IconButton, useDisclosure } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons';
@@ -11,16 +11,15 @@ import NodeTemplatesModal from './NodeTemplatesModal';
import 'reactflow/dist/style.css';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
-import { connectionLineStyle, defaultEdgeOptions } from '../constants';
+import { connectionLineStyle, defaultEdgeOptions, maxZoom, minZoom } from '../constants';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context';
import { useWorkflow } from './hooks/useWorkflow';
import HelperLines from './components/HelperLines';
import FlowController from './components/FlowController';
import ContextMenu from './components/ContextMenu';
-
-export const minZoom = 0.1;
-export const maxZoom = 1.5;
+import { WorkflowActionContext, WorkflowInitContext } from '../context/workflowInitContext';
+import { WorkflowEventContext } from '../context/workflowEventContext';
const NodeSimple = dynamic(() => import('./nodes/NodeSimple'));
const nodeTypes: Record = {
@@ -66,9 +65,12 @@ const edgeTypes = {
};
const Workflow = () => {
- const { nodes, edges, menu, reactFlowWrapper, workflowControlMode } = useContextSelector(
- WorkflowContext,
- (v) => v
+ const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
+ const reactFlowWrapper = useContextSelector(WorkflowEventContext, (v) => v.reactFlowWrapper);
+ const workflowControlMode = useContextSelector(
+ WorkflowEventContext,
+ (v) => v.workflowControlMode
);
const {
@@ -125,7 +127,7 @@ const Workflow = () => {
>
- {menu && }
+
import('./CurlImportModal'));
const defaultFormBody = {
@@ -80,9 +81,10 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
}) {
const { t } = useTranslation();
const { toast } = useToast();
+
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
- const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const { appDetail } = useContextSelector(AppContext, (v) => v);
const { isOpen: isOpenCurl, onOpen: onOpenCurl, onClose: onCloseCurl } = useDisclosure();
@@ -256,8 +258,9 @@ export function RenderHttpProps({
}) {
const { t } = useTranslation();
const [selectedTab, setSelectedTab] = useState(TabEnum.params);
+
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
- const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const { appDetail } = useContextSelector(AppContext, (v) => v);
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/NodePluginConfig.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/NodePluginConfig.tsx
index 48fd924e2..ce0bd249e 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/NodePluginConfig.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/NodePluginConfig.tsx
@@ -53,24 +53,28 @@ const NodePluginConfig = ({ data, selected }: NodeProps) => {
[chatConfig, setAppDetail]
);
- return (
-
-
-
-
-
-
-
-
- );
+ const Render = useMemo(() => {
+ return (
+
+
+
+
+
+
+
+
+ );
+ }, [componentsProps, data, selected]);
+
+ return Render;
};
export default React.memo(NodePluginConfig);
@@ -116,8 +120,10 @@ function Instruction({ chatConfig: { instruction }, setAppDetail }: ComponentPro
function FileSelectConfig({ chatConfig: { fileSelectConfig }, setAppDetail }: ComponentProps) {
const { t } = useTranslation();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
- const nodes = useContextSelector(WorkflowContext, (v) => v.nodes);
- const pluginInputNode = nodes.find((item) => item.type === FlowNodeTypeEnum.pluginInput)!;
+ const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
+ const pluginInputNode = nodeList.find(
+ (item) => item.flowNodeType === FlowNodeTypeEnum.pluginInput
+ )!;
return (
<>
@@ -137,7 +143,7 @@ function FileSelectConfig({ chatConfig: { fileSelectConfig }, setAppDetail }: Co
// Dynamic add or delete userFilesInput
const canUploadFiles = e.canSelectFile || e.canSelectImg;
- const repeatKey = pluginInputNode?.data.outputs.find(
+ const repeatKey = pluginInputNode?.outputs.find(
(item) => item.key === userFilesInput.key
);
if (canUploadFiles) {
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSystemConfig.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSystemConfig.tsx
index c0d3b06dd..382520547 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSystemConfig.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSystemConfig.tsx
@@ -29,7 +29,8 @@ type ComponentProps = {
};
const NodeUserGuide = ({ data, selected }: NodeProps) => {
- const { appDetail, setAppDetail } = useContextSelector(AppContext, (v) => v);
+ const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
+ const setAppDetail = useContextSelector(AppContext, (v) => v.setAppDetail);
const chatConfig = useMemo(() => {
return getAppChatConfig({
@@ -47,45 +48,49 @@ const NodeUserGuide = ({ data, selected }: NodeProps) => {
[chatConfig, setAppDetail]
);
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
+ const Render = useMemo(() => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }, [componentsProps, data, selected]);
+
+ return Render;
};
export default React.memo(NodeUserGuide);
@@ -218,8 +223,10 @@ function QuestionInputGuide({ chatConfig: { chatInputGuide }, setAppDetail }: Co
function FileSelectConfig({ chatConfig: { fileSelectConfig }, setAppDetail }: ComponentProps) {
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
- const nodes = useContextSelector(WorkflowContext, (v) => v.nodes);
- const workflowStartNode = nodes.find((item) => item.type === FlowNodeTypeEnum.workflowStart)!;
+ const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
+ const workflowStartNode = nodeList.find(
+ (item) => item.flowNodeType === FlowNodeTypeEnum.workflowStart
+ )!;
return (
item.key === userFilesInput.key
);
if (canUploadFiles) {
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx
index 1707c8298..71eb32f9e 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx
@@ -34,6 +34,7 @@ import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { useCreation, useMemoizedFn } from 'ahooks';
import { getEditorVariables } from '../../utils';
import { isArray } from 'lodash';
+import { WorkflowActionContext } from '../../context/workflowInitContext';
const NodeVariableUpdate = ({ data, selected }: NodeProps) => {
const { inputs = [], nodeId } = data;
@@ -42,7 +43,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps) =>
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
- const edges = useContextSelector(WorkflowContext, (v) => v.edges);
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const menuList = useRef([
{
@@ -263,44 +264,48 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps) =>
}
);
- return (
-
-
- <>
- {updateList.map((updateItem, index) => (
-
- ))}
- >
-
- }
- iconSpacing={1}
- w={'full'}
- size={'sm'}
- onClick={() => {
- onUpdateList([
- ...updateList,
- {
- variable: ['', ''],
- value: ['', ''],
- renderType: FlowNodeInputTypeEnum.input
- }
- ]);
- }}
+ const Render = useMemo(() => {
+ return (
+
+
+ <>
+ {updateList.map((updateItem, index) => (
+
+ ))}
+ >
+
- {t('common:common.Add New')}
-
-
-
-
- );
+ }
+ iconSpacing={1}
+ w={'full'}
+ size={'sm'}
+ onClick={() => {
+ onUpdateList([
+ ...updateList,
+ {
+ variable: ['', ''],
+ value: ['', ''],
+ renderType: FlowNodeInputTypeEnum.input
+ }
+ ]);
+ }}
+ >
+ {t('common:common.Add New')}
+
+
+
+
+ );
+ }, [ValueRender, data, onUpdateList, selected, t, updateList]);
+
+ return Render;
};
export default React.memo(NodeVariableUpdate);
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeWorkflowStart.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeWorkflowStart.tsx
index dc3cedf4d..28dfda5ab 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeWorkflowStart.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeWorkflowStart.tsx
@@ -24,8 +24,8 @@ import MyDivider from '@fastgpt/web/components/common/MyDivider';
const NodeStart = ({ data, selected }: NodeProps) => {
const { t } = useTranslation();
const { nodeId, outputs } = data;
+ const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
- const { appDetail } = useContextSelector(AppContext, (v) => v);
const customGlobalVariables = useCreation(() => {
const globalVariables = formatEditorVariablePickerIcon(
@@ -62,34 +62,37 @@ const NodeStart = ({ data, selected }: NodeProps) => {
})),
[t]
);
+ const Render = useMemo(() => {
+ return (
+
+
+
+
+
+
+
+ {customGlobalVariables.length > 0 && (
+ <>
+
+
+ >
+ )}
- return (
-
-
-
-
-
-
-
- {customGlobalVariables.length > 0 && (
- <>
-
-
- >
- )}
+
+
+
+ );
+ }, [customGlobalVariables, data, nodeId, outputs, selected, systemVariables, t]);
-
-
-
- );
+ return Render;
};
export default React.memo(NodeStart);
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx
index 29b96f3c6..39c3907b8 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx
@@ -5,6 +5,7 @@ import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../../context';
+import { WorkflowActionContext } from '../../../../context/workflowInitContext';
export const ConnectionSourceHandle = ({
nodeId,
@@ -13,7 +14,8 @@ export const ConnectionSourceHandle = ({
nodeId: string;
isFoldNode?: boolean;
}) => {
- const { connectingEdge, nodeList, edges } = useContextSelector(WorkflowContext, (ctx) => ctx);
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
+ const { connectingEdge, nodeList } = useContextSelector(WorkflowContext, (ctx) => ctx);
const { showSourceHandle, RightHandle, LeftHandlee, TopHandlee, BottomHandlee } = useMemo(() => {
const node = nodeList.find((node) => node.nodeId === nodeId);
@@ -135,7 +137,8 @@ export const ConnectionTargetHandle = React.memo(function ConnectionTargetHandle
}: {
nodeId: string;
}) {
- const { connectingEdge, nodeList, edges } = useContextSelector(WorkflowContext, (ctx) => ctx);
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
+ const { connectingEdge, nodeList } = useContextSelector(WorkflowContext, (ctx) => ctx);
const { LeftHandle, rightHandle, topHandle, bottomHandle } = useMemo(() => {
const node = nodeList.find((node) => node.nodeId === nodeId);
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ToolHandle.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ToolHandle.tsx
index b5f0fe8b8..9eb2f76a9 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ToolHandle.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ToolHandle.tsx
@@ -6,6 +6,7 @@ import { Connection, Handle, Position } from 'reactflow';
import { useCallback, useMemo } from 'react';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
+import { WorkflowActionContext } from '../../../../context/workflowInitContext';
const handleSize = '16px';
@@ -16,7 +17,7 @@ type ToolHandleProps = BoxProps & {
export const ToolTargetHandle = ({ show, nodeId }: ToolHandleProps) => {
const { t } = useTranslation();
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
- const edges = useContextSelector(WorkflowContext, (v) => v.edges);
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const handleId = NodeOutputKeyEnum.selectedTools;
@@ -64,7 +65,7 @@ export const ToolTargetHandle = ({ show, nodeId }: ToolHandleProps) => {
export const ToolSourceHandle = () => {
const { t } = useTranslation();
- const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
+ const setEdges = useContextSelector(WorkflowActionContext, (v) => v.setEdges);
/* onConnect edge, delete tool input and switch */
const onConnect = useCallback(
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx
index 106084e45..427f68c79 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx
@@ -5,6 +5,11 @@ import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../../context';
import MyIcon from '@fastgpt/web/components/common/Icon';
+import {
+ WorkflowActionContext,
+ WorkflowInitContext
+} from '../../../../context/workflowInitContext';
+import { WorkflowEventContext } from '../../../../context/workflowEventContext';
type Props = {
nodeId: string;
@@ -24,11 +29,10 @@ const MySourceHandle = React.memo(function MySourceHandle({
highlightStyle: Record;
connectedStyle: Record;
}) {
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
- const edges = useContextSelector(WorkflowContext, (v) => v.edges);
-
- const nodes = useContextSelector(WorkflowContext, (v) => v.nodes);
- const hoverNodeId = useContextSelector(WorkflowContext, (v) => v.hoverNodeId);
+ const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
+ const hoverNodeId = useContextSelector(WorkflowEventContext, (v) => v.hoverNodeId);
const node = useMemo(() => nodes.find((node) => node.data.nodeId === nodeId), [nodes, nodeId]);
const connected = edges.some((edge) => edge.sourceHandle === handleId);
@@ -142,7 +146,8 @@ const MyTargetHandle = React.memo(function MyTargetHandle({
highlightStyle: Record;
connectedStyle: Record;
}) {
- const { connectingEdge, edges } = useContextSelector(WorkflowContext, (ctx) => ctx);
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
+ const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
const connected = edges.some((edge) => edge.targetHandle === handleId);
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx
index 987d76a78..04e71ab57 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx
@@ -26,6 +26,8 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useWorkflowUtils } from '../../hooks/useUtils';
import { WholeResponseContent } from '@/components/core/chat/components/WholeResponseModal';
import { getDocPath } from '@/web/common/system/doc';
+import { WorkflowActionContext } from '../../../context/workflowInitContext';
+import { WorkflowEventContext } from '../../../context/workflowEventContext';
type Props = FlowNodeItemType & {
children?: React.ReactNode | React.ReactNode[] | string;
@@ -68,10 +70,10 @@ const NodeCard = (props: Props) => {
customStyle
} = props;
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
- const setHoverNodeId = useContextSelector(WorkflowContext, (v) => v.setHoverNodeId);
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const onResetNode = useContextSelector(WorkflowContext, (v) => v.onResetNode);
+ const setHoverNodeId = useContextSelector(WorkflowEventContext, (v) => v.setHoverNodeId);
// custom title edit
const { onOpenModal: onOpenCustomTitleModal, EditModal: EditTitleModal } = useEditTitle({
@@ -391,7 +393,8 @@ const MenuRender = React.memo(function MenuRender({
const { t } = useTranslation();
const { openDebugNode, DebugInputModal } = useDebug();
- const { setNodes, setEdges, onNodesChange } = useContextSelector(WorkflowContext, (v) => v);
+ const setNodes = useContextSelector(WorkflowActionContext, (v) => v.setNodes);
+ const setEdges = useContextSelector(WorkflowActionContext, (v) => v.setEdges);
const { computedNewNodeName } = useWorkflowUtils();
const onCopyNode = useCallback(
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/Label.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/Label.tsx
index 184dcb8ca..d8fb9eb49 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/Label.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/Label.tsx
@@ -42,58 +42,41 @@ const InputLabel = ({ nodeId, input }: Props) => {
},
[input, nodeId, onChangeNode, renderTypeList]
);
+ const renderType = renderTypeList?.[selectedTypeIndex || 0];
- const RenderLabel = useMemo(() => {
- const renderType = renderTypeList?.[selectedTypeIndex || 0];
-
- return (
-
-
-
- {t(label as any)}
-
- {description && }
-
- {/* value type */}
- {[FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.fileSelect].includes(
- renderType
- ) && }
-
- {/* input type select */}
- {renderTypeList && renderTypeList.length > 1 && (
-
-
-
- )}
-
- {/* Variable picker tip */}
- {input.renderTypeList[input.selectedTypeIndex ?? 0] === FlowNodeInputTypeEnum.textarea && (
- <>
-
-
- >
- )}
+ return (
+
+
+
+ {t(label as any)}
+
+ {description && }
- );
- }, [
- description,
- input.renderTypeList,
- input.selectedTypeIndex,
- label,
- onChangeRenderType,
- renderTypeList,
- required,
- selectedTypeIndex,
- t,
- valueDesc,
- valueType
- ]);
+ {/* value type */}
+ {[FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.fileSelect].includes(renderType) && (
+
+ )}
- return RenderLabel;
+ {/* input type select */}
+ {renderTypeList && renderTypeList.length > 1 && (
+
+
+
+ )}
+
+ {/* Variable picker tip */}
+ {input.renderTypeList[input.selectedTypeIndex ?? 0] === FlowNodeInputTypeEnum.textarea && (
+ <>
+
+
+ >
+ )}
+
+ );
};
export default React.memo(InputLabel);
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx
index 2c95ae45c..bfb860016 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx
@@ -85,54 +85,45 @@ type Props = {
const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) => {
const { feConfigs } = useSystemStore();
- const copyInputs = useMemo(
- () =>
- JSON.stringify(
- flowInputList.filter((input) => {
- if (input.isPro && !feConfigs?.isPlus) return false;
- return true;
- })
- ),
- [feConfigs?.isPlus, flowInputList]
- );
const filterInputs = useMemo(() => {
- return JSON.parse(copyInputs) as FlowNodeInputItemType[];
- }, [copyInputs]);
-
- const memoCustomComponent = useMemo(() => CustomComponent || {}, [CustomComponent]);
-
- const Render = useMemo(() => {
- return filterInputs.map((input) => {
- const renderType = input.renderTypeList?.[input.selectedTypeIndex || 0];
- const isDynamic = !!input.canEdit;
-
- const RenderComponent = (() => {
- if (renderType === FlowNodeInputTypeEnum.custom && memoCustomComponent[input.key]) {
- return <>{memoCustomComponent[input.key]({ ...input })}>;
- }
-
- const Component = RenderList.find((item) => item.types.includes(renderType))?.Component;
-
- if (!Component) return null;
- return ;
- })();
-
- return renderType !== FlowNodeInputTypeEnum.hidden && !isDynamic ? (
-
- {!!input.label && !hideLabelTypeList.includes(renderType) && (
-
- )}
- {!!RenderComponent && (
-
- {RenderComponent}
-
- )}
-
- ) : null;
+ return flowInputList.filter((input) => {
+ if (input.isPro && !feConfigs?.isPlus) return false;
+ return true;
});
- }, [filterInputs, mb, memoCustomComponent, nodeId]);
+ }, [feConfigs?.isPlus, flowInputList]);
- return <>{Render}>;
+ return (
+ <>
+ {filterInputs.map((input) => {
+ const renderType = input.renderTypeList?.[input.selectedTypeIndex || 0];
+ const isDynamic = !!input.canEdit;
+
+ const RenderComponent = (() => {
+ if (renderType === FlowNodeInputTypeEnum.custom && CustomComponent?.[input.key]) {
+ return <>{CustomComponent?.[input.key]({ ...input })}>;
+ }
+
+ const Component = RenderList.find((item) => item.types.includes(renderType))?.Component;
+
+ if (!Component) return null;
+ return ;
+ })();
+
+ return renderType !== FlowNodeInputTypeEnum.hidden && !isDynamic ? (
+
+ {!!input.label && !hideLabelTypeList.includes(renderType) && (
+
+ )}
+ {!!RenderComponent && (
+
+ {RenderComponent}
+
+ )}
+
+ ) : null;
+ })}
+ >
+ );
};
export default React.memo(RenderInput);
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx
index 21f4ac0f1..43e703fa4 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx
@@ -50,25 +50,21 @@ const JsonEditor = ({ inputs = [], item, nodeId }: RenderInputProps) => {
}
return JSON.stringify(item.value, null, 2);
}, [item.value]);
-
- const Render = useMemo(() => {
- return (
- {
- update(e);
- }}
- variables={variables}
- />
- );
- }, [item.placeholder, t, update, value, variables]);
-
- return Render;
+ console.log(12121);
+ return (
+ {
+ update(e);
+ }}
+ variables={variables}
+ />
+ );
};
export default React.memo(JsonEditor);
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx
index c07f2857a..f6e5389e6 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx
@@ -18,6 +18,7 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { AppContext } from '@/pages/app/detail/components/context';
+import { WorkflowActionContext } from '../../../../../context/workflowInitContext';
const MultipleRowSelect = dynamic(() =>
import('@fastgpt/web/components/common/MySelect/MultipleRowSelect').then(
@@ -59,8 +60,9 @@ export const useReference = ({
valueType?: WorkflowIOValueTypeEnum;
}) => {
const { t } = useTranslation();
- const { appDetail } = useContextSelector(AppContext, (v) => v);
- const { nodeList, edges } = useContextSelector(WorkflowContext, (v) => v);
+ const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
+ const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
// 获取可选的变量列表
const referenceList = useMemo(() => {
@@ -319,7 +321,7 @@ const MultipleReferenceSelector = ({
popDirection={popDirection}
/>
);
- }, [getSelectValue, list, onSelect, placeholder, popDirection, t, value]);
+ }, [getSelectValue, list, onSelect, placeholder, popDirection, value]);
return ArraySelector;
};
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/TextInput.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/TextInput.tsx
index 2aeb00e58..b612512be 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/TextInput.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/TextInput.tsx
@@ -7,12 +7,14 @@ import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponent
import { useCreation } from 'ahooks';
import { AppContext } from '@/pages/app/detail/components/context';
import { getEditorVariables } from '../../../../../utils';
+import { WorkflowActionContext } from '../../../../../context/workflowInitContext';
const TextInputRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
const { t } = useTranslation();
- const { nodeList, edges, onChangeNode } = useContextSelector(WorkflowContext, (v) => v);
-
- const { appDetail } = useContextSelector(AppContext, (v) => v);
+ const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
+ const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
+ const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
// get variable
const variables = useCreation(() => {
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx
index 102f9908a..37dc6e481 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx
@@ -7,11 +7,12 @@ import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponent
import { useCreation } from 'ahooks';
import { AppContext } from '@/pages/app/detail/components/context';
import { getEditorVariables } from '../../../../../utils';
+import { WorkflowActionContext } from '../../../../../context/workflowInitContext';
const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
const { t } = useTranslation();
+ const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
- const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { appDetail } = useContextSelector(AppContext, (v) => v);
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderOutput/Label.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderOutput/Label.tsx
index d22421967..d2929bc73 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderOutput/Label.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderOutput/Label.tsx
@@ -13,44 +13,40 @@ const OutputLabel = ({ nodeId, output }: { nodeId: string; output: FlowNodeOutpu
const { t } = useTranslation();
const { label = '', description, valueType, valueDesc } = output;
- const Render = useMemo(() => {
- return (
-
-
+
+
-
- {t(label as any)}
-
- {description && }
-
-
- {output.type === FlowNodeOutputTypeEnum.source && (
-
- )}
-
- );
- }, [output.type, output.key, t, label, description, valueType, valueDesc, nodeId]);
-
- return Render;
+ {t(label as any)}
+
+ {description && }
+
+
+ {output.type === FlowNodeOutputTypeEnum.source && (
+
+ )}
+
+ );
};
export default React.memo(OutputLabel);
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/constants.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/constants.tsx
index cbeedb840..47c9315a3 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/constants.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/constants.tsx
@@ -2,6 +2,9 @@ import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import React from 'react';
import { DefaultEdgeOptions } from 'reactflow';
+export const minZoom = 0.1;
+export const maxZoom = 1.5;
+
export const connectionLineStyle: React.CSSProperties = {
strokeWidth: 2,
stroke: '#487FFF'
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/context/index.tsx
similarity index 78%
rename from projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx
rename to projects/app/src/pages/app/detail/components/WorkflowComponents/context/index.tsx
index 5f2998081..a6cf006f2 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/context/index.tsx
@@ -15,7 +15,7 @@ import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/wor
import { FlowNodeChangeProps } from '@fastgpt/global/core/workflow/type/fe';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import { useToast } from '@fastgpt/web/hooks/useToast';
-import { useDebounceEffect, useLocalStorageState, useMemoizedFn, useUpdateEffect } from 'ahooks';
+import { useLocalStorageState, useMemoizedFn, useUpdateEffect } from 'ahooks';
import React, {
Dispatch,
SetStateAction,
@@ -25,31 +25,40 @@ import React, {
useRef,
useState
} from 'react';
-import {
- Edge,
- EdgeChange,
- Node,
- NodeChange,
- OnConnectStartParams,
- useEdgesState,
- useNodesState,
- useReactFlow
-} from 'reactflow';
+import { Edge, Node, OnConnectStartParams, ReactFlowProvider, useReactFlow } from 'reactflow';
import { createContext, useContextSelector } from 'use-context-selector';
-import { defaultRunningStatus } from './constants';
+import { defaultRunningStatus } from '../constants';
import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils';
-import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
import { AppContext } from '@/pages/app/detail/components/context';
-import ChatTest from './Flow/ChatTest';
+import ChatTest from '../Flow/ChatTest';
import { useDisclosure } from '@chakra-ui/react';
-import { uiWorkflow2StoreWorkflow } from './utils';
+import { uiWorkflow2StoreWorkflow } from '../utils';
import { useTranslation } from 'next-i18next';
import { formatTime2YMDHMS, formatTime2YMDHMW } from '@fastgpt/global/common/string/time';
import { cloneDeep } from 'lodash';
-import { SetState } from 'ahooks/lib/createUseStorageState';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
+import WorkflowInitContextProvider, { WorkflowActionContext } from './workflowInitContext';
+import WorkflowEventContextProvider from './workflowEventContext';
+
+export const ReactFlowCustomProvider = ({
+ templates,
+ children
+}: {
+ templates: FlowNodeTemplateType[];
+ children: React.ReactNode;
+}) => {
+ return (
+
+
+
+ {children}
+
+
+
+ );
+};
type OnChange = (changes: ChangesType[]) => void;
@@ -65,33 +74,22 @@ type WorkflowContextType = {
appId?: string;
basicNodeTemplates: FlowNodeTemplateType[];
filterAppIds?: string[];
- reactFlowWrapper: React.RefObject | null;
- mouseInCanvas: boolean;
// nodes
- nodes: Node[];
nodeList: FlowNodeItemType[];
- setNodes: Dispatch[]>>;
- onNodesChange: OnChange;
hasToolNode: boolean;
- hoverNodeId?: string;
- setHoverNodeId: React.Dispatch>;
+
onUpdateNodeError: (node: string, isError: Boolean) => void;
onResetNode: (e: { id: string; node: FlowNodeTemplateType }) => void;
onChangeNode: (e: FlowNodeChangeProps) => void;
getNodeDynamicInputs: (nodeId: string) => FlowNodeInputItemType[];
// edges
- edges: Edge[];
- setEdges: Dispatch[]>>;
- onEdgesChange: OnChange;
onDelEdge: (e: {
nodeId: string;
sourceHandle?: string | undefined;
targetHandle?: string | undefined;
}) => void;
- hoverEdgeId?: string;
- setHoverEdgeId: React.Dispatch>;
onSwitchTmpVersion: (data: WorkflowSnapshotsType, customTitle: string) => boolean;
onSwitchCloudVersion: (appVersion: AppVersionSchemaType) => boolean;
@@ -102,6 +100,19 @@ type WorkflowContextType = {
undo: () => void;
canRedo: boolean;
canUndo: boolean;
+ pushPastSnapshot: ({
+ pastNodes,
+ pastEdges,
+ customTitle,
+ chatConfig,
+ isSaved
+ }: {
+ pastNodes: Node[];
+ pastEdges: Edge[];
+ customTitle?: string;
+ chatConfig: AppChatConfigType;
+ isSaved?: boolean;
+ }) => boolean;
// connect
connectingEdge?: OnConnectStartParams;
@@ -159,10 +170,6 @@ type WorkflowContextType = {
}) => Promise;
onStopNodeDebug: () => void;
- // version history
- showHistoryModal: boolean;
- setShowHistoryModal: React.Dispatch>;
-
// chat test
setWorkflowTestData: React.Dispatch<
React.SetStateAction<
@@ -173,15 +180,6 @@ type WorkflowContextType = {
| undefined
>
>;
-
- //
- workflowControlMode?: 'drag' | 'select';
- setWorkflowControlMode: (value?: SetState<'drag' | 'select'> | undefined) => void;
- menu: {
- top: number;
- left: number;
- } | null;
- setMenu: (value: React.SetStateAction<{ top: number; left: number } | null>) => void;
};
type DebugDataType = {
@@ -198,32 +196,11 @@ export const WorkflowContext = createContext({
throw new Error('Function not implemented.');
},
basicNodeTemplates: [],
- reactFlowWrapper: null,
- nodes: [],
nodeList: [],
- mouseInCanvas: false,
- setNodes: function (
- value: React.SetStateAction[]>
- ): void {
- throw new Error('Function not implemented.');
- },
- onNodesChange: function (changes: NodeChange[]): void {
- throw new Error('Function not implemented.');
- },
hasToolNode: false,
- setHoverNodeId: function (value: React.SetStateAction): void {
- throw new Error('Function not implemented.');
- },
onUpdateNodeError: function (node: string, isError: Boolean): void {
throw new Error('Function not implemented.');
},
- edges: [],
- setEdges: function (value: React.SetStateAction[]>): void {
- throw new Error('Function not implemented.');
- },
- onEdgesChange: function (changes: EdgeChange[]): void {
- throw new Error('Function not implemented.');
- },
onResetNode: function (e: { id: string; node: FlowNodeTemplateType }): void {
throw new Error('Function not implemented.');
},
@@ -272,9 +249,6 @@ export const WorkflowContext = createContext({
onChangeNode: function (e: FlowNodeChangeProps): void {
throw new Error('Function not implemented.');
},
- setHoverEdgeId: function (value: React.SetStateAction): void {
- throw new Error('Function not implemented.');
- },
setWorkflowTestData: function (
value: React.SetStateAction<
{ nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] } | undefined
@@ -292,10 +266,6 @@ export const WorkflowContext = createContext({
| undefined {
throw new Error('Function not implemented.');
},
- showHistoryModal: false,
- setShowHistoryModal: function (value: React.SetStateAction): void {
- throw new Error('Function not implemented.');
- },
getNodeDynamicInputs: function (nodeId: string): FlowNodeInputItemType[] {
throw new Error('Function not implemented.');
},
@@ -312,18 +282,27 @@ export const WorkflowContext = createContext({
},
canRedo: false,
canUndo: false,
- workflowControlMode: 'drag',
- setWorkflowControlMode: function (value?: SetState<'drag' | 'select'> | undefined): void {
- throw new Error('Function not implemented.');
- },
+
onSwitchTmpVersion: function (data: WorkflowSnapshotsType, customTitle: string): boolean {
throw new Error('Function not implemented.');
},
onSwitchCloudVersion: function (appVersion: AppVersionSchemaType): boolean {
throw new Error('Function not implemented.');
},
- menu: null,
- setMenu: function (value: React.SetStateAction<{ top: number; left: number } | null>): void {
+
+ pushPastSnapshot: function ({
+ pastNodes,
+ pastEdges,
+ customTitle,
+ chatConfig,
+ isSaved
+ }: {
+ pastNodes: Node[];
+ pastEdges: Edge[];
+ customTitle?: string;
+ chatConfig: AppChatConfigType;
+ isSaved?: boolean;
+ }): boolean {
throw new Error('Function not implemented.');
}
});
@@ -337,39 +316,14 @@ const WorkflowContextProvider = ({
}) => {
const { t } = useTranslation();
const { toast } = useToast();
- const reactFlowWrapper = useRef(null);
- const { appDetail, setAppDetail } = useContextSelector(AppContext, (v) => v);
+ const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
+ const setAppDetail = useContextSelector(AppContext, (v) => v.setAppDetail);
const appId = appDetail._id;
- const [workflowControlMode, setWorkflowControlMode] = useLocalStorageState<'drag' | 'select'>(
- 'workflow-control-mode',
- {
- defaultValue: 'drag',
- listenStorageChange: true
- }
- );
-
- // Mouse in canvas
- const [mouseInCanvas, setMouseInCanvas] = useState(false);
- useEffect(() => {
- const handleMouseInCanvas = (e: MouseEvent) => {
- setMouseInCanvas(true);
- };
- const handleMouseOutCanvas = (e: MouseEvent) => {
- setMouseInCanvas(false);
- };
- reactFlowWrapper?.current?.addEventListener('mouseenter', handleMouseInCanvas);
- reactFlowWrapper?.current?.addEventListener('mouseleave', handleMouseOutCanvas);
- return () => {
- reactFlowWrapper?.current?.removeEventListener('mouseenter', handleMouseInCanvas);
- reactFlowWrapper?.current?.removeEventListener('mouseleave', handleMouseOutCanvas);
- };
- }, [reactFlowWrapper?.current]);
-
/* edge */
- const [edges, setEdges, onEdgesChange] = useEdgesState([]);
- const [hoverEdgeId, setHoverEdgeId] = useState();
+ const edges = useContextSelector(WorkflowActionContext, (state) => state.edges);
+ const setEdges = useContextSelector(WorkflowActionContext, (state) => state.setEdges);
const onDelEdge = useCallback(
({
nodeId,
@@ -397,32 +351,16 @@ const WorkflowContextProvider = ({
const [connectingEdge, setConnectingEdge] = useState();
/* node */
- const [nodes = [], setNodes, onNodesChange] = useNodesState([]);
- const [hoverNodeId, setHoverNodeId] = useState();
+ const setNodes = useContextSelector(WorkflowActionContext, (state) => state.setNodes);
+ const getNodes = useContextSelector(WorkflowActionContext, (state) => state.getNodes);
+ const nodeListString = useContextSelector(WorkflowActionContext, (state) => state.nodeListString);
- const nodeListString = JSON.stringify(nodes.map((node) => node.data));
+ console.log(121211111111);
const nodeList = useMemo(
() => JSON.parse(nodeListString) as FlowNodeItemType[],
[nodeListString]
);
- // Elevate childNodes
- useEffect(() => {
- setNodes((nodes) =>
- nodes.map((node) => (node.data.parentNodeId ? { ...node, zIndex: 1001 } : node))
- );
- }, [nodeList]);
- // Elevate edges of childNodes
- useEffect(() => {
- setEdges((state) =>
- state.map((item) =>
- nodeList.some((node) => item.source === node.nodeId && node.parentNodeId)
- ? { ...item, zIndex: 1001 }
- : item
- )
- );
- }, [edges.length]);
-
const hasToolNode = useMemo(() => {
return !!nodeList.find((node) => node.flowNodeType === FlowNodeTypeEnum.tools);
}, [nodeList]);
@@ -571,6 +509,7 @@ const WorkflowContextProvider = ({
/* ui flow to store data */
const { fitView } = useReactFlow();
const flowData2StoreDataAndCheck = useMemoizedFn((hideTip = false) => {
+ const nodes = getNodes();
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
if (!checkResults) {
@@ -593,6 +532,7 @@ const WorkflowContextProvider = ({
});
const flowData2StoreData = useMemoizedFn(() => {
+ const nodes = getNodes();
return uiWorkflow2StoreWorkflow({ nodes, edges });
});
@@ -892,22 +832,6 @@ const WorkflowContextProvider = ({
});
});
- // Auto save snapshot
- useDebounceEffect(
- () => {
- if (nodes.length === 0 || !appDetail.chatConfig) return;
-
- pushPastSnapshot({
- pastNodes: nodes,
- pastEdges: edges,
- customTitle: formatTime2YMDHMS(new Date()),
- chatConfig: appDetail.chatConfig
- });
- },
- [nodes, edges, appDetail.chatConfig],
- { wait: 500 }
- );
-
const undo = useMemoizedFn(() => {
if (past[1]) {
setFuture((future) => [past[0], ...future]);
@@ -978,88 +902,80 @@ const WorkflowContextProvider = ({
]
);
- /* Version histories */
- const [showHistoryModal, setShowHistoryModal] = useState(false);
+ const value = useMemo(
+ () => ({
+ appId,
+ basicNodeTemplates,
- /* event bus */
- useEffect(() => {
- eventBus.on(EventNameEnum.requestWorkflowStore, () => {
- eventBus.emit(EventNameEnum.receiveWorkflowStore, {
- nodes,
- edges
- });
- });
- return () => {
- eventBus.off(EventNameEnum.requestWorkflowStore);
- };
- }, [edges, nodes]);
+ // node
+ nodeList,
+ hasToolNode,
+ onUpdateNodeError,
+ onResetNode,
+ onChangeNode,
+ getNodeDynamicInputs,
- const [menu, setMenu] = useState<{ top: number; left: number } | null>(null);
+ // edge
+ connectingEdge,
+ setConnectingEdge,
+ onDelEdge,
- const value = {
- appId,
- reactFlowWrapper,
- basicNodeTemplates,
- workflowControlMode,
- setWorkflowControlMode,
- mouseInCanvas,
+ // snapshots
+ past,
+ setPast,
+ future,
+ undo,
+ redo,
+ canUndo: past.length > 1,
+ canRedo: !!future.length,
+ onSwitchTmpVersion,
+ onSwitchCloudVersion,
+ pushPastSnapshot,
- // node
- nodes,
- setNodes,
- onNodesChange,
- nodeList,
- hasToolNode,
- hoverNodeId,
- setHoverNodeId,
- onUpdateNodeError,
- onResetNode,
- onChangeNode,
- getNodeDynamicInputs,
+ // function
+ splitToolInputs,
+ initData,
+ flowData2StoreDataAndCheck,
+ flowData2StoreData,
- // edge
- edges,
- setEdges,
- hoverEdgeId,
- setHoverEdgeId,
- onEdgesChange,
- connectingEdge,
- setConnectingEdge,
- onDelEdge,
+ // debug
+ workflowDebugData,
+ onNextNodeDebug,
+ onStartNodeDebug,
+ onStopNodeDebug,
- // snapshots
- past,
- setPast,
- future,
- undo,
- redo,
- canUndo: past.length > 1,
- canRedo: !!future.length,
- onSwitchTmpVersion,
- onSwitchCloudVersion,
-
- // function
- splitToolInputs,
- initData,
- flowData2StoreDataAndCheck,
- flowData2StoreData,
-
- // debug
- workflowDebugData,
- onNextNodeDebug,
- onStartNodeDebug,
- onStopNodeDebug,
-
- // version history
- showHistoryModal,
- setShowHistoryModal,
-
- // chat test
- setWorkflowTestData,
-
- menu,
- setMenu
- };
+ // chat test
+ setWorkflowTestData
+ }),
+ [
+ appId,
+ basicNodeTemplates,
+ connectingEdge,
+ flowData2StoreData,
+ flowData2StoreDataAndCheck,
+ future,
+ getNodeDynamicInputs,
+ hasToolNode,
+ initData,
+ nodeList,
+ onChangeNode,
+ onDelEdge,
+ onNextNodeDebug,
+ onResetNode,
+ onStartNodeDebug,
+ onStopNodeDebug,
+ onSwitchCloudVersion,
+ onSwitchTmpVersion,
+ onUpdateNodeError,
+ past,
+ pushPastSnapshot,
+ redo,
+ setPast,
+ splitToolInputs,
+ undo,
+ workflowDebugData
+ ]
+ );
return (
@@ -1068,18 +984,4 @@ const WorkflowContextProvider = ({
);
};
-
-export default WorkflowContextProvider;
-
-type GetWorkflowStoreResponse = {
- nodes: Node[];
- edges: Edge[];
-};
-export const getWorkflowStore = () =>
- new Promise((resolve) => {
- eventBus.on(EventNameEnum.receiveWorkflowStore, (data: GetWorkflowStoreResponse) => {
- resolve(data);
- eventBus.off(EventNameEnum.receiveWorkflowStore);
- });
- eventBus.emit(EventNameEnum.requestWorkflowStore);
- });
+export default React.memo(WorkflowContextProvider);
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/context/workflowEventContext.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/context/workflowEventContext.tsx
new file mode 100644
index 000000000..8073b11be
--- /dev/null
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/context/workflowEventContext.tsx
@@ -0,0 +1,119 @@
+import React, { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
+import { createContext } from 'use-context-selector';
+import { useLocalStorageState } from 'ahooks';
+import { SetState } from 'ahooks/lib/createUseStorageState';
+
+type WorkflowEventContextType = {
+ mouseInCanvas: boolean;
+ reactFlowWrapper: React.RefObject | null;
+ hoverNodeId?: string;
+ setHoverNodeId: React.Dispatch>;
+ hoverEdgeId?: string;
+ setHoverEdgeId: React.Dispatch>;
+ workflowControlMode?: 'drag' | 'select';
+ setWorkflowControlMode: (value?: SetState<'drag' | 'select'> | undefined) => void;
+ menu: {
+ top: number;
+ left: number;
+ } | null;
+ setMenu: (value: React.SetStateAction<{ top: number; left: number } | null>) => void;
+ // version history
+ showHistoryModal: boolean;
+ setShowHistoryModal: React.Dispatch>;
+};
+
+export const WorkflowEventContext = createContext({
+ mouseInCanvas: false,
+ reactFlowWrapper: null,
+ setHoverNodeId: function (value: React.SetStateAction): void {
+ throw new Error('Function not implemented.');
+ },
+ setHoverEdgeId: function (value: React.SetStateAction): void {
+ throw new Error('Function not implemented.');
+ },
+ workflowControlMode: 'drag',
+ setWorkflowControlMode: function (value?: SetState<'drag' | 'select'> | undefined): void {
+ throw new Error('Function not implemented.');
+ },
+ menu: null,
+ setMenu: function (value: React.SetStateAction<{ top: number; left: number } | null>): void {
+ throw new Error('Function not implemented.');
+ },
+ showHistoryModal: false,
+ setShowHistoryModal: function (value: React.SetStateAction): void {
+ throw new Error('Function not implemented.');
+ }
+});
+
+const WorkflowEventContextProvider = ({ children }: { children: ReactNode }) => {
+ // Watch mouse in canvas
+ const reactFlowWrapper = useRef(null);
+ const [mouseInCanvas, setMouseInCanvas] = useState(false);
+ useEffect(() => {
+ const handleMouseInCanvas = (e: MouseEvent) => {
+ setMouseInCanvas(true);
+ };
+ const handleMouseOutCanvas = (e: MouseEvent) => {
+ setMouseInCanvas(false);
+ };
+ reactFlowWrapper?.current?.addEventListener('mouseenter', handleMouseInCanvas);
+ reactFlowWrapper?.current?.addEventListener('mouseleave', handleMouseOutCanvas);
+ return () => {
+ reactFlowWrapper?.current?.removeEventListener('mouseenter', handleMouseInCanvas);
+ reactFlowWrapper?.current?.removeEventListener('mouseleave', handleMouseOutCanvas);
+ };
+ }, [reactFlowWrapper?.current, setMouseInCanvas]);
+
+ // Watch hover node
+ const [hoverNodeId, setHoverNodeId] = useState();
+ // Watch hover edge
+ const [hoverEdgeId, setHoverEdgeId] = useState();
+
+ const [workflowControlMode, setWorkflowControlMode] = useLocalStorageState<'drag' | 'select'>(
+ 'workflow-control-mode',
+ {
+ defaultValue: 'drag',
+ listenStorageChange: true
+ }
+ );
+
+ const [menu, setMenu] = useState<{ top: number; left: number } | null>(null);
+
+ /* Version histories */
+ const [showHistoryModal, setShowHistoryModal] = useState(false);
+
+ const contextValue = useMemo(
+ () => ({
+ mouseInCanvas,
+ reactFlowWrapper,
+ hoverNodeId,
+ setHoverNodeId,
+ hoverEdgeId,
+ setHoverEdgeId,
+ workflowControlMode,
+ setWorkflowControlMode,
+ menu,
+ setMenu,
+ showHistoryModal,
+ setShowHistoryModal
+ }),
+ [
+ mouseInCanvas,
+ hoverNodeId,
+ setHoverNodeId,
+ hoverEdgeId,
+ setHoverEdgeId,
+ workflowControlMode,
+ setWorkflowControlMode,
+ menu,
+ setMenu,
+ showHistoryModal,
+ setShowHistoryModal
+ ]
+ );
+ return (
+ {children}
+ );
+};
+
+export default WorkflowEventContextProvider;
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/context/workflowInitContext.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/context/workflowInitContext.tsx
new file mode 100644
index 000000000..56019632d
--- /dev/null
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/context/workflowInitContext.tsx
@@ -0,0 +1,138 @@
+import { createContext } from 'use-context-selector';
+import { postWorkflowDebug } from '@/web/core/workflow/api';
+import {
+ checkWorkflowNodeAndConnection,
+ compareSnapshot,
+ storeEdgesRenderEdge,
+ storeNode2FlowNode
+} from '@/web/core/workflow/utils';
+import { getErrText } from '@fastgpt/global/common/error/utils';
+import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
+import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
+import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
+import { FlowNodeItemType, StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
+import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
+import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
+import { FlowNodeChangeProps } from '@fastgpt/global/core/workflow/type/fe';
+import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
+import { useToast } from '@fastgpt/web/hooks/useToast';
+import { useDebounceEffect, useLocalStorageState, useMemoizedFn, useUpdateEffect } from 'ahooks';
+import React, {
+ Dispatch,
+ SetStateAction,
+ ReactNode,
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState
+} from 'react';
+import {
+ Edge,
+ EdgeChange,
+ Node,
+ NodeChange,
+ OnConnectStartParams,
+ useEdgesState,
+ useNodesState,
+ useReactFlow
+} from 'reactflow';
+
+type OnChange = (changes: ChangesType[]) => void;
+
+type WorkflowInitContextType = {
+ nodes: Node[];
+};
+export const WorkflowInitContext = createContext({
+ nodes: []
+});
+
+type WorkflowActionContextType = {
+ setNodes: Dispatch[]>>;
+ onNodesChange: OnChange;
+ getNodes: () => Node[];
+ nodeListString: string;
+ edges: Edge[];
+ setEdges: Dispatch[]>>;
+ onEdgesChange: OnChange;
+};
+export const WorkflowActionContext = createContext({
+ setNodes: function (
+ value: React.SetStateAction[]>
+ ): void {
+ throw new Error('Function not implemented.');
+ },
+ onNodesChange: function (changes: NodeChange[]): void {
+ throw new Error('Function not implemented.');
+ },
+ getNodes: function (): Node[] {
+ throw new Error('Function not implemented.');
+ },
+ nodeListString: JSON.stringify([]),
+ edges: [],
+ setEdges: function (value: React.SetStateAction[]>): void {
+ throw new Error('Function not implemented.');
+ },
+ onEdgesChange: function (changes: EdgeChange[]): void {
+ throw new Error('Function not implemented.');
+ }
+});
+
+const WorkflowInitContextProvider = ({ children }: { children: ReactNode }) => {
+ // Nodes
+ const [nodes = [], setNodes, onNodesChange] = useNodesState([]);
+ const getNodes = useMemoizedFn(() => nodes);
+ const nodeListString = JSON.stringify(nodes.map((node) => node.data));
+ const nodeList = useMemo(
+ () => JSON.parse(nodeListString) as FlowNodeItemType[],
+ [nodeListString]
+ );
+
+ // Edges
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
+
+ // Elevate childNodes
+ useEffect(() => {
+ setNodes((nodes) =>
+ nodes.map((node) => (node.data.parentNodeId ? { ...node, zIndex: 1001 } : node))
+ );
+ }, [nodeList]);
+ // Elevate edges of childNodes
+ useEffect(() => {
+ setEdges((state) =>
+ state.map((item) =>
+ nodeList.some((node) => item.source === node.nodeId && node.parentNodeId)
+ ? { ...item, zIndex: 1001 }
+ : item
+ )
+ );
+ }, [edges.length]);
+
+ const actionContextValue = useMemo(
+ () => ({
+ setNodes,
+ onNodesChange,
+ getNodes,
+ nodeListString,
+
+ edges,
+ setEdges,
+ onEdgesChange
+ }),
+ [setNodes, onNodesChange, getNodes, nodeListString, edges, setEdges, onEdgesChange]
+ );
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export default WorkflowInitContextProvider;
diff --git a/projects/app/src/pages/app/detail/components/context.tsx b/projects/app/src/pages/app/detail/components/context.tsx
index a25632fc3..40aeb7a94 100644
--- a/projects/app/src/pages/app/detail/components/context.tsx
+++ b/projects/app/src/pages/app/detail/components/context.tsx
@@ -1,4 +1,4 @@
-import { Dispatch, ReactNode, SetStateAction, useCallback, useState } from 'react';
+import { Dispatch, ReactNode, SetStateAction, useCallback, useMemo, useState } from 'react';
import { createContext } from 'use-context-selector';
import { defaultApp } from '@/web/core/app/constants';
import { delAppById, getAppDetailById, putAppById } from '@/web/core/app/api';
@@ -186,22 +186,39 @@ const AppContextProvider = ({ children }: { children: ReactNode }) => {
[appDetail.name, deleteApp, openConfirmDel, t]
);
- const contextValue: AppContextType = {
- appId,
- currentTab,
- route2Tab,
- appDetail,
- setAppDetail,
- loadingApp,
- updateAppDetail,
- onOpenInfoEdit,
- onOpenTeamTagModal,
- onDelApp,
- onSaveApp,
- appLatestVersion,
- reloadAppLatestVersion,
- reloadApp
- };
+ const contextValue: AppContextType = useMemo(
+ () => ({
+ appId,
+ currentTab,
+ route2Tab,
+ appDetail,
+ setAppDetail,
+ loadingApp,
+ updateAppDetail,
+ onOpenInfoEdit,
+ onOpenTeamTagModal,
+ onDelApp,
+ onSaveApp,
+ appLatestVersion,
+ reloadAppLatestVersion,
+ reloadApp
+ }),
+ [
+ appDetail,
+ appId,
+ appLatestVersion,
+ currentTab,
+ loadingApp,
+ onDelApp,
+ onOpenInfoEdit,
+ onOpenTeamTagModal,
+ onSaveApp,
+ reloadApp,
+ reloadAppLatestVersion,
+ route2Tab,
+ updateAppDetail
+ ]
+ );
return (
diff --git a/projects/app/src/web/common/utils/eventbus.ts b/projects/app/src/web/common/utils/eventbus.ts
index 7bb7fe22f..8f21c04e8 100644
--- a/projects/app/src/web/common/utils/eventbus.ts
+++ b/projects/app/src/web/common/utils/eventbus.ts
@@ -1,9 +1,6 @@
export enum EventNameEnum {
sendQuestion = 'sendQuestion',
- editQuestion = 'editQuestion',
-
- requestWorkflowStore = 'requestWorkflowStore',
- receiveWorkflowStore = 'receiveWorkflowStore'
+ editQuestion = 'editQuestion'
}
export const eventBus = {