@@ -129,9 +133,10 @@ export const GraphSearchContent: React.FC
= ({
Node Type: {nodeType}
- {node.data?.metadata?.customized_name && (
+ {!!node.data?.metadata?.customized_name && (
- Custom Name: {node.data.metadata.customized_name}
+ Custom Name:{" "}
+ {String(node.data.metadata.customized_name)}
)}
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/NewSearchGraph/GraphMenuSearchBar/useGraphMenuSearchBar.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/NewSearchGraph/GraphMenuSearchBar/useGraphMenuSearchBar.tsx
index 4342f4ee61..fe9d0c1bae 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/NewSearchGraph/GraphMenuSearchBar/useGraphMenuSearchBar.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/NewSearchGraph/GraphMenuSearchBar/useGraphMenuSearchBar.tsx
@@ -1,5 +1,5 @@
import { useState, useMemo, useDeferredValue } from "react";
-import { CustomNode } from "@/app/(platform)/build/components/legacy-builder/CustomNode/CustomNode";
+import { CustomNode } from "../../../FlowEditor/nodes/CustomNode/CustomNode";
import { beautifyString } from "@/lib/utils";
import jaro from "jaro-winkler";
@@ -67,10 +67,10 @@ function calculateNodeScore(
const nodeTitle = (node.data?.title || "").toLowerCase(); // This includes the ID
const nodeId = (node.id || "").toLowerCase();
const nodeDescription = (node.data?.description || "").toLowerCase();
- const blockType = (node.data?.blockType || "").toLowerCase();
+ const blockType = (node.data?.title || "").toLowerCase();
const beautifiedBlockType = beautifyString(blockType).toLowerCase();
- const customizedName = (
- node.data?.metadata?.customized_name || ""
+ const customizedName = String(
+ node.data?.metadata?.customized_name || "",
).toLowerCase();
// Get input and output names with defensive checks
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/useNewControlPanel.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/useNewControlPanel.ts
index c80ec1149a..4d06271b68 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/useNewControlPanel.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/NewControlPanel/useNewControlPanel.ts
@@ -1,54 +1,18 @@
-import { GraphID } from "@/lib/autogpt-server-api";
-import { useSearchParams } from "next/navigation";
import { useState } from "react";
export interface NewControlPanelProps {
- // flowExecutionID: GraphExecutionID | undefined;
visualizeBeads?: "no" | "static" | "animate";
}
export const useNewControlPanel = ({
- // flowExecutionID,
visualizeBeads: _visualizeBeads,
}: NewControlPanelProps) => {
const [blockMenuSelected, setBlockMenuSelected] = useState<
"save" | "block" | "search" | ""
>("");
- const query = useSearchParams();
- const _graphVersion = query.get("flowVersion");
- const _graphVersionParsed = _graphVersion
- ? parseInt(_graphVersion)
- : undefined;
-
- const _flowID = (query.get("flowID") as GraphID | null) ?? undefined;
- // const {
- // agentDescription,
- // setAgentDescription,
- // saveAgent,
- // agentName,
- // setAgentName,
- // savedAgent,
- // isSaving,
- // isRunning,
- // isStopping,
- // } = useAgentGraph(
- // flowID,
- // graphVersion,
- // flowExecutionID,
- // visualizeBeads !== "no",
- // );
return {
blockMenuSelected,
setBlockMenuSelected,
- // agentDescription,
- // setAgentDescription,
- // saveAgent,
- // agentName,
- // setAgentName,
- // savedAgent,
- // isSaving,
- // isRunning,
- // isStopping,
};
};
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/BlocksControl.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/BlocksControl.tsx
deleted file mode 100644
index 99b66fe1dc..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/BlocksControl.tsx
+++ /dev/null
@@ -1,443 +0,0 @@
-import React, { useCallback, useMemo, useState, useDeferredValue } from "react";
-import { Card, CardContent, CardHeader } from "@/components/__legacy__/ui/card";
-import { Label } from "@/components/__legacy__/ui/label";
-import { Button } from "@/components/__legacy__/ui/button";
-import { Input } from "@/components/__legacy__/ui/input";
-import { TextRenderer } from "@/components/__legacy__/ui/render";
-import { ScrollArea } from "@/components/__legacy__/ui/scroll-area";
-import { CustomNode } from "@/app/(platform)/build/components/legacy-builder/CustomNode/CustomNode";
-import { beautifyString } from "@/lib/utils";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/__legacy__/ui/popover";
-import {
- Block,
- BlockIORootSchema,
- BlockUIType,
- GraphInputSchema,
- GraphOutputSchema,
- SpecialBlockID,
-} from "@/lib/autogpt-server-api";
-import { MagnifyingGlassIcon, PlusIcon } from "@radix-ui/react-icons";
-import { IconToyBrick } from "@/components/__legacy__/ui/icons";
-import { getPrimaryCategoryColor } from "@/lib/utils";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/atoms/Tooltip/BaseTooltip";
-import { GraphMeta } from "@/lib/autogpt-server-api";
-import jaro from "jaro-winkler";
-import { getV1GetSpecificGraph } from "@/app/api/__generated__/endpoints/graphs/graphs";
-import { okData } from "@/app/api/helpers";
-
-type _Block = Omit
& {
- uiKey?: string;
- inputSchema: BlockIORootSchema | GraphInputSchema;
- outputSchema: BlockIORootSchema | GraphOutputSchema;
- hardcodedValues?: Record;
- _cached?: {
- blockName: string;
- beautifiedName: string;
- description: string;
- };
-};
-
-// Hook to preprocess blocks with cached expensive operations
-const useSearchableBlocks = (blocks: _Block[]): _Block[] => {
- return useMemo(
- () =>
- blocks.map((block) => {
- if (!block._cached) {
- block._cached = {
- blockName: block.name.toLowerCase(),
- beautifiedName: beautifyString(block.name).toLowerCase(),
- description: block.description.toLowerCase(),
- };
- }
- return block;
- }),
- [blocks],
- );
-};
-
-interface BlocksControlProps {
- blocks: _Block[];
- addBlock: (
- id: string,
- name: string,
- hardcodedValues: Record,
- ) => void;
- pinBlocksPopover: boolean;
- flows: GraphMeta[];
- nodes: CustomNode[];
-}
-
-/**
- * A React functional component that displays a control for managing blocks.
- *
- * @component
- * @param {Object} BlocksControlProps - The properties for the BlocksControl component.
- * @param {Block[]} BlocksControlProps.blocks - An array of blocks to be displayed and filtered.
- * @param {(id: string, name: string) => void} BlocksControlProps.addBlock - A function to call when a block is added.
- * @returns The rendered BlocksControl component.
- */
-export function BlocksControl({
- blocks: _blocks,
- addBlock,
- pinBlocksPopover,
- flows,
- nodes,
-}: BlocksControlProps) {
- const [searchQuery, setSearchQuery] = useState("");
- const deferredSearchQuery = useDeferredValue(searchQuery);
- const [selectedCategory, setSelectedCategory] = useState(null);
-
- const blocks = useSearchableBlocks(_blocks);
-
- const graphHasWebhookNodes = nodes.some((n) =>
- [BlockUIType.WEBHOOK, BlockUIType.WEBHOOK_MANUAL].includes(n.data.uiType),
- );
- const graphHasInputNodes = nodes.some(
- (n) => n.data.uiType == BlockUIType.INPUT,
- );
-
- const filteredAvailableBlocks = useMemo(() => {
- const blockList = blocks
- .filter((b) => b.uiType !== BlockUIType.AGENT)
- .sort((a, b) => a.name.localeCompare(b.name));
-
- // Agent blocks are created from GraphMeta which doesn't include schemas.
- // Schemas will be fetched on-demand when the block is actually added.
- const agentBlockList = flows
- .map((flow): _Block => {
- return {
- id: SpecialBlockID.AGENT,
- name: flow.name,
- description:
- `Ver.${flow.version}` +
- (flow.description ? ` | ${flow.description}` : ""),
- categories: [{ category: "AGENT", description: "" }],
- // Empty schemas - will be populated when block is added
- inputSchema: { type: "object", properties: {} },
- outputSchema: { type: "object", properties: {} },
- staticOutput: false,
- uiType: BlockUIType.AGENT,
- costs: [],
- uiKey: flow.id,
- hardcodedValues: {
- graph_id: flow.id,
- graph_version: flow.version,
- // Schemas will be fetched on-demand when block is added
- },
- };
- })
- .map(
- (agentBlock): _Block => ({
- ...agentBlock,
- _cached: {
- blockName: agentBlock.name.toLowerCase(),
- beautifiedName: beautifyString(agentBlock.name).toLowerCase(),
- description: agentBlock.description.toLowerCase(),
- },
- }),
- );
-
- return blockList
- .concat(agentBlockList)
- .map((block) => ({
- block,
- score: blockScoreForQuery(block, deferredSearchQuery),
- }))
- .filter(
- ({ block, score }) =>
- score > 0 &&
- (!selectedCategory ||
- block.categories.some((cat) => cat.category === selectedCategory)),
- )
- .sort((a, b) => b.score - a.score)
- .map(({ block }) => ({
- ...block,
- notAvailable:
- (block.uiType == BlockUIType.WEBHOOK &&
- graphHasWebhookNodes &&
- "Agents can only have one webhook-triggered block") ||
- (block.uiType == BlockUIType.WEBHOOK &&
- graphHasInputNodes &&
- "Webhook-triggered blocks can't be used together with input blocks") ||
- (block.uiType == BlockUIType.INPUT &&
- graphHasWebhookNodes &&
- "Input blocks can't be used together with a webhook-triggered block") ||
- null,
- }));
- }, [
- blocks,
- flows,
- selectedCategory,
- deferredSearchQuery,
- graphHasInputNodes,
- graphHasWebhookNodes,
- ]);
-
- const resetFilters = useCallback(() => {
- setSearchQuery("");
- setSelectedCategory(null);
- }, []);
-
- // Handler to add a block, fetching graph data on-demand for agent blocks
- const handleAddBlock = useCallback(
- async (block: _Block & { notAvailable: string | null }) => {
- if (block.notAvailable) return;
-
- // For agent blocks, fetch the full graph to get schemas
- if (block.uiType === BlockUIType.AGENT && block.hardcodedValues) {
- const graphID = block.hardcodedValues.graph_id as string;
- const graphVersion = block.hardcodedValues.graph_version as number;
- const graphData = okData(
- await getV1GetSpecificGraph(graphID, { version: graphVersion }),
- );
-
- if (graphData) {
- addBlock(block.id, block.name, {
- ...block.hardcodedValues,
- input_schema: graphData.input_schema,
- output_schema: graphData.output_schema,
- });
- } else {
- // Fallback: add without schemas (will be incomplete)
- console.error("Failed to fetch graph data for agent block");
- addBlock(block.id, block.name, block.hardcodedValues || {});
- }
- } else {
- addBlock(block.id, block.name, block.hardcodedValues || {});
- }
- },
- [addBlock],
- );
-
- // Extract unique categories from blocks
- const categories = useMemo(() => {
- return Array.from(
- new Set([
- null,
- ...blocks
- .flatMap((block) => block.categories.map((cat) => cat.category))
- .sort(),
- ]),
- );
- }, [blocks]);
-
- return (
- open || resetFilters()}
- >
-
-
-
-
-
-
-
-
- Blocks
-
-
-
-
-
-
- Blocks
-
-
-
-
- setSearchQuery(e.target.value)}
- className="rounded-lg px-8 py-5 dark:bg-slate-800 dark:text-white"
- data-id="blocks-control-search-input"
- autoComplete="off"
- />
-
-
- {categories.map((category) => {
- const color = getPrimaryCategoryColor([
- { category: category || "All", description: "" },
- ]);
- const colorClass =
- selectedCategory === category ? `${color}` : "";
- return (
-
- setSelectedCategory(
- selectedCategory === category ? null : category,
- )
- }
- >
- {beautifyString((category || "All").toLowerCase())}
-
- );
- })}
-
-
-
-
- {filteredAvailableBlocks.map((block) => (
- {
- if (block.notAvailable) return;
- e.dataTransfer.effectAllowed = "copy";
- e.dataTransfer.setData(
- "application/reactflow",
- JSON.stringify({
- blockId: block.id,
- blockName: block.name,
- hardcodedValues: block?.hardcodedValues || {},
- }),
- );
- }}
- onClick={() => handleAddBlock(block)}
- title={block.notAvailable ?? undefined}
- >
-
-
-
-
- ))}
-
-
-
-
-
- );
-}
-
-/**
- * Evaluates how well a block matches the search query and returns a relevance score.
- * The scoring algorithm works as follows:
- * - Returns 1 if no query (all blocks match equally)
- * - Normalized query for case-insensitive matching
- * - Returns 3 for exact substring matches in block name (highest priority)
- * - Returns 2 when all query words appear in the block name (regardless of order)
- * - Returns 1.X for blocks with names similar to query using Jaro-Winkler distance (X is similarity score)
- * - Returns 0.5 when all query words appear in the block description (lowest priority)
- * - Returns 0 for no match
- *
- * Higher scores will appear first in search results.
- */
-function blockScoreForQuery(block: _Block, query: string): number {
- if (!query) return 1;
- const normalizedQuery = query.toLowerCase().trim();
- const queryWords = normalizedQuery.split(/\s+/);
-
- // Use cached values for performance
- const { blockName, beautifiedName, description } = block._cached!;
-
- // 1. Exact match in name (highest priority)
- if (
- blockName.includes(normalizedQuery) ||
- beautifiedName.includes(normalizedQuery)
- ) {
- return 3;
- }
-
- // 2. All query words in name (regardless of order)
- const allWordsInName = queryWords.every(
- (word) => blockName.includes(word) || beautifiedName.includes(word),
- );
- if (allWordsInName) return 2;
-
- // 3. Similarity with name (Jaro-Winkler)
- const similarityThreshold = 0.65;
- const nameSimilarity = jaro(blockName, normalizedQuery);
- const beautifiedSimilarity = jaro(beautifiedName, normalizedQuery);
- const maxSimilarity = Math.max(nameSimilarity, beautifiedSimilarity);
- if (maxSimilarity > similarityThreshold) {
- return 1 + maxSimilarity; // Score between 1 and 2
- }
-
- // 4. All query words in description (lower priority)
- const allWordsInDescription = queryWords.every((word) =>
- description.includes(word),
- );
- if (allWordsInDescription) return 0.5;
-
- return 0;
-}
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/BuildActionBar.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/BuildActionBar.tsx
deleted file mode 100644
index 9d12439d8d..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/BuildActionBar.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import React from "react";
-import { cn } from "@/lib/utils";
-import { Button } from "@/components/__legacy__/ui/button";
-import { LogOut } from "lucide-react";
-import { ClockIcon, WarningIcon } from "@phosphor-icons/react";
-import { IconPlay, IconSquare } from "@/components/__legacy__/ui/icons";
-
-interface Props {
- onClickAgentOutputs?: () => void;
- onClickRunAgent?: () => void;
- onClickStopRun: () => void;
- onClickScheduleButton?: () => void;
- isRunning: boolean;
- isDisabled: boolean;
- className?: string;
- resolutionModeActive?: boolean;
-}
-
-export const BuildActionBar: React.FC = ({
- onClickAgentOutputs,
- onClickRunAgent,
- onClickStopRun,
- onClickScheduleButton,
- isRunning,
- isDisabled,
- className,
- resolutionModeActive = false,
-}) => {
- const buttonClasses =
- "flex items-center gap-2 text-sm font-medium md:text-lg";
-
- // Show resolution mode message instead of action buttons
- if (resolutionModeActive) {
- return (
-
-
-
-
- Remove incompatible connections to continue
-
-
-
- );
- }
-
- return (
-
-
- {onClickAgentOutputs && (
-
- Agent Outputs
-
- )}
-
- {!isRunning ? (
-
- Run
-
- ) : (
-
- Stop
-
- )}
-
- {onClickScheduleButton && (
-
-
- Schedule Run
-
- )}
-
-
- );
-};
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/ConnectionLine.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/ConnectionLine.tsx
deleted file mode 100644
index 0a790aedd4..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/ConnectionLine.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import {
- BaseEdge,
- ConnectionLineComponentProps,
- Node,
- getBezierPath,
- Position,
-} from "@xyflow/react";
-
-export default function ConnectionLine({
- fromPosition,
- fromHandle,
- fromX,
- fromY,
- toPosition,
- toX,
- toY,
-}: ConnectionLineComponentProps) {
- const sourceX =
- fromPosition === Position.Right
- ? fromX + ((fromHandle?.width ?? 0) / 2 - 5)
- : fromX - ((fromHandle?.width ?? 0) / 2 - 5);
-
- const [path] = getBezierPath({
- sourceX: sourceX,
- sourceY: fromY,
- sourcePosition: fromPosition,
- targetX: toX,
- targetY: toY,
- targetPosition: toPosition,
- });
-
- return ;
-}
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/ControlPanel.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/ControlPanel.tsx
deleted file mode 100644
index ecf4f443d5..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/ControlPanel.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { Card, CardContent } from "@/components/__legacy__/ui/card";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/atoms/Tooltip/BaseTooltip";
-import { Button } from "@/components/__legacy__/ui/button";
-import { Separator } from "@/components/__legacy__/ui/separator";
-import { cn } from "@/lib/utils";
-import React from "react";
-
-/**
- * Represents a control element for the ControlPanel Component.
- * @type {Object} Control
- * @property {React.ReactNode} icon - The icon of the control from lucide-react https://lucide.dev/icons/
- * @property {string} label - The label of the control, to be leveraged by ToolTip.
- * @property {onclick} onClick - The function to be executed when the control is clicked.
- */
-export type Control = {
- icon: React.ReactNode;
- label: string;
- disabled?: boolean;
- onClick: () => void;
-};
-
-interface ControlPanelProps {
- controls: Control[];
- topChildren?: React.ReactNode;
- botChildren?: React.ReactNode;
- className?: string;
-}
-
-/**
- * ControlPanel component displays a panel with controls as icons.tsx with the ability to take in children.
- * @param {Object} ControlPanelProps - The properties of the control panel component.
- * @param {Array} ControlPanelProps.controls - An array of control objects representing actions to be preformed.
- * @param {Array} ControlPanelProps.children - The child components of the control panel.
- * @param {string} ControlPanelProps.className - Additional CSS class names for the control panel.
- * @returns The rendered control panel component.
- */
-export const ControlPanel = ({
- controls,
- topChildren,
- botChildren,
- className,
-}: ControlPanelProps) => {
- return (
-
-
-
- {topChildren}
-
- {controls.map((control, index) => (
-
-
-
- control.onClick()}
- data-id={`control-button-${index}`}
- data-testid={`blocks-control-${control.label.toLowerCase()}-button`}
- disabled={control.disabled || false}
- className="dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
- >
- {control.icon}
- {control.label}
-
-
-
-
- {control.label}
-
-
- ))}
-
- {botChildren}
-
-
-
- );
-};
-export default ControlPanel;
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomEdge/CustomEdge.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomEdge/CustomEdge.tsx
deleted file mode 100644
index 5ca5393d69..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomEdge/CustomEdge.tsx
+++ /dev/null
@@ -1,240 +0,0 @@
-import React, {
- useCallback,
- useContext,
- useEffect,
- useState,
- useRef,
-} from "react";
-import {
- BaseEdge,
- EdgeLabelRenderer,
- EdgeProps,
- useReactFlow,
- XYPosition,
- Edge,
- Node,
-} from "@xyflow/react";
-import "./customedge.css";
-import { X } from "lucide-react";
-import { BuilderContext } from "../Flow/Flow";
-import { NodeExecutionResult } from "@/lib/autogpt-server-api";
-import { useCustomEdge } from "./useCustomEdge";
-
-export type CustomEdgeData = {
- edgeColor: string;
- sourcePos?: XYPosition;
- isStatic?: boolean;
- beadUp: number;
- beadDown: number;
- beadData?: Map;
-};
-
-type Bead = {
- t: number;
- targetT: number;
- startTime: number;
-};
-
-export type CustomEdge = Edge;
-
-export function CustomEdge({
- id,
- data,
- selected,
- sourceX,
- sourceY,
- targetX,
- targetY,
- markerEnd,
-}: EdgeProps) {
- const [beads, setBeads] = useState<{
- beads: Bead[];
- created: number;
- destroyed: number;
- }>({ beads: [], created: 0, destroyed: 0 });
- const beadsRef = useRef(beads);
- const { svgPath, length, getPointForT, getTForDistance } = useCustomEdge(
- sourceX - 5,
- sourceY - 5,
- targetX + 3,
- targetY - 5,
- );
- const { deleteElements } = useReactFlow();
- const builderContext = useContext(BuilderContext);
- const { visualizeBeads } = builderContext ?? {
- visualizeBeads: "no",
- };
-
- // Check if this edge is broken (during resolution mode)
- const isBroken =
- builderContext?.resolutionMode?.active &&
- builderContext?.resolutionMode?.brokenEdgeIds?.includes(id);
-
- const onEdgeRemoveClick = () => {
- deleteElements({ edges: [{ id }] });
- };
-
- const animationDuration = 500; // Duration in milliseconds for bead to travel the curve
- const beadDiameter = 12;
- const deltaTime = 16;
-
- 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 {
- ...bead,
- t: visualizeBeads === "animate" ? bead.t : t,
- targetT: t,
- } as Bead;
- });
- },
- [getTForDistance, length, visualizeBeads],
- );
-
- beadsRef.current = beads;
- useEffect(() => {
- const beadUp: number = data?.beadUp ?? 0;
- const beadDown: number = data?.beadDown ?? 0;
-
- if (
- beadUp === 0 &&
- beadDown === 0 &&
- (beads.created > 0 || beads.destroyed > 0)
- ) {
- setBeads({ beads: [], created: 0, destroyed: 0 });
- return;
- }
-
- // Add beads
- if (beadUp > beads.created) {
- setBeads(({ beads, created, destroyed }) => {
- const newBeads = [];
- for (let i = 0; i < beadUp - created; i++) {
- newBeads.push({ t: 0, targetT: 0, startTime: Date.now() });
- }
-
- const b = setTargetPositions([...beads, ...newBeads]);
- return { beads: b, created: beadUp, destroyed };
- });
- }
-
- // Animate and remove beads
- const interval = setInterval(
- ({ current: beads }) => {
- // If there are no beads visible or moving, stop re-rendering
- if (
- (beadUp === beads.created && beads.created === beads.destroyed) ||
- beads.beads.every((bead) => bead.t >= bead.targetT)
- ) {
- clearInterval(interval);
- return;
- }
-
- setBeads(({ beads, created, destroyed }) => {
- let destroyedCount = 0;
-
- const newBeads = beads
- .map((bead) => {
- const progressIncrement = deltaTime / animationDuration;
- const t = Math.min(
- bead.t + bead.targetT * progressIncrement,
- bead.targetT,
- );
-
- return { ...bead, t };
- })
- .filter((bead, index) => {
- const removeCount = beadDown - destroyed;
- if (bead.t >= bead.targetT && index < removeCount) {
- destroyedCount++;
- return false;
- }
- return true;
- });
-
- return {
- beads: setTargetPositions(newBeads),
- created,
- destroyed: destroyed + destroyedCount,
- };
- });
- },
- deltaTime,
- beadsRef,
- );
-
- return () => clearInterval(interval);
- }, [data?.beadUp, data?.beadDown, setTargetPositions, visualizeBeads]);
-
- const middle = getPointForT(0.5);
-
- // Determine edge color - red for broken edges
- const baseColor = data?.edgeColor ?? "#555555";
- const edgeColor = isBroken ? "#ef4444" : baseColor;
- // Add opacity to hex color (99 = 60% opacity, 80 = 50% opacity)
- const strokeColor = isBroken
- ? `${edgeColor}99`
- : selected
- ? edgeColor
- : `${edgeColor}80`;
-
- return (
- <>
-
-
-
-
-
-
-
-
-
- {beads.beads.map((bead, index) => {
- const pos = getPointForT(bead.t);
- return (
-
- );
- })}
- >
- );
-}
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomEdge/customedge.css b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomEdge/customedge.css
deleted file mode 100644
index 6babb8e770..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomEdge/customedge.css
+++ /dev/null
@@ -1,48 +0,0 @@
-.edge-label-renderer {
- position: absolute;
- pointer-events: all;
-}
-
-.edge-label-button {
- width: 20px;
- height: 20px;
- background: #eee;
- border: 1px solid #fff;
- cursor: pointer;
- border-radius: 50%;
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 0;
- color: #555;
- opacity: 0;
- transition:
- opacity 0.2s ease-in-out,
- background-color 0.2s ease-in-out;
-}
-
-.edge-label-button.visible {
- opacity: 1;
-}
-
-.edge-label-button:hover {
- box-shadow: 0 0 6px 2px rgba(0, 0, 0, 0.08);
- background: #f0f0f0;
-}
-
-.edge-label-button svg {
- width: 14px;
- height: 14px;
-}
-
-.react-flow__edge-interaction {
- cursor: pointer;
-}
-
-.react-flow__edges > svg:has(> g.selected) {
- z-index: 10 !important;
-}
-
-.react-flow__edgelabel-renderer {
- z-index: 11 !important;
-}
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomEdge/useCustomEdge.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomEdge/useCustomEdge.ts
deleted file mode 100644
index f8fdeda411..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomEdge/useCustomEdge.ts
+++ /dev/null
@@ -1,157 +0,0 @@
-import { useCallback, useMemo } from "react";
-
-type XYPosition = {
- x: number;
- y: number;
-};
-
-export type BezierPath = {
- sourcePosition: XYPosition;
- control1: XYPosition;
- control2: XYPosition;
- targetPosition: XYPosition;
-};
-
-export function useCustomEdge(
- sourceX: number,
- sourceY: number,
- targetX: number,
- targetY: number,
-) {
- const path: BezierPath = useMemo(() => {
- const xDifference = Math.abs(sourceX - targetX);
- const yDifference = Math.abs(sourceY - targetY);
- const xControlDistance =
- sourceX < targetX ? 64 : Math.max(xDifference / 2, 64);
- const yControlDistance = yDifference < 128 && sourceX > targetX ? -64 : 0;
-
- return {
- sourcePosition: { x: sourceX, y: sourceY },
- control1: {
- x: sourceX + xControlDistance,
- y: sourceY + yControlDistance,
- },
- control2: {
- x: targetX - xControlDistance,
- y: targetY + yControlDistance,
- },
- targetPosition: { x: targetX, y: targetY },
- };
- }, [sourceX, sourceY, targetX, targetY]);
-
- const svgPath = useMemo(
- () =>
- `M ${path.sourcePosition.x} ${path.sourcePosition.y} ` +
- `C ${path.control1.x} ${path.control1.y} ${path.control2.x} ${path.control2.y} ` +
- `${path.targetPosition.x}, ${path.targetPosition.y}`,
- [path],
- );
-
- const getPointForT = useCallback(
- (t: number) => {
- // Bezier formula: (1-t)^3 * p0 + 3*(1-t)^2*t*p1 + 3*(1-t)*t^2*p2 + t^3*p3
- const x =
- Math.pow(1 - t, 3) * path.sourcePosition.x +
- 3 * Math.pow(1 - t, 2) * t * path.control1.x +
- 3 * (1 - t) * Math.pow(t, 2) * path.control2.x +
- Math.pow(t, 3) * path.targetPosition.x;
-
- const y =
- Math.pow(1 - t, 3) * path.sourcePosition.y +
- 3 * Math.pow(1 - t, 2) * t * path.control1.y +
- 3 * (1 - t) * Math.pow(t, 2) * path.control2.y +
- Math.pow(t, 3) * path.targetPosition.y;
-
- return { x, y };
- },
- [path],
- );
-
- const getArcLength = useCallback(
- (t: number, samples: number = 100) => {
- let length = 0;
- let prevPoint = getPointForT(0);
-
- for (let i = 1; i <= samples; i++) {
- const currT = (i / samples) * t;
- const currPoint = getPointForT(currT);
- length += Math.sqrt(
- Math.pow(currPoint.x - prevPoint.x, 2) +
- Math.pow(currPoint.y - prevPoint.y, 2),
- );
- prevPoint = currPoint;
- }
-
- return length;
- },
- [getPointForT],
- );
-
- const length = useMemo(() => {
- return getArcLength(1);
- }, [getArcLength]);
-
- const getBezierDerivative = useCallback(
- (t: number) => {
- const mt = 1 - t;
- const x =
- 3 *
- (mt * mt * (path.control1.x - path.sourcePosition.x) +
- 2 * mt * t * (path.control2.x - path.control1.x) +
- t * t * (path.targetPosition.x - path.control2.x));
- const y =
- 3 *
- (mt * mt * (path.control1.y - path.sourcePosition.y) +
- 2 * mt * t * (path.control2.y - path.control1.y) +
- t * t * (path.targetPosition.y - path.control2.y));
- return { x, y };
- },
- [path],
- );
-
- const getTForDistance = useCallback(
- (distance: number, epsilon: number = 0.0001) => {
- if (distance < 0) {
- distance = length + distance; // If distance is negative, calculate from the end of the curve
- }
-
- let t = distance / getArcLength(1);
- let prevT = 0;
-
- while (Math.abs(t - prevT) > epsilon) {
- prevT = t;
- const length = getArcLength(t);
- const derivative = Math.sqrt(
- Math.pow(getBezierDerivative(t).x, 2) +
- Math.pow(getBezierDerivative(t).y, 2),
- );
- t -= (length - distance) / derivative;
- t = Math.max(0, Math.min(1, t)); // Clamp t between 0 and 1
- }
-
- return t;
- },
- [getArcLength, getBezierDerivative, length],
- );
-
- const getPointAtDistance = useCallback(
- (distance: number) => {
- if (distance < 0) {
- distance = length + distance; // If distance is negative, calculate from the end of the curve
- }
-
- const t = getTForDistance(distance);
- return getPointForT(t);
- },
- [getTForDistance, getPointForT, length],
- );
-
- return {
- path,
- svgPath,
- length,
- getPointForT,
- getTForDistance,
- getPointAtDistance,
- };
-}
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomNode/CustomNode.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomNode/CustomNode.tsx
deleted file mode 100644
index 834603cc4a..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomNode/CustomNode.tsx
+++ /dev/null
@@ -1,1270 +0,0 @@
-import { getV1GetAyrshareSsoUrl } from "@/app/api/__generated__/endpoints/integrations/integrations";
-import { Input } from "@/components/__legacy__/ui/input";
-import { TextRenderer } from "@/components/__legacy__/ui/render";
-import { Button } from "@/components/atoms/Button/Button";
-import { Switch } from "@/components/atoms/Switch/Switch";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/atoms/Tooltip/BaseTooltip";
-import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
-import { toast } from "@/components/molecules/Toast/use-toast";
-import useCredits from "@/hooks/useCredits";
-import {
- BlockCost,
- BlockIORootSchema,
- BlockIOStringSubSchema,
- BlockIOSubSchema,
- BlockUIType,
- Category,
- GraphInputSchema,
- GraphOutputSchema,
- NodeExecutionResult,
-} from "@/lib/autogpt-server-api";
-import {
- beautifyString,
- cn,
- fillObjectDefaultsFromSchema,
- getPrimaryCategoryColor,
- getValue,
- hasNonNullNonObjectValue,
- isObject,
- parseKeys,
- setNestedProperty,
-} from "@/lib/utils";
-import { InfoIcon, Key } from "@phosphor-icons/react";
-import * as ContextMenu from "@radix-ui/react-context-menu";
-import {
- CopyIcon,
- DotsVerticalIcon,
- ExitIcon,
- Pencil1Icon,
- TrashIcon,
-} from "@radix-ui/react-icons";
-import * as Separator from "@radix-ui/react-separator";
-import { Edge, NodeProps, useReactFlow, Node as XYNode } from "@xyflow/react";
-import "@xyflow/react/dist/style.css";
-import Link from "next/link";
-import React, {
- useCallback,
- useContext,
- useEffect,
- useRef,
- useState,
-} from "react";
-import { Badge } from "@/components/__legacy__/ui/badge";
-import { IconCoin } from "@/components/__legacy__/ui/icons";
-import { Alert, AlertDescription } from "@/components/molecules/Alert/Alert";
-import { BuilderContext } from "../Flow/Flow";
-import { history } from "../history";
-import InputModalComponent from "../InputModalComponent";
-import NodeHandle from "../NodeHandle";
-import { NodeGenericInputField, NodeTextBoxInput } from "../NodeInputs";
-import NodeOutputs from "../NodeOutputs";
-import OutputModalComponent from "../OutputModalComponent";
-import "./customnode.css";
-import { SubAgentUpdateBar } from "./SubAgentUpdateBar";
-import { IncompatibilityDialog } from "./IncompatibilityDialog";
-import {
- useSubAgentUpdate,
- createUpdatedAgentNodeInputs,
- getBrokenEdgeIDs,
-} from "../../../hooks/useSubAgentUpdate";
-
-export type ConnectedEdge = {
- id: string;
- source: string;
- sourceHandle: string;
- target: string;
- targetHandle: string;
-};
-
-export type CustomNodeData = {
- blockType: string;
- blockCosts: BlockCost[];
- title: string;
- description: string;
- categories: Category[];
- inputSchema: BlockIORootSchema;
- outputSchema: BlockIORootSchema;
- hardcodedValues: { [key: string]: any };
- connections: ConnectedEdge[];
- isOutputOpen: boolean;
- status?: NodeExecutionResult["status"];
- /** executionResults contains outputs across multiple executions
- * with the last element being the most recent output */
- executionResults?: {
- execId: string;
- data: NodeExecutionResult["output_data"];
- status: NodeExecutionResult["status"];
- }[];
- block_id: string;
- backend_id?: string;
- errors?: { [key: string]: string };
- isOutputStatic?: boolean;
- uiType: BlockUIType;
- metadata?: { customized_name?: string; [key: string]: any };
-};
-
-export type CustomNode = XYNode;
-
-export const CustomNode = React.memo(
- function CustomNode({ data, id, height, selected }: NodeProps) {
- const [isOutputOpen, setIsOutputOpen] = useState(
- data.isOutputOpen || false,
- );
- const [isAdvancedOpen, setIsAdvancedOpen] = useState(false);
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [activeKey, setActiveKey] = useState(null);
- const [inputModalValue, setInputModalValue] = useState("");
- const [isOutputModalOpen, setIsOutputModalOpen] = useState(false);
- const [isEditingTitle, setIsEditingTitle] = useState(false);
- const [customTitle, setCustomTitle] = useState(
- data.metadata?.customized_name || "",
- );
- const [isTitleHovered, setIsTitleHovered] = useState(false);
- const titleInputRef = useRef(null);
- const { updateNodeData, deleteElements, addNodes, getNode } = useReactFlow<
- CustomNode,
- Edge
- >();
- const isInitialSetup = useRef(true);
- const builderContext = useContext(BuilderContext);
- const { formatCredits } = useCredits();
- const [isLoading, setIsLoading] = useState(false);
-
- let subGraphID = "";
-
- if (!builderContext) {
- throw new Error(
- "BuilderContext consumer must be inside FlowEditor component",
- );
- }
-
- const {
- libraryAgent,
- setIsAnyModalOpen,
- getNextNodeId,
- availableFlows,
- resolutionMode,
- enterResolutionMode,
- } = builderContext;
-
- // Check if this node is in resolution mode (moved up for schema merge logic)
- const isInResolutionMode =
- resolutionMode.active && resolutionMode.nodeId === id;
-
- if (data.uiType === BlockUIType.AGENT) {
- // Display the graph's schema instead AgentExecutorBlock's schema.
- const currentInputSchema = data.hardcodedValues?.input_schema || {};
- const currentOutputSchema = data.hardcodedValues?.output_schema || {};
- subGraphID = data.hardcodedValues?.graph_id || subGraphID;
-
- // During resolution mode, merge old connected inputs/outputs with new schema
- if (isInResolutionMode && resolutionMode.pendingUpdate) {
- const newInputSchema =
- (resolutionMode.pendingUpdate.input_schema as BlockIORootSchema) ||
- {};
- const newOutputSchema =
- (resolutionMode.pendingUpdate.output_schema as BlockIORootSchema) ||
- {};
-
- // Merge input schemas: start with new schema, add old connected inputs that are missing
- const mergedInputProps = { ...newInputSchema.properties };
- const incomp = resolutionMode.incompatibilities;
- if (incomp && currentInputSchema.properties) {
- // Add back missing inputs that have connections (so user can see/delete them)
- incomp.missingInputs.forEach((inputName) => {
- if (currentInputSchema.properties[inputName]) {
- mergedInputProps[inputName] =
- currentInputSchema.properties[inputName];
- }
- });
- // Add back inputs with type mismatches (keep old type so connection still works visually)
- incomp.inputTypeMismatches.forEach((mismatch) => {
- if (currentInputSchema.properties[mismatch.name]) {
- mergedInputProps[mismatch.name] =
- currentInputSchema.properties[mismatch.name];
- }
- });
- }
-
- // Merge output schemas: start with new schema, add old connected outputs that are missing
- const mergedOutputProps = { ...newOutputSchema.properties };
- if (incomp && currentOutputSchema.properties) {
- incomp.missingOutputs.forEach((outputName) => {
- if (currentOutputSchema.properties[outputName]) {
- mergedOutputProps[outputName] =
- currentOutputSchema.properties[outputName];
- }
- });
- }
-
- data.inputSchema = {
- ...newInputSchema,
- properties: mergedInputProps,
- };
- data.outputSchema = {
- ...newOutputSchema,
- properties: mergedOutputProps,
- };
- } else {
- data.inputSchema = currentInputSchema;
- data.outputSchema = currentOutputSchema;
- }
- }
-
- const setHardcodedValues = useCallback(
- (values: any) => {
- updateNodeData(id, { hardcodedValues: values });
- },
- [id, updateNodeData],
- );
-
- // Sub-agent update detection
- const isAgentBlock = data.uiType === BlockUIType.AGENT;
- const graphId = isAgentBlock ? data.hardcodedValues?.graph_id : undefined;
- const graphVersion = isAgentBlock
- ? data.hardcodedValues?.graph_version
- : undefined;
-
- const subAgentUpdate = useSubAgentUpdate(
- id,
- graphId,
- graphVersion,
- isAgentBlock
- ? (data.hardcodedValues?.input_schema as GraphInputSchema)
- : undefined,
- isAgentBlock
- ? (data.hardcodedValues?.output_schema as GraphOutputSchema)
- : undefined,
- data.connections,
- availableFlows,
- );
-
- const [showIncompatibilityDialog, setShowIncompatibilityDialog] =
- useState(false);
-
- // Helper to check if a handle is broken (for resolution mode)
- const isInputHandleBroken = useCallback(
- (handleName: string): boolean => {
- if (!isInResolutionMode || !resolutionMode.incompatibilities) {
- return false;
- }
- const incomp = resolutionMode.incompatibilities;
- return (
- incomp.missingInputs.includes(handleName) ||
- incomp.inputTypeMismatches.some((m) => m.name === handleName)
- );
- },
- [isInResolutionMode, resolutionMode.incompatibilities],
- );
-
- const isOutputHandleBroken = useCallback(
- (handleName: string): boolean => {
- if (!isInResolutionMode || !resolutionMode.incompatibilities) {
- return false;
- }
- return resolutionMode.incompatibilities.missingOutputs.includes(
- handleName,
- );
- },
- [isInResolutionMode, resolutionMode.incompatibilities],
- );
-
- // Handle update button click
- const handleUpdateClick = useCallback(() => {
- if (!subAgentUpdate.latestGraph) return;
-
- if (subAgentUpdate.isCompatible) {
- // Compatible update - directly apply
- const updatedValues = createUpdatedAgentNodeInputs(
- data.hardcodedValues,
- subAgentUpdate.latestGraph,
- );
- setHardcodedValues(updatedValues);
- toast({
- title: "Agent updated",
- description: `Updated to version ${subAgentUpdate.latestVersion}`,
- });
- } else {
- // Incompatible update - show dialog
- setShowIncompatibilityDialog(true);
- }
- }, [subAgentUpdate, data.hardcodedValues, setHardcodedValues]);
-
- // Handle confirm incompatible update
- const handleConfirmIncompatibleUpdate = useCallback(() => {
- if (!subAgentUpdate.latestGraph || !subAgentUpdate.incompatibilities) {
- return;
- }
-
- // Create the updated values but DON'T apply them yet
- const updatedValues = createUpdatedAgentNodeInputs(
- data.hardcodedValues,
- subAgentUpdate.latestGraph,
- );
-
- // Get broken edge IDs
- const brokenEdgeIds = getBrokenEdgeIDs(
- data.connections,
- subAgentUpdate.incompatibilities,
- id,
- );
-
- // Enter resolution mode with pending update (don't apply schema yet)
- enterResolutionMode(
- id,
- subAgentUpdate.incompatibilities,
- brokenEdgeIds,
- updatedValues,
- );
-
- setShowIncompatibilityDialog(false);
- }, [
- subAgentUpdate,
- data.hardcodedValues,
- data.connections,
- id,
- enterResolutionMode,
- ]);
-
- useEffect(() => {
- if (data.executionResults || data.status) {
- setIsOutputOpen(true);
- }
- }, [data.executionResults, data.status]);
-
- useEffect(() => {
- setIsOutputOpen(data.isOutputOpen);
- }, [data.isOutputOpen]);
-
- useEffect(() => {
- setIsAnyModalOpen?.(isModalOpen || isOutputModalOpen);
- }, [isModalOpen, isOutputModalOpen, data, setIsAnyModalOpen]);
-
- const handleTitleEdit = useCallback(() => {
- setIsEditingTitle(true);
- setTimeout(() => {
- titleInputRef.current?.focus();
- titleInputRef.current?.select();
- }, 0);
- }, []);
-
- const handleTitleSave = useCallback(() => {
- setIsEditingTitle(false);
- const newMetadata = {
- ...data.metadata,
- customized_name: customTitle.trim() || undefined,
- };
- updateNodeData(id, { metadata: newMetadata });
- }, [customTitle, data.metadata, id, updateNodeData]);
-
- const handleTitleKeyDown = useCallback(
- (e: React.KeyboardEvent) => {
- if (e.key === "Enter") {
- handleTitleSave();
- } else if (e.key === "Escape") {
- setCustomTitle(data.metadata?.customized_name || "");
- setIsEditingTitle(false);
- }
- },
- [handleTitleSave, data.metadata],
- );
-
- const displayTitle =
- customTitle ||
- beautifyString(data.blockType?.replace(/Block$/, "") || data.title);
-
- useEffect(() => {
- isInitialSetup.current = false;
- if (data.backend_id) return; // don't auto-modify existing nodes
-
- if (data.uiType === BlockUIType.AGENT) {
- setHardcodedValues({
- ...data.hardcodedValues,
- inputs: fillObjectDefaultsFromSchema(
- data.hardcodedValues.inputs ?? {},
- data.inputSchema,
- ),
- });
- } else {
- setHardcodedValues(
- fillObjectDefaultsFromSchema(data.hardcodedValues, data.inputSchema),
- );
- }
- }, []);
-
- const setErrors = useCallback(
- (errors: { [key: string]: string }) => {
- updateNodeData(id, { errors });
- },
- [id, updateNodeData],
- );
-
- const toggleAdvancedSettings = (checked: boolean) => {
- setIsAdvancedOpen(checked);
- };
-
- const generateOutputHandles = (
- schema: BlockIORootSchema,
- nodeType: BlockUIType,
- ) => {
- if (
- !schema?.properties ||
- nodeType === BlockUIType.OUTPUT ||
- nodeType === BlockUIType.NOTE
- )
- return null;
-
- const renderHandles = (
- propSchema: { [key: string]: BlockIOSubSchema },
- keyPrefix = "",
- titlePrefix = "",
- ) => {
- return Object.keys(propSchema).map((propKey) => {
- const fieldSchema = propSchema[propKey];
- const fieldTitle =
- titlePrefix + (fieldSchema.title || beautifyString(propKey));
-
- return (
-
-
- {"properties" in fieldSchema &&
- renderHandles(
- fieldSchema.properties,
- `${keyPrefix}${propKey}_#_`,
- `${fieldTitle}.`,
- )}
-
- );
- });
- };
-
- return renderHandles(schema.properties);
- };
-
- const generateAyrshareSSOHandles = () => {
- const handleSSOLogin = async () => {
- setIsLoading(true);
- try {
- const { data, status } = await getV1GetAyrshareSsoUrl();
- if (status !== 200) {
- throw new Error(data.detail);
- }
- const popup = window.open(data.sso_url, "_blank", "popup=true");
- if (!popup) {
- throw new Error(
- "Please allow popups for this site to be able to login with Ayrshare",
- );
- }
- } catch (error) {
- toast({
- title: "Error",
- description: `Error getting SSO URL: ${error}`,
- variant: "destructive",
- });
- } finally {
- setIsLoading(false);
- }
- };
-
- return (
-
-
- {isLoading ? (
- "Loading..."
- ) : (
- <>
-
- Connect Social Media Accounts
- >
- )}
-
-
-
- );
- };
-
- const generateInputHandles = (
- schema: BlockIORootSchema,
- nodeType: BlockUIType,
- ) => {
- if (!schema?.properties) return null;
- const keys = Object.entries(schema.properties);
- switch (nodeType) {
- case BlockUIType.NOTE:
- // For NOTE blocks, don't render any input handles
- const [noteKey, noteSchema] = keys[0];
- return (
-
-
-
- );
-
- default:
- const getInputPropKey = (key: string) =>
- nodeType == BlockUIType.AGENT ? `inputs.${key}` : key;
-
- return keys.map(([propKey, propSchema]) => {
- const isRequired = data.inputSchema.required?.includes(propKey);
- const isAdvanced = propSchema.advanced;
- const isHidden = propSchema.hidden;
- const isConnectable =
- // No input connection handles on INPUT and WEBHOOK blocks
- ![
- BlockUIType.INPUT,
- BlockUIType.WEBHOOK,
- BlockUIType.WEBHOOK_MANUAL,
- ].includes(nodeType) &&
- // No input connection handles for credentials
- propKey !== "credentials" &&
- !propKey.endsWith("_credentials") &&
- // For OUTPUT blocks, only show the 'value' (hides 'name') input connection handle
- !(nodeType == BlockUIType.OUTPUT && propKey == "name");
- const isConnected = isInputHandleConnected(propKey);
-
- return (
- !isHidden &&
- (isRequired || isAdvancedOpen || isConnected || !isAdvanced) && (
-
- {isConnectable &&
- !(
- "oneOf" in propSchema &&
- propSchema.oneOf &&
- "discriminator" in propSchema &&
- propSchema.discriminator
- ) ? (
-
- ) : (
- propKey !== "credentials" &&
- !propKey.endsWith("_credentials") && (
-
-
- {propSchema.title || beautifyString(propKey)}
-
-
-
- )
- )}
- {isConnected || (
-
- )}
-
- )
- );
- });
- }
- };
- const handleInputChange = useCallback(
- (path: string, value: any) => {
- const keys = parseKeys(path);
- const newValues = JSON.parse(JSON.stringify(data.hardcodedValues));
- let current = newValues;
-
- for (let i = 0; i < keys.length - 1; i++) {
- const { key: currentKey, index } = keys[i];
- if (index !== undefined) {
- if (!current[currentKey]) current[currentKey] = [];
- if (!current[currentKey][index]) current[currentKey][index] = {};
- current = current[currentKey][index];
- } else {
- if (!current[currentKey]) current[currentKey] = {};
- current = current[currentKey];
- }
- }
-
- const lastKey = keys[keys.length - 1];
- if (lastKey.index !== undefined) {
- if (!current[lastKey.key]) current[lastKey.key] = [];
- current[lastKey.key][lastKey.index] = value;
- } else {
- current[lastKey.key] = value;
- }
-
- if (!isInitialSetup.current) {
- history.push({
- type: "UPDATE_INPUT",
- payload: { nodeId: id, oldValues: data.hardcodedValues, newValues },
- undo: () => setHardcodedValues(data.hardcodedValues),
- redo: () => setHardcodedValues(newValues),
- });
- }
-
- setHardcodedValues(newValues);
- const errors = data.errors || {};
- // Remove error with the same key
- setNestedProperty(errors, path, null);
- setErrors({ ...errors });
- },
- [data.hardcodedValues, id, setHardcodedValues, data.errors, setErrors],
- );
-
- const isInputHandleConnected = (key: string) => {
- return (
- data.connections &&
- data.connections.some((conn: any) => {
- if (typeof conn === "string") {
- const [_source, target] = conn.split(" -> ");
- return target.includes(key) && target.includes(data.title);
- }
- return conn.target === id && conn.targetHandle === key;
- })
- );
- };
-
- const isOutputHandleConnected = (key: string) => {
- return (
- data.connections &&
- data.connections.some((conn: any) => {
- if (typeof conn === "string") {
- const [source, _target] = conn.split(" -> ");
- return source.includes(key) && source.includes(data.title);
- }
- return conn.source === id && conn.sourceHandle === key;
- })
- );
- };
-
- const handleInputClick = useCallback(
- (key: string) => {
- console.debug(`Opening modal for key: ${key}`);
- setActiveKey(key);
- const value = getValue(key, data.hardcodedValues);
- setInputModalValue(
- typeof value === "object" ? JSON.stringify(value, null, 2) : value,
- );
- setIsModalOpen(true);
- },
- [data.hardcodedValues],
- );
-
- const handleModalSave = useCallback(
- (value: string) => {
- if (activeKey) {
- try {
- const parsedValue = JSON.parse(value);
- // Validate that the parsed value is safe before using it
- if (isObject(parsedValue) || Array.isArray(parsedValue)) {
- handleInputChange(activeKey, parsedValue);
- } else {
- // For primitive values, use the original string
- handleInputChange(activeKey, value);
- }
- } catch {
- // If JSON parsing fails, treat as plain text
- handleInputChange(activeKey, value);
- }
- }
- setIsModalOpen(false);
- setActiveKey(null);
- },
- [activeKey, handleInputChange],
- );
-
- const handleOutputClick = () => {
- setIsOutputModalOpen(true);
- };
-
- const deleteNode = useCallback(() => {
- console.debug("Deleting node:", id);
-
- // Remove the node
- deleteElements({ nodes: [{ id }] });
- }, [id, deleteElements]);
-
- const copyNode = useCallback(() => {
- const newId = getNextNodeId();
- const currentNode = getNode(id);
-
- if (!currentNode) {
- console.error("Cannot copy node: current node not found");
- return;
- }
-
- const verticalOffset = height ?? 100;
-
- const newNode: CustomNode = {
- id: newId,
- type: currentNode.type,
- position: {
- x: currentNode.position.x,
- y: currentNode.position.y - verticalOffset - 20,
- },
- data: {
- ...data,
- title: `${data.title} (Copy)`,
- block_id: data.block_id,
- connections: [],
- isOutputOpen: false,
- metadata: {
- ...data.metadata,
- customized_name: undefined, // Don't copy the custom name
- },
- },
- };
-
- addNodes(newNode);
-
- history.push({
- type: "ADD_NODE",
- payload: { node: { ...newNode, ...newNode.data } as CustomNodeData },
- undo: () => deleteElements({ nodes: [{ id: newId }] }),
- redo: () => addNodes(newNode),
- });
- }, [id, data, height, addNodes, deleteElements, getNode, getNextNodeId]);
-
- const hasConfigErrors =
- data.errors && hasNonNullNonObjectValue(data.errors);
- const outputData = data.executionResults?.at(-1)?.data;
- const hasOutputError =
- typeof outputData === "object" &&
- outputData !== null &&
- "error" in outputData;
-
- useEffect(() => {
- if (hasConfigErrors) {
- const filteredErrors = Object.fromEntries(
- Object.entries(data.errors || {}).filter(([, value]) =>
- hasNonNullNonObjectValue(value),
- ),
- );
- console.error(
- "Block configuration errors for",
- data.title,
- ":",
- filteredErrors,
- );
- }
- if (hasOutputError) {
- console.error(
- "Block output contains error for",
- data.title,
- ":",
- outputData.error,
- );
- }
- }, [hasConfigErrors, hasOutputError, data.errors, outputData, data.title]);
-
- const blockClasses = [
- "custom-node",
- "dark-theme",
- "rounded-xl",
- "bg-white/[.9] dark:bg-gray-800/[.9]",
- "border border-gray-300 dark:border-gray-600",
- data.uiType === BlockUIType.NOTE ? "w-[300px]" : "w-[500px]",
- data.uiType === BlockUIType.NOTE
- ? "bg-yellow-100 dark:bg-yellow-900"
- : "bg-white dark:bg-gray-800",
- selected ? "shadow-2xl" : "",
- ]
- .filter(Boolean)
- .join(" ");
-
- const errorClass =
- hasConfigErrors || hasOutputError
- ? "border-red-200 dark:border-red-800 border-2"
- : "";
-
- const statusClass = (() => {
- if (hasConfigErrors || hasOutputError)
- return "border-red-200 dark:border-red-800 border-4";
- switch (data.status?.toLowerCase()) {
- case "completed":
- return "border-green-200 dark:border-green-800 border-4";
- case "running":
- return "border-yellow-200 dark:border-yellow-800 border-4";
- case "failed":
- return "border-red-200 dark:border-red-800 border-4";
- case "incomplete":
- return "border-purple-200 dark:border-purple-800 border-4";
- case "queued":
- return "border-cyan-200 dark:border-cyan-800 border-4";
- case "review":
- return "border-orange-200 dark:border-orange-800 border-4";
- default:
- return "";
- }
- })();
-
- const statusBackgroundClass = (() => {
- if (hasConfigErrors || hasOutputError)
- return "bg-red-200 dark:bg-red-800";
- switch (data.status?.toLowerCase()) {
- case "completed":
- return "bg-green-200 dark:bg-green-800";
- case "running":
- return "bg-yellow-200 dark:bg-yellow-800";
- case "failed":
- return "bg-red-200 dark:bg-red-800";
- case "incomplete":
- return "bg-purple-200 dark:bg-purple-800";
- case "queued":
- return "bg-cyan-200 dark:bg-cyan-800";
- case "review":
- return "bg-orange-200 dark:bg-orange-800";
- default:
- return "";
- }
- })();
-
- const hasAdvancedFields =
- data.inputSchema?.properties &&
- Object.entries(data.inputSchema.properties).some(([key, value]) => {
- return (
- value.advanced === true && !data.inputSchema.required?.includes(key)
- );
- });
-
- const inputValues = data.hardcodedValues;
-
- const isCostFilterMatch = (costFilter: any, inputValues: any): boolean => {
- /*
- Filter rules:
- - If costFilter is an object, then check if costFilter is the subset of inputValues
- - Otherwise, check if costFilter is equal to inputValues.
- - Undefined, null, and empty string are considered as equal.
- */
- return typeof costFilter === "object" && typeof inputValues === "object"
- ? Object.entries(costFilter).every(
- ([k, v]) =>
- (!v && !inputValues[k]) || isCostFilterMatch(v, inputValues[k]),
- )
- : costFilter === inputValues;
- };
-
- const blockCost =
- data.blockCosts &&
- data.blockCosts.find((cost) =>
- isCostFilterMatch(cost.cost_filter, inputValues),
- );
-
- const LineSeparator = () => (
-
-
-
- );
-
- const ContextMenuContent = () => (
-
-
-
- Copy
-
- {subGraphID && (
- window.open(`/build?flowID=${subGraphID}`)}
- className="flex cursor-pointer items-center rounded-md px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700"
- >
-
- Open agent
-
- )}
-
-
-
- Delete
-
-
- );
-
- const onContextButtonTrigger = (e: React.MouseEvent) => {
- e.preventDefault();
- const rect = e.currentTarget.getBoundingClientRect();
- const event = new MouseEvent("contextmenu", {
- bubbles: true,
- clientX: rect.left + rect.width / 2,
- clientY: rect.top + rect.height / 2,
- });
- e.currentTarget.dispatchEvent(event);
- };
-
- const stripeColor = getPrimaryCategoryColor(data.categories);
-
- const nodeContent = () => (
-
- {/* Header */}
-
- {/* Color Stripe */}
-
-
-
-
-
setIsTitleHovered(true)}
- onMouseLeave={() => setIsTitleHovered(false)}
- >
- {isEditingTitle ? (
-
setCustomTitle(e.target.value)}
- onBlur={handleTitleSave}
- onKeyDown={handleTitleKeyDown}
- className="h-7 w-auto min-w-[100px] max-w-[200px] px-2 py-1 text-lg font-semibold"
- placeholder={beautifyString(
- data.blockType?.replace(/Block$/, "") || data.title,
- )}
- />
- ) : (
-
-
-
-
-
-
- {customTitle && (
-
-
- Type:{" "}
- {beautifyString(
- data.blockType?.replace(/Block$/, "") || data.title,
- )}
-
-
- )}
-
- )}
- {isTitleHovered && !isEditingTitle && (
-
-
-
- )}
-
-
- #{(data.backend_id || id).split("-")[0]}
-
-
-
-
-
-
-
-
-
- {blockCost && (
-
-
- {" "}
-
- {formatCredits(blockCost.cost_amount)}
-
- {" \/ "}
- {blockCost.cost_type}
-
-
- )}
- {data.categories.map((category) => (
-
- {beautifyString(category.category.toLowerCase())}
-
- ))}
-
-
-
-
-
-
- {/* Sub-agent Update Bar - shown below header */}
- {isAgentBlock && (subAgentUpdate.hasUpdate || isInResolutionMode) && (
-
- )}
-
- {/* Body */}
-
- {/* Input Handles */}
- {data.uiType !== BlockUIType.NOTE ? (
-
-
- {data.uiType === BlockUIType.AYRSHARE ? (
- <>
- {generateAyrshareSSOHandles()}
- {generateInputHandles(
- data.inputSchema,
- BlockUIType.STANDARD,
- )}
- >
- ) : [BlockUIType.WEBHOOK, BlockUIType.WEBHOOK_MANUAL].includes(
- data.uiType,
- ) ? (
- <>
-
-
-
-
- You can set up and manage this trigger in your{" "}
-
- Agent Library
-
- {!data.backend_id && " (after saving the graph)"}.
-
-
-
-
- {generateInputHandles(data.inputSchema, data.uiType)}
-
- >
- ) : (
- data.inputSchema &&
- generateInputHandles(data.inputSchema, data.uiType)
- )}
-
-
- ) : (
-
- {data.inputSchema &&
- generateInputHandles(data.inputSchema, data.uiType)}
-
- )}
-
- {/* Advanced Settings */}
- {data.uiType !== BlockUIType.NOTE && hasAdvancedFields && (
- <>
-
-
- Advanced
-
-
- >
- )}
- {/* Output Handles */}
- {data.uiType !== BlockUIType.NOTE && (
- <>
-
-
-
- {data.outputSchema &&
- generateOutputHandles(data.outputSchema, data.uiType)}
-
-
- >
- )}
-
- {/* End Body */}
- {/* Footer */}
-
- {/* Display Outputs */}
- {isOutputOpen && data.uiType !== BlockUIType.NOTE && (
-
- {(data.executionResults?.length ?? 0) > 0 ? (
-
-
-
-
-
- View More
-
-
-
- ) : (
-
- )}
-
-
- {hasConfigErrors || hasOutputError
- ? "Error"
- : data.status
- ? beautifyString(data.status)
- : "Not Run"}
-
-
-
- )}
-
-
setIsModalOpen(false)}
- onSave={handleModalSave}
- defaultValue={inputModalValue}
- key={activeKey}
- />
- setIsOutputModalOpen(false)}
- executionResults={data.executionResults?.toReversed() || []}
- />
-
- );
-
- return (
- <>
-
- {nodeContent()}
-
-
- {/* Incompatibility Dialog for sub-agent updates */}
- {isAgentBlock && subAgentUpdate.incompatibilities && (
- setShowIncompatibilityDialog(false)}
- onConfirm={handleConfirmIncompatibleUpdate}
- currentVersion={subAgentUpdate.currentVersion}
- latestVersion={subAgentUpdate.latestVersion}
- agentName={data.blockType || "Agent"}
- incompatibilities={subAgentUpdate.incompatibilities}
- />
- )}
- >
- );
- },
- (prevProps, nextProps) => {
- // Only re-render if the 'data' prop has changed
- return prevProps.data === nextProps.data;
- },
-);
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomNode/IncompatibilityDialog.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomNode/IncompatibilityDialog.tsx
deleted file mode 100644
index 951fe2eab5..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomNode/IncompatibilityDialog.tsx
+++ /dev/null
@@ -1,244 +0,0 @@
-import React from "react";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/__legacy__/ui/dialog";
-import { Button } from "@/components/__legacy__/ui/button";
-import { AlertTriangle, XCircle, PlusCircle } from "lucide-react";
-import { IncompatibilityInfo } from "../../../hooks/useSubAgentUpdate/types";
-import { beautifyString } from "@/lib/utils";
-import { Alert, AlertDescription } from "@/components/molecules/Alert/Alert";
-
-interface IncompatibilityDialogProps {
- isOpen: boolean;
- onClose: () => void;
- onConfirm: () => void;
- currentVersion: number;
- latestVersion: number;
- agentName: string;
- incompatibilities: IncompatibilityInfo;
-}
-
-export const IncompatibilityDialog: React.FC = ({
- isOpen,
- onClose,
- onConfirm,
- currentVersion,
- latestVersion,
- agentName,
- incompatibilities,
-}) => {
- const hasMissingInputs = incompatibilities.missingInputs.length > 0;
- const hasMissingOutputs = incompatibilities.missingOutputs.length > 0;
- const hasNewInputs = incompatibilities.newInputs.length > 0;
- const hasNewOutputs = incompatibilities.newOutputs.length > 0;
- const hasNewRequired = incompatibilities.newRequiredInputs.length > 0;
- const hasTypeMismatches = incompatibilities.inputTypeMismatches.length > 0;
-
- const hasInputChanges = hasMissingInputs || hasNewInputs;
- const hasOutputChanges = hasMissingOutputs || hasNewOutputs;
-
- return (
- !open && onClose()}>
-
-
-
-
- Incompatible Update
-
-
- Updating {beautifyString(agentName)} from v
- {currentVersion} to v{latestVersion} will break some connections.
-
-
-
-
- {/* Input changes - two column layout */}
- {hasInputChanges && (
-
}
- leftTitle="Removed"
- leftItems={incompatibilities.missingInputs}
- rightIcon={
}
- rightTitle="Added"
- rightItems={incompatibilities.newInputs}
- />
- )}
-
- {/* Output changes - two column layout */}
- {hasOutputChanges && (
-
}
- leftTitle="Removed"
- leftItems={incompatibilities.missingOutputs}
- rightIcon={
}
- rightTitle="Added"
- rightItems={incompatibilities.newOutputs}
- />
- )}
-
- {hasTypeMismatches && (
-
}
- title="Type Changed"
- description="These connected inputs have a different type:"
- items={incompatibilities.inputTypeMismatches.map(
- (m) => `${m.name} (${m.oldType} → ${m.newType})`,
- )}
- />
- )}
-
- {hasNewRequired && (
-
}
- title="New Required Inputs"
- description="These inputs are now required:"
- items={incompatibilities.newRequiredInputs}
- />
- )}
-
-
-
-
- If you proceed, you'll need to remove the broken connections
- before you can save or run your agent.
-
-
-
-
-
- Cancel
-
-
- Update Anyway
-
-
-
-
- );
-};
-
-interface TwoColumnSectionProps {
- title: string;
- leftIcon: React.ReactNode;
- leftTitle: string;
- leftItems: string[];
- rightIcon: React.ReactNode;
- rightTitle: string;
- rightItems: string[];
-}
-
-const TwoColumnSection: React.FC = ({
- title,
- leftIcon,
- leftTitle,
- leftItems,
- rightIcon,
- rightTitle,
- rightItems,
-}) => (
-
-
{title}
-
- {/* Left column - Breaking changes */}
-
-
- {leftIcon}
- {leftTitle}
-
-
- {leftItems.length > 0 ? (
- leftItems.map((item) => (
-
-
- {item}
-
-
- ))
- ) : (
-
- None
-
- )}
-
-
-
- {/* Right column - Possible solutions */}
-
-
- {rightIcon}
- {rightTitle}
-
-
- {rightItems.length > 0 ? (
- rightItems.map((item) => (
-
-
- {item}
-
-
- ))
- ) : (
-
- None
-
- )}
-
-
-
-
-);
-
-interface SingleColumnSectionProps {
- icon: React.ReactNode;
- title: string;
- description: string;
- items: string[];
-}
-
-const SingleColumnSection: React.FC = ({
- icon,
- title,
- description,
- items,
-}) => (
-
-
- {icon}
- {title}
-
-
- {description}
-
-
- {items.map((item) => (
-
-
- {item}
-
-
- ))}
-
-
-);
-
-export default IncompatibilityDialog;
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomNode/SubAgentUpdateBar.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomNode/SubAgentUpdateBar.tsx
deleted file mode 100644
index 5f421d90d8..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomNode/SubAgentUpdateBar.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import React from "react";
-import { Button } from "@/components/__legacy__/ui/button";
-import { ArrowUp, AlertTriangle, Info } from "lucide-react";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/atoms/Tooltip/BaseTooltip";
-import { IncompatibilityInfo } from "../../../hooks/useSubAgentUpdate/types";
-import { cn } from "@/lib/utils";
-
-interface SubAgentUpdateBarProps {
- currentVersion: number;
- latestVersion: number;
- isCompatible: boolean;
- incompatibilities: IncompatibilityInfo | null;
- onUpdate: () => void;
- isInResolutionMode?: boolean;
-}
-
-export const SubAgentUpdateBar: React.FC = ({
- currentVersion,
- latestVersion,
- isCompatible,
- incompatibilities,
- onUpdate,
- isInResolutionMode = false,
-}) => {
- if (isInResolutionMode) {
- return ;
- }
-
- return (
-
-
-
-
- Update available (v{currentVersion} → v{latestVersion})
-
- {!isCompatible && (
-
-
-
-
-
- Incompatible changes detected
-
- Click Update to see details
-
-
-
- )}
-
-
- Update
-
-
- );
-};
-
-interface ResolutionModeBarProps {
- incompatibilities: IncompatibilityInfo | null;
-}
-
-const ResolutionModeBar: React.FC = ({
- incompatibilities,
-}) => {
- const formatIncompatibilities = () => {
- if (!incompatibilities) return "No incompatibilities";
-
- const items: string[] = [];
-
- if (incompatibilities.missingInputs.length > 0) {
- items.push(
- `Missing inputs: ${incompatibilities.missingInputs.join(", ")}`,
- );
- }
- if (incompatibilities.missingOutputs.length > 0) {
- items.push(
- `Missing outputs: ${incompatibilities.missingOutputs.join(", ")}`,
- );
- }
- if (incompatibilities.newRequiredInputs.length > 0) {
- items.push(
- `New required inputs: ${incompatibilities.newRequiredInputs.join(", ")}`,
- );
- }
- if (incompatibilities.inputTypeMismatches.length > 0) {
- const mismatches = incompatibilities.inputTypeMismatches
- .map((m) => `${m.name} (${m.oldType} → ${m.newType})`)
- .join(", ");
- items.push(`Type changed: ${mismatches}`);
- }
-
- return items.join("\n");
- };
-
- return (
-
-
-
-
- Remove incompatible connections
-
-
-
-
-
-
- Incompatible changes:
- {formatIncompatibilities()}
-
- Delete the red connections to continue
-
-
-
-
-
- );
-};
-
-export default SubAgentUpdateBar;
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomNode/customnode.css b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomNode/customnode.css
deleted file mode 100644
index eebd798095..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/CustomNode/customnode.css
+++ /dev/null
@@ -1,131 +0,0 @@
-.custom-node {
- color: #000000;
- box-sizing: border-box;
- transition: border-color 0.3s ease-in-out;
-}
-
-.custom-node .custom-switch {
- padding: 0.5rem 1.25rem;
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-
-.error-message {
- color: #d9534f;
- font-size: 13px;
- padding-left: 0.5rem;
-}
-
-/* Existing styles */
-.handle-container {
- display: flex;
- position: relative;
- margin-bottom: 0px;
- padding: 5px;
- min-height: 44px;
- height: 100%;
-}
-
-.react-flow__handle {
- background: transparent;
- width: auto;
- height: auto;
- border: 0;
- position: relative;
- transform: none;
-}
-
-.border-error {
- border: 1px solid #d9534f;
-}
-
-.select-input {
- width: 100%;
- padding: 5px;
- border-radius: 4px;
- border: 1px solid #000;
- background: #fff;
- color: #000;
-}
-
-.radio-label {
- display: block;
- margin: 5px 0;
- color: #000;
-}
-
-.number-input {
- width: 100%;
- padding: 5px;
- border-radius: 4px;
- background: #fff;
- color: #000;
-}
-
-.array-item-container {
- display: flex;
- align-items: center;
- margin-bottom: 5px;
-}
-
-.array-item-input {
- flex-grow: 1;
- padding: 5px;
- border-radius: 4px;
- border: 1px solid #000;
- background: #fff;
- color: #000;
-}
-
-.array-item-remove {
- background: #d9534f;
- border: none;
- color: white;
- cursor: pointer;
- margin-left: 5px;
- border-radius: 4px;
- padding: 5px 10px;
-}
-
-.array-item-add {
- background: #5bc0de;
- border: none;
- color: white;
- cursor: pointer;
- border-radius: 4px;
- padding: 5px 10px;
- margin-top: 5px;
-}
-
-.error-message {
- color: #d9534f;
- font-size: 13px;
- margin-top: 5px;
- margin-left: 5px;
-}
-
-/* Styles for node states */
-.completed {
- border-color: #27ae60; /* Green border for completed nodes */
-}
-
-.running {
- border-color: #f39c12; /* Orange border for running nodes */
-}
-
-.failed {
- border-color: #c0392b; /* Red border for failed nodes */
-}
-
-.incomplete {
- border-color: #9f14ab; /* Pink border for incomplete nodes */
-}
-
-.queued {
- border-color: #25e6e6; /* Cyan border for queued nodes */
-}
-
-.custom-switch {
- padding-left: 2px;
-}
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/DataTable.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/DataTable.tsx
deleted file mode 100644
index c58bdac642..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/DataTable.tsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import { beautifyString } from "@/lib/utils";
-import { Clipboard, Maximize2 } from "lucide-react";
-import React, { useMemo, useState } from "react";
-import { Button } from "../../../../../components/__legacy__/ui/button";
-import { ContentRenderer } from "../../../../../components/__legacy__/ui/render";
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "../../../../../components/__legacy__/ui/table";
-import type { OutputMetadata } from "@/components/contextual/OutputRenderers";
-import {
- globalRegistry,
- OutputItem,
-} from "@/components/contextual/OutputRenderers";
-import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
-import { useToast } from "../../../../../components/molecules/Toast/use-toast";
-import ExpandableOutputDialog from "./ExpandableOutputDialog";
-
-type DataTableProps = {
- title?: string;
- truncateLongData?: boolean;
- data: { [key: string]: Array };
-};
-
-export default function DataTable({
- title,
- truncateLongData,
- data,
-}: DataTableProps) {
- const { toast } = useToast();
- const enableEnhancedOutputHandling = useGetFlag(
- Flag.ENABLE_ENHANCED_OUTPUT_HANDLING,
- );
- const [expandedDialog, setExpandedDialog] = useState<{
- isOpen: boolean;
- execId: string;
- pinName: string;
- data: any[];
- } | null>(null);
-
- // Prepare renderers for each item when enhanced mode is enabled
- const getItemRenderer = useMemo(() => {
- if (!enableEnhancedOutputHandling) return null;
- return (item: unknown) => {
- const metadata: OutputMetadata = {};
- return globalRegistry.getRenderer(item, metadata);
- };
- }, [enableEnhancedOutputHandling]);
-
- const copyData = (pin: string, data: string) => {
- navigator.clipboard.writeText(data).then(() => {
- toast({
- title: `"${pin}" data copied to clipboard!`,
- duration: 2000,
- });
- });
- };
-
- const openExpandedView = (pinName: string, pinData: any[]) => {
- setExpandedDialog({
- isOpen: true,
- execId: title || "Unknown Execution",
- pinName,
- data: pinData,
- });
- };
-
- const closeExpandedView = () => {
- setExpandedDialog(null);
- };
-
- return (
- <>
- {title && {title} }
-
-
-
- Pin
- Data
-
-
-
- {Object.entries(data).map(([key, value]) => (
-
-
- {beautifyString(key)}
-
-
-
-
- openExpandedView(key, value)}
- title="Expand Full View"
- >
-
-
-
- copyData(
- beautifyString(key),
- value
- .map((i) =>
- typeof i === "object"
- ? JSON.stringify(i, null, 2)
- : String(i),
- )
- .join(", "),
- )
- }
- title="Copy Data"
- >
-
-
-
- {value.map((item, index) => {
- const renderer = getItemRenderer?.(item);
- if (enableEnhancedOutputHandling && renderer) {
- const metadata: OutputMetadata = {};
- return (
-
-
- {index < value.length - 1 && ", "}
-
- );
- }
- return (
-
-
- {index < value.length - 1 && ", "}
-
- );
- })}
-
-
-
- ))}
-
-
-
- {expandedDialog && (
-
- )}
- >
- );
-}
diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/ExpandableOutputDialog.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/ExpandableOutputDialog.tsx
deleted file mode 100644
index 1ccb3d1261..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/legacy-builder/ExpandableOutputDialog.tsx
+++ /dev/null
@@ -1,269 +0,0 @@
-import type { OutputMetadata } from "@/components/contextual/OutputRenderers";
-import {
- globalRegistry,
- OutputActions,
- OutputItem,
-} from "@/components/contextual/OutputRenderers";
-import { Dialog } from "@/components/molecules/Dialog/Dialog";
-import { beautifyString } from "@/lib/utils";
-import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
-import { Clipboard, Maximize2 } from "lucide-react";
-import React, { FC, useMemo, useState } from "react";
-import { Button } from "../../../../../components/__legacy__/ui/button";
-import { ContentRenderer } from "../../../../../components/__legacy__/ui/render";
-import { ScrollArea } from "../../../../../components/__legacy__/ui/scroll-area";
-import { Separator } from "../../../../../components/__legacy__/ui/separator";
-import { Switch } from "../../../../../components/atoms/Switch/Switch";
-import { useToast } from "../../../../../components/molecules/Toast/use-toast";
-
-interface ExpandableOutputDialogProps {
- isOpen: boolean;
- onClose: () => void;
- execId: string;
- pinName: string;
- data: any[];
-}
-
-const ExpandableOutputDialog: FC = ({
- isOpen,
- onClose,
- execId,
- pinName,
- data,
-}) => {
- const { toast } = useToast();
- const enableEnhancedOutputHandling = useGetFlag(
- Flag.ENABLE_ENHANCED_OUTPUT_HANDLING,
- );
- const [useEnhancedRenderer, setUseEnhancedRenderer] = useState(false);
-
- // Prepare items for the enhanced renderer system
- const outputItems = useMemo(() => {
- if (!data || !useEnhancedRenderer) return [];
-
- const items: Array<{
- key: string;
- label: string;
- value: unknown;
- metadata?: OutputMetadata;
- renderer: any;
- }> = [];
-
- data.forEach((value, index) => {
- const metadata: OutputMetadata = {};
-
- // Extract metadata from the value if it's an object
- if (
- typeof value === "object" &&
- value !== null &&
- !React.isValidElement(value)
- ) {
- const objValue = value as any;
- if (objValue.type) metadata.type = objValue.type;
- if (objValue.mimeType) metadata.mimeType = objValue.mimeType;
- if (objValue.filename) metadata.filename = objValue.filename;
- if (objValue.language) metadata.language = objValue.language;
- }
-
- const renderer = globalRegistry.getRenderer(value, metadata);
- if (renderer) {
- items.push({
- key: `item-${index}`,
- label: index === 0 ? beautifyString(pinName) : "",
- value,
- metadata,
- renderer,
- });
- } else {
- // Fallback to text renderer
- const textRenderer = globalRegistry
- .getAllRenderers()
- .find((r) => r.name === "TextRenderer");
- if (textRenderer) {
- items.push({
- key: `item-${index}`,
- label: index === 0 ? beautifyString(pinName) : "",
- value:
- typeof value === "string"
- ? value
- : JSON.stringify(value, null, 2),
- metadata,
- renderer: textRenderer,
- });
- }
- }
- });
-
- return items;
- }, [data, useEnhancedRenderer, pinName]);
-
- const copyData = () => {
- const formattedData = data
- .map((item) =>
- typeof item === "object" ? JSON.stringify(item, null, 2) : String(item),
- )
- .join("\n\n");
-
- navigator.clipboard.writeText(formattedData).then(() => {
- toast({
- title: `"${beautifyString(pinName)}" data copied to clipboard!`,
- duration: 2000,
- });
- });
- };
-
- return (
-
-
-
- Full Output Preview
-
- {enableEnhancedOutputHandling && (
-
-
- Enhanced Rendering
-
-
-
- )}
-
- }
- controlled={{
- isOpen,
- set: (open) => {
- if (!open) onClose();
- },
- }}
- onClose={onClose}
- styling={{
- maxWidth: "56rem",
- width: "90vw",
- height: "90vh",
- }}
- >
-