import type { Edge, Node } from '@xyflow/svelte'; export const JOIN_MODE_ANY = 'any'; export const JOIN_MODE_ALL = 'all'; export type JoinMode = typeof JOIN_MODE_ANY | typeof JOIN_MODE_ALL; export type JoinModeAnalysis = { incomingCount: number; mode: JoinMode | null; invalidMode: boolean; allAllowed: boolean; allDisabledReason: string; }; const text = (value: unknown) => (value == null ? '' : String(value).trim()); export function parseJoinMode(value: unknown): JoinMode | null { if (value === undefined) { return JOIN_MODE_ANY; } const normalized = text(value).toLowerCase(); if (normalized === JOIN_MODE_ANY) { return JOIN_MODE_ANY; } if (normalized === JOIN_MODE_ALL) { return JOIN_MODE_ALL; } return null; } export function getJoinModeBadge(value: unknown) { return parseJoinMode(value) === JOIN_MODE_ALL ? '等待全部' : ''; } export function analyzeJoinMode( nodes: Node[], edges: Edge[], nodeId: string, ): JoinModeAnalysis { const targetNode = nodes.find((node) => node.id === nodeId); const directInward = edges.filter((edge) => edge.target === nodeId); const hasJoinMode = targetNode?.data ? Object.prototype.hasOwnProperty.call(targetNode.data, 'joinMode') : false; const mode = parseJoinMode(targetNode?.data?.joinMode); const invalidMode = hasJoinMode && mode === null; if (!targetNode) { return { incomingCount: directInward.length, mode, invalidMode, allAllowed: false, allDisabledReason: '节点不存在,请刷新画布后重试。', }; } if (text(targetNode.parentId)) { return { incomingCount: directInward.length, mode, invalidMode, allAllowed: false, allDisabledReason: '显式循环子图暂不支持等待全部上游,请使用“任一上游完成”。', }; } if (directInward.length <= 1) { return { incomingCount: directInward.length, mode, invalidMode, allAllowed: true, allDisabledReason: '', }; } const guaranteedNodes = findGuaranteedNodes(nodes, edges); const allAllowed = directInward.every( (edge) => !hasEdgeCondition(edge) && guaranteedNodes.has(edge.source), ); if (allAllowed) { return { incomingCount: directInward.length, mode, invalidMode, allAllowed: true, allDisabledReason: '', }; } let reason = '存在条件、互斥或无法证明必达的上游路径,可能永久等待。请调整连线或使用“任一上游完成”。'; if (directInward.some(hasEdgeCondition)) { reason = '存在带条件的直接入边,部分入边可能不会到达。请调整连线或使用“任一上游完成”。'; } else if (directInward.some((edge) => { const source = nodes.find((node) => node.id === edge.source); return hasAdvancedCondition(source); })) { reason = '上游节点包含高级执行条件,无法保证每条入边都会到达。请调整条件或使用“任一上游完成”。'; } return { incomingCount: directInward.length, mode, invalidMode, allAllowed: false, allDisabledReason: reason, }; } function findGuaranteedNodes(nodes: Node[], edges: Edge[]) { const guaranteed = new Set( nodes .filter((node) => !text(node.parentId) && node.type === 'startNode') .map((node) => node.id), ); const inwardByTarget = new Map(); edges.forEach((edge) => { const inward = inwardByTarget.get(edge.target) || []; inward.push(edge); inwardByTarget.set(edge.target, inward); }); let changed = true; while (changed) { changed = false; nodes.forEach((node) => { if ( text(node.parentId) || guaranteed.has(node.id) || hasAdvancedCondition(node) ) { return; } const mode = parseJoinMode(node.data?.joinMode); if (!mode) { return; } const inward = inwardByTarget.get(node.id) || []; const isGuaranteed = mode === JOIN_MODE_ALL ? inward.length > 0 && inward.every( (edge) => !hasEdgeCondition(edge) && guaranteed.has(edge.source), ) : inward.some( (edge) => !hasEdgeCondition(edge) && guaranteed.has(edge.source), ); if (isGuaranteed) { guaranteed.add(node.id); changed = true; } }); } return guaranteed; } function hasAdvancedCondition(node: Node | undefined) { return Boolean(text(node?.data?.condition)); } function hasEdgeCondition(edge: Edge) { return Boolean(text(edge.data?.condition)); }