### What problem does this PR solve? feat: build react flow nodes and edges from mock data #918 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.7.0
| export type DSLComponents = Record<string, Operator>; | |||||
| export interface DSL { | |||||
| components: DSLComponents; | |||||
| history: any[]; | |||||
| path: string[]; | |||||
| answer: any[]; | |||||
| } | |||||
| export interface Operator { | |||||
| obj: OperatorNode; | |||||
| downstream: string[]; | |||||
| upstream: string[]; | |||||
| } | |||||
| export interface OperatorNode { | |||||
| component_name: string; | |||||
| params: Record<string, unknown>; | |||||
| } |
| OnConnect, | OnConnect, | ||||
| OnEdgesChange, | OnEdgesChange, | ||||
| OnNodesChange, | OnNodesChange, | ||||
| Position, | |||||
| addEdge, | addEdge, | ||||
| applyEdgeChanges, | applyEdgeChanges, | ||||
| applyNodeChanges, | applyNodeChanges, | ||||
| const initialNodes = [ | const initialNodes = [ | ||||
| { | { | ||||
| sourcePosition: Position.Left, | |||||
| targetPosition: Position.Right, | |||||
| id: 'node-1', | id: 'node-1', | ||||
| type: 'textUpdater', | type: 'textUpdater', | ||||
| position: { x: 200, y: 50 }, | |||||
| data: { value: 123 }, | |||||
| position: { x: 400, y: 100 }, | |||||
| data: { label: 123 }, | |||||
| }, | }, | ||||
| { | { | ||||
| sourcePosition: Position.Right, | |||||
| targetPosition: Position.Left, | |||||
| id: '1', | id: '1', | ||||
| data: { label: 'Hello' }, | data: { label: 'Hello' }, | ||||
| position: { x: 0, y: 0 }, | |||||
| position: { x: 0, y: 50 }, | |||||
| type: 'input', | type: 'input', | ||||
| }, | }, | ||||
| { | { | ||||
| sourcePosition: Position.Right, | |||||
| targetPosition: Position.Left, | |||||
| id: '2', | id: '2', | ||||
| data: { label: 'World' }, | data: { label: 'World' }, | ||||
| position: { x: 100, y: 100 }, | |||||
| position: { x: 200, y: 50 }, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| interface IProps { | interface IProps { | ||||
| sideWidth: number; | sideWidth: number; | ||||
| showDrawer(): void; | |||||
| } | } | ||||
| function FlowCanvas({ sideWidth }: IProps) { | function FlowCanvas({ sideWidth }: IProps) { |
| .textUpdaterNode { | .textUpdaterNode { | ||||
| height: 50px; | |||||
| border: 1px solid #eee; | |||||
| // height: 50px; | |||||
| border: 1px solid black; | |||||
| padding: 5px; | padding: 5px; | ||||
| border-radius: 5px; | border-radius: 5px; | ||||
| background: white; | background: white; |
| import { useCallback } from 'react'; | |||||
| import { Handle, NodeProps, Position } from 'reactflow'; | import { Handle, NodeProps, Position } from 'reactflow'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const handleStyle = { left: 10 }; | |||||
| export function TextUpdaterNode({ | export function TextUpdaterNode({ | ||||
| data, | data, | ||||
| isConnectable = true, | isConnectable = true, | ||||
| }: NodeProps<{ value: number }>) { | |||||
| const onChange = useCallback((evt) => { | |||||
| console.log(evt.target.value); | |||||
| }, []); | |||||
| }: NodeProps<{ label: string }>) { | |||||
| return ( | return ( | ||||
| <div className={styles.textUpdaterNode}> | <div className={styles.textUpdaterNode}> | ||||
| <Handle | <Handle | ||||
| type="target" | type="target" | ||||
| position={Position.Top} | |||||
| position={Position.Left} | |||||
| isConnectable={isConnectable} | isConnectable={isConnectable} | ||||
| /> | /> | ||||
| <Handle | <Handle | ||||
| type="source" | type="source" | ||||
| position={Position.Bottom} | |||||
| // style={handleStyle} | |||||
| position={Position.Right} | |||||
| isConnectable={isConnectable} | isConnectable={isConnectable} | ||||
| /> | /> | ||||
| <div> | |||||
| <label htmlFor="text">Text:</label> | |||||
| <input id="text" name="text" onChange={onChange} className="nodrag" /> | |||||
| </div> | |||||
| {/* <Handle | |||||
| type="source" | |||||
| position={Position.Bottom} | |||||
| id="b" | |||||
| isConnectable={isConnectable} | |||||
| /> */} | |||||
| <div>{data.label}</div> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } |
| import { useSetModalState } from '@/hooks/commonHooks'; | import { useSetModalState } from '@/hooks/commonHooks'; | ||||
| import React, { Dispatch, SetStateAction, useCallback, useState } from 'react'; | import React, { Dispatch, SetStateAction, useCallback, useState } from 'react'; | ||||
| import { Node, ReactFlowInstance } from 'reactflow'; | |||||
| import { Node, Position, ReactFlowInstance } from 'reactflow'; | |||||
| import { v4 as uuidv4 } from 'uuid'; | import { v4 as uuidv4 } from 'uuid'; | ||||
| export const useHandleDrag = () => { | export const useHandleDrag = () => { | ||||
| }); | }); | ||||
| const newNode = { | const newNode = { | ||||
| id: uuidv4(), | id: uuidv4(), | ||||
| type, | |||||
| type: 'textUpdater', | |||||
| position: position || { | position: position || { | ||||
| x: 0, | x: 0, | ||||
| y: 0, | y: 0, | ||||
| }, | }, | ||||
| data: { label: `${type} node` }, | |||||
| data: { label: `${type}` }, | |||||
| sourcePosition: Position.Right, | |||||
| targetPosition: Position.Left, | |||||
| }; | }; | ||||
| setNodes((nds) => nds.concat(newNode)); | setNodes((nds) => nds.concat(newNode)); |
| export interface DSLComponentList { | |||||
| id: string; | |||||
| name: string; | |||||
| } |
| { name: 'Retrieval', icon: <RocketOutlined />, description: '' }, | { name: 'Retrieval', icon: <RocketOutlined />, description: '' }, | ||||
| { name: 'Generate', icon: <MergeCellsOutlined />, description: '' }, | { name: 'Generate', icon: <MergeCellsOutlined />, description: '' }, | ||||
| ]; | ]; | ||||
| export const dsl = { | |||||
| components: { | |||||
| begin: { | |||||
| obj: { | |||||
| component_name: 'Begin', | |||||
| params: {}, | |||||
| }, | |||||
| downstream: ['Answer:China'], | |||||
| upstream: [], | |||||
| }, | |||||
| 'Answer:China': { | |||||
| obj: { | |||||
| component_name: 'Answer', | |||||
| params: {}, | |||||
| }, | |||||
| downstream: ['Retrieval:China'], | |||||
| upstream: ['begin', 'Generate:China'], | |||||
| }, | |||||
| 'Retrieval:China': { | |||||
| obj: { | |||||
| component_name: 'Retrieval', | |||||
| params: {}, | |||||
| }, | |||||
| downstream: ['Generate:China'], | |||||
| upstream: ['Answer:China'], | |||||
| }, | |||||
| 'Generate:China': { | |||||
| obj: { | |||||
| component_name: 'Generate', | |||||
| params: {}, | |||||
| }, | |||||
| downstream: ['Answer:China'], | |||||
| upstream: ['Retrieval:China'], | |||||
| }, | |||||
| }, | |||||
| history: [], | |||||
| path: ['begin'], | |||||
| answer: [], | |||||
| }; |
| import { DSLComponents } from '@/interfaces/database/flow'; | |||||
| import { Edge, Node, Position } from 'reactflow'; | |||||
| import { v4 as uuidv4 } from 'uuid'; | |||||
| export const buildNodesFromDSLComponents = (data: DSLComponents) => { | |||||
| const nodes: Node[] = []; | |||||
| const edges: Edge[] = []; | |||||
| Object.entries(data).forEach(([key, value]) => { | |||||
| const downstream = [...value.downstream]; | |||||
| const upstream = [...value.upstream]; | |||||
| nodes.push({ | |||||
| id: key, | |||||
| type: 'textUpdater', | |||||
| position: { x: 0, y: 0 }, | |||||
| data: { | |||||
| label: value.obj.component_name, | |||||
| params: value.obj.params, | |||||
| downstream: downstream, | |||||
| upstream: upstream, | |||||
| }, | |||||
| sourcePosition: Position.Left, | |||||
| targetPosition: Position.Right, | |||||
| }); | |||||
| // intermediate node | |||||
| // The first and last nodes do not need to be considered | |||||
| if (upstream.length > 0 && downstream.length > 0) { | |||||
| for (let i = 0; i < upstream.length; i++) { | |||||
| const up = upstream[i]; | |||||
| for (let j = 0; j < downstream.length; j++) { | |||||
| const down = downstream[j]; | |||||
| edges.push({ | |||||
| id: uuidv4(), | |||||
| label: '', | |||||
| type: 'step', | |||||
| source: up, | |||||
| target: down, | |||||
| }); | |||||
| } | |||||
| } | |||||
| } | |||||
| }); | |||||
| }; |