fix(rnd): Fix execution error on non-saved agent (#8054)

This commit is contained in:
Zamil Majdy
2024-09-16 14:35:31 -05:00
committed by GitHub
parent 22ce8e0047
commit b1347a92de
7 changed files with 72 additions and 56 deletions

View File

@@ -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 ? <IconPlay /> : <IconSquare />,
onClick: !isRunning
? () => runnerUIRef.current?.runOrOpenInput()
: requestStopRun,
disabled: !savedAgent,
},
{
label: "Runner Output",

View File

@@ -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) => (
<Tooltip key={index} delayDuration={500}>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => control.onClick()}
data-id={`control-button-${index}`}
>
{control.icon}
<span className="sr-only">{control.label}</span>
</Button>
<div>
<Button
variant="ghost"
size="icon"
onClick={() => control.onClick()}
data-id={`control-button-${index}`}
disabled={control.disabled || false}
>
{control.icon}
<span className="sr-only">{control.label}</span>
</Button>
</div>
</TooltipTrigger>
<TooltipContent side="right">{control.label}</TooltipContent>
</Tooltip>

View File

@@ -380,7 +380,7 @@ const NodeKeyValueInput: FC<{
<Input
type="text"
placeholder="Value"
value={value ?? ""}
defaultValue={value ?? ""}
onBlur={(e) =>
updateKeyValuePairs(
keyValuePairs.toSpliced(index, 1, {
@@ -563,7 +563,7 @@ const NodeStringInput: FC<{
<Input
type="text"
id={selfKey}
value={schema.secret && value ? "********" : value}
defaultValue={schema.secret && value ? "********" : value}
readOnly={schema.secret}
placeholder={
schema?.placeholder || `Enter ${beautifyString(displayName)}`
@@ -658,7 +658,7 @@ const NodeNumberInput: FC<{
<Input
type="number"
id={selfKey}
value={value}
defaultValue={value}
onBlur={(e) => handleInputChange(selfKey, parseFloat(e.target.value))}
placeholder={
schema.placeholder || `Enter ${beautifyString(displayName)}`

View File

@@ -6,20 +6,7 @@ export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ 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<HTMLInputElement>();
React.useEffect(() => {
if (
ref &&
ref.current &&
ref.current.value !== value &&
type !== "file"
) {
ref.current.value = value;
}
}, [value, type, ref]);
({ className, type, ...props }, ref) => {
return (
<input
type={type}
@@ -29,7 +16,6 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
className,
)}
ref={ref}
defaultValue={type !== "file" ? value : undefined}
{...props}
/>
);

View File

@@ -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);
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);
}
setNodesSyncedWithSavedAgent(false);
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);
// 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

View File

@@ -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<string, string> = {
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]",
};

View File

@@ -155,7 +155,7 @@ def execute_node(
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))