diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index 53b60413a6..722a2ac303 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -155,6 +155,7 @@ export const NODES_INITIAL_DATA = { export const NODE_WIDTH = 220 export const X_OFFSET = 64 +export const NODE_WIDTH_X_OFFSET = NODE_WIDTH + X_OFFSET export const Y_OFFSET = 39 export const TREE_DEEPTH = 20 diff --git a/web/app/components/workflow/hooks.ts b/web/app/components/workflow/hooks.ts index 89d0a2b639..7dba3c12ac 100644 --- a/web/app/components/workflow/hooks.ts +++ b/web/app/components/workflow/hooks.ts @@ -14,20 +14,21 @@ import { Position, getConnectedEdges, getIncomers, + getOutgoers, useReactFlow, useStoreApi, } from 'reactflow' -import type { - BlockEnum, - Node, -} from './types' +import type { Node } from './types' import { + BlockEnum, NodeRunningStatus, WorkflowRunningStatus, } from './types' import { NODES_EXTRA_DATA, NODES_INITIAL_DATA, + NODE_WIDTH_X_OFFSET, + Y_OFFSET, } from './constants' import { getLayoutByDagre } from './utils' import { useStore } from './store' @@ -468,6 +469,8 @@ export const useWorkflow = () => { } = store.getState() const nodes = getNodes() const currentNode = nodes.find(node => node.id === currentNodeId)! + const outgoers = getOutgoers(currentNode, nodes, edges).sort((a, b) => a.position.y - b.position.y) + const lastOutgoer = outgoers[outgoers.length - 1] const nextNode: Node = { id: `${Date.now()}`, type: 'custom', @@ -477,8 +480,8 @@ export const useWorkflow = () => { selected: true, }, position: { - x: currentNode.position.x + 304, - y: currentNode.position.y, + x: lastOutgoer ? lastOutgoer.position.x : currentNode.position.x + NODE_WIDTH_X_OFFSET, + y: lastOutgoer ? lastOutgoer.position.y + lastOutgoer.height! + Y_OFFSET : currentNode.position.y, }, targetPosition: Position.Left, } @@ -504,6 +507,69 @@ export const useWorkflow = () => { handleSyncWorkflowDraft() }, [store, nodesInitialData, handleSyncWorkflowDraft]) + const handleNodeAddPrev = useCallback(( + currentNodeId: string, + nodeType: BlockEnum, + targetHandle: string, + toolDefaultValue?: ToolDefaultValue, + ) => { + const { runningStatus } = useStore.getState() + + if (runningStatus) + return + + const { + getNodes, + setNodes, + edges, + setEdges, + } = store.getState() + const nodes = getNodes() + const currentNodeIndex = nodes.findIndex(node => node.id === currentNodeId) + const currentNode = nodes[currentNodeIndex] + const prevNode: Node = { + id: `${Date.now()}`, + type: 'custom', + data: { + ...nodesInitialData[nodeType], + ...(toolDefaultValue || {}), + selected: true, + }, + position: { + x: currentNode.position.x, + y: currentNode.position.y, + }, + targetPosition: Position.Left, + } + const newNodes = produce(nodes, (draft) => { + draft.forEach((node, index) => { + node.data.selected = false + + if (index === currentNodeIndex) + node.position.x += NODE_WIDTH_X_OFFSET + }) + draft.push(prevNode) + }) + setNodes(newNodes) + + if (prevNode.type !== BlockEnum.IfElse && prevNode.type !== BlockEnum.QuestionClassifier) { + const newEdge = { + id: `${prevNode.id}-${currentNode.id}`, + type: 'custom', + source: prevNode.id, + sourceHandle: 'source', + target: currentNode.id, + targetHandle, + } + const newEdges = produce(edges, (draft) => { + draft.push(newEdge) + }) + setEdges(newEdges) + } + + handleSyncWorkflowDraft() + }, [store, nodesInitialData, handleSyncWorkflowDraft]) + const handleNodeChange = useCallback(( currentNodeId: string, nodeType: BlockEnum, @@ -691,6 +757,7 @@ export const useWorkflow = () => { handleNodeDelete, handleNodeDataUpdate, handleNodeAddNext, + handleNodeAddPrev, handleNodeChange, handleEdgeEnter, diff --git a/web/app/components/workflow/nodes/_base/components/node-control.tsx b/web/app/components/workflow/nodes/_base/components/node-control.tsx index 0b1a6c4690..c9b266ff93 100644 --- a/web/app/components/workflow/nodes/_base/components/node-control.tsx +++ b/web/app/components/workflow/nodes/_base/components/node-control.tsx @@ -4,21 +4,23 @@ import { useCallback, useState, } from 'react' +import { useTranslation } from 'react-i18next' import { useWorkflow } from '../../../hooks' import type { Node } from '../../../types' import { canRunBySingle } from '../../../utils' import PanelOperator from './panel-operator' -import { Loading02 } from '@/app/components/base/icons/src/vender/line/general' import { Play, Stop, } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' +import TooltipPlus from '@/app/components/base/tooltip-plus' type NodeControlProps = Pick const NodeControl: FC = ({ id, data, }) => { + const { t } = useTranslation() const [open, setOpen] = useState(false) const { handleNodeDataUpdate } = useWorkflow() @@ -38,14 +40,6 @@ const NodeControl: FC = ({ className='flex items-center px-0.5 h-6 bg-white rounded-lg border-[0.5px] border-gray-100 shadow-xs text-gray-500' onClick={e => e.stopPropagation()} > - { - data._isSingleRun && ( -
- - RUNNING -
- ) - } { canRunBySingle(data.type) && (
= ({ onClick={() => { handleNodeDataUpdate({ id, - data: { _isSingleRun: !data._isSingleRun }, + data: { + _isSingleRun: !data._isSingleRun, + }, }) }} > { data._isSingleRun ? - : + : ( + + + + ) }
) diff --git a/web/app/components/workflow/nodes/_base/components/node-handle.tsx b/web/app/components/workflow/nodes/_base/components/node-handle.tsx index a2ac20b8e7..7efe521843 100644 --- a/web/app/components/workflow/nodes/_base/components/node-handle.tsx +++ b/web/app/components/workflow/nodes/_base/components/node-handle.tsx @@ -30,6 +30,7 @@ export const NodeTargetHandle = ({ nodeSelectorClassName, }: NodeHandleProps) => { const [open, setOpen] = useState(false) + const { handleNodeAddPrev } = useWorkflow() const edges = useEdges() const connectedEdges = getConnectedEdges([{ id } as Node], edges) const connected = connectedEdges.find(edge => edge.targetHandle === handleId && edge.target === id) @@ -42,6 +43,9 @@ export const NodeTargetHandle = ({ if (!connected) setOpen(v => !v) }, [connected]) + const handleSelect = useCallback((type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => { + handleNodeAddPrev(id, type, handleId, toolDefaultValue) + }, [handleNodeAddPrev, id, handleId]) return ( <> @@ -64,7 +68,7 @@ export const NodeTargetHandle = ({ {}} + onSelect={handleSelect} asChild placement='left' triggerClassName={open => ` diff --git a/web/app/components/workflow/nodes/_base/node.tsx b/web/app/components/workflow/nodes/_base/node.tsx index b3c14c931a..9e3bfc7caa 100644 --- a/web/app/components/workflow/nodes/_base/node.tsx +++ b/web/app/components/workflow/nodes/_base/node.tsx @@ -106,12 +106,12 @@ const BaseNode: FC = ({ ) } -
+
{cloneElement(children, { id, data })}
{ data.desc && ( -
+
{data.desc}
) diff --git a/web/app/components/workflow/nodes/question-classifier/node.tsx b/web/app/components/workflow/nodes/question-classifier/node.tsx index 4a62d0fffd..5e4ac9a7b2 100644 --- a/web/app/components/workflow/nodes/question-classifier/node.tsx +++ b/web/app/components/workflow/nodes/question-classifier/node.tsx @@ -33,24 +33,28 @@ const Node: FC> = (props) => { readonly /> )} -
- {topics.map((topic, index) => ( -
- - + { + !!topics.length && ( +
+ {topics.map((topic, index) => ( +
+ + +
+ ))}
- ))} -
+ ) + }
) } diff --git a/web/app/components/workflow/utils.ts b/web/app/components/workflow/utils.ts index f9749d0187..8e40b78ffa 100644 --- a/web/app/components/workflow/utils.ts +++ b/web/app/components/workflow/utils.ts @@ -171,6 +171,6 @@ export const canRunBySingle = (nodeType: BlockEnum) => { || nodeType === BlockEnum.Tool } -export const getVariables = () => { +export const getVariables = (currentNodeId: string) => { }