From b1347a92de1dc01a88bd5a16e3e4b63770d2782a Mon Sep 17 00:00:00 2001 From: Zamil Majdy Date: Mon, 16 Sep 2024 14:35:31 -0500 Subject: [PATCH] fix(rnd): Fix execution error on non-saved agent (#8054) --- rnd/autogpt_builder/src/components/Flow.tsx | 7 ++- .../components/edit/control/ControlPanel.tsx | 22 ++++--- .../src/components/node-input-components.tsx | 6 +- .../src/components/ui/input.tsx | 16 +---- .../src/hooks/useAgentGraph.ts | 62 +++++++++++++------ rnd/autogpt_builder/src/lib/utils.ts | 11 ++-- .../autogpt_server/executor/manager.py | 2 +- 7 files changed, 71 insertions(+), 55 deletions(-) diff --git a/rnd/autogpt_builder/src/components/Flow.tsx b/rnd/autogpt_builder/src/components/Flow.tsx index 2a6aca271504..b0c112a5aa95 100644 --- a/rnd/autogpt_builder/src/components/Flow.tsx +++ b/rnd/autogpt_builder/src/components/Flow.tsx @@ -557,11 +557,16 @@ const FlowEditor: React.FC<{ onClick: handleRedo, }, { - label: !isRunning ? "Run" : "Stop", + label: !savedAgent + ? "Please save the agent to run" + : !isRunning + ? "Run" + : "Stop", icon: !isRunning ? : , onClick: !isRunning ? () => runnerUIRef.current?.runOrOpenInput() : requestStopRun, + disabled: !savedAgent, }, { label: "Runner Output", diff --git a/rnd/autogpt_builder/src/components/edit/control/ControlPanel.tsx b/rnd/autogpt_builder/src/components/edit/control/ControlPanel.tsx index 5abbab30b33b..a74bb302f9c1 100644 --- a/rnd/autogpt_builder/src/components/edit/control/ControlPanel.tsx +++ b/rnd/autogpt_builder/src/components/edit/control/ControlPanel.tsx @@ -19,6 +19,7 @@ import React from "react"; export type Control = { icon: React.ReactNode; label: string; + disabled?: boolean; onClick: () => void; }; @@ -50,15 +51,18 @@ export const ControlPanel = ({ {controls.map((control, index) => ( - +
+ +
{control.label}
diff --git a/rnd/autogpt_builder/src/components/node-input-components.tsx b/rnd/autogpt_builder/src/components/node-input-components.tsx index f0a2bf85f48e..9799876756cf 100644 --- a/rnd/autogpt_builder/src/components/node-input-components.tsx +++ b/rnd/autogpt_builder/src/components/node-input-components.tsx @@ -380,7 +380,7 @@ const NodeKeyValueInput: FC<{ updateKeyValuePairs( keyValuePairs.toSpliced(index, 1, { @@ -563,7 +563,7 @@ const NodeStringInput: FC<{ handleInputChange(selfKey, parseFloat(e.target.value))} placeholder={ schema.placeholder || `Enter ${beautifyString(displayName)}` diff --git a/rnd/autogpt_builder/src/components/ui/input.tsx b/rnd/autogpt_builder/src/components/ui/input.tsx index 0118e78222c3..6aacccf0c502 100644 --- a/rnd/autogpt_builder/src/components/ui/input.tsx +++ b/rnd/autogpt_builder/src/components/ui/input.tsx @@ -6,20 +6,7 @@ export interface InputProps extends React.InputHTMLAttributes {} const Input = React.forwardRef( - ({ className, type, value, ...props }, ref) => { - // This ref allows the `Input` component to be both controlled and uncontrolled. - // The HTMLvalue will only be updated if the value prop changes, but the user can still type in the input. - ref = ref || React.createRef(); - React.useEffect(() => { - if ( - ref && - ref.current && - ref.current.value !== value && - type !== "file" - ) { - ref.current.value = value; - } - }, [value, type, ref]); + ({ className, type, ...props }, ref) => { return ( ( className, )} ref={ref} - defaultValue={type !== "file" ? value : undefined} {...props} /> ); diff --git a/rnd/autogpt_builder/src/hooks/useAgentGraph.ts b/rnd/autogpt_builder/src/hooks/useAgentGraph.ts index 2eb60a1dd661..708354ffa614 100644 --- a/rnd/autogpt_builder/src/hooks/useAgentGraph.ts +++ b/rnd/autogpt_builder/src/hooks/useAgentGraph.ts @@ -139,8 +139,8 @@ export default function useAgentGraph( id: node.id, type: "custom", position: { - x: node.metadata.position.x, - y: node.metadata.position.y, + x: node?.metadata?.position?.x || 0, + y: node?.metadata?.position?.y || 0, }, data: { block_id: block.id, @@ -614,7 +614,7 @@ export default function useAgentGraph( })); return { - id: node.data.backend_id, + id: node.id, block_id: node.data.block_id, input_default: inputDefault, input_nodes: inputNodes, @@ -643,35 +643,59 @@ export default function useAgentGraph( nodes: formattedNodes, links: links, }; - if (savedAgent && deepEquals(payload, savedAgent, true)) { + + // To avoid saving the same graph, we compare the payload with the saved agent. + // Differences in IDs are ignored. + const comparedPayload = { + ...(({ id, ...rest }) => rest)(payload), + nodes: payload.nodes.map( + ({ id, data, input_nodes, output_nodes, ...rest }) => rest, + ), + links: payload.links.map(({ source_id, sink_id, ...rest }) => rest), + }; + const comparedSavedAgent = { + name: savedAgent?.name, + description: savedAgent?.description, + nodes: savedAgent?.nodes.map((v) => ({ + block_id: v.block_id, + input_default: v.input_default, + metadata: v.metadata, + })), + links: savedAgent?.links.map((v) => ({ + sink_name: v.sink_name, + source_name: v.source_name, + })), + }; + + let newSavedAgent = null; + if (savedAgent && deepEquals(comparedPayload, comparedSavedAgent)) { console.warn("No need to save: Graph is the same as version on server"); - // Trigger state change - setSavedAgent(savedAgent); - return; + newSavedAgent = savedAgent; } else { console.debug( "Saving new Graph version; old vs new:", - savedAgent, + comparedPayload, payload, ); - } + setNodesSyncedWithSavedAgent(false); - setNodesSyncedWithSavedAgent(false); + newSavedAgent = savedAgent + ? await (savedAgent.is_template + ? api.updateTemplate(savedAgent.id, payload) + : api.updateGraph(savedAgent.id, payload)) + : await (asTemplate + ? api.createTemplate(payload) + : api.createGraph(payload)); - const newSavedAgent = savedAgent - ? await (savedAgent.is_template - ? api.updateTemplate(savedAgent.id, payload) - : api.updateGraph(savedAgent.id, payload)) - : await (asTemplate - ? api.createTemplate(payload) - : api.createGraph(payload)); - console.debug("Response from the API:", newSavedAgent); + console.debug("Response from the API:", newSavedAgent); + } // Route the URL to the new flow ID if it's a new agent. if (!savedAgent) { const path = new URLSearchParams(searchParams); path.set("flowID", newSavedAgent.id); - router.replace(`${pathname}?${path.toString()}`); + router.push(`${pathname}?${path.toString()}`); + return; } // Update the node IDs on the frontend diff --git a/rnd/autogpt_builder/src/lib/utils.ts b/rnd/autogpt_builder/src/lib/utils.ts index 07b673cbd194..053724599d3d 100644 --- a/rnd/autogpt_builder/src/lib/utils.ts +++ b/rnd/autogpt_builder/src/lib/utils.ts @@ -20,21 +20,18 @@ export function hashString(str: string): number { } /** Derived from https://stackoverflow.com/a/32922084 */ -export function deepEquals(x: any, y: any, allowMissingKeys = false): boolean { +export function deepEquals(x: any, y: any): boolean { const ok = Object.keys, tx = typeof x, ty = typeof y; - const sk = (a: object, b: object) => ok(a).filter((k) => k in b); - const skipLengthCheck = allowMissingKeys && !Array.isArray(x); - const res = x && y && tx === ty && (tx === "object" - ? (skipLengthCheck || ok(x).length === ok(y).length) && - sk(x, y).every((key) => deepEquals(x[key], y[key], allowMissingKeys)) + ? ok(x).length === ok(y).length && + ok(x).every((key) => deepEquals(x[key], y[key])) : x === y); return res; } @@ -188,7 +185,7 @@ export const categoryColorMap: Record = { SEARCH: "bg-blue-300/[.7]", BASIC: "bg-purple-300/[.7]", INPUT: "bg-cyan-300/[.7]", - OUTPUT: "bg-brown-300/[.7]", + OUTPUT: "bg-red-300/[.7]", LOGIC: "bg-teal-300/[.7]", }; diff --git a/rnd/autogpt_server/autogpt_server/executor/manager.py b/rnd/autogpt_server/autogpt_server/executor/manager.py index 374f446b3ad1..8de219118ab8 100644 --- a/rnd/autogpt_server/autogpt_server/executor/manager.py +++ b/rnd/autogpt_server/autogpt_server/executor/manager.py @@ -155,7 +155,7 @@ def update_execution(status: ExecutionStatus) -> ExecutionResult: try: credit = wait(user_credit.get_or_refill_credit(user_id)) if credit < 0: - raise ValueError("Insufficient credit: {credit}") + raise ValueError(f"Insufficient credit: {credit}") for output_name, output_data in node_block.execute(input_data): output_size += len(json.dumps(output_data))