mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-03-17 03:00:27 -04:00
Compare commits
6 Commits
fix/copilo
...
otto/secrt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dee131bc1f | ||
|
|
4e54af28aa | ||
|
|
4e7790aeee | ||
|
|
11f25fa6e7 | ||
|
|
3da169457f | ||
|
|
f6d8e5a715 |
@@ -867,9 +867,67 @@ class GraphModel(Graph, GraphMeta):
|
||||
|
||||
return node_errors
|
||||
|
||||
@staticmethod
|
||||
def prune_invalid_links(graph: BaseGraph) -> int:
|
||||
"""
|
||||
Remove invalid/orphan links from the graph.
|
||||
|
||||
This removes links that:
|
||||
- Reference non-existent source or sink nodes
|
||||
- Reference invalid block IDs
|
||||
|
||||
Note: Pin name validation is handled separately in _validate_graph_structure.
|
||||
|
||||
Returns the number of links pruned.
|
||||
"""
|
||||
node_map = {v.id: v for v in graph.nodes}
|
||||
original_count = len(graph.links)
|
||||
valid_links = []
|
||||
|
||||
for link in graph.links:
|
||||
source_node = node_map.get(link.source_id)
|
||||
sink_node = node_map.get(link.sink_id)
|
||||
|
||||
# Skip if either node doesn't exist
|
||||
if not source_node or not sink_node:
|
||||
logger.warning(
|
||||
f"Pruning orphan link: source={link.source_id}, sink={link.sink_id} "
|
||||
f"- node(s) not found"
|
||||
)
|
||||
continue
|
||||
|
||||
# Skip if source block doesn't exist
|
||||
source_block = get_block(source_node.block_id)
|
||||
if not source_block:
|
||||
logger.warning(
|
||||
f"Pruning link with invalid source block: {source_node.block_id}"
|
||||
)
|
||||
continue
|
||||
|
||||
# Skip if sink block doesn't exist
|
||||
sink_block = get_block(sink_node.block_id)
|
||||
if not sink_block:
|
||||
logger.warning(
|
||||
f"Pruning link with invalid sink block: {sink_node.block_id}"
|
||||
)
|
||||
continue
|
||||
|
||||
valid_links.append(link)
|
||||
|
||||
graph.links = valid_links
|
||||
pruned_count = original_count - len(valid_links)
|
||||
|
||||
if pruned_count > 0:
|
||||
logger.info(f"Pruned {pruned_count} invalid link(s) from graph {graph.id}")
|
||||
|
||||
return pruned_count
|
||||
|
||||
@staticmethod
|
||||
def _validate_graph_structure(graph: BaseGraph):
|
||||
"""Validate graph structure (links, connections, etc.)"""
|
||||
# First, prune invalid links to clean up orphan edges
|
||||
GraphModel.prune_invalid_links(graph)
|
||||
|
||||
node_map = {v.id: v for v in graph.nodes}
|
||||
|
||||
def is_static_output_block(nid: str) -> bool:
|
||||
|
||||
@@ -133,22 +133,23 @@ export const useFlow = () => {
|
||||
}
|
||||
}, [availableGraphs, setAvailableSubGraphs]);
|
||||
|
||||
// adding nodes
|
||||
// adding nodes and links together to avoid race condition
|
||||
// Links depend on nodes existing, so we must add nodes first
|
||||
useEffect(() => {
|
||||
if (customNodes.length > 0) {
|
||||
// Clear both stores to prevent stale data from previous graphs
|
||||
useNodeStore.getState().setNodes([]);
|
||||
useNodeStore.getState().clearResolutionState();
|
||||
addNodes(customNodes);
|
||||
}
|
||||
}, [customNodes, addNodes]);
|
||||
|
||||
// adding links
|
||||
useEffect(() => {
|
||||
if (graph?.links) {
|
||||
useEdgeStore.getState().setEdges([]);
|
||||
addLinks(graph.links);
|
||||
|
||||
addNodes(customNodes);
|
||||
|
||||
// Only add links after nodes are in the store
|
||||
if (graph?.links) {
|
||||
addLinks(graph.links);
|
||||
}
|
||||
}
|
||||
}, [graph?.links, addLinks]);
|
||||
}, [customNodes, graph?.links, addNodes, addLinks]);
|
||||
|
||||
useEffect(() => {
|
||||
if (customNodes.length > 0 && graph?.links) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { NodeExecutionResult } from "@/app/api/__generated__/models/nodeExecutio
|
||||
import { cleanUpHandleId } from "@/components/renderers/InputRenderer/helpers";
|
||||
import { useHistoryStore } from "./historyStore";
|
||||
import { useNodeStore } from "./nodeStore";
|
||||
import { filterValidEdges, filterValidLinks } from "./linkValidations";
|
||||
|
||||
type EdgeStore = {
|
||||
edges: CustomEdge[];
|
||||
@@ -120,12 +121,47 @@ export const useEdgeStore = create<EdgeStore>((set, get) => ({
|
||||
isOutputConnected: (nodeId, handle) =>
|
||||
get().edges.some((e) => e.source === nodeId && e.sourceHandle === handle),
|
||||
|
||||
getBackendLinks: () => get().edges.map(customEdgeToLink),
|
||||
getBackendLinks: () => {
|
||||
const nodeIds = new Set(useNodeStore.getState().nodes.map((n) => n.id));
|
||||
const validEdges = filterValidEdges(get().edges, nodeIds);
|
||||
return validEdges.map(customEdgeToLink);
|
||||
},
|
||||
|
||||
addLinks: (links) => {
|
||||
links.forEach((link) => {
|
||||
get().addEdge(linkToCustomEdge(link));
|
||||
});
|
||||
const nodeIds = new Set(useNodeStore.getState().nodes.map((n) => n.id));
|
||||
const validLinks = filterValidLinks(links, nodeIds);
|
||||
|
||||
// Convert validated links to edges, avoiding individual addEdge calls
|
||||
// which would push to history for each edge (causing history pollution)
|
||||
const newEdges: CustomEdge[] = [];
|
||||
const existingEdges = get().edges;
|
||||
|
||||
for (const link of validLinks) {
|
||||
const edge = linkToCustomEdge(link);
|
||||
|
||||
// Skip if edge already exists
|
||||
const exists = existingEdges.some(
|
||||
(e) =>
|
||||
e.source === edge.source &&
|
||||
e.target === edge.target &&
|
||||
e.sourceHandle === edge.sourceHandle &&
|
||||
e.targetHandle === edge.targetHandle,
|
||||
);
|
||||
if (!exists) {
|
||||
newEdges.push(edge);
|
||||
}
|
||||
}
|
||||
|
||||
if (newEdges.length > 0) {
|
||||
// Bulk add all edges at once, pushing to history only once
|
||||
const prevState = {
|
||||
nodes: useNodeStore.getState().nodes,
|
||||
edges: existingEdges,
|
||||
};
|
||||
|
||||
set((state) => ({ edges: [...state.edges, ...newEdges] }));
|
||||
useHistoryStore.getState().pushState(prevState);
|
||||
}
|
||||
},
|
||||
|
||||
getAllHandleIdsOfANode: (nodeId) =>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Link } from "@/app/api/__generated__/models/link";
|
||||
import { CustomEdge } from "../components/FlowEditor/edges/CustomEdge";
|
||||
|
||||
/**
|
||||
* Filter out edges that reference non-existent nodes.
|
||||
* Used before sending edges to the backend during save.
|
||||
*/
|
||||
export function filterValidEdges(
|
||||
edges: CustomEdge[],
|
||||
nodeIds: Set<string>,
|
||||
): CustomEdge[] {
|
||||
return edges.filter((edge) => {
|
||||
const isValid = nodeIds.has(edge.source) && nodeIds.has(edge.target);
|
||||
if (!isValid) {
|
||||
console.warn(
|
||||
`[linkValidations] Filtering out invalid edge during save: source=${edge.source}, target=${edge.target}`,
|
||||
);
|
||||
}
|
||||
return isValid;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out links that reference non-existent nodes.
|
||||
* Used when loading links from the backend to prevent orphan edges.
|
||||
*/
|
||||
export function filterValidLinks(links: Link[], nodeIds: Set<string>): Link[] {
|
||||
return links.filter((link) => {
|
||||
const isValid = nodeIds.has(link.source_id) && nodeIds.has(link.sink_id);
|
||||
if (!isValid) {
|
||||
console.warn(
|
||||
`[linkValidations] Skipping invalid link: source=${link.source_id}, sink=${link.sink_id} - node(s) not found`,
|
||||
);
|
||||
}
|
||||
return isValid;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user