mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
refactor(builder): Fix linting warning and errors (#8021)
* Fix lint errors * Fix dependency loop
This commit is contained in:
committed by
GitHub
parent
e17ea22a0a
commit
2618d1d87c
@@ -26,8 +26,8 @@ export default function Error({
|
||||
Oops, something went wrong!
|
||||
</h1>
|
||||
<p className="mt-4 text-muted-foreground">
|
||||
We're sorry, but an unexpected error has occurred. Please try again
|
||||
later or contact support if the issue persists.
|
||||
We're sorry, but an unexpected error has occurred. Please try
|
||||
again later or contact support if the issue persists.
|
||||
</p>
|
||||
<div className="mt-6 flex flex-row justify-center gap-4">
|
||||
<Button onClick={reset} variant="outline">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import AutoGPTServerAPI, {
|
||||
GraphMeta,
|
||||
@@ -22,54 +22,57 @@ const Monitor = () => {
|
||||
const [selectedFlow, setSelectedFlow] = useState<GraphMeta | null>(null);
|
||||
const [selectedRun, setSelectedRun] = useState<FlowRun | null>(null);
|
||||
|
||||
const api = new AutoGPTServerAPI();
|
||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
||||
|
||||
useEffect(() => fetchFlowsAndRuns(), []);
|
||||
const refreshFlowRuns = useCallback(
|
||||
(flowID: string) => {
|
||||
// Fetch flow run IDs
|
||||
api.listGraphRunIDs(flowID).then((runIDs) =>
|
||||
runIDs.map((runID) => {
|
||||
let run;
|
||||
if (
|
||||
(run = flowRuns.find((fr) => fr.id == runID)) &&
|
||||
!["waiting", "running"].includes(run.status)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch flow run
|
||||
api.getGraphExecutionInfo(flowID, runID).then((execInfo) =>
|
||||
setFlowRuns((flowRuns) => {
|
||||
if (execInfo.length == 0) return flowRuns;
|
||||
|
||||
const flowRunIndex = flowRuns.findIndex((fr) => fr.id == runID);
|
||||
const flowRun = flowRunFromNodeExecutionResults(execInfo);
|
||||
if (flowRunIndex > -1) {
|
||||
flowRuns.splice(flowRunIndex, 1, flowRun);
|
||||
} else {
|
||||
flowRuns.push(flowRun);
|
||||
}
|
||||
return [...flowRuns];
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
[api, flowRuns],
|
||||
);
|
||||
|
||||
const fetchFlowsAndRuns = useCallback(() => {
|
||||
api.listGraphs().then((flows) => {
|
||||
setFlows(flows);
|
||||
flows.map((flow) => refreshFlowRuns(flow.id));
|
||||
});
|
||||
}, [api, refreshFlowRuns]);
|
||||
|
||||
useEffect(() => fetchFlowsAndRuns(), [fetchFlowsAndRuns]);
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(
|
||||
() => flows.map((f) => refreshFlowRuns(f.id)),
|
||||
5000,
|
||||
);
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
function fetchFlowsAndRuns() {
|
||||
api.listGraphs().then((flows) => {
|
||||
setFlows(flows);
|
||||
flows.map((flow) => refreshFlowRuns(flow.id));
|
||||
});
|
||||
}
|
||||
|
||||
function refreshFlowRuns(flowID: string) {
|
||||
// Fetch flow run IDs
|
||||
api.listGraphRunIDs(flowID).then((runIDs) =>
|
||||
runIDs.map((runID) => {
|
||||
let run;
|
||||
if (
|
||||
(run = flowRuns.find((fr) => fr.id == runID)) &&
|
||||
!["waiting", "running"].includes(run.status)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch flow run
|
||||
api.getGraphExecutionInfo(flowID, runID).then((execInfo) =>
|
||||
setFlowRuns((flowRuns) => {
|
||||
if (execInfo.length == 0) return flowRuns;
|
||||
|
||||
const flowRunIndex = flowRuns.findIndex((fr) => fr.id == runID);
|
||||
const flowRun = flowRunFromNodeExecutionResults(execInfo);
|
||||
if (flowRunIndex > -1) {
|
||||
flowRuns.splice(flowRunIndex, 1, flowRun);
|
||||
} else {
|
||||
flowRuns.push(flowRun);
|
||||
}
|
||||
return [...flowRuns];
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [flows, refreshFlowRuns]);
|
||||
|
||||
const column1 = "md:col-span-2 xl:col-span-3 xxl:col-span-2";
|
||||
const column2 = "md:col-span-3 lg:col-span-2 xl:col-span-3 space-y-4";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import React, { useCallback, useContext, useEffect, useState } from "react";
|
||||
import {
|
||||
BaseEdge,
|
||||
EdgeLabelRenderer,
|
||||
@@ -65,24 +65,27 @@ export function CustomEdge({
|
||||
const beadDiameter = 12;
|
||||
const deltaTime = 16;
|
||||
|
||||
function setTargetPositions(beads: Bead[]) {
|
||||
const distanceBetween = Math.min(
|
||||
(length - beadDiameter) / (beads.length + 1),
|
||||
beadDiameter,
|
||||
);
|
||||
const setTargetPositions = useCallback(
|
||||
(beads: Bead[]) => {
|
||||
const distanceBetween = Math.min(
|
||||
(length - beadDiameter) / (beads.length + 1),
|
||||
beadDiameter,
|
||||
);
|
||||
|
||||
return beads.map((bead, index) => {
|
||||
const distanceFromEnd = beadDiameter * 1.35;
|
||||
const targetPosition = distanceBetween * index + distanceFromEnd;
|
||||
const t = getTForDistance(-targetPosition);
|
||||
return beads.map((bead, index) => {
|
||||
const distanceFromEnd = beadDiameter * 1.35;
|
||||
const targetPosition = distanceBetween * index + distanceFromEnd;
|
||||
const t = getTForDistance(-targetPosition);
|
||||
|
||||
return {
|
||||
...bead,
|
||||
t: visualizeBeads === "animate" ? bead.t : t,
|
||||
targetT: t,
|
||||
} as Bead;
|
||||
});
|
||||
}
|
||||
return {
|
||||
...bead,
|
||||
t: visualizeBeads === "animate" ? bead.t : t,
|
||||
targetT: t,
|
||||
} as Bead;
|
||||
});
|
||||
},
|
||||
[getTForDistance, length, visualizeBeads],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.beadUp === 0 && data?.beadDown === 0) {
|
||||
@@ -170,7 +173,7 @@ export function CustomEdge({
|
||||
}, deltaTime);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [data]);
|
||||
}, [data, setTargetPositions, visualizeBeads]);
|
||||
|
||||
const middle = getPointForT(0.5);
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ export function CustomNode({ data, id, width, height }: NodeProps<CustomNode>) {
|
||||
|
||||
useEffect(() => {
|
||||
setIsAnyModalOpen?.(isModalOpen || isOutputModalOpen);
|
||||
}, [isModalOpen, isOutputModalOpen, data]);
|
||||
}, [isModalOpen, isOutputModalOpen, data, setIsAnyModalOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
isInitialSetup.current = false;
|
||||
|
||||
@@ -121,7 +121,7 @@ const FlowEditor: React.FC<{
|
||||
localStorage.setItem("shepherd-tour", "yes");
|
||||
}
|
||||
}
|
||||
}, [availableNodes, tutorialStarted]);
|
||||
}, [availableNodes, tutorialStarted, router, pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
@@ -256,7 +256,7 @@ const FlowEditor: React.FC<{
|
||||
}
|
||||
|
||||
const edgeColor = getTypeColor(
|
||||
getOutputType(connection.source!, connection.sourceHandle!),
|
||||
getOutputType(nodes, connection.source!, connection.sourceHandle!),
|
||||
);
|
||||
const sourceNode = getNode(connection.source!);
|
||||
const newEdge: CustomEdge = {
|
||||
@@ -295,6 +295,7 @@ const FlowEditor: React.FC<{
|
||||
addEdges,
|
||||
deleteElements,
|
||||
clearNodesStatusAndOutput,
|
||||
nodes,
|
||||
edges,
|
||||
formatEdgeID,
|
||||
getOutputType,
|
||||
@@ -377,7 +378,7 @@ const FlowEditor: React.FC<{
|
||||
clearNodesStatusAndOutput();
|
||||
}
|
||||
},
|
||||
[setNodes, clearNodesStatusAndOutput],
|
||||
[setNodes, clearNodesStatusAndOutput, setEdges],
|
||||
);
|
||||
|
||||
const getNextNodeId = useCallback(() => {
|
||||
@@ -434,7 +435,6 @@ const FlowEditor: React.FC<{
|
||||
nodeId,
|
||||
availableNodes,
|
||||
addNodes,
|
||||
setNodes,
|
||||
deleteElements,
|
||||
clearNodesStatusAndOutput,
|
||||
x,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import AutoGPTServerAPI, { GraphMeta } from "@/lib/autogpt-server-api";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
@@ -45,10 +45,10 @@ export const AgentFlowList = ({
|
||||
className?: string;
|
||||
}) => {
|
||||
const [templates, setTemplates] = useState<GraphMeta[]>([]);
|
||||
const api = new AutoGPTServerAPI();
|
||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
||||
useEffect(() => {
|
||||
api.listTemplates().then((templates) => setTemplates(templates));
|
||||
}, []);
|
||||
}, [api]);
|
||||
|
||||
return (
|
||||
<Card className={className}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import AutoGPTServerAPI, {
|
||||
Graph,
|
||||
GraphMeta,
|
||||
@@ -28,7 +28,7 @@ export const FlowInfo: React.FC<
|
||||
flowVersion?: number | "all";
|
||||
}
|
||||
> = ({ flow, flowRuns, flowVersion, ...props }) => {
|
||||
const api = new AutoGPTServerAPI();
|
||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
||||
|
||||
const [flowVersions, setFlowVersions] = useState<Graph[] | null>(null);
|
||||
const [selectedVersion, setSelectedFlowVersion] = useState(
|
||||
@@ -41,7 +41,7 @@ export const FlowInfo: React.FC<
|
||||
|
||||
useEffect(() => {
|
||||
api.getGraphAllVersions(flow.id).then((result) => setFlowVersions(result));
|
||||
}, [flow.id]);
|
||||
}, [flow.id, api]);
|
||||
|
||||
return (
|
||||
<Card {...props}>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
BlockIONumberSubSchema,
|
||||
BlockIOBooleanSubSchema,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { FC, useCallback, useEffect, useState } from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Switch } from "./ui/switch";
|
||||
import {
|
||||
@@ -296,7 +296,7 @@ const NodeKeyValueInput: FC<{
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
const getPairValues = () => {
|
||||
const getPairValues = useCallback(() => {
|
||||
let defaultEntries = new Map<string, any>();
|
||||
|
||||
connections
|
||||
@@ -311,7 +311,7 @@ const NodeKeyValueInput: FC<{
|
||||
});
|
||||
|
||||
return Array.from(defaultEntries, ([key, value]) => ({ key, value }));
|
||||
};
|
||||
}, [connections, entries, schema.default, selfKey]);
|
||||
|
||||
const [keyValuePairs, setKeyValuePairs] = useState<
|
||||
{ key: string; value: string | number | null }[]
|
||||
@@ -319,7 +319,7 @@ const NodeKeyValueInput: FC<{
|
||||
|
||||
useEffect(
|
||||
() => setKeyValuePairs(getPairValues()),
|
||||
[connections, entries, schema.default],
|
||||
[connections, entries, schema.default, getPairValues],
|
||||
);
|
||||
|
||||
function updateKeyValuePairs(newPairs: typeof keyValuePairs) {
|
||||
|
||||
@@ -19,7 +19,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
) {
|
||||
ref.current.value = value;
|
||||
}
|
||||
}, [value, type]);
|
||||
}, [value, type, ref]);
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
|
||||
@@ -61,8 +61,10 @@ export default function useAgentGraph(
|
||||
const [nodes, setNodes] = useState<CustomNode[]>([]);
|
||||
const [edges, setEdges] = useState<CustomEdge[]>([]);
|
||||
|
||||
const apiUrl = process.env.NEXT_PUBLIC_AGPT_SERVER_URL!;
|
||||
const api = useMemo(() => new AutoGPTServerAPI(apiUrl), [apiUrl]);
|
||||
const api = useMemo(
|
||||
() => new AutoGPTServerAPI(process.env.NEXT_PUBLIC_AGPT_SERVER_URL!),
|
||||
[],
|
||||
);
|
||||
|
||||
// Connect to WebSocket
|
||||
useEffect(() => {
|
||||
@@ -89,16 +91,227 @@ export default function useAgentGraph(
|
||||
.getBlocks()
|
||||
.then((blocks) => setAvailableNodes(blocks))
|
||||
.catch();
|
||||
}, [api]);
|
||||
|
||||
//TODO to utils? repeated in Flow
|
||||
const formatEdgeID = useCallback((conn: Link | Connection): string => {
|
||||
if ("sink_id" in conn) {
|
||||
return `${conn.source_id}_${conn.source_name}_${conn.sink_id}_${conn.sink_name}`;
|
||||
} else {
|
||||
return `${conn.source}_${conn.sourceHandle}_${conn.target}_${conn.targetHandle}`;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getOutputType = useCallback(
|
||||
(nodes: CustomNode[], nodeId: string, handleId: string) => {
|
||||
const node = nodes.find((n) => n.id === nodeId);
|
||||
if (!node) return "unknown";
|
||||
|
||||
const outputSchema = node.data.outputSchema;
|
||||
if (!outputSchema) return "unknown";
|
||||
|
||||
const outputHandle = outputSchema.properties[handleId];
|
||||
if (!("type" in outputHandle)) return "unknown";
|
||||
return outputHandle.type;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// Load existing graph
|
||||
const loadGraph = useCallback(
|
||||
(graph: Graph) => {
|
||||
setSavedAgent(graph);
|
||||
setAgentName(graph.name);
|
||||
setAgentDescription(graph.description);
|
||||
|
||||
setNodes(() => {
|
||||
const newNodes = graph.nodes.map((node) => {
|
||||
const block = availableNodes.find(
|
||||
(block) => block.id === node.block_id,
|
||||
)!;
|
||||
const newNode: CustomNode = {
|
||||
id: node.id,
|
||||
type: "custom",
|
||||
position: {
|
||||
x: node.metadata.position.x,
|
||||
y: node.metadata.position.y,
|
||||
},
|
||||
data: {
|
||||
block_id: block.id,
|
||||
blockType: block.name,
|
||||
categories: block.categories,
|
||||
description: block.description,
|
||||
title: `${block.name} ${node.id}`,
|
||||
inputSchema: block.inputSchema,
|
||||
outputSchema: block.outputSchema,
|
||||
hardcodedValues: node.input_default,
|
||||
connections: graph.links
|
||||
.filter((l) => [l.source_id, l.sink_id].includes(node.id))
|
||||
.map((link) => ({
|
||||
edge_id: formatEdgeID(link),
|
||||
source: link.source_id,
|
||||
sourceHandle: link.source_name,
|
||||
target: link.sink_id,
|
||||
targetHandle: link.sink_name,
|
||||
})),
|
||||
isOutputOpen: false,
|
||||
},
|
||||
};
|
||||
return newNode;
|
||||
});
|
||||
setEdges((_) =>
|
||||
graph.links.map((link) => ({
|
||||
id: formatEdgeID(link),
|
||||
type: "custom",
|
||||
data: {
|
||||
edgeColor: getTypeColor(
|
||||
getOutputType(newNodes, link.source_id, link.source_name!),
|
||||
),
|
||||
sourcePos: newNodes.find((node) => node.id === link.source_id)
|
||||
?.position,
|
||||
isStatic: link.is_static,
|
||||
beadUp: 0,
|
||||
beadDown: 0,
|
||||
beadData: [],
|
||||
},
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
strokeWidth: 2,
|
||||
color: getTypeColor(
|
||||
getOutputType(newNodes, link.source_id, link.source_name!),
|
||||
),
|
||||
},
|
||||
source: link.source_id,
|
||||
target: link.sink_id,
|
||||
sourceHandle: link.source_name || undefined,
|
||||
targetHandle: link.sink_name || undefined,
|
||||
})),
|
||||
);
|
||||
return newNodes;
|
||||
});
|
||||
},
|
||||
[availableNodes, formatEdgeID, getOutputType],
|
||||
);
|
||||
|
||||
const getFrontendId = useCallback(
|
||||
(backendId: string, nodes: CustomNode[]) => {
|
||||
const node = nodes.find((node) => node.data.backend_id === backendId);
|
||||
return node?.id;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const updateEdgeBeads = useCallback(
|
||||
(executionData: NodeExecutionResult) => {
|
||||
setEdges((edges) => {
|
||||
return edges.map((e) => {
|
||||
const edge = { ...e, data: { ...e.data } } as CustomEdge;
|
||||
|
||||
if (executionData.status === "COMPLETED") {
|
||||
// Produce output beads
|
||||
for (let key in executionData.output_data) {
|
||||
if (
|
||||
edge.source !== getFrontendId(executionData.node_id, nodes) ||
|
||||
edge.sourceHandle !== key
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
edge.data!.beadUp = (edge.data!.beadUp ?? 0) + 1;
|
||||
// For static edges beadDown is always one less than beadUp
|
||||
// Because there's no queueing and one bead is always at the connection point
|
||||
if (edge.data?.isStatic) {
|
||||
edge.data!.beadDown = (edge.data!.beadUp ?? 0) - 1;
|
||||
edge.data!.beadData = edge.data!.beadData!.slice(0, -1);
|
||||
continue;
|
||||
}
|
||||
//todo kcze this assumes output at key is always array with one element
|
||||
edge.data!.beadData = [
|
||||
executionData.output_data[key][0],
|
||||
...edge.data!.beadData!,
|
||||
];
|
||||
}
|
||||
} else if (executionData.status === "RUNNING") {
|
||||
// Consume input beads
|
||||
for (let key in executionData.input_data) {
|
||||
if (
|
||||
edge.target !== getFrontendId(executionData.node_id, nodes) ||
|
||||
edge.targetHandle !== key
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// Skip decreasing bead count if edge doesn't match or if it's static
|
||||
if (
|
||||
edge.data!.beadData![edge.data!.beadData!.length - 1] !==
|
||||
executionData.input_data[key] ||
|
||||
edge.data?.isStatic
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
edge.data!.beadDown = (edge.data!.beadDown ?? 0) + 1;
|
||||
edge.data!.beadData = edge.data!.beadData!.slice(0, -1);
|
||||
}
|
||||
}
|
||||
return edge;
|
||||
});
|
||||
});
|
||||
},
|
||||
[getFrontendId, nodes],
|
||||
);
|
||||
|
||||
const updateNodesWithExecutionData = useCallback(
|
||||
(executionData: NodeExecutionResult) => {
|
||||
if (passDataToBeads) {
|
||||
updateEdgeBeads(executionData);
|
||||
}
|
||||
setNodes((nodes) => {
|
||||
const nodeId = nodes.find(
|
||||
(node) => node.data.backend_id === executionData.node_id,
|
||||
)?.id;
|
||||
if (!nodeId) {
|
||||
console.error(
|
||||
"Node not found for execution data:",
|
||||
executionData,
|
||||
"This shouldn't happen and means that the frontend and backend are out of sync.",
|
||||
);
|
||||
return nodes;
|
||||
}
|
||||
return nodes.map((node) =>
|
||||
node.id === nodeId
|
||||
? {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
status: executionData.status,
|
||||
executionResults:
|
||||
Object.keys(executionData.output_data).length > 0
|
||||
? [
|
||||
...(node.data.executionResults || []),
|
||||
{
|
||||
execId: executionData.node_exec_id,
|
||||
data: executionData.output_data,
|
||||
},
|
||||
]
|
||||
: node.data.executionResults,
|
||||
isOutputOpen: true,
|
||||
},
|
||||
}
|
||||
: node,
|
||||
);
|
||||
});
|
||||
},
|
||||
[passDataToBeads, updateEdgeBeads],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!flowID || availableNodes.length == 0) return;
|
||||
|
||||
(template ? api.getTemplate(flowID) : api.getGraph(flowID)).then((graph) =>
|
||||
loadGraph(graph),
|
||||
(template ? api.getTemplate(flowID) : api.getGraph(flowID)).then(
|
||||
(graph) => {
|
||||
console.log("Loading graph");
|
||||
loadGraph(graph);
|
||||
},
|
||||
);
|
||||
}, [flowID, template, availableNodes]);
|
||||
}, [flowID, template, availableNodes, api, loadGraph]);
|
||||
|
||||
// Update nodes with execution data
|
||||
useEffect(() => {
|
||||
@@ -119,7 +332,68 @@ export default function useAgentGraph(
|
||||
});
|
||||
return [];
|
||||
});
|
||||
}, [updateQueue, nodesSyncedWithSavedAgent]);
|
||||
}, [updateQueue, nodesSyncedWithSavedAgent, updateNodesWithExecutionData]);
|
||||
|
||||
const validateNodes = useCallback((): boolean => {
|
||||
let isValid = true;
|
||||
|
||||
nodes.forEach((node) => {
|
||||
const validate = ajv.compile(node.data.inputSchema);
|
||||
const errors = {} as { [key: string]: string };
|
||||
|
||||
// Validate values against schema using AJV
|
||||
const valid = validate(node.data.hardcodedValues);
|
||||
if (!valid) {
|
||||
// Populate errors if validation fails
|
||||
validate.errors?.forEach((error) => {
|
||||
// Skip error if there's an edge connected
|
||||
const path =
|
||||
"dataPath" in error
|
||||
? (error.dataPath as string)
|
||||
: error.instancePath;
|
||||
const handle = path.split(/[\/.]/)[0];
|
||||
if (
|
||||
node.data.connections.some(
|
||||
(conn) => conn.target === node.id || conn.targetHandle === handle,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
console.warn("Error", error);
|
||||
isValid = false;
|
||||
if (path && error.message) {
|
||||
const key = path.slice(1);
|
||||
console.log("Error", key, error.message);
|
||||
setNestedProperty(
|
||||
errors,
|
||||
key,
|
||||
error.message[0].toUpperCase() + error.message.slice(1),
|
||||
);
|
||||
} else if (error.keyword === "required") {
|
||||
const key = error.params.missingProperty;
|
||||
setNestedProperty(errors, key, "This field is required");
|
||||
}
|
||||
});
|
||||
}
|
||||
// Set errors
|
||||
setNodes((nodes) => {
|
||||
return nodes.map((n) => {
|
||||
if (n.id === node.id) {
|
||||
return {
|
||||
...n,
|
||||
data: {
|
||||
...n.data,
|
||||
errors,
|
||||
},
|
||||
};
|
||||
}
|
||||
return n;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return isValid;
|
||||
}, [nodes]);
|
||||
|
||||
// Handle user requests
|
||||
useEffect(() => {
|
||||
@@ -224,7 +498,13 @@ export default function useAgentGraph(
|
||||
.stopGraphExecution(savedAgent.id, saveRunRequest.activeExecutionID)
|
||||
.then(() => setSaveRunRequest({ request: "none", state: "none" }));
|
||||
}
|
||||
}, [saveRunRequest, savedAgent, nodesSyncedWithSavedAgent]);
|
||||
}, [
|
||||
api,
|
||||
saveRunRequest,
|
||||
savedAgent,
|
||||
nodesSyncedWithSavedAgent,
|
||||
validateNodes,
|
||||
]);
|
||||
|
||||
// Check if node ids are synced with saved agent
|
||||
useEffect(() => {
|
||||
@@ -241,275 +521,6 @@ export default function useAgentGraph(
|
||||
setNodesSyncedWithSavedAgent(oneNodeSynced);
|
||||
}, [savedAgent, nodes]);
|
||||
|
||||
const validateNodes = useCallback((): boolean => {
|
||||
let isValid = true;
|
||||
|
||||
nodes.forEach((node) => {
|
||||
const validate = ajv.compile(node.data.inputSchema);
|
||||
const errors = {} as { [key: string]: string };
|
||||
|
||||
// Validate values against schema using AJV
|
||||
const valid = validate(node.data.hardcodedValues);
|
||||
if (!valid) {
|
||||
// Populate errors if validation fails
|
||||
validate.errors?.forEach((error) => {
|
||||
// Skip error if there's an edge connected
|
||||
const path =
|
||||
"dataPath" in error
|
||||
? (error.dataPath as string)
|
||||
: error.instancePath;
|
||||
const handle = path.split(/[\/.]/)[0];
|
||||
if (
|
||||
node.data.connections.some(
|
||||
(conn) => conn.target === node.id || conn.targetHandle === handle,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
console.warn("Error", error);
|
||||
isValid = false;
|
||||
if (path && error.message) {
|
||||
const key = path.slice(1);
|
||||
console.log("Error", key, error.message);
|
||||
setNestedProperty(
|
||||
errors,
|
||||
key,
|
||||
error.message[0].toUpperCase() + error.message.slice(1),
|
||||
);
|
||||
} else if (error.keyword === "required") {
|
||||
const key = error.params.missingProperty;
|
||||
setNestedProperty(errors, key, "This field is required");
|
||||
}
|
||||
});
|
||||
}
|
||||
// Set errors
|
||||
setNodes((nodes) => {
|
||||
return nodes.map((n) => {
|
||||
if (n.id === node.id) {
|
||||
return {
|
||||
...n,
|
||||
data: {
|
||||
...n.data,
|
||||
errors,
|
||||
},
|
||||
};
|
||||
}
|
||||
return n;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return isValid;
|
||||
}, [nodes]);
|
||||
|
||||
const getFrontendId = useCallback(
|
||||
(backendId: string, nodes: CustomNode[]) => {
|
||||
const node = nodes.find((node) => node.data.backend_id === backendId);
|
||||
return node?.id;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const updateEdgeBeads = useCallback(
|
||||
(executionData: NodeExecutionResult) => {
|
||||
setEdges((edges) => {
|
||||
return edges.map((e) => {
|
||||
const edge = { ...e, data: { ...e.data } } as CustomEdge;
|
||||
|
||||
if (executionData.status === "COMPLETED") {
|
||||
// Produce output beads
|
||||
for (let key in executionData.output_data) {
|
||||
if (
|
||||
edge.source !== getFrontendId(executionData.node_id, nodes) ||
|
||||
edge.sourceHandle !== key
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
edge.data!.beadUp = (edge.data!.beadUp ?? 0) + 1;
|
||||
// For static edges beadDown is always one less than beadUp
|
||||
// Because there's no queueing and one bead is always at the connection point
|
||||
if (edge.data?.isStatic) {
|
||||
edge.data!.beadDown = (edge.data!.beadUp ?? 0) - 1;
|
||||
edge.data!.beadData = edge.data!.beadData!.slice(0, -1);
|
||||
continue;
|
||||
}
|
||||
//todo kcze this assumes output at key is always array with one element
|
||||
edge.data!.beadData = [
|
||||
executionData.output_data[key][0],
|
||||
...edge.data!.beadData!,
|
||||
];
|
||||
}
|
||||
} else if (executionData.status === "RUNNING") {
|
||||
// Consume input beads
|
||||
for (let key in executionData.input_data) {
|
||||
if (
|
||||
edge.target !== getFrontendId(executionData.node_id, nodes) ||
|
||||
edge.targetHandle !== key
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// Skip decreasing bead count if edge doesn't match or if it's static
|
||||
if (
|
||||
edge.data!.beadData![edge.data!.beadData!.length - 1] !==
|
||||
executionData.input_data[key] ||
|
||||
edge.data?.isStatic
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
edge.data!.beadDown = (edge.data!.beadDown ?? 0) + 1;
|
||||
edge.data!.beadData = edge.data!.beadData!.slice(0, -1);
|
||||
}
|
||||
}
|
||||
return edge;
|
||||
});
|
||||
});
|
||||
},
|
||||
[edges],
|
||||
);
|
||||
|
||||
const updateNodesWithExecutionData = useCallback(
|
||||
(executionData: NodeExecutionResult) => {
|
||||
if (passDataToBeads) {
|
||||
updateEdgeBeads(executionData);
|
||||
}
|
||||
setNodes((nodes) => {
|
||||
const nodeId = nodes.find(
|
||||
(node) => node.data.backend_id === executionData.node_id,
|
||||
)?.id;
|
||||
if (!nodeId) {
|
||||
console.error(
|
||||
"Node not found for execution data:",
|
||||
executionData,
|
||||
"This shouldn't happen and means that the frontend and backend are out of sync.",
|
||||
);
|
||||
return nodes;
|
||||
}
|
||||
return nodes.map((node) =>
|
||||
node.id === nodeId
|
||||
? {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
status: executionData.status,
|
||||
executionResults:
|
||||
Object.keys(executionData.output_data).length > 0
|
||||
? [
|
||||
...(node.data.executionResults || []),
|
||||
{
|
||||
execId: executionData.node_exec_id,
|
||||
data: executionData.output_data,
|
||||
},
|
||||
]
|
||||
: node.data.executionResults,
|
||||
isOutputOpen: true,
|
||||
},
|
||||
}
|
||||
: node,
|
||||
);
|
||||
});
|
||||
},
|
||||
[nodes],
|
||||
);
|
||||
|
||||
//TODO to utils? repeated in Flow
|
||||
const formatEdgeID = useCallback((conn: Link | Connection): string => {
|
||||
if ("sink_id" in conn) {
|
||||
return `${conn.source_id}_${conn.source_name}_${conn.sink_id}_${conn.sink_name}`;
|
||||
} else {
|
||||
return `${conn.source}_${conn.sourceHandle}_${conn.target}_${conn.targetHandle}`;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getOutputType = useCallback(
|
||||
(nodeId: string, handleId: string) => {
|
||||
const node = nodes.find((n) => n.id === nodeId);
|
||||
if (!node) return "unknown";
|
||||
|
||||
const outputSchema = node.data.outputSchema;
|
||||
if (!outputSchema) return "unknown";
|
||||
|
||||
const outputHandle = outputSchema.properties[handleId];
|
||||
if (!("type" in outputHandle)) return "unknown";
|
||||
return outputHandle.type;
|
||||
},
|
||||
[nodes],
|
||||
);
|
||||
|
||||
const loadGraph = useCallback(
|
||||
(graph: Graph) => {
|
||||
setSavedAgent(graph);
|
||||
setAgentName(graph.name);
|
||||
setAgentDescription(graph.description);
|
||||
|
||||
setNodes(() => {
|
||||
const newNodes = graph.nodes.map((node) => {
|
||||
const block = availableNodes.find(
|
||||
(block) => block.id === node.block_id,
|
||||
)!;
|
||||
const newNode: CustomNode = {
|
||||
id: node.id,
|
||||
type: "custom",
|
||||
position: {
|
||||
x: node.metadata.position.x,
|
||||
y: node.metadata.position.y,
|
||||
},
|
||||
data: {
|
||||
block_id: block.id,
|
||||
blockType: block.name,
|
||||
categories: block.categories,
|
||||
description: block.description,
|
||||
title: `${block.name} ${node.id}`,
|
||||
inputSchema: block.inputSchema,
|
||||
outputSchema: block.outputSchema,
|
||||
hardcodedValues: node.input_default,
|
||||
connections: graph.links
|
||||
.filter((l) => [l.source_id, l.sink_id].includes(node.id))
|
||||
.map((link) => ({
|
||||
edge_id: formatEdgeID(link),
|
||||
source: link.source_id,
|
||||
sourceHandle: link.source_name,
|
||||
target: link.sink_id,
|
||||
targetHandle: link.sink_name,
|
||||
})),
|
||||
isOutputOpen: false,
|
||||
},
|
||||
};
|
||||
return newNode;
|
||||
});
|
||||
setEdges((_) =>
|
||||
graph.links.map((link) => ({
|
||||
id: formatEdgeID(link),
|
||||
type: "custom",
|
||||
data: {
|
||||
edgeColor: getTypeColor(
|
||||
getOutputType(link.source_id, link.source_name!),
|
||||
),
|
||||
sourcePos: nodes.find((node) => node.id === link.source_id)
|
||||
?.position,
|
||||
isStatic: link.is_static,
|
||||
beadUp: 0,
|
||||
beadDown: 0,
|
||||
beadData: [],
|
||||
},
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
strokeWidth: 2,
|
||||
color: getTypeColor(
|
||||
getOutputType(link.source_id, link.source_name!),
|
||||
),
|
||||
},
|
||||
source: link.source_id,
|
||||
target: link.sink_id,
|
||||
sourceHandle: link.source_name || undefined,
|
||||
targetHandle: link.sink_name || undefined,
|
||||
})),
|
||||
);
|
||||
return newNodes;
|
||||
});
|
||||
},
|
||||
[availableNodes],
|
||||
);
|
||||
|
||||
const prepareNodeInputData = useCallback(
|
||||
(node: CustomNode) => {
|
||||
console.debug(
|
||||
@@ -696,7 +707,15 @@ export default function useAgentGraph(
|
||||
}));
|
||||
});
|
||||
},
|
||||
[nodes, edges, savedAgent],
|
||||
[
|
||||
api,
|
||||
nodes,
|
||||
edges,
|
||||
savedAgent,
|
||||
agentName,
|
||||
agentDescription,
|
||||
prepareNodeInputData,
|
||||
],
|
||||
);
|
||||
|
||||
const requestSave = useCallback(
|
||||
|
||||
@@ -84,12 +84,12 @@ export function useBezierPath(
|
||||
|
||||
return length;
|
||||
},
|
||||
[path],
|
||||
[getPointForT],
|
||||
);
|
||||
|
||||
const length = useMemo(() => {
|
||||
return getArcLength(1);
|
||||
}, [path]);
|
||||
}, [getArcLength]);
|
||||
|
||||
const getBezierDerivative = useCallback(
|
||||
(t: number) => {
|
||||
@@ -131,7 +131,7 @@ export function useBezierPath(
|
||||
|
||||
return t;
|
||||
},
|
||||
[path],
|
||||
[getArcLength, getBezierDerivative, length],
|
||||
);
|
||||
|
||||
const getPointAtDistance = useCallback(
|
||||
@@ -143,7 +143,7 @@ export function useBezierPath(
|
||||
const t = getTForDistance(distance);
|
||||
return getPointForT(t);
|
||||
},
|
||||
[path],
|
||||
[getTForDistance, getPointForT, length],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user