evo-ai/frontend/app/agents/workflows/utils.ts

200 lines
8.5 KiB
TypeScript

/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/agents/workflows/utils.ts │
│ Developed by: Davidson Gomes │
│ Creation date: May 13, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
*/
import { Node, NodePositionChange, XYPosition } from "@xyflow/react";
type GetHelperLinesResult = {
horizontal?: number;
vertical?: number;
snapPosition: Partial<XYPosition>;
};
// this utility function can be called with a position change (inside onNodesChange)
// it checks all other nodes and calculated the helper line positions and the position where the current node should snap to
export function getHelperLines(
change: NodePositionChange,
nodes: Node[],
distance = 5,
): GetHelperLinesResult {
const defaultResult = {
horizontal: undefined,
vertical: undefined,
snapPosition: { x: undefined, y: undefined },
};
const nodeA = nodes.find((node) => node.id === change.id);
if (!nodeA || !change.position) {
return defaultResult;
}
const nodeABounds = {
left: change.position.x,
right: change.position.x + (nodeA.measured?.width ?? 0),
top: change.position.y,
bottom: change.position.y + (nodeA.measured?.height ?? 0),
width: nodeA.measured?.width ?? 0,
height: nodeA.measured?.height ?? 0,
};
let horizontalDistance = distance;
let verticalDistance = distance;
return nodes
.filter((node) => node.id !== nodeA.id)
.reduce<GetHelperLinesResult>((result, nodeB) => {
const nodeBBounds = {
left: nodeB.position.x,
right: nodeB.position.x + (nodeB.measured?.width ?? 0),
top: nodeB.position.y,
bottom: nodeB.position.y + (nodeB.measured?.height ?? 0),
width: nodeB.measured?.width ?? 0,
height: nodeB.measured?.height ?? 0,
};
// |‾‾‾‾‾‾‾‾‾‾‾|
// | A |
// |___________|
// |
// |
// |‾‾‾‾‾‾‾‾‾‾‾|
// | B |
// |___________|
const distanceLeftLeft = Math.abs(nodeABounds.left - nodeBBounds.left);
if (distanceLeftLeft < verticalDistance) {
result.snapPosition.x = nodeBBounds.left;
result.vertical = nodeBBounds.left;
verticalDistance = distanceLeftLeft;
}
// |‾‾‾‾‾‾‾‾‾‾‾|
// | A |
// |___________|
// |
// |
// |‾‾‾‾‾‾‾‾‾‾‾|
// | B |
// |___________|
const distanceRightRight = Math.abs(
nodeABounds.right - nodeBBounds.right,
);
if (distanceRightRight < verticalDistance) {
result.snapPosition.x = nodeBBounds.right - nodeABounds.width;
result.vertical = nodeBBounds.right;
verticalDistance = distanceRightRight;
}
// |‾‾‾‾‾‾‾‾‾‾‾|
// | A |
// |___________|
// |
// |
// |‾‾‾‾‾‾‾‾‾‾‾|
// | B |
// |___________|
const distanceLeftRight = Math.abs(nodeABounds.left - nodeBBounds.right);
if (distanceLeftRight < verticalDistance) {
result.snapPosition.x = nodeBBounds.right;
result.vertical = nodeBBounds.right;
verticalDistance = distanceLeftRight;
}
// |‾‾‾‾‾‾‾‾‾‾‾|
// | A |
// |___________|
// |
// |
// |‾‾‾‾‾‾‾‾‾‾‾|
// | B |
// |___________|
const distanceRightLeft = Math.abs(nodeABounds.right - nodeBBounds.left);
if (distanceRightLeft < verticalDistance) {
result.snapPosition.x = nodeBBounds.left - nodeABounds.width;
result.vertical = nodeBBounds.left;
verticalDistance = distanceRightLeft;
}
// |‾‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾‾|
// | A | | B |
// |___________| |___________|
const distanceTopTop = Math.abs(nodeABounds.top - nodeBBounds.top);
if (distanceTopTop < horizontalDistance) {
result.snapPosition.y = nodeBBounds.top;
result.horizontal = nodeBBounds.top;
horizontalDistance = distanceTopTop;
}
// |‾‾‾‾‾‾‾‾‾‾‾|
// | A |
// |___________|_________________
// | |
// | B |
// |___________|
const distanceBottomTop = Math.abs(nodeABounds.bottom - nodeBBounds.top);
if (distanceBottomTop < horizontalDistance) {
result.snapPosition.y = nodeBBounds.top - nodeABounds.height;
result.horizontal = nodeBBounds.top;
horizontalDistance = distanceBottomTop;
}
// |‾‾‾‾‾‾‾‾‾‾‾| |‾‾‾‾‾‾‾‾‾‾‾|
// | A | | B |
// |___________|_____|___________|
const distanceBottomBottom = Math.abs(
nodeABounds.bottom - nodeBBounds.bottom,
);
if (distanceBottomBottom < horizontalDistance) {
result.snapPosition.y = nodeBBounds.bottom - nodeABounds.height;
result.horizontal = nodeBBounds.bottom;
horizontalDistance = distanceBottomBottom;
}
// |‾‾‾‾‾‾‾‾‾‾‾|
// | B |
// | |
// |‾‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// | A |
// |___________|
const distanceTopBottom = Math.abs(nodeABounds.top - nodeBBounds.bottom);
if (distanceTopBottom < horizontalDistance) {
result.snapPosition.y = nodeBBounds.bottom;
result.horizontal = nodeBBounds.bottom;
horizontalDistance = distanceTopBottom;
}
return result;
}, defaultResult);
}