feat(frontend): add context menu in custom node (#11342)

- depends one https://github.com/Significant-Gravitas/AutoGPT/pull/11339

In this PR, I’ve added a dropdown menu to the custom node. This allows
you to delete a node, copy a node, and if the node is an agent node, you
can also navigate to that specific agent.

<img width="633" height="403" alt="Screenshot 2025-11-08 at 7 24 38 PM"
src="https://github.com/user-attachments/assets/89dd2906-95f5-40a5-82d1-de05075e4f30"
/>

###Changes
- Added context menu to custom nodes with copy, delete, and open agent
options
- Added performance optimization with memo for custom edge

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] All three buttons are working perfectly.
  - [x] The “Go to graph” option is only visible in the sub-graph node.
This commit is contained in:
Abhimanyu Yadav
2025-11-11 11:34:56 +05:30
committed by GitHub
parent 9371528aab
commit 43638defa2
2 changed files with 99 additions and 17 deletions

View File

@@ -11,6 +11,7 @@ import { NodeBadges } from "./components/NodeBadges";
import { NodeExecutionBadge } from "./components/NodeExecutionBadge";
import { nodeStyleBasedOnStatus } from "./helpers";
import { NodeDataRenderer } from "./components/NodeOutput/NodeOutput";
import { NodeContextMenu } from "./components/NodeContextMenu";
type StandardNodeBlockType = {
data: CustomNodeData;
@@ -37,24 +38,30 @@ export const StandardNodeBlock = ({
>
<div className="rounded-xlarge bg-white">
{/* Header */}
<div className="flex h-auto flex-col gap-2 rounded-xlarge border-b border-slate-200/50 bg-gradient-to-r from-slate-50/80 to-white/90 px-4 py-4">
{/* Upper section */}
<div className="flex items-center gap-2">
<Text
variant="large-semibold"
className="tracking-tight text-slate-800"
>
{beautifyString(data.title)}
</Text>
<Text variant="small" className="!font-medium !text-slate-500">
#{nodeId.split("-")[0]}
</Text>
</div>
{/* Lower section */}
<div className="flex space-x-2">
<NodeCost blockCosts={data.costs} nodeId={nodeId} />
<NodeBadges categories={data.categories} />
<div className="flex h-auto items-start justify-between gap-2 rounded-xlarge border-b border-slate-200/50 bg-gradient-to-r from-slate-50/80 to-white/90 px-4 py-4">
<div className="flex flex-col gap-2">
{/* Upper section */}
<div className="flex items-center gap-2">
<Text
variant="large-semibold"
className="tracking-tight text-slate-800"
>
{beautifyString(data.title)}
</Text>
<Text variant="small" className="!font-medium !text-slate-500">
#{nodeId.split("-")[0]}
</Text>
</div>
{/* Lower section */}
<div className="flex space-x-2">
<NodeCost blockCosts={data.costs} nodeId={nodeId} />
<NodeBadges categories={data.categories} />
</div>
</div>
<NodeContextMenu
subGraphID={data.hardcodedValues?.graph_id}
nodeId={nodeId}
/>
</div>
{/* Input Handles */}
<div className="bg-white pr-6">

View File

@@ -0,0 +1,75 @@
import { Separator } from "@/components/__legacy__/ui/separator";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/molecules/DropdownMenu/DropdownMenu";
import { DotsThreeOutlineVerticalIcon } from "@phosphor-icons/react";
import { Copy, Trash2, ExternalLink } from "lucide-react";
import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
import { useCopyPasteStore } from "@/app/(platform)/build/stores/copyPasteStore";
import { useReactFlow } from "@xyflow/react";
export const NodeContextMenu = ({
nodeId,
subGraphID,
}: {
nodeId: string;
subGraphID?: string;
}) => {
const { deleteElements } = useReactFlow();
const handleCopy = () => {
useNodeStore.setState((state) => ({
nodes: state.nodes.map((node) => ({
...node,
selected: node.id === nodeId,
})),
}));
useCopyPasteStore.getState().pasteNodes();
};
const handleDelete = () => {
deleteElements({ nodes: [{ id: nodeId }] });
};
return (
<DropdownMenu>
<DropdownMenuTrigger className="py-2">
<DotsThreeOutlineVerticalIcon size={16} weight="fill" />
</DropdownMenuTrigger>
<DropdownMenuContent
side="right"
align="start"
className="rounded-xlarge"
>
<DropdownMenuItem onClick={handleCopy} className="hover:rounded-xlarge">
<Copy className="mr-2 h-4 w-4" />
Copy
</DropdownMenuItem>
{subGraphID && (
<DropdownMenuItem
onClick={() => window.open(`/build?flowID=${subGraphID}`)}
className="hover:rounded-xlarge"
>
<ExternalLink className="mr-2 h-4 w-4" />
Open Agent
</DropdownMenuItem>
)}
<Separator className="my-2" />
<DropdownMenuItem
onClick={handleDelete}
className="text-red-600 hover:rounded-xlarge"
>
<Trash2 className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};