import {type Edge, getIncomers, type Node} from '@xyflow/react' import type {Option} from 'amis/lib/Schema' import {find, has, isEqual, max, min, unique} from 'licia' import {type DependencyList, type MouseEvent as ReactMouseEvent, useCallback, useRef} from 'react' import Queue from 'yocto-queue' import {originTypeMap} from '../../pages/ai/task/InputSchema.tsx' import {useFlowStore} from './store/FlowStore.ts' import {type OutputVariable} from './types.ts' export const getAllIncomerNodeById: (id: string, nodes: Node[], edges: Edge[]) => string[] = (id, nodes, edges) => { let queue = new Queue() queue.enqueue(find(nodes, node => isEqual(node.id, id))!) let result: string[] = [] while (queue.size !== 0) { let currentNode = queue.dequeue()! for (const incomer of getIncomers(currentNode, nodes, edges)) { result.push(incomer.id) queue.enqueue(incomer) } } return unique(result, (a, b) => isEqual(a, b)) } export const getAllIncomerNodeOutputVariables: (id: string, inputSchema: Record>, nodes: Node[], edges: Edge[], data: any) => OutputVariable[] = (id, inputSchema, nodes, edges, data) => { let inputSchemaVariables: OutputVariable[] = Object.keys(inputSchema).map(key => ({ group: '流程入参', name: `${key}${inputSchema[key]?.label ? ` (${inputSchema[key].label})` : ''}`, type: originTypeMap[inputSchema[key]?.type ?? ''], variable: key, })) let currentNode = find(nodes, n => isEqual(id, n.id)) if (!currentNode) { return inputSchemaVariables } let incomerIds = getAllIncomerNodeById(id, nodes, edges) if (currentNode.parentId) { incomerIds = [ ...incomerIds, ...getAllIncomerNodeById(currentNode.parentId, nodes, edges), ] } let incomerVariables: OutputVariable[] = [] for (const incomerId of incomerIds) { let nodeData = data[incomerId] ?? {} let group = incomerId if (has(nodeData, 'node') && has(nodeData.node, 'name')) { group = `${nodeData.node.name} ${incomerId}` } if (has(nodeData, 'outputs')) { let outputs = nodeData?.outputs ?? {} for (const key of Object.keys(outputs)) { incomerVariables.push({ group: group, name: key, type: outputs[key].type, variable: `${incomerId}.${key}`, }) } } } return [ ...inputSchemaVariables, ...(currentNode.parentId ? [ { group: '循环入参', name: 'loopIndex (当前迭代索引)', type: 'number', variable: 'loopIndex', } as OutputVariable, { group: '循环入参', name: 'loopItem (当前迭代对象)', type: 'object', variable: 'loopItem', } as OutputVariable, ] : []), ...incomerVariables, ] } export const generateAllIncomerOutputVariablesFormOptions: (id: string, inputSchema: Record>, nodes: Node[], edges: Edge[], data: any) => Option[] = (id, inputSchema, nodes, edges, data) => { let optionMap: Record = {} for (const item of getAllIncomerNodeOutputVariables(id, inputSchema, nodes, edges, data)) { if (!optionMap[item.group]) { optionMap[item.group] = [] } optionMap[item.group].push({ label: item.name, value: item.variable, }) } return Object.keys(optionMap) .map(key => ({ label: key, children: optionMap[key], })) } type ConditionOperator = string | { label: string, value: string } const textOperators: ConditionOperator[] = ['equal', 'not_equal', 'is_empty', 'is_not_empty', 'like', 'not_like', 'starts_with', 'ends_with'] const textDefaultOperator: string = 'equal' const booleanOperators: ConditionOperator[] = [ {label: '为真', value: 'is_true'}, {label: '为假', value: 'is_false'}, ] const booleanDefaultOperator: string = 'is_true' const numberOperators: ConditionOperator[] = [ 'equal', 'not_equal', {label: '大于', value: 'greater'}, {label: '大于或等于', value: 'greater_equal'}, {label: '小于', value: 'less'}, {label: '小于或等于', value: 'less_equal'}, ] const numberDefaultOperator: string = 'equal' export const generateAllIncomerOutputVariablesConditions: (id: string, inputSchema: Record>, nodes: Node[], edges: Edge[], data: any) => Option[] = (id, inputSchema, nodes, edges, data) => { let optionMap: Record = {} for (const item of getAllIncomerNodeOutputVariables(id, inputSchema, nodes, edges, data)) { if (!optionMap[item.group]) { optionMap[item.group] = [] } optionMap[item.group].push({ label: item.name, type: 'custom', name: item.variable, ...(item.type === 'text' ? { value: { type: 'input-text', required: true, clearable: true, }, defaultOp: textDefaultOperator, operators: textOperators, } : {}), ...(item.type === 'boolean' ? { value: { type: 'wrapper', size: 'none', }, defaultOp: booleanDefaultOperator, operators: booleanOperators, } : {}), ...(item.type === 'number' ? { value: { type: 'input-number', required: true, clearable: true, }, defaultOp: numberDefaultOperator, operators: numberOperators, } : {}), }) } return Object.keys(optionMap) .map(key => ({ label: key, children: optionMap[key], })) } // 处理循环节点的边界问题 export const useNodeDrag = (deps: DependencyList) => { const currentPosition = useRef({x: 0, y: 0} as { x: number, y: number }) const {setNode, getNodeById} = useFlowStore() const onNodeDragStart = useCallback(() => { }, deps) const onNodeDrag = useCallback((event: ReactMouseEvent, node: Node) => { event.stopPropagation() if (node.parentId) { let parentNode = getNodeById(node.parentId) if (parentNode) { let newPosition = { x: max(min(node.position.x, (parentNode.measured?.width ?? 0) - (node.measured?.width ?? 0) - 28), 28), y: max(min(node.position.y, (parentNode.measured?.height ?? 0) - (node.measured?.height ?? 0) - 28), 130), } setNode({ ...node, position: newPosition, }) currentPosition.current = newPosition } } }, deps) const onNodeDragEnd = useCallback((_event: ReactMouseEvent, node: Node) => { if (node.parentId) { setNode({ ...node, position: currentPosition.current, }) } }, deps) return { onNodeDragStart, onNodeDrag, onNodeDragEnd, } }