From a17c0e5bf62518390fbb7d36a3b5847a615c6815 Mon Sep 17 00:00:00 2001 From: StyleZhang Date: Tue, 6 Feb 2024 17:05:26 +0800 Subject: [PATCH] init --- web/app/components/workflow/context.tsx | 20 +++ web/app/components/workflow/custom-edge.tsx | 30 +++- web/app/components/workflow/hooks.ts | 13 +- web/app/components/workflow/index.tsx | 135 +++++++++++++----- .../components/workflow/nodes/_base/node.tsx | 12 +- .../components/workflow/nodes/_base/panel.tsx | 63 ++++++-- .../components/workflow/nodes/constants.ts | 11 ++ web/app/components/workflow/nodes/index.tsx | 33 ++++- .../components/workflow/nodes/start/node.tsx | 5 +- .../components/workflow/nodes/start/panel.tsx | 6 +- web/app/components/workflow/types.ts | 9 ++ 11 files changed, 273 insertions(+), 64 deletions(-) create mode 100644 web/app/components/workflow/nodes/constants.ts diff --git a/web/app/components/workflow/context.tsx b/web/app/components/workflow/context.tsx index e69de29bb2..6b129165a2 100644 --- a/web/app/components/workflow/context.tsx +++ b/web/app/components/workflow/context.tsx @@ -0,0 +1,20 @@ +'use client' + +import { createContext, useContext } from 'use-context-selector' +import type { Edge } from 'reactflow' +import type { Node } from './types' + +export type WorkflowContextValue = { + nodes: Node[] + edges: Edge[] + selectedNodeId?: string + handleSelectedNodeIdChange: (nodeId: string) => void + selectedNode?: Node +} + +export const WorkflowContext = createContext({ + nodes: [], + edges: [], + handleSelectedNodeIdChange: () => {}, +}) +export const useWorkflowContext = () => useContext(WorkflowContext) diff --git a/web/app/components/workflow/custom-edge.tsx b/web/app/components/workflow/custom-edge.tsx index 9a6958e7c5..7da3a6869b 100644 --- a/web/app/components/workflow/custom-edge.tsx +++ b/web/app/components/workflow/custom-edge.tsx @@ -1,7 +1,9 @@ +import { memo } from 'react' import type { EdgeProps } from 'reactflow' import { BaseEdge, - getBezierPath, + EdgeLabelRenderer, + getSmoothStepPath, } from 'reactflow' const CustomEdge = ({ @@ -11,19 +13,39 @@ const CustomEdge = ({ targetX, targetY, }: EdgeProps) => { - const [edgePath] = getBezierPath({ + const [ + edgePath, + labelX, + labelY, + ] = getSmoothStepPath({ sourceX, sourceY, targetX, targetY, + borderRadius: 30, }) - console.log('edgePath', edgePath) return ( <> + +
+ Topic 2 +
+
) } -export default CustomEdge +export default memo(CustomEdge) diff --git a/web/app/components/workflow/hooks.ts b/web/app/components/workflow/hooks.ts index 0da99b09e9..5b2a191402 100644 --- a/web/app/components/workflow/hooks.ts +++ b/web/app/components/workflow/hooks.ts @@ -1,15 +1,22 @@ import { useCallback, + useMemo, useState, } from 'react' +import type { Node } from './types' -export const useWorkflow = () => { +export const useWorkflow = (nodes: Node[]) => { const [selectedNodeId, setSelectedNodeId] = useState('') - const handleSelectedNodeId = useCallback((nodeId: string) => setSelectedNodeId(nodeId), []) + const handleSelectedNodeIdChange = useCallback((nodeId: string) => setSelectedNodeId(nodeId), []) + + const selectedNode = useMemo(() => { + return nodes.find(node => node.id === selectedNodeId) + }, [nodes, selectedNodeId]) return { selectedNodeId, - handleSelectedNodeId, + selectedNode, + handleSelectedNodeIdChange, } } diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 9b6d7a6788..7edd0e2781 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -1,9 +1,20 @@ +import type { FC } from 'react' import ReactFlow, { Background, + ReactFlowProvider, + useEdgesState, + useNodesState, } from 'reactflow' import 'reactflow/dist/style.css' +import { + WorkflowContext, + useWorkflowContext, +} from './context' +import { useWorkflow } from './hooks' import Header from './header' -import CustomNode from './nodes' +import CustomNode, { + Panel, +} from './nodes' import CustomEdge from './custom-edge' const nodeTypes = { @@ -13,47 +24,69 @@ const edgeTypes = { custom: CustomEdge, } +const initialNodes = [ + { + id: '1', + type: 'custom', + position: { x: 330, y: 30 }, + data: { type: 'start' }, + }, + { + id: '2', + type: 'custom', + position: { x: 330, y: 212 }, + data: { type: 'start' }, + }, + { + id: '3', + type: 'custom', + position: { x: 150, y: 394 }, + data: { type: 'start' }, + }, + { + id: '4', + type: 'custom', + position: { x: 510, y: 394 }, + data: { type: 'start' }, + }, +] + +const initialEdges = [ + { + id: '1', + source: '1', + target: '2', + type: 'custom', + }, + { + id: '2', + source: '2', + target: '3', + type: 'custom', + }, + { + id: '3', + source: '2', + target: '4', + type: 'custom', + }, +] + const Workflow = () => { + const { + nodes, + edges, + } = useWorkflowContext() + return (
+ { ) } -export default Workflow +const WorkflowWrap: FC = () => { + const [nodes] = useNodesState(initialNodes) + const [edges] = useEdgesState(initialEdges) + const { + selectedNodeId, + handleSelectedNodeIdChange, + selectedNode, + } = useWorkflow(nodes) + + return ( + + + + ) +} + +const WorkflowWrapWithReactFlowProvider = () => { + return ( + + + + ) +} + +export default WorkflowWrapWithReactFlowProvider diff --git a/web/app/components/workflow/nodes/_base/node.tsx b/web/app/components/workflow/nodes/_base/node.tsx index c44ae5287d..2c803dc0c1 100644 --- a/web/app/components/workflow/nodes/_base/node.tsx +++ b/web/app/components/workflow/nodes/_base/node.tsx @@ -3,6 +3,8 @@ import type { ReactNode, } from 'react' import { memo } from 'react' +import { useNodeId } from 'reactflow' +import { useWorkflowContext } from '../../context' import { Plus } from '@/app/components/base/icons/src/vender/line/general' type BaseNodeProps = { @@ -12,12 +14,20 @@ type BaseNodeProps = { const BaseNode: FC = ({ children, }) => { + const nodeId = useNodeId() + const { + selectedNodeId, + handleSelectedNodeIdChange, + } = useWorkflowContext() + return (
handleSelectedNodeIdChange(nodeId || '')} >
diff --git a/web/app/components/workflow/nodes/_base/panel.tsx b/web/app/components/workflow/nodes/_base/panel.tsx index e72593d14e..afcb536b2d 100644 --- a/web/app/components/workflow/nodes/_base/panel.tsx +++ b/web/app/components/workflow/nodes/_base/panel.tsx @@ -2,6 +2,14 @@ import type { FC, ReactNode, } from 'react' +import { useState } from 'react' +import { useWorkflowContext } from '../../context' +import { XClose } from '@/app/components/base/icons/src/vender/line/general' + +enum TabEnum { + Inputs = 'inputs', + Outputs = 'outputs', +} type BasePanelProps = { defaultElement?: ReactNode @@ -14,26 +22,63 @@ const BasePanel: FC = ({ inputsElement, ouputsElement, }) => { + const initialActiveTab = inputsElement ? TabEnum.Inputs : ouputsElement ? TabEnum.Outputs : '' + const [activeTab, setActiveTab] = useState(initialActiveTab) + const { handleSelectedNodeIdChange } = useWorkflowContext() + return ( -
+
-
-
LLM
+
+
LLM
+
+
handleSelectedNodeIdChange('')} + > + +
+
Add description...
-
- inputs -
+ { + (inputsElement || ouputsElement) && ( +
+ { + inputsElement && ( +
setActiveTab(TabEnum.Inputs)} + > + inputs +
+ ) + } + { + ouputsElement && ( +
setActiveTab(TabEnum.Outputs)} + > + outpus +
+ ) + } +
+ ) + }
{defaultElement} - {inputsElement} - {ouputsElement} + {activeTab === TabEnum.Inputs && inputsElement} + {activeTab === TabEnum.Outputs && ouputsElement} +
+
+ next step
-
) } diff --git a/web/app/components/workflow/nodes/constants.ts b/web/app/components/workflow/nodes/constants.ts new file mode 100644 index 0000000000..25ee51a6c2 --- /dev/null +++ b/web/app/components/workflow/nodes/constants.ts @@ -0,0 +1,11 @@ +import type { ComponentType } from 'react' +import StartNode from './start/node' +import StartPanel from './start/panel' + +export const NodeMap: Record = { + start: StartNode, +} + +export const PanelMap: Record = { + start: StartPanel, +} diff --git a/web/app/components/workflow/nodes/index.tsx b/web/app/components/workflow/nodes/index.tsx index 0c6b51911d..5e2bacfd34 100644 --- a/web/app/components/workflow/nodes/index.tsx +++ b/web/app/components/workflow/nodes/index.tsx @@ -1,14 +1,21 @@ +import { memo } from 'react' import { Handle, Position, + useNodeId, } from 'reactflow' -import StartNode from './start/node' - -const NodeMap = { - 'start-node': StartNode, -} +import { useWorkflowContext } from '../context' +import { + NodeMap, + PanelMap, +} from './constants' const CustomNode = () => { + const nodeId = useNodeId() + const { nodes } = useWorkflowContext() + const currentNode = nodes.find(node => node.id === nodeId) + const NodeComponent = NodeMap[currentNode!.data.type as string] + return ( <> { position={Position.Top} className='!-top-0.5 !w-2 !h-0.5 !bg-primary-500 !rounded-none !border-none !min-h-[2px]' /> - + { ) } -export default CustomNode +export const Panel = () => { + const { selectedNode } = useWorkflowContext() + const PanelComponent = PanelMap[selectedNode?.data.type || ''] + + if (!PanelComponent) + return null + + return ( + + ) +} + +export default memo(CustomNode) diff --git a/web/app/components/workflow/nodes/start/node.tsx b/web/app/components/workflow/nodes/start/node.tsx index 04b9c9e714..129f617ebc 100644 --- a/web/app/components/workflow/nodes/start/node.tsx +++ b/web/app/components/workflow/nodes/start/node.tsx @@ -1,9 +1,10 @@ +import type { FC } from 'react' import BaseNode from '../_base/node' -const Node = () => { +const Node: FC = () => { return ( - start node +
start node
) } diff --git a/web/app/components/workflow/nodes/start/panel.tsx b/web/app/components/workflow/nodes/start/panel.tsx index 2e41356140..bed1dfe64c 100644 --- a/web/app/components/workflow/nodes/start/panel.tsx +++ b/web/app/components/workflow/nodes/start/panel.tsx @@ -1,9 +1,11 @@ +import type { FC } from 'react' import BasePanel from '../_base/panel' -const Panel = () => { +const Panel: FC = () => { return ( start panel
} + inputsElement={
start panel inputs
} + ouputsElement={
start panel outputs
} /> ) } diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index e69de29bb2..72d1012973 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -0,0 +1,9 @@ +import type { Node as ReactFlowNode } from 'reactflow' + +export type NodeData = { + type: string + name?: string + icon?: any + description?: string +} +export type Node = ReactFlowNode