import {DeleteFilled, EditFilled, FolderFilled, PlusCircleFilled, SaveFilled} from '@ant-design/icons' import { addEdge, applyEdgeChanges, applyNodeChanges, Background, BackgroundVariant, Controls, type Edge, Handle, MiniMap, type Node, type NodeProps, type OnConnect, type OnEdgesChange, type OnNodesChange, Position, ReactFlow, } from '@xyflow/react' import {useMount} from 'ahooks' import type {Schema} from 'amis' import {Button, Card, Drawer, Dropdown, message, Space} from 'antd' import {arrToMap, filter, find, findIdx, isEqual, isNil, randomId} from 'licia' import {type JSX, useState} from 'react' import styled from 'styled-components' import '@xyflow/react/dist/style.css' import {create} from 'zustand/react' import {amisRender, commonInfo} from '../util/amis.tsx' const FlowableDiv = styled.div` height: 93vh; .toolbar { z-index: 999; position: absolute; } ` type AmisNodeType = 'normal' | 'start' | 'end' const AmisNode = ( props: NodeProps, type: AmisNodeType, name: String, description?: String, columnSchema?: Schema[], ) => { const {id, data} = props const {removeNode, editNode} = data return (
, }, { key: 'remove', label: '删除', icon: , }, ], onClick: menu => { switch (menu.key) { case 'edit': // @ts-ignore editNode(id, name, description, columnSchema) break case 'remove': // @ts-ignore removeNode(id) break } } }} >
{description}
{isEqual(type, 'start') || isEqual(type, 'normal') ? : undefined} {isEqual(type, 'end') || isEqual(type, 'normal') ? : undefined}
) } const StartAmisNode = (props: NodeProps) => AmisNode( props, 'start', '开始节点', '定义输入变量', [ { type: 'input-kvs', name: 'fields', addButtonText: '新增入参', draggable: false, keyItem: { label: '参数名称', }, valueItems: [ { type: 'input-text', name: 'description', label: '参数描述', }, { type: 'select', name: 'type', label: '参数类型', required: true, selectFirst: true, options: [ { label: '文本', value: 'text', }, { label: '数字', value: 'number', }, { label: '文件', value: 'files', }, ], }, ], }, ], ) const EndAmisNode = (props: NodeProps) => AmisNode( props, 'end', '结束节点', '定义输出变量', [ { type: 'input-kvs', name: 'fields', addButtonText: '新增输出', draggable: false, keyItem: { label: '参数名称', }, valueItems: [ { type: 'select', name: 'type', label: '参数', required: true, selectFirst: true, options: [], }, ], }, ], ) const LlmAmisNode = (props: NodeProps) => AmisNode( props, 'normal', '大模型节点', '使用大模型对话', [ { type: 'select', name: 'model', label: '大模型', required: true, selectFirst: true, options: [ { label: 'Qwen3', value: 'qwen3', }, { label: 'Deepseek', value: 'deepseek', }, ], }, { type: 'textarea', name: 'systemPrompt', label: '系统提示词', required: true, }, ], ) const initialNodes: Node[] = [ { id: 'BMFP3Eov94', type: 'start-amis-node', position: {x: 10, y: 100}, data: {}, }, { id: 'PYK8LjduQ1', type: 'end-amis-node', position: {x: 500, y: 100}, data: {}, }, ] const initialEdges: Edge[] = [] const useStore = create<{ data: Record, getData: () => Record, setData: (data: Record) => void, getDataById: (id: string) => any, setDataById: (id: string, data: any) => void, }>((set, get) => ({ data: {}, getData: () => get().data, setData: (data) => set(data), getDataById: id => get().data[id], setDataById: (id, data) => { let updateData = get().data updateData[id] = data set({ data: updateData, }) }, })) const useFlowStore = create<{ nodes: Node[], onNodesChange: OnNodesChange, addNode: (node: Node) => void, removeNode: (id: string) => void, setNodes: (nodes: Node[]) => void, edges: Edge[], onEdgesChange: OnEdgesChange, setEdges: (edges: Edge[]) => void, onConnect: OnConnect, }>((set, get) => ({ nodes: [], onNodesChange: changes => { set({ nodes: applyNodeChanges(changes, get().nodes), }) }, addNode: node => set({nodes: get().nodes.concat(node)}), removeNode: id => { set({ nodes: filter(get().nodes, node => !isEqual(node.id, id)) }) }, setNodes: nodes => set({nodes}), edges: [], onEdgesChange: changes => { set({ edges: applyEdgeChanges(changes, get().edges), }) }, setEdges: edges => set({edges}), onConnect: connection => { set({ edges: addEdge(connection, get().edges), }) }, })) function Test() { const [messageApi, contextHolder] = message.useMessage() const [nodeDef] = useState<{ key: string, name: string, component: (props: NodeProps) => JSX.Element }[]>([ { key: 'start-amis-node', name: '开始', component: StartAmisNode, }, { key: 'end-amis-node', name: '结束', component: EndAmisNode, }, { key: 'llm-amis-node', name: '大模型', component: LlmAmisNode, }, ]) const [open, setOpen] = useState(false) const onClose = () => setOpen(false) const {getData, getDataById, setDataById} = useStore() const { nodes, addNode, removeNode, setNodes, onNodesChange, edges, setEdges, onEdgesChange, onConnect, } = useFlowStore() const [currentNodeForm, setCurrentNodeForm] = useState() const editNode = (id: string, name: string, description: string, columnSchema?: Schema[]) => { if (!isNil(columnSchema)) { setCurrentNodeForm( amisRender( { debug: commonInfo.debug, title: name, type: 'form', wrapWithPanel: false, onEvent: { change: { actions: [ { actionType: 'custom', // @ts-ignore script: (context, action, event) => { console.log(id, context.props.data) setDataById(id, context.props.data) }, } ] } }, body: columnSchema, }, getDataById(id), ) ) setOpen(true) } } useMount(() => { for (let node of initialNodes) { node.data = { getDataById, setDataById, removeNode, editNode, } } setNodes(initialNodes) setEdges(initialEdges) }) return ( {contextHolder} ({key: def.key, label: def.name})), onClick: ({key}) => { if (isEqual(key, 'start-amis-node') && findIdx(nodes, (node: Node) => isEqual(key, node.type)) > -1) { messageApi.error('只能存在1个开始节点') return } if (isEqual(key, 'end-amis-node') && findIdx(nodes, (node: Node) => isEqual(key, node.type)) > -1) { messageApi.error('只能存在1个结束节点') return } addNode({ id: randomId(10), type: key, position: {x: 100, y: 100}, data: { getDataById, setDataById, removeNode, editNode, }, }) }, }} > {currentNodeForm} def.key), key => find(nodeDef, def => isEqual(key, def.key))!.component) } > ) } export default Test