| type: VarType.arrayFile, | type: VarType.arrayFile, | ||||
| }, | }, | ||||
| ] | ] | ||||
| export const WORKFLOW_DATA_UPDATE = 'WORKFLOW_DATA_UPDATE' |
| const handleRestore = useCallback(() => { | const handleRestore = useCallback(() => { | ||||
| workflowStore.setState({ isRestoring: false }) | workflowStore.setState({ isRestoring: false }) | ||||
| workflowStore.setState({ backupDraft: undefined }) | |||||
| handleSyncWorkflowDraft(true) | handleSyncWorkflowDraft(true) | ||||
| }, [handleSyncWorkflowDraft, workflowStore]) | }, [handleSyncWorkflowDraft, workflowStore]) | ||||
| Viewport, | Viewport, | ||||
| } from 'reactflow' | } from 'reactflow' | ||||
| import { | import { | ||||
| changeNodesAndEdgesId, | |||||
| getLayoutByDagre, | getLayoutByDagre, | ||||
| initialEdges, | initialEdges, | ||||
| initialNodes, | initialNodes, | ||||
| import { | import { | ||||
| AUTO_LAYOUT_OFFSET, | AUTO_LAYOUT_OFFSET, | ||||
| SUPPORT_OUTPUT_VARS_NODE, | SUPPORT_OUTPUT_VARS_NODE, | ||||
| WORKFLOW_DATA_UPDATE, | |||||
| } from '../constants' | } from '../constants' | ||||
| import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils' | import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils' | ||||
| import { useNodesExtraData } from './use-nodes-data' | import { useNodesExtraData } from './use-nodes-data' | ||||
| fetchAllCustomTools, | fetchAllCustomTools, | ||||
| } from '@/service/tools' | } from '@/service/tools' | ||||
| import I18n from '@/context/i18n' | import I18n from '@/context/i18n' | ||||
| import { useEventEmitterContextContext } from '@/context/event-emitter' | |||||
| export const useIsChatMode = () => { | export const useIsChatMode = () => { | ||||
| const appDetail = useAppStore(s => s.appDetail) | const appDetail = useAppStore(s => s.appDetail) | ||||
| const workflowStore = useWorkflowStore() | const workflowStore = useWorkflowStore() | ||||
| const nodesExtraData = useNodesExtraData() | const nodesExtraData = useNodesExtraData() | ||||
| const { handleSyncWorkflowDraft } = useNodesSyncDraft() | const { handleSyncWorkflowDraft } = useNodesSyncDraft() | ||||
| const { eventEmitter } = useEventEmitterContextContext() | |||||
| const handleLayout = useCallback(async () => { | const handleLayout = useCallback(async () => { | ||||
| workflowStore.setState({ nodeAnimation: true }) | workflowStore.setState({ nodeAnimation: true }) | ||||
| }, [locale]) | }, [locale]) | ||||
| const renderTreeFromRecord = useCallback((nodes: Node[], edges: Edge[], viewport?: Viewport) => { | 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) | if (viewport) | ||||
| setViewport(viewport) | setViewport(viewport) | ||||
| }, [store, reactflow]) | |||||
| }, [reactflow, eventEmitter]) | |||||
| const getNode = useCallback((nodeId?: string) => { | const getNode = useCallback((nodeId?: string) => { | ||||
| const { getNodes } = store.getState() | const { getNodes } = store.getState() |
| import ReactFlow, { | import ReactFlow, { | ||||
| Background, | Background, | ||||
| ReactFlowProvider, | ReactFlowProvider, | ||||
| useEdgesState, | |||||
| useNodesState, | |||||
| useOnViewportChange, | useOnViewportChange, | ||||
| } from 'reactflow' | } from 'reactflow' | ||||
| import type { Viewport } from 'reactflow' | import type { Viewport } from 'reactflow' | ||||
| initialEdges, | initialEdges, | ||||
| initialNodes, | initialNodes, | ||||
| } from './utils' | } from './utils' | ||||
| import { WORKFLOW_DATA_UPDATE } from './constants' | |||||
| import Loading from '@/app/components/base/loading' | import Loading from '@/app/components/base/loading' | ||||
| import { FeaturesProvider } from '@/app/components/base/features' | import { FeaturesProvider } from '@/app/components/base/features' | ||||
| import type { Features as FeaturesData } from '@/app/components/base/features/types' | import type { Features as FeaturesData } from '@/app/components/base/features/types' | ||||
| import { useEventEmitterContextContext } from '@/context/event-emitter' | |||||
| const nodeTypes = { | const nodeTypes = { | ||||
| custom: CustomNode, | custom: CustomNode, | ||||
| viewport?: Viewport | viewport?: Viewport | ||||
| } | } | ||||
| const Workflow: FC<WorkflowProps> = memo(({ | const Workflow: FC<WorkflowProps> = memo(({ | ||||
| nodes, | |||||
| edges, | |||||
| nodes: originalNodes, | |||||
| edges: originalEdges, | |||||
| viewport, | viewport, | ||||
| }) => { | }) => { | ||||
| const [nodes, setNodes] = useNodesState(originalNodes) | |||||
| const [edges, setEdges] = useEdgesState(originalEdges) | |||||
| const showFeaturesPanel = useStore(state => state.showFeaturesPanel) | const showFeaturesPanel = useStore(state => state.showFeaturesPanel) | ||||
| const nodeAnimation = useStore(s => s.nodeAnimation) | const nodeAnimation = useStore(s => s.nodeAnimation) | ||||
| const { | const { | ||||
| const { workflowReadOnly } = useWorkflowReadOnly() | const { workflowReadOnly } = useWorkflowReadOnly() | ||||
| const { nodesReadOnly } = useNodesReadOnly() | 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(() => { | useEffect(() => { | ||||
| setAutoFreeze(false) | setAutoFreeze(false) | ||||
| import { | import { | ||||
| useNodesExtraData, | useNodesExtraData, | ||||
| useNodesInteractions, | useNodesInteractions, | ||||
| useNodesReadOnly, | |||||
| } from '../../../hooks' | } from '../../../hooks' | ||||
| import { useStore } from '../../../store' | import { useStore } from '../../../store' | ||||
| const [open, setOpen] = useState(false) | const [open, setOpen] = useState(false) | ||||
| const { handleNodeAdd } = useNodesInteractions() | const { handleNodeAdd } = useNodesInteractions() | ||||
| const nodesExtraData = useNodesExtraData() | const nodesExtraData = useNodesExtraData() | ||||
| const { getNodesReadOnly } = useNodesReadOnly() | |||||
| const connected = data._connectedTargetHandleIds?.includes(handleId) | const connected = data._connectedTargetHandleIds?.includes(handleId) | ||||
| const availablePrevNodes = nodesExtraData[data.type].availablePrevNodes | const availablePrevNodes = nodesExtraData[data.type].availablePrevNodes | ||||
| const isConnectable = !!availablePrevNodes.length | const isConnectable = !!availablePrevNodes.length | ||||
| onClick={handleHandleClick} | onClick={handleHandleClick} | ||||
| > | > | ||||
| { | { | ||||
| !connected && isConnectable && !data._isInvalidConnection && ( | |||||
| !connected && isConnectable && !data._isInvalidConnection && !getNodesReadOnly() && ( | |||||
| <BlockSelector | <BlockSelector | ||||
| open={open} | open={open} | ||||
| onOpenChange={handleOpenChange} | onOpenChange={handleOpenChange} | ||||
| const [open, setOpen] = useState(false) | const [open, setOpen] = useState(false) | ||||
| const { handleNodeAdd } = useNodesInteractions() | const { handleNodeAdd } = useNodesInteractions() | ||||
| const nodesExtraData = useNodesExtraData() | const nodesExtraData = useNodesExtraData() | ||||
| const { getNodesReadOnly } = useNodesReadOnly() | |||||
| const availableNextNodes = nodesExtraData[data.type].availableNextNodes | const availableNextNodes = nodesExtraData[data.type].availableNextNodes | ||||
| const isConnectable = !!availableNextNodes.length | const isConnectable = !!availableNextNodes.length | ||||
| const connected = data._connectedSourceHandleIds?.includes(handleId) | const connected = data._connectedSourceHandleIds?.includes(handleId) | ||||
| onClick={handleHandleClick} | onClick={handleHandleClick} | ||||
| > | > | ||||
| { | { | ||||
| !connected && isConnectable && !data._isInvalidConnection && ( | |||||
| !connected && isConnectable && !data._isInvalidConnection && !getNodesReadOnly() && ( | |||||
| <BlockSelector | <BlockSelector | ||||
| open={open} | open={open} | ||||
| onOpenChange={handleOpenChange} | onOpenChange={handleOpenChange} |
| `} | `} | ||||
| > | > | ||||
| { | { | ||||
| data.type !== BlockEnum.VariableAssigner && !data._runningStatus && !nodesReadOnly && ( | |||||
| data.type !== BlockEnum.VariableAssigner && !data._runningStatus && ( | |||||
| <NodeTargetHandle | <NodeTargetHandle | ||||
| id={id} | id={id} | ||||
| data={data} | data={data} | ||||
| ) | ) | ||||
| } | } | ||||
| { | { | ||||
| data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._runningStatus && !nodesReadOnly && ( | |||||
| data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._runningStatus && ( | |||||
| <NodeSourceHandle | <NodeSourceHandle | ||||
| id={id} | id={id} | ||||
| data={data} | data={data} |
| getOutgoers, | getOutgoers, | ||||
| } from 'reactflow' | } from 'reactflow' | ||||
| import dagre from 'dagre' | import dagre from 'dagre' | ||||
| import { v4 as uuid4 } from 'uuid' | |||||
| import { | import { | ||||
| cloneDeep, | cloneDeep, | ||||
| uniqBy, | uniqBy, | ||||
| language, | 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[]] | |||||
| } |
| "sortablejs": "^1.15.0", | "sortablejs": "^1.15.0", | ||||
| "swr": "^2.1.0", | "swr": "^2.1.0", | ||||
| "use-context-selector": "^1.4.1", | "use-context-selector": "^1.4.1", | ||||
| "uuid": "^9.0.1", | |||||
| "zustand": "^4.5.1" | "zustand": "^4.5.1" | ||||
| }, | }, | ||||
| "devDependencies": { | "devDependencies": { | ||||
| "@types/react-window-infinite-loader": "^1.0.6", | "@types/react-window-infinite-loader": "^1.0.6", | ||||
| "@types/recordrtc": "^5.6.11", | "@types/recordrtc": "^5.6.11", | ||||
| "@types/sortablejs": "^1.15.1", | "@types/sortablejs": "^1.15.1", | ||||
| "@types/uuid": "^9.0.8", | |||||
| "autoprefixer": "^10.4.14", | "autoprefixer": "^10.4.14", | ||||
| "cross-env": "^7.0.3", | "cross-env": "^7.0.3", | ||||
| "eslint": "^8.36.0", | "eslint": "^8.36.0", |