{isWebhook &&
}
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeContainer.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeContainer.tsx
index 657f1ca048..f8d5b2e089 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeContainer.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeContainer.tsx
@@ -3,15 +3,18 @@ import { nodeStyleBasedOnStatus } from "../helpers";
import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
import { useShallow } from "zustand/react/shallow";
+import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecutionStatus";
export const NodeContainer = ({
children,
nodeId,
selected,
+ hasErrors, // these are configuration errors that occur before executing the graph -- more like validation errors
}: {
children: React.ReactNode;
nodeId: string;
selected: boolean;
+ hasErrors?: boolean;
}) => {
const status = useNodeStore(
useShallow((state) => state.getNodeStatus(nodeId)),
@@ -22,6 +25,7 @@ export const NodeContainer = ({
"z-12 max-w-[370px] rounded-xlarge ring-1 ring-slate-200/60",
selected && "shadow-lg ring-2 ring-slate-200",
status && nodeStyleBasedOnStatus[status],
+ hasErrors ? nodeStyleBasedOnStatus[AgentExecutionStatus.FAILED] : "",
)}
>
{children}
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/stores/nodeStore.ts b/autogpt_platform/frontend/src/app/(platform)/build/stores/nodeStore.ts
index 3beba0c615..2f41c3bb46 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/stores/nodeStore.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/build/stores/nodeStore.ts
@@ -53,6 +53,15 @@ type NodeStore = {
getNodeExecutionResult: (nodeId: string) => NodeExecutionResult | undefined;
getNodeBlockUIType: (nodeId: string) => BlockUIType;
hasWebhookNodes: () => boolean;
+
+ updateNodeErrors: (nodeId: string, errors: { [key: string]: string }) => void;
+ clearNodeErrors: (nodeId: string) => void;
+ getNodeErrors: (nodeId: string) => { [key: string]: string } | undefined;
+ setNodeErrorsForBackendId: (
+ backendId: string,
+ errors: { [key: string]: string },
+ ) => void;
+ clearAllNodeErrors: () => void; // Add this
};
export const useNodeStore = create
((set, get) => ({
@@ -253,4 +262,47 @@ export const useNodeStore = create((set, get) => ({
[BlockUIType.WEBHOOK, BlockUIType.WEBHOOK_MANUAL].includes(n.data.uiType),
);
},
+
+ updateNodeErrors: (nodeId: string, errors: { [key: string]: string }) => {
+ set((state) => ({
+ nodes: state.nodes.map((n) =>
+ n.id === nodeId ? { ...n, data: { ...n.data, errors } } : n,
+ ),
+ }));
+ },
+
+ clearNodeErrors: (nodeId: string) => {
+ set((state) => ({
+ nodes: state.nodes.map((n) =>
+ n.id === nodeId ? { ...n, data: { ...n.data, errors: undefined } } : n,
+ ),
+ }));
+ },
+
+ getNodeErrors: (nodeId: string) => {
+ return get().nodes.find((n) => n.id === nodeId)?.data?.errors;
+ },
+
+ setNodeErrorsForBackendId: (
+ backendId: string,
+ errors: { [key: string]: string },
+ ) => {
+ set((state) => ({
+ nodes: state.nodes.map((n) => {
+ // Match by backend_id if nodes have it, or by id
+ const matches =
+ n.data.metadata?.backend_id === backendId || n.id === backendId;
+ return matches ? { ...n, data: { ...n.data, errors } } : n;
+ }),
+ }));
+ },
+
+ clearAllNodeErrors: () => {
+ set((state) => ({
+ nodes: state.nodes.map((n) => ({
+ ...n,
+ data: { ...n.data, errors: undefined },
+ })),
+ }));
+ },
}));
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/templates/FieldTemplate.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/templates/FieldTemplate.tsx
index b4db9d4159..a056782939 100644
--- a/autogpt_platform/frontend/src/components/renderers/input-renderer/templates/FieldTemplate.tsx
+++ b/autogpt_platform/frontend/src/components/renderers/input-renderer/templates/FieldTemplate.tsx
@@ -23,6 +23,7 @@ import { cn } from "@/lib/utils";
import { BlockIOCredentialsSubSchema } from "@/lib/autogpt-server-api";
import { BlockUIType } from "@/lib/autogpt-server-api";
import NodeHandle from "@/app/(platform)/build/components/FlowEditor/handlers/NodeHandle";
+import { getFieldErrorKey } from "../utils/helpers";
const FieldTemplate: React.FC = ({
id: fieldId,
@@ -42,6 +43,11 @@ const FieldTemplate: React.FC = ({
(state) => state.nodeAdvancedStates[nodeId] ?? false,
);
+ const nodeErrors = useNodeStore((state) => {
+ const node = state.nodes.find((n) => n.id === nodeId);
+ return node?.data?.errors;
+ });
+
const { isArrayItem, arrayFieldHandleId } = useContext(ArrayEditorContext);
const isAnyOf =
@@ -89,6 +95,13 @@ const FieldTemplate: React.FC = ({
shouldShowHandle = false;
}
+ const fieldErrorKey = getFieldErrorKey(fieldId);
+ const fieldError =
+ nodeErrors?.[fieldErrorKey] ||
+ nodeErrors?.[fieldErrorKey.replace(/_/g, ".")] ||
+ nodeErrors?.[fieldErrorKey.replace(/\./g, "_")] ||
+ null;
+
return (
= ({
{children}
- )}{" "}
+ )}
+ {fieldError && (
+
+ {fieldError}
+
+ )}
);
};
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/utils/helpers.ts b/autogpt_platform/frontend/src/components/renderers/input-renderer/utils/helpers.ts
new file mode 100644
index 0000000000..51b628d923
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/input-renderer/utils/helpers.ts
@@ -0,0 +1,4 @@
+export const getFieldErrorKey = (fieldId: string): string => {
+ const withoutRoot = fieldId.startsWith("root_") ? fieldId.slice(5) : fieldId;
+ return withoutRoot;
+};