| @@ -402,3 +402,5 @@ export const TOOL_OUTPUT_STRUCT: Var[] = [ | |||
| type: VarType.arrayFile, | |||
| }, | |||
| ] | |||
| export const WORKFLOW_DATA_UPDATE = 'WORKFLOW_DATA_UPDATE' | |||
| @@ -73,6 +73,7 @@ const Header: FC = () => { | |||
| const handleRestore = useCallback(() => { | |||
| workflowStore.setState({ isRestoring: false }) | |||
| workflowStore.setState({ backupDraft: undefined }) | |||
| handleSyncWorkflowDraft(true) | |||
| }, [handleSyncWorkflowDraft, workflowStore]) | |||
| @@ -19,6 +19,7 @@ import type { | |||
| Viewport, | |||
| } from 'reactflow' | |||
| import { | |||
| changeNodesAndEdgesId, | |||
| getLayoutByDagre, | |||
| initialEdges, | |||
| initialNodes, | |||
| @@ -39,6 +40,7 @@ import { | |||
| import { | |||
| AUTO_LAYOUT_OFFSET, | |||
| SUPPORT_OUTPUT_VARS_NODE, | |||
| WORKFLOW_DATA_UPDATE, | |||
| } from '../constants' | |||
| import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils' | |||
| import { useNodesExtraData } from './use-nodes-data' | |||
| @@ -56,6 +58,8 @@ import { | |||
| fetchAllCustomTools, | |||
| } from '@/service/tools' | |||
| import I18n from '@/context/i18n' | |||
| import { useEventEmitterContextContext } from '@/context/event-emitter' | |||
| export const useIsChatMode = () => { | |||
| const appDetail = useAppStore(s => s.appDetail) | |||
| @@ -69,6 +73,7 @@ export const useWorkflow = () => { | |||
| const workflowStore = useWorkflowStore() | |||
| const nodesExtraData = useNodesExtraData() | |||
| const { handleSyncWorkflowDraft } = useNodesSyncDraft() | |||
| const { eventEmitter } = useEventEmitterContextContext() | |||
| const handleLayout = useCallback(async () => { | |||
| workflowStore.setState({ nodeAnimation: true }) | |||
| @@ -314,15 +319,21 @@ export const useWorkflow = () => { | |||
| }, [locale]) | |||
| const renderTreeFromRecord = useCallback((nodes: Node[], edges: Edge[], viewport?: Viewport) => { | |||
| const { setNodes } = store.getState() | |||
| const { setViewport, setEdges } = reactflow | |||
| const { setViewport } = reactflow | |||
| const [newNodes, newEdges] = changeNodesAndEdgesId(nodes, edges) | |||
| setNodes(initialNodes(nodes, edges)) | |||
| setEdges(initialEdges(edges, nodes)) | |||
| eventEmitter?.emit({ | |||
| type: WORKFLOW_DATA_UPDATE, | |||
| payload: { | |||
| nodes: initialNodes(newNodes, newEdges), | |||
| edges: initialEdges(newEdges, newNodes), | |||
| }, | |||
| } as any) | |||
| if (viewport) | |||
| setViewport(viewport) | |||
| }, [store, reactflow]) | |||
| }, [reactflow, eventEmitter]) | |||
| const getNode = useCallback((nodeId?: string) => { | |||
| const { getNodes } = store.getState() | |||
| @@ -14,6 +14,8 @@ import { | |||
| import ReactFlow, { | |||
| Background, | |||
| ReactFlowProvider, | |||
| useEdgesState, | |||
| useNodesState, | |||
| useOnViewportChange, | |||
| } from 'reactflow' | |||
| import type { Viewport } from 'reactflow' | |||
| @@ -46,9 +48,11 @@ import { | |||
| initialEdges, | |||
| initialNodes, | |||
| } from './utils' | |||
| import { WORKFLOW_DATA_UPDATE } from './constants' | |||
| import Loading from '@/app/components/base/loading' | |||
| import { FeaturesProvider } from '@/app/components/base/features' | |||
| import type { Features as FeaturesData } from '@/app/components/base/features/types' | |||
| import { useEventEmitterContextContext } from '@/context/event-emitter' | |||
| const nodeTypes = { | |||
| custom: CustomNode, | |||
| @@ -63,10 +67,12 @@ type WorkflowProps = { | |||
| viewport?: Viewport | |||
| } | |||
| const Workflow: FC<WorkflowProps> = memo(({ | |||
| nodes, | |||
| edges, | |||
| nodes: originalNodes, | |||
| edges: originalEdges, | |||
| viewport, | |||
| }) => { | |||
| const [nodes, setNodes] = useNodesState(originalNodes) | |||
| const [edges, setEdges] = useEdgesState(originalEdges) | |||
| const showFeaturesPanel = useStore(state => state.showFeaturesPanel) | |||
| const nodeAnimation = useStore(s => s.nodeAnimation) | |||
| const { | |||
| @@ -76,6 +82,15 @@ const Workflow: FC<WorkflowProps> = memo(({ | |||
| const { workflowReadOnly } = useWorkflowReadOnly() | |||
| const { nodesReadOnly } = useNodesReadOnly() | |||
| const { eventEmitter } = useEventEmitterContextContext() | |||
| eventEmitter?.useSubscription((v: any) => { | |||
| if (v.type === WORKFLOW_DATA_UPDATE) { | |||
| setNodes(v.payload.nodes) | |||
| setEdges(v.payload.edges) | |||
| } | |||
| }) | |||
| useEffect(() => { | |||
| setAutoFreeze(false) | |||
| @@ -16,6 +16,7 @@ import type { ToolDefaultValue } from '../../../block-selector/types' | |||
| import { | |||
| useNodesExtraData, | |||
| useNodesInteractions, | |||
| useNodesReadOnly, | |||
| } from '../../../hooks' | |||
| import { useStore } from '../../../store' | |||
| @@ -35,6 +36,7 @@ export const NodeTargetHandle = memo(({ | |||
| const [open, setOpen] = useState(false) | |||
| const { handleNodeAdd } = useNodesInteractions() | |||
| const nodesExtraData = useNodesExtraData() | |||
| const { getNodesReadOnly } = useNodesReadOnly() | |||
| const connected = data._connectedTargetHandleIds?.includes(handleId) | |||
| const availablePrevNodes = nodesExtraData[data.type].availablePrevNodes | |||
| const isConnectable = !!availablePrevNodes.length | |||
| @@ -78,7 +80,7 @@ export const NodeTargetHandle = memo(({ | |||
| onClick={handleHandleClick} | |||
| > | |||
| { | |||
| !connected && isConnectable && !data._isInvalidConnection && ( | |||
| !connected && isConnectable && !data._isInvalidConnection && !getNodesReadOnly() && ( | |||
| <BlockSelector | |||
| open={open} | |||
| onOpenChange={handleOpenChange} | |||
| @@ -113,6 +115,7 @@ export const NodeSourceHandle = memo(({ | |||
| const [open, setOpen] = useState(false) | |||
| const { handleNodeAdd } = useNodesInteractions() | |||
| const nodesExtraData = useNodesExtraData() | |||
| const { getNodesReadOnly } = useNodesReadOnly() | |||
| const availableNextNodes = nodesExtraData[data.type].availableNextNodes | |||
| const isConnectable = !!availableNextNodes.length | |||
| const connected = data._connectedSourceHandleIds?.includes(handleId) | |||
| @@ -159,7 +162,7 @@ export const NodeSourceHandle = memo(({ | |||
| onClick={handleHandleClick} | |||
| > | |||
| { | |||
| !connected && isConnectable && !data._isInvalidConnection && ( | |||
| !connected && isConnectable && !data._isInvalidConnection && !getNodesReadOnly() && ( | |||
| <BlockSelector | |||
| open={open} | |||
| onOpenChange={handleOpenChange} | |||
| @@ -58,7 +58,7 @@ const BaseNode: FC<BaseNodeProps> = ({ | |||
| `} | |||
| > | |||
| { | |||
| data.type !== BlockEnum.VariableAssigner && !data._runningStatus && !nodesReadOnly && ( | |||
| data.type !== BlockEnum.VariableAssigner && !data._runningStatus && ( | |||
| <NodeTargetHandle | |||
| id={id} | |||
| data={data} | |||
| @@ -68,7 +68,7 @@ const BaseNode: FC<BaseNodeProps> = ({ | |||
| ) | |||
| } | |||
| { | |||
| data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._runningStatus && !nodesReadOnly && ( | |||
| data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._runningStatus && ( | |||
| <NodeSourceHandle | |||
| id={id} | |||
| data={data} | |||
| @@ -4,6 +4,7 @@ import { | |||
| getOutgoers, | |||
| } from 'reactflow' | |||
| import dagre from 'dagre' | |||
| import { v4 as uuid4 } from 'uuid' | |||
| import { | |||
| cloneDeep, | |||
| uniqBy, | |||
| @@ -331,3 +332,28 @@ export const getToolCheckParams = ( | |||
| language, | |||
| } | |||
| } | |||
| export const changeNodesAndEdgesId = (nodes: Node[], edges: Edge[]) => { | |||
| const idMap = nodes.reduce((acc, node) => { | |||
| acc[node.id] = uuid4() | |||
| return acc | |||
| }, {} as Record<string, string>) | |||
| const newNodes = nodes.map((node) => { | |||
| return { | |||
| ...node, | |||
| id: idMap[node.id], | |||
| } | |||
| }) | |||
| const newEdges = edges.map((edge) => { | |||
| return { | |||
| ...edge, | |||
| source: idMap[edge.source], | |||
| target: idMap[edge.target], | |||
| } | |||
| }) | |||
| return [newNodes, newEdges] as [Node[], Edge[]] | |||
| } | |||
| @@ -83,6 +83,7 @@ | |||
| "sortablejs": "^1.15.0", | |||
| "swr": "^2.1.0", | |||
| "use-context-selector": "^1.4.1", | |||
| "uuid": "^9.0.1", | |||
| "zustand": "^4.5.1" | |||
| }, | |||
| "devDependencies": { | |||
| @@ -104,6 +105,7 @@ | |||
| "@types/react-window-infinite-loader": "^1.0.6", | |||
| "@types/recordrtc": "^5.6.11", | |||
| "@types/sortablejs": "^1.15.1", | |||
| "@types/uuid": "^9.0.8", | |||
| "autoprefixer": "^10.4.14", | |||
| "cross-env": "^7.0.3", | |||
| "eslint": "^8.36.0", | |||