fix: implemented cycle check on isValidConnection (#8716)

* Implemented cycle check on isValidConnection

* Implemented loop check if the loop done results in a loopcomponent loop
This commit is contained in:
Lucas Oliveira 2025-06-26 11:13:11 -03:00 committed by GitHub
commit 39af1ded5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -329,10 +329,11 @@ export function unselectAllNodesEdges(nodes: Node[], edges: Edge[]) {
}
export function isValidConnection(
{ source, target, sourceHandle, targetHandle }: Connection,
connection: Connection,
nodes?: AllNodeType[],
edges?: EdgeType[],
): boolean {
const { source, target, sourceHandle, targetHandle } = connection;
if (source === target) {
return false;
}
@ -342,6 +343,33 @@ export function isValidConnection(
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
// Helper to find the edge between two nodes
function findEdgeBetween(srcId: string, tgtId: string) {
return edgesArray.find((e) => e.source === srcId && e.target === tgtId);
}
// Modified hasCycle to return the path of edges forming the loop
const findCyclePath = (
node: AllNodeType,
visited = new Set(),
path: EdgeType[] = [],
): EdgeType[] | null => {
if (visited.has(node.id)) return null;
visited.add(node.id);
for (const outgoer of getOutgoers(node, nodesArray, edgesArray)) {
const edge = findEdgeBetween(node.id, outgoer.id);
if (!edge) continue;
if (outgoer.id === source) {
// This edge would close the loop
return [...path, edge];
}
const result = findCyclePath(outgoer, visited, [...path, edge]);
if (result) return result;
}
return null;
};
if (
targetHandleObject.inputTypes?.some(
(n) => n === sourceHandleObject.dataType,
@ -359,22 +387,43 @@ export function isValidConnection(
t === targetHandleObject.type,
)
) {
let targetNode = nodesArray.find((node) => node.id === target!)?.data?.node;
if (!targetNode) {
if (!edgesArray.find((e) => e.targetHandle === targetHandle)) {
let targetNode = nodesArray.find((node) => node.id === target!);
let targetNodeDataNode = targetNode?.data?.node;
if (
(!targetNodeDataNode &&
!edgesArray.find((e) => e.targetHandle === targetHandle)) ||
(targetNodeDataNode &&
targetHandleObject.output_types &&
!edgesArray.find((e) => e.targetHandle === targetHandle)) ||
(targetNodeDataNode &&
!targetHandleObject.output_types &&
((!targetNodeDataNode.template[targetHandleObject.fieldName].list &&
!edgesArray.find((e) => e.targetHandle === targetHandle)) ||
targetNodeDataNode.template[targetHandleObject.fieldName].list))
) {
// If the current target handle is a loop component, allow connection immediately
if (targetHandleObject.output_types) {
return true;
}
} else if (
targetHandleObject.output_types &&
!edgesArray.find((e) => e.targetHandle === targetHandle)
) {
return true;
} else if (
!targetHandleObject.output_types &&
((!targetNode.template[targetHandleObject.fieldName].list &&
!edgesArray.find((e) => e.targetHandle === targetHandle)) ||
targetNode.template[targetHandleObject.fieldName].list)
) {
// Check for loop and if any edge in the loop is a loop component
let cyclePath: EdgeType[] | null = null;
if (targetNode) {
cyclePath = findCyclePath(targetNode);
}
if (cyclePath) {
// Check if any edge in the cycle path is a loop component
const hasLoopComponent = cyclePath.some((edge) => {
try {
const th = scapeJSONParse(edge.targetHandle!);
return !!th.output_types;
} catch {
return false;
}
});
if (!hasLoopComponent) {
return false;
}
}
return true;
}
}