import { type Edge, type Node, useNodesData, useStore } from '@xyflow/svelte'; import type { Parameter } from '#types'; import { getCurrentNodeId, getOptions } from '#components/utils/NodeUtils'; import { getStartNodeParameterLabel } from '#components/utils/startNodeParameterLabel'; import { nodeIcons } from '../../consts'; import { buildLoopReferenceParameters, buildLoopScopeParameters, isArrayDataType, projectParameterDataType, } from '../../utils/loopScope'; const fillRefNodeIds = ( refNodeIds: string[], currentNodeId: string, edges: Edge[], ) => { for (const edge of edges) { if (edge.target === currentNodeId && edge.source) { refNodeIds.push(edge.source); fillRefNodeIds(refNodeIds, edge.source, edges); } } }; const getChildren = ( params: any, parentId: string, nodeType: string, parentPathLabel = '', ancestorCollectionDepth = 0, parentIsCollection = false, ) => { if (!params || params.length === 0) return []; return params.map((param: any) => { const isCollection = isArrayDataType(param.dataType) && param.children && param.children.length > 0; const childBaseLabel = param.formLabel || param.displayName || param.name; const normalizedChildLabel = String(childBaseLabel || '').trim(); const pathLabel = !parentPathLabel ? normalizedChildLabel : parentIsCollection ? `${parentPathLabel}.[].${normalizedChildLabel}` : `${parentPathLabel}.${normalizedChildLabel}`; const dataType = projectParameterDataType( param, ancestorCollectionDepth, ); const nextCollectionDepth = ancestorCollectionDepth + (isCollection ? 1 : 0); return { label: pathLabel, dataType, value: parentId + '.' + param.name, selectable: true, nodeType: nodeType, displayLabel: pathLabel, pathLabel, itemTypeLabel: ancestorCollectionDepth > 0 ? '数组项字段' : undefined, isCollection, children: getChildren( param.children, parentId + '.' + param.name, nodeType, pathLabel, nextCollectionDepth, isCollection, ), }; }); }; const nodeToOptions = ( node: Node, currentNode: Node, nodes: Node[], ) => { const options = getOptions(); const nodeType = node.type || ''; let icon: string | undefined = nodeIcons[nodeType]; if (!icon && options?.customNodes && options.customNodes[nodeType]) { icon = options.customNodes[nodeType].icon; } // 如果仍然获取不到,尝试使用 data.icon (作为回退) if (!icon && node.data && node.data.icon) { icon = node.data.icon as string; } const title = node.data.title; if (nodeType === 'startNode') { const parameters = node.data.parameters as Array; const children = []; if (parameters) for (const parameter of parameters) { const label = getStartNodeParameterLabel(parameter); children.push({ label, dataType: projectParameterDataType(parameter), value: node.id + '.' + parameter.name, selectable: true, nodeType: nodeType, }); } return { label: title, icon: icon, value: node.id, selectable: false, nodeType: nodeType, children, }; } else if (nodeType === 'loopNode') { const referenceParameters = buildLoopReferenceParameters( node, currentNode, nodes, ); if (!referenceParameters.length) { return undefined; } return { label: title, icon: icon, value: node.id, selectable: false, nodeType: nodeType, children: getChildren( referenceParameters, node.id, nodeType, ), }; } else { const outputDefs = node.data.outputDefs; if (outputDefs) { return { label: title, icon: icon, value: node.id, selectable: false, nodeType: nodeType, children: getChildren(outputDefs, node.id, nodeType), }; } } }; export const useRefOptions: any = ( useChildrenOnly: boolean | (() => boolean) = false, currentRef: string | (() => string) = '', ) => { const currentNodeId = getCurrentNodeId(); const currentNode = useNodesData(currentNodeId); const { nodes, edges, nodeLookup } = $derived(useStore()); const isChildrenOnly = () => typeof useChildrenOnly === 'function' ? useChildrenOnly() : useChildrenOnly; const getCurrentRef = () => typeof currentRef === 'function' ? currentRef() : currentRef; let selectItems = $derived.by(() => { const resultOptions = []; if (!currentNode.current) { return { items: [], selected: undefined, }; } //通过 nodeLookup.get 才会得到有 parentId 的 node const cNode = nodeLookup.get(currentNodeId)!; if (isChildrenOnly()) { for (const node of nodes) { if (node.parentId === currentNode.current.id) { const nodeOptions = nodeToOptions( node, cNode, nodes, ); nodeOptions && resultOptions.push(nodeOptions); } } } else { const refNodeIds: string[] = []; fillRefNodeIds(refNodeIds, currentNodeId, edges); for (const node of nodes) { const isScopedLoop = node.type === 'loopNode' && buildLoopScopeParameters(node, cNode, nodes).length > 0; if (refNodeIds.includes(node.id) || isScopedLoop) { const nodeOptions = nodeToOptions( node, cNode, nodes, ); nodeOptions && resultOptions.push(nodeOptions); } } } const items = resultOptions; const stack = [...items]; let selected; const currentValue = getCurrentRef(); // 单次深度优先扫描定位当前引用,避免为每个参数额外构建全量索引。 while (stack.length > 0) { const item = stack.pop(); if (item?.value === currentValue) { selected = item; break; } if (item?.children?.length) { stack.push(...item.children); } } return { items, selected }; }); return { get current() { return selectItems.items; }, get selected() { return selectItems.selected; }, }; };