diff --git a/web/app/(commonLayout)/workflow/page.tsx b/web/app/(commonLayout)/workflow/page.tsx index b9f19457f7..4907730ab3 100644 --- a/web/app/(commonLayout)/workflow/page.tsx +++ b/web/app/(commonLayout)/workflow/page.tsx @@ -29,6 +29,12 @@ const initialNodes = [ position: { x: 738, y: 330 }, data: { type: 'llm' }, }, + { + id: '5', + type: 'custom', + position: { x: 1100, y: 130 }, + data: { type: 'llm' }, + }, ] const initialEdges = [ @@ -36,20 +42,23 @@ const initialEdges = [ id: '0', type: 'custom', source: '1', + sourceHandle: 'source', target: '2', }, { id: '1', type: 'custom', source: '2', + sourceHandle: 'condition1', target: '3', }, - { - id: '2', - type: 'custom', - source: '2', - target: '4', - }, + // { + // id: '2', + // type: 'custom', + // source: '2', + // sourceHandle: 'condition2', + // target: '4', + // }, ] const Page: FC = () => { diff --git a/web/app/components/workflow/block-selector/index.tsx b/web/app/components/workflow/block-selector/index.tsx index 6056924aea..dd4b66233e 100644 --- a/web/app/components/workflow/block-selector/index.tsx +++ b/web/app/components/workflow/block-selector/index.tsx @@ -29,7 +29,7 @@ type NodeSelectorProps = { placement?: Placement offset?: OffsetOptions triggerStyle?: React.CSSProperties - triggerClassName?: string + triggerClassName?: (open: boolean) => string popupClassName?: string asChild?: boolean } @@ -68,7 +68,7 @@ const NodeSelector: FC = ({ className={` flex items-center justify-center w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10 group-hover:flex - ${triggerClassName} + ${triggerClassName?.(open)} `} style={triggerStyle} > diff --git a/web/app/components/workflow/hooks.ts b/web/app/components/workflow/hooks.ts index 5449ba2a8d..1dfcd01eb0 100644 --- a/web/app/components/workflow/hooks.ts +++ b/web/app/components/workflow/hooks.ts @@ -17,9 +17,17 @@ export const useWorkflow = () => { const handleEnterNode = useCallback((_, node) => { const { + getNodes, + setNodes, edges, setEdges, } = store.getState() + const newNodes = produce(getNodes(), (draft) => { + const currentNode = draft.find(n => n.id === node.id) + if (currentNode) + currentNode.data = { ...currentNode.data, hovering: true } + }) + setNodes(newNodes) const newEdges = produce(edges, (draft) => { const connectedEdges = getConnectedEdges([node], edges) @@ -33,9 +41,17 @@ export const useWorkflow = () => { }, [store]) const handleLeaveNode = useCallback((_, node) => { const { + getNodes, + setNodes, edges, setEdges, } = store.getState() + const newNodes = produce(getNodes(), (draft) => { + const currentNode = draft.find(n => n.id === node.id) + if (currentNode) + currentNode.data = { ...currentNode.data, hovering: false } + }) + setNodes(newNodes) const newEdges = produce(edges, (draft) => { const connectedEdges = getConnectedEdges([node], edges) diff --git a/web/app/components/workflow/nodes/_base/components/next-step.tsx b/web/app/components/workflow/nodes/_base/components/next-step.tsx index 4d8626863f..c977fc1354 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step.tsx @@ -17,6 +17,7 @@ const NextStep = () => { const store = useStoreApi() const selectedNode = useStore(state => state.selectedNode) const outgoers: Node[] = getOutgoers(selectedNode as Node, store.getState().getNodes(), store.getState().edges) + const svgHeight = outgoers.length > 1 ? (outgoers.length + 1) * 36 + 12 * outgoers.length : 36 const renderAddNextNodeTrigger = useCallback((open: boolean) => { return ( @@ -53,9 +54,9 @@ const NextStep = () => {
- + { - (!outgoers.length || outgoers.length === 1) && ( + outgoers.length < 2 && ( { + const store = useStoreApi() + const incomers = getIncomers({ id } as Node, store.getState().getNodes(), store.getState().edges) + + return ( + <> + + { + incomers.length === 0 && data.type !== BlockEnum.Start && ( + {}} + asChild + placement='left' + triggerClassName={open => ` + hidden absolute -left-2 top-4 + ${data.hovering && '!flex'} + ${open && '!flex'} + `} + /> + ) + } + + ) +} + +type NodeSourceHandleProps = { + handleId?: string + handleClassName?: string + nodeSelectorClassName?: string +} & Pick +export const NodeSourceHandle = ({ + id, + data, + handleId, + handleClassName, + nodeSelectorClassName, +}: NodeSourceHandleProps) => { + const store = useStoreApi() + const connectedEdges = getConnectedEdges([{ id } as Node], store.getState().edges) + const connected = connectedEdges.find(edge => edge.sourceHandle === handleId) + + return ( + <> + + { + !connected && ( + {}} + asChild + triggerClassName={open => ` + hidden + ${nodeSelectorClassName} + ${data.hovering && '!flex'} + ${open && '!flex'} + `} + /> + ) + } + + ) +} diff --git a/web/app/components/workflow/nodes/_base/node.tsx b/web/app/components/workflow/nodes/_base/node.tsx index f0aa5721c0..a32c34f88e 100644 --- a/web/app/components/workflow/nodes/_base/node.tsx +++ b/web/app/components/workflow/nodes/_base/node.tsx @@ -9,7 +9,6 @@ import { import type { NodeProps } from 'reactflow' import BlockIcon from '../../block-icon' import { useWorkflow } from '../../hooks' -import BlockSelector from '../../block-selector' import NodeControl from './components/node-control' type BaseNodeProps = { @@ -48,10 +47,6 @@ const BaseNode: FC = ({
Define the initial parameters for launching a workflow
- {}} - asChild - /> ) } diff --git a/web/app/components/workflow/nodes/if-else/node.tsx b/web/app/components/workflow/nodes/if-else/node.tsx index 39738e2903..084eda9e8e 100644 --- a/web/app/components/workflow/nodes/if-else/node.tsx +++ b/web/app/components/workflow/nodes/if-else/node.tsx @@ -1,5 +1,7 @@ -import type { FC } from 'react' +import { memo } from 'react' import { useTranslation } from 'react-i18next' +import type { NodeProps } from 'reactflow' +import { NodeSourceHandle } from '../_base/components/node-handle' import { mockData } from './mock' import { ComparisonOperator } from './types' import { isEmptyRelatedOperator } from './utils' @@ -12,12 +14,21 @@ const notTranslateKey = [ ComparisonOperator.lessThan, ComparisonOperator.lessThanOrEqual, ] -const Node: FC = () => { +const Node = (props: Pick) => { const { t } = useTranslation() const { conditions, logical_operator } = mockData return (
+
+
IF
+ +
{t(`${i18nPrefix}.conditions`)}
{conditions.map((condition, i) => ( @@ -34,8 +45,17 @@ const Node: FC = () => {
))}
+
+
ELSE
+ +
) } -export default Node +export default memo(Node) diff --git a/web/app/components/workflow/nodes/index.tsx b/web/app/components/workflow/nodes/index.tsx index 88782fc61c..15cb3a454e 100644 --- a/web/app/components/workflow/nodes/index.tsx +++ b/web/app/components/workflow/nodes/index.tsx @@ -1,17 +1,16 @@ import { memo } from 'react' import type { NodeProps } from 'reactflow' -import { - Handle, - Position, -} from 'reactflow' -import type { SelectedNode } from '../types' -import { BlockEnum } from '../types' +import { BlockEnum, type SelectedNode } from '../types' import { NodeComponentMap, PanelComponentMap, } from './constants' import BaseNode from './_base/node' import BasePanel from './_base/panel' +import { + NodeSourceHandle, + NodeTargetHandle, +} from './_base/components/node-handle' const CustomNode = memo((props: NodeProps) => { const nodeData = props.data @@ -19,27 +18,20 @@ const CustomNode = memo((props: NodeProps) => { return ( <> - + - + { + nodeData.type !== BlockEnum.IfElse && ( + + ) + } ) })