Loading Flow
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/components/RunningBackground.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/components/RunningBackground.tsx
new file mode 100644
index 0000000000..720bda48ca
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/components/RunningBackground.tsx
@@ -0,0 +1,157 @@
+export const RunningBackground = () => {
+ return (
+
+ );
+};
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
index 4d1a8f3d92..9696e7777c 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts
@@ -1,5 +1,8 @@
import { useGetV2GetSpecificBlocks } from "@/app/api/__generated__/endpoints/default/default";
-import { useGetV1GetSpecificGraph } from "@/app/api/__generated__/endpoints/graphs/graphs";
+import {
+ useGetV1GetExecutionDetails,
+ useGetV1GetSpecificGraph,
+} from "@/app/api/__generated__/endpoints/graphs/graphs";
import { BlockInfo } from "@/app/api/__generated__/models/blockInfo";
import { GraphModel } from "@/app/api/__generated__/models/graphModel";
import { parseAsInteger, parseAsString, useQueryStates } from "nuqs";
@@ -8,16 +11,39 @@ import { useShallow } from "zustand/react/shallow";
import { useEffect, useMemo } from "react";
import { convertNodesPlusBlockInfoIntoCustomNodes } from "../../helper";
import { useEdgeStore } from "../../../stores/edgeStore";
+import { GetV1GetExecutionDetails200 } from "@/app/api/__generated__/models/getV1GetExecutionDetails200";
+import { useGraphStore } from "../../../stores/graphStore";
+import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecutionStatus";
export const useFlow = () => {
const addNodes = useNodeStore(useShallow((state) => state.addNodes));
const addLinks = useEdgeStore(useShallow((state) => state.addLinks));
-
- const [{ flowID, flowVersion }] = useQueryStates({
+ const updateNodeStatus = useNodeStore(
+ useShallow((state) => state.updateNodeStatus),
+ );
+ const updateNodeExecutionResult = useNodeStore(
+ useShallow((state) => state.updateNodeExecutionResult),
+ );
+ const setIsGraphRunning = useGraphStore(
+ useShallow((state) => state.setIsGraphRunning),
+ );
+ const [{ flowID, flowVersion, flowExecutionID }] = useQueryStates({
flowID: parseAsString,
flowVersion: parseAsInteger,
+ flowExecutionID: parseAsString,
});
+ const { data: executionDetails } = useGetV1GetExecutionDetails(
+ flowID || "",
+ flowExecutionID || "",
+ {
+ query: {
+ select: (res) => res.data as GetV1GetExecutionDetails200,
+ enabled: !!flowID && !!flowExecutionID,
+ },
+ },
+ );
+
const { data: graph, isLoading: isGraphLoading } = useGetV1GetSpecificGraph(
flowID ?? "",
flowVersion !== null ? { version: flowVersion } : {},
@@ -57,21 +83,52 @@ export const useFlow = () => {
}, [nodes, blocks]);
useEffect(() => {
+ // adding nodes
if (customNodes.length > 0) {
useNodeStore.getState().setNodes([]);
addNodes(customNodes);
}
+ // adding links
if (graph?.links) {
useEdgeStore.getState().setConnections([]);
addLinks(graph.links);
}
- }, [customNodes, addNodes, graph?.links]);
+
+ // update graph running status
+ const isRunning =
+ executionDetails?.status === AgentExecutionStatus.RUNNING ||
+ executionDetails?.status === AgentExecutionStatus.QUEUED;
+ setIsGraphRunning(isRunning);
+
+ // update node execution status in nodes
+ if (
+ executionDetails &&
+ "node_executions" in executionDetails &&
+ executionDetails.node_executions
+ ) {
+ executionDetails.node_executions.forEach((nodeExecution) => {
+ updateNodeStatus(nodeExecution.node_id, nodeExecution.status);
+ });
+ }
+
+ // update node execution results in nodes
+ if (
+ executionDetails &&
+ "node_executions" in executionDetails &&
+ executionDetails.node_executions
+ ) {
+ executionDetails.node_executions.forEach((nodeExecution) => {
+ updateNodeExecutionResult(nodeExecution.node_id, nodeExecution);
+ });
+ }
+ }, [customNodes, addNodes, graph?.links, executionDetails, updateNodeStatus]);
useEffect(() => {
return () => {
useNodeStore.getState().setNodes([]);
useEdgeStore.getState().setConnections([]);
+ setIsGraphRunning(false);
};
}, []);
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlowRealtime.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlowRealtime.ts
new file mode 100644
index 0000000000..7ab55554f5
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlowRealtime.ts
@@ -0,0 +1,89 @@
+// In this hook, I am only keeping websocket related code.
+
+import { GraphExecutionID } from "@/lib/autogpt-server-api";
+import { useBackendAPI } from "@/lib/autogpt-server-api/context";
+import { parseAsString, useQueryStates } from "nuqs";
+import { useEffect } from "react";
+import { useNodeStore } from "../../../stores/nodeStore";
+import { useShallow } from "zustand/react/shallow";
+import { NodeExecutionResult } from "@/app/api/__generated__/models/nodeExecutionResult";
+import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecutionStatus";
+import { useGraphStore } from "../../../stores/graphStore";
+
+export const useFlowRealtime = () => {
+ const api = useBackendAPI();
+ const updateNodeExecutionResult = useNodeStore(
+ useShallow((state) => state.updateNodeExecutionResult),
+ );
+ const updateStatus = useNodeStore(
+ useShallow((state) => state.updateNodeStatus),
+ );
+ const setIsGraphRunning = useGraphStore(
+ useShallow((state) => state.setIsGraphRunning),
+ );
+
+ const [{ flowExecutionID, flowID }] = useQueryStates({
+ flowExecutionID: parseAsString,
+ flowID: parseAsString,
+ });
+
+ useEffect(() => {
+ const deregisterNodeExecutionEvent = api.onWebSocketMessage(
+ "node_execution_event",
+ (data) => {
+ if (data.graph_exec_id != flowExecutionID) {
+ return;
+ }
+ // TODO: Update the states of nodes
+ updateNodeExecutionResult(
+ data.node_id,
+ data as unknown as NodeExecutionResult,
+ );
+ updateStatus(data.node_id, data.status);
+ },
+ );
+
+ const deregisterGraphExecutionStatusEvent = api.onWebSocketMessage(
+ "graph_execution_event",
+ (graphExecution) => {
+ if (graphExecution.id != flowExecutionID) {
+ return;
+ }
+
+ const isRunning =
+ graphExecution.status === AgentExecutionStatus.RUNNING ||
+ graphExecution.status === AgentExecutionStatus.QUEUED;
+
+ setIsGraphRunning(isRunning);
+ },
+ );
+
+ const deregisterGraphExecutionSubscription =
+ flowID && flowExecutionID
+ ? api.onWebSocketConnect(() => {
+ // Subscribe to execution updates
+ api
+ .subscribeToGraphExecution(flowExecutionID as GraphExecutionID) // TODO: We are currently using a manual type, we need to fix it in future
+ .then(() => {
+ console.debug(
+ `Subscribed to updates for execution #${flowExecutionID}`,
+ );
+ })
+ .catch((error) =>
+ console.error(
+ `Failed to subscribe to updates for execution #${flowExecutionID}:`,
+ error,
+ ),
+ );
+ })
+ : () => {};
+
+ return () => {
+ deregisterNodeExecutionEvent();
+ deregisterGraphExecutionSubscription();
+ deregisterGraphExecutionStatusEvent();
+ };
+ }, [api, flowExecutionID]);
+
+ return {};
+};
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/components/ObjectEditor/ObjectEditor.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/components/ObjectEditor/ObjectEditor.tsx
index 6c3dd1e96c..87d593a493 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/components/ObjectEditor/ObjectEditor.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/components/ObjectEditor/ObjectEditor.tsx
@@ -147,6 +147,7 @@ export const ObjectEditor = React.forwardRef
(
type="button"
variant="secondary"
size="small"
+ className="min-w-10"
onClick={() => removeProperty(key)}
disabled={disabled}
>
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/CustomNode.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/CustomNode.tsx
index 882c5ab039..baca71cb72 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/CustomNode.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/CustomNode.tsx
@@ -6,6 +6,8 @@ import { StickyNoteBlock } from "./StickyNoteBlock";
import { BlockInfoCategoriesItem } from "@/app/api/__generated__/models/blockInfoCategoriesItem";
import { StandardNodeBlock } from "./StandardNodeBlock";
import { BlockCost } from "@/app/api/__generated__/models/blockCost";
+import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecutionStatus";
+import { NodeExecutionResult } from "@/app/api/__generated__/models/nodeExecutionResult";
export type CustomNodeData = {
hardcodedValues: {
@@ -17,6 +19,8 @@ export type CustomNodeData = {
outputSchema: RJSFSchema;
uiType: BlockUIType;
block_id: string;
+ status?: AgentExecutionStatus;
+ nodeExecutionResult?: NodeExecutionResult;
// TODO : We need better type safety for the following backend fields.
costs: BlockCost[];
categories: BlockInfoCategoriesItem[];
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/StandardNodeBlock.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/StandardNodeBlock.tsx
index 9a33f38a68..dc7f17e5d3 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/StandardNodeBlock.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/StandardNodeBlock.tsx
@@ -8,6 +8,9 @@ import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
import { OutputHandler } from "../OutputHandler";
import { NodeCost } from "./components/NodeCost";
import { NodeBadges } from "./components/NodeBadges";
+import { NodeExecutionBadge } from "./components/NodeExecutionBadge";
+import { nodeStyleBasedOnStatus } from "./helpers";
+import { NodeDataRenderer } from "./components/NodeDataRenderer";
type StandardNodeBlockType = {
data: CustomNodeData;
@@ -23,57 +26,60 @@ export const StandardNodeBlock = ({
(state) => state.nodeAdvancedStates[nodeId] || false,
);
const setShowAdvanced = useNodeStore((state) => state.setShowAdvanced);
-
+ const status = useNodeStore((state) => state.getNodeStatus(nodeId));
return (
- {/* Header */}
-
- {/* Upper section */}
-
-
- {beautifyString(data.title)}
-
-
- #{nodeId.split("-")[0]}
-
+
+ {/* Header */}
+
+ {/* Upper section */}
+
+
+ {beautifyString(data.title)}
+
+
+ #{nodeId.split("-")[0]}
+
+
+ {/* Lower section */}
+
+
+
+
- {/* Lower section */}
-
-
-
+ {/* Input Handles */}
+
+
-
+ {/* Advanced Button */}
+
+
+ Advanced
+
+ setShowAdvanced(nodeId, checked)}
+ checked={showAdvanced}
+ />
+
+ {/* Output Handles */}
+
- {/* Input Handles */}
-
-
+
-
- {/* Advanced Button */}
-
-
- Advanced
-
- setShowAdvanced(nodeId, checked)}
- checked={showAdvanced}
- />
-
-
- {/* Output Handles */}
-
+ {status &&
}
);
};
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/StickyNoteBlock.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/StickyNoteBlock.tsx
index 0510ba1982..9d4eb7f4f4 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/StickyNoteBlock.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/StickyNoteBlock.tsx
@@ -42,7 +42,7 @@ export const StickyNoteBlock = ({ data, id }: StickyNoteBlockType) => {
style={{ transform: `rotate(${angle}deg)` }}
>
- Notes #{id}
+ Notes #{id.split("-")[0]}
{
const { formatCredits } = useCredits();
- const hardcodedValues = useNodeStore((state) =>
- state.getHardCodedValues(nodeId),
+ const hardcodedValues = useNodeStore(
+ useShallow((state) => state.getHardCodedValues(nodeId)),
);
+
const blockCost =
blockCosts &&
blockCosts.find((cost) =>
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeDataRenderer.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeDataRenderer.tsx
new file mode 100644
index 0000000000..304d10fd13
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeDataRenderer.tsx
@@ -0,0 +1,122 @@
+import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
+import { Button } from "@/components/atoms/Button/Button";
+import { Text } from "@/components/atoms/Text/Text";
+import { beautifyString } from "@/lib/utils";
+import {
+ ArrowSquareInIcon,
+ CaretDownIcon,
+ CopyIcon,
+ InfoIcon,
+} from "@phosphor-icons/react";
+import { useState } from "react";
+
+import { useShallow } from "zustand/react/shallow";
+
+export const NodeDataRenderer = ({ nodeId }: { nodeId: string }) => {
+ const [isExpanded, setIsExpanded] = useState(true);
+
+ const nodeExecutionResult = useNodeStore(
+ useShallow((state) => state.getNodeExecutionResult(nodeId)),
+ );
+
+ const data = {
+ "[Input]": nodeExecutionResult?.input_data,
+ ...nodeExecutionResult?.output_data,
+ };
+
+ // Don't render if there's no data
+ if (!nodeExecutionResult || Object.keys(data).length === 0) {
+ return null;
+ }
+
+ // Need to Fix - when we are on build page and try to rerun the graph again, it gives error
+
+ return (
+
+
+
+ Node Output
+
+
+
+
+ {isExpanded && (
+ <>
+
+ {Object.entries(data || {}).map(([key, value]) => (
+
+
+
+ Pin:
+
+
+ {beautifyString(key)}
+
+
+
+
+ Data:
+
+
+
+ {JSON.stringify(value, null, 2)}
+
+
+ {/* TODO: Add tooltip for each button and also make all these blocks working */}
+
+
+
+
+
+
+
+ ))}
+
+
+ {/* TODO: Currently this button is not working, need to make it working */}
+
+ >
+ )}
+
+ );
+};
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeExecutionBadge.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeExecutionBadge.tsx
new file mode 100644
index 0000000000..d47ba4ea7d
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeExecutionBadge.tsx
@@ -0,0 +1,32 @@
+import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecutionStatus";
+import { Badge } from "@/components/__legacy__/ui/badge";
+import { LoadingSpinner } from "@/components/__legacy__/ui/loading";
+import { cn } from "@/lib/utils";
+
+const statusStyles: Record = {
+ INCOMPLETE: "text-slate-700 border-slate-400",
+ QUEUED: "text-blue-700 border-blue-400",
+ RUNNING: "text-amber-700 border-amber-400",
+ COMPLETED: "text-green-700 border-green-400",
+ TERMINATED: "text-orange-700 border-orange-400",
+ FAILED: "text-red-700 border-red-400",
+};
+
+export const NodeExecutionBadge = ({
+ status,
+}: {
+ status: AgentExecutionStatus;
+}) => {
+ return (
+
+
+ {status}
+ {status === AgentExecutionStatus.RUNNING && (
+
+ )}
+
+
+ );
+};
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/helpers.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/helpers.ts
new file mode 100644
index 0000000000..5547468828
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/helpers.ts
@@ -0,0 +1,10 @@
+import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecutionStatus";
+
+export const nodeStyleBasedOnStatus: Record = {
+ INCOMPLETE: "ring-slate-300 bg-slate-300",
+ QUEUED: " ring-blue-300 bg-blue-300",
+ RUNNING: "ring-amber-300 bg-amber-300",
+ COMPLETED: "ring-green-300 bg-green-300",
+ TERMINATED: "ring-orange-300 bg-orange-300 ",
+ FAILED: "ring-red-300 bg-red-300",
+};
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/OutputHandler.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/OutputHandler.tsx
index 5ac148b6ab..9d99a95812 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/OutputHandler.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/OutputHandler.tsx
@@ -35,7 +35,7 @@ export const OutputHandler = ({
>
Output{" "}
= ({
}
return (
-
+
{label && schema.type && (