Skip to content

Commit 1154f86

Browse files
authored
feat(frontend): add inline node title editing with double-click (#11370)
- depends on #11368 This PR adds the ability to rename nodes directly in the flow editor by double-clicking on their titles. https://github.com/user-attachments/assets/1de3fc5c-f859-425e-b4cf-dfb21c3efe3d ### Changes 🏗️ - **Added inline node title editing functionality:** - Users can now double-click on any node title to enter edit mode - Custom titles are saved on Enter key or blur, canceled on Escape key - Custom node names are persisted in the node's metadata as `customized_name` - Added tooltip to display full title when text is truncated - **Modified node data handling:** - Updated `nodeStore` to include `customized_name` in metadata when converting nodes - Modified `helper.ts` to pass metadata (including custom titles) to custom nodes - Added metadata property to `CustomNodeData` type - **UI improvements:** - Added hover cursor indication for editable titles - Implemented proper focus management during editing - Maintained consistent styling between display and edit modes ### 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] Double-click on various node types to enter edit mode - [x] Type new names and press Enter to save - [x] Press Escape to cancel editing and revert to original name - [x] Click outside the input field to save changes - [x] Verify custom names persist after page refresh - [x] Test with long node names to ensure tooltip appears - [x] Verify custom names are saved with the graph - [x] Test editing on all node types (standard, input, output, webhook, etc.)
1 parent 73c93cf commit 1154f86

File tree

6 files changed

+96
-24
lines changed

6 files changed

+96
-24
lines changed

autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/CustomNode.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { NodeExecutionBadge } from "./components/NodeExecutionBadge";
1818
import { cn } from "@/lib/utils";
1919
import { WebhookDisclaimer } from "./components/WebhookDisclaimer";
2020
import { AyrshareConnectButton } from "./components/AyrshareConnectButton";
21+
import { NodeModelMetadata } from "@/app/api/__generated__/models/nodeModelMetadata";
2122

2223
export type CustomNodeData = {
2324
hardcodedValues: {
@@ -35,6 +36,7 @@ export type CustomNodeData = {
3536
// TODO : We need better type safety for the following backend fields.
3637
costs: BlockCost[];
3738
categories: BlockInfoCategoriesItem[];
39+
metadata?: NodeModelMetadata;
3840
};
3941

4042
export type CustomNode = XYNode<CustomNodeData, "custom">;

autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeContextMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export const NodeContextMenu = ({
4747
>
4848
<DropdownMenuItem onClick={handleCopy} className="hover:rounded-xlarge">
4949
<Copy className="mr-2 h-4 w-4" />
50-
Copy
50+
Copy Node
5151
</DropdownMenuItem>
5252

5353
{subGraphID && (
Lines changed: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import { Text } from "@/components/atoms/Text/Text";
2-
import { beautifyString } from "@/lib/utils";
2+
import { beautifyString, cn } from "@/lib/utils";
33
import { NodeCost } from "./NodeCost";
44
import { NodeBadges } from "./NodeBadges";
55
import { NodeContextMenu } from "./NodeContextMenu";
66
import { CustomNodeData } from "../CustomNode";
7+
import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
8+
import { useState } from "react";
9+
import {
10+
Tooltip,
11+
TooltipContent,
12+
TooltipProvider,
13+
TooltipTrigger,
14+
} from "@/components/atoms/Tooltip/BaseTooltip";
715

816
export const NodeHeader = ({
917
data,
@@ -12,31 +20,86 @@ export const NodeHeader = ({
1220
data: CustomNodeData;
1321
nodeId: string;
1422
}) => {
23+
const updateNodeData = useNodeStore((state) => state.updateNodeData);
24+
const title = (data.metadata?.customized_name as string) || data.title;
25+
const [isEditingTitle, setIsEditingTitle] = useState(false);
26+
const [editedTitle, setEditedTitle] = useState(title);
27+
28+
const handleTitleEdit = () => {
29+
updateNodeData(nodeId, {
30+
metadata: { ...data.metadata, customized_name: editedTitle },
31+
});
32+
setIsEditingTitle(false);
33+
};
34+
35+
const handleTitleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
36+
if (e.key === "Enter") handleTitleEdit();
37+
if (e.key === "Escape") {
38+
setEditedTitle(title);
39+
setIsEditingTitle(false);
40+
}
41+
};
42+
1543
return (
16-
<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">
17-
<div className="flex flex-col gap-2">
18-
{/* Upper section */}
19-
<div className="flex items-center gap-2">
20-
<Text
21-
variant="large-semibold"
22-
className="tracking-tight text-slate-800"
44+
<div className="flex h-auto flex-col gap-1 rounded-xlarge border-b border-slate-200/50 bg-gradient-to-r from-slate-50/80 to-white/90 px-4 py-4 pt-3">
45+
{/* Title row with context menu */}
46+
<div className="flex items-start justify-between gap-2">
47+
<div className="flex min-w-0 flex-1 items-center gap-2">
48+
<div
49+
onDoubleClick={() => setIsEditingTitle(true)}
50+
className="flex w-fit min-w-0 flex-1 items-center hover:cursor-pointer"
2351
>
24-
{beautifyString(data.title)}
25-
</Text>
26-
<Text variant="small" className="!font-medium !text-slate-500">
27-
#{nodeId.split("-")[0]}
28-
</Text>
29-
</div>
30-
{/* Lower section */}
31-
<div className="flex space-x-2">
32-
<NodeCost blockCosts={data.costs} nodeId={nodeId} />
33-
<NodeBadges categories={data.categories} />
52+
{isEditingTitle ? (
53+
<input
54+
id="node-title-input"
55+
value={editedTitle}
56+
onChange={(e) => setEditedTitle(e.target.value)}
57+
autoFocus
58+
className={cn(
59+
"m-0 h-fit w-full border-none bg-transparent p-0 focus:outline-none focus:ring-0",
60+
"font-sans text-[1rem] font-semibold leading-[1.5rem] text-zinc-800",
61+
)}
62+
onBlur={handleTitleEdit}
63+
onKeyDown={handleTitleKeyDown}
64+
/>
65+
) : (
66+
<TooltipProvider>
67+
<Tooltip>
68+
<TooltipTrigger asChild>
69+
<div>
70+
<Text variant="large-semibold" className="line-clamp-1">
71+
{beautifyString(title)}
72+
</Text>
73+
</div>
74+
</TooltipTrigger>
75+
<TooltipContent>
76+
<p>{beautifyString(title)}</p>
77+
</TooltipContent>
78+
</Tooltip>
79+
</TooltipProvider>
80+
)}
81+
</div>
82+
83+
<div className="flex items-center gap-2">
84+
<Text
85+
variant="small"
86+
className="shrink-0 !font-medium !text-slate-500"
87+
>
88+
#{nodeId.split("-")[0]}
89+
</Text>
90+
<NodeContextMenu
91+
subGraphID={data.hardcodedValues?.graph_id}
92+
nodeId={nodeId}
93+
/>
94+
</div>
3495
</div>
3596
</div>
36-
<NodeContextMenu
37-
subGraphID={data.hardcodedValues?.graph_id}
38-
nodeId={nodeId}
39-
/>
97+
98+
{/* Metadata row */}
99+
<div className="flex flex-wrap items-center gap-2">
100+
<NodeCost blockCosts={data.costs} nodeId={nodeId} />
101+
<NodeBadges categories={data.categories} />
102+
</div>
40103
</div>
41104
);
42105
};

autogpt_platform/frontend/src/app/(platform)/build/components/helper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const convertNodesPlusBlockInfoIntoCustomNodes = (
3838
);
3939
const customNode: CustomNode = {
4040
id: node.id ?? "",
41-
data: customNodeData,
41+
data: { ...customNodeData, metadata: node.metadata },
4242
type: "custom",
4343
position: {
4444
x:

autogpt_platform/frontend/src/app/(platform)/build/hooks/useSaveGraph.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ export const useSaveGraph = ({
6969
},
7070
onError: (error) => {
7171
onError?.(error);
72+
toast({
73+
title: "Error saving graph",
74+
description:
75+
(error as any).message ?? "An unexpected error occurred.",
76+
variant: "destructive",
77+
});
7278
},
7379
},
7480
});

autogpt_platform/frontend/src/app/(platform)/build/stores/nodeStore.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export const useNodeStore = create<NodeStore>((set, get) => ({
136136
metadata: {
137137
// TODO: Add more metadata
138138
position: node.position,
139+
customized_name: node.data.metadata?.customized_name,
139140
},
140141
};
141142
},

0 commit comments

Comments
 (0)