import {type Connection, type Edge, getOutgoers, type Node} from '@xyflow/react' import {find, has, isEmpty, isEqual, lpad, toStr} from 'licia' import NodeRegistry from './NodeRegistry.tsx' export class CheckError extends Error { readonly id: string constructor( id: number, message: string, ) { super(message) this.id = `E${lpad(toStr(id), 6, '0')}` } public toString(): string { return `${this.id}: ${this.message}` } } const getNodeById = (id: string, nodes: Node[]) => find(nodes, (n: Node) => isEqual(n.id, id)) // @ts-ignore export const checkAddNode: (type: string, nodes: Node[], edges: Edge[]) => void = (type, nodes, edges) => { } export const sourceNodeNotFoundError = () => new CheckError(200, '连线起始节点未找到') export const targetNodeNotFoundError = () => new CheckError(201, '连线目标节点未找到') export const nodeToSelfError = () => new CheckError(203, '节点不能直连自身') export const hasCycleError = () => new CheckError(204, '禁止流程循环') const hasCycle = (sourceNode: Node, targetNode: Node, nodes: Node[], edges: Edge[], visited = new Set()) => { if (visited.has(targetNode.id)) return false visited.add(targetNode.id) for (const outgoer of getOutgoers(targetNode, nodes, edges)) { if (isEqual(outgoer.id, sourceNode.id)) return true if (hasCycle(sourceNode, outgoer, nodes, edges, visited)) return true } } export const checkAddConnection: (connection: Connection, nodes: Node[], edges: Edge[]) => void = (connection, nodes, edges) => { let sourceNode = getNodeById(connection.source, nodes) if (!sourceNode) { throw sourceNodeNotFoundError() } let targetNode = getNodeById(connection.target, nodes) if (!targetNode) { throw targetNodeNotFoundError() } // 禁止流程出现环,必须是有向无环图 if (isEqual(sourceNode.id, targetNode.id)) { throw nodeToSelfError() } else if (hasCycle(sourceNode, targetNode, nodes, edges)) { throw hasCycleError() } // let newEdges = [...clone(edges), {...connection, id: uuid()}] // let {hasAbnormalEdges} = getParallelInfo(nodes, newEdges) // if (hasAbnormalEdges) { // throw hasRedundantEdgeError() // } } export const atLeastOneNode = () => new CheckError(300, '至少包含一个节点') export const hasUnfinishedNode = (nodeId: string) => new CheckError(301, `存在尚未配置完成的节点: ${nodeId}`) export const nodeTypeNotFound = () => new CheckError(302, '节点类型不存在') export const nodeError = (nodeId: string, reason?: string) => new CheckError(303, reason ?? `节点配置存在错误:${nodeId}`) // @ts-ignore export const checkSave: (inputSchema: Record>, nodes: Node[], edges: Edge[], data: any) => void = (inputSchema, nodes, edges, data) => { if (isEmpty(nodes)) { throw atLeastOneNode() } for (let node of nodes) { let nodeId = node.id if (!has(data, nodeId) || !data[nodeId]?.finished) { throw hasUnfinishedNode(nodeId) } if (!has(node, 'type')) { throw nodeTypeNotFound() } let nodeType = node.type! let nodeDefine = NodeRegistry[nodeType] for (let checker of nodeDefine.checkers) { let checkResult = checker(nodeId, inputSchema, nodes, edges, data) if (checkResult.error) { throw nodeError(nodeId, checkResult.message) } } } }