refactor(web): 优化流程节点的定义和实现
This commit is contained in:
@@ -1,23 +1,12 @@
|
||||
import {PlusCircleFilled, RollbackOutlined, SaveFilled} from '@ant-design/icons'
|
||||
import {
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Controls,
|
||||
type Edge,
|
||||
MiniMap,
|
||||
type Node,
|
||||
type NodeProps,
|
||||
ReactFlow,
|
||||
} from '@xyflow/react'
|
||||
import type {Schema} from 'amis'
|
||||
import {Button, Drawer, Dropdown, message, Popconfirm, Space} from 'antd'
|
||||
import {arrToMap, find, isEqual, isNil, randomId} from 'licia'
|
||||
import {type JSX, type MemoExoticComponent, useEffect, useState} from 'react'
|
||||
import {Background, BackgroundVariant, Controls, type Edge, MiniMap, type Node, ReactFlow} from '@xyflow/react'
|
||||
import {Button, Dropdown, message, Popconfirm, Space} from 'antd'
|
||||
import {arrToMap, find, isEqual, randomId} from 'licia'
|
||||
import {useEffect} from 'react'
|
||||
import {useNavigate} from 'react-router'
|
||||
import styled from 'styled-components'
|
||||
import '@xyflow/react/dist/style.css'
|
||||
import {useShallow} from 'zustand/react/shallow'
|
||||
import {amisRender, commonInfo, horizontalFormOptions} from '../../util/amis.tsx'
|
||||
import {commonInfo} from '../../util/amis.tsx'
|
||||
import {checkAddConnection, checkAddNode, checkSave} from './FlowChecker.tsx'
|
||||
import CodeNode from './node/CodeNode.tsx'
|
||||
import KnowledgeNode from './node/KnowledgeNode.tsx'
|
||||
@@ -29,8 +18,6 @@ import {useDataStore} from './store/DataStore.ts'
|
||||
import {useFlowStore} from './store/FlowStore.ts'
|
||||
|
||||
const FlowableDiv = styled.div`
|
||||
height: 100%;
|
||||
|
||||
.react-flow__node.selectable {
|
||||
&:focus {
|
||||
box-shadow: 0 0 20px 1px #e8e8e8;
|
||||
@@ -65,6 +52,34 @@ const FlowableDiv = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const nodeDefine = [
|
||||
{
|
||||
key: 'output-node',
|
||||
name: '输出',
|
||||
component: OutputNode,
|
||||
},
|
||||
{
|
||||
key: 'llm-node',
|
||||
name: '大模型',
|
||||
component: LlmNode,
|
||||
},
|
||||
{
|
||||
key: 'knowledge-node',
|
||||
name: '知识库',
|
||||
component: KnowledgeNode,
|
||||
},
|
||||
{
|
||||
key: 'code-node',
|
||||
name: '代码执行',
|
||||
component: CodeNode,
|
||||
},
|
||||
{
|
||||
key: 'switch-node',
|
||||
name: '条件分支',
|
||||
component: SwitchNode,
|
||||
},
|
||||
]
|
||||
|
||||
export type GraphData = { nodes: Node[], edges: Edge[], data: any }
|
||||
|
||||
export type FlowEditorProps = {
|
||||
@@ -76,47 +91,8 @@ export type FlowEditorProps = {
|
||||
function FlowEditor(props: FlowEditorProps) {
|
||||
const navigate = useNavigate()
|
||||
const [messageApi, contextHolder] = message.useMessage()
|
||||
const [nodeDef] = useState<{
|
||||
key: string,
|
||||
name: string,
|
||||
component: MemoExoticComponent<(props: NodeProps) => JSX.Element>
|
||||
}[]>([
|
||||
{
|
||||
key: 'output-node',
|
||||
name: '输出',
|
||||
component: OutputNode,
|
||||
},
|
||||
{
|
||||
key: 'llm-node',
|
||||
name: '大模型',
|
||||
component: LlmNode,
|
||||
},
|
||||
{
|
||||
key: 'knowledge-node',
|
||||
name: '知识库',
|
||||
component: KnowledgeNode,
|
||||
},
|
||||
{
|
||||
key: 'code-node',
|
||||
name: '代码执行',
|
||||
component: CodeNode,
|
||||
},
|
||||
{
|
||||
key: 'switch-node',
|
||||
name: '条件分支',
|
||||
component: SwitchNode,
|
||||
},
|
||||
])
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const {data, setData, getDataById, setDataById} = useDataStore(
|
||||
useShallow(state => ({
|
||||
data: state.data,
|
||||
setData: state.setData,
|
||||
getDataById: state.getDataById,
|
||||
setDataById: state.setDataById,
|
||||
})),
|
||||
)
|
||||
const {data, setData} = useDataStore()
|
||||
const {
|
||||
nodes,
|
||||
addNode,
|
||||
@@ -126,106 +102,9 @@ function FlowEditor(props: FlowEditorProps) {
|
||||
setEdges,
|
||||
onEdgesChange,
|
||||
onConnect,
|
||||
} = useFlowStore(
|
||||
useShallow(state => ({
|
||||
nodes: state.nodes,
|
||||
getNodes: state.getNodes,
|
||||
addNode: state.addNode,
|
||||
removeNode: state.removeNode,
|
||||
setNodes: state.setNodes,
|
||||
onNodesChange: state.onNodesChange,
|
||||
edges: state.edges,
|
||||
getEdges: state.getEdges,
|
||||
setEdges: state.setEdges,
|
||||
onEdgesChange: state.onEdgesChange,
|
||||
onConnect: state.onConnect,
|
||||
})),
|
||||
)
|
||||
} = useFlowStore()
|
||||
|
||||
const {
|
||||
setInputSchema,
|
||||
} = useContextStore()
|
||||
|
||||
const [currentNodeForm, setCurrentNodeForm] = useState<JSX.Element>()
|
||||
const editNode = (id: string, columnSchema?: Schema[]) => {
|
||||
if (!isNil(columnSchema)) {
|
||||
setCurrentNodeForm(
|
||||
amisRender(
|
||||
{
|
||||
type: 'wrapper',
|
||||
size: 'none',
|
||||
body: [
|
||||
{
|
||||
debug: commonInfo.debug,
|
||||
type: 'form',
|
||||
...horizontalFormOptions(),
|
||||
wrapWithPanel: false,
|
||||
onEvent: {
|
||||
submitSucc: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
setDataById(
|
||||
id,
|
||||
{
|
||||
...context.props.data,
|
||||
finished: true,
|
||||
},
|
||||
)
|
||||
setOpen(false)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
body: [
|
||||
...(columnSchema ?? []),
|
||||
{
|
||||
type: 'wrapper',
|
||||
size: 'none',
|
||||
className: 'space-x-2 text-right',
|
||||
body: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '取消',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
setOpen(false)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'submit',
|
||||
label: '保存',
|
||||
level: 'primary',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
getDataById(id),
|
||||
),
|
||||
)
|
||||
setOpen(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 用于透传node操作到主流程
|
||||
const initialNodeHandlers = {
|
||||
editNode,
|
||||
}
|
||||
const {setInputSchema} = useContextStore()
|
||||
|
||||
useEffect(() => {
|
||||
// language=JSON
|
||||
@@ -236,10 +115,6 @@ function FlowEditor(props: FlowEditorProps) {
|
||||
|
||||
let initialNodeData = props.graphData?.data ?? {}
|
||||
setData(initialNodeData)
|
||||
|
||||
for (let node of initialNodes) {
|
||||
node.data = initialNodeHandlers
|
||||
}
|
||||
setNodes(initialNodes)
|
||||
setEdges(initialEdges)
|
||||
|
||||
@@ -247,12 +122,12 @@ function FlowEditor(props: FlowEditorProps) {
|
||||
}, [props.graphData])
|
||||
|
||||
return (
|
||||
<FlowableDiv>
|
||||
<FlowableDiv className="h-full w-full">
|
||||
{contextHolder}
|
||||
<Space className="toolbar">
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: nodeDef.map(def => ({key: def.key, label: def.name})),
|
||||
items: nodeDefine.map(def => ({key: def.key, label: def.name})),
|
||||
onClick: ({key}) => {
|
||||
try {
|
||||
if (commonInfo.debug) {
|
||||
@@ -263,7 +138,7 @@ function FlowEditor(props: FlowEditorProps) {
|
||||
id: randomId(10),
|
||||
type: key,
|
||||
position: {x: 100, y: 100},
|
||||
data: initialNodeHandlers,
|
||||
data: {},
|
||||
})
|
||||
} catch (e) {
|
||||
// @ts-ignore
|
||||
@@ -303,16 +178,6 @@ function FlowEditor(props: FlowEditorProps) {
|
||||
保存
|
||||
</Button>
|
||||
</Space>
|
||||
<Drawer
|
||||
title="节点编辑"
|
||||
open={open}
|
||||
closeIcon={false}
|
||||
maskClosable={false}
|
||||
destroyOnHidden
|
||||
size="large"
|
||||
>
|
||||
{currentNodeForm}
|
||||
</Drawer>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
@@ -332,8 +197,8 @@ function FlowEditor(props: FlowEditorProps) {
|
||||
}}
|
||||
// @ts-ignore
|
||||
nodeTypes={arrToMap(
|
||||
nodeDef.map(def => def.key),
|
||||
key => find(nodeDef, def => isEqual(key, def.key))!.component)
|
||||
nodeDefine.map(def => def.key),
|
||||
key => find(nodeDefine, def => isEqual(key, def.key))!.component)
|
||||
}
|
||||
>
|
||||
<Controls/>
|
||||
|
||||
Reference in New Issue
Block a user