11import * as LucideIcons from 'lucide-react'
22import { useEffect , useState , useRef , useCallback } from 'react'
3- import { X , ExternalLink , Loader2 , Trash2 , ChevronDown , ChevronRight , Circle , CheckCircle2 , AlertCircle } from 'lucide-react'
3+ import { X , ExternalLink , Loader2 , Trash2 , ChevronDown , ChevronRight , Circle , CheckCircle2 , AlertCircle , Pencil , Check } from 'lucide-react'
44import { useNavigate } from 'react-router-dom'
55import { Button } from '@/components/ui/button'
66import { Input } from '@/components/ui/input'
@@ -452,6 +452,18 @@ export function ConfigPanel({
452452 const [ dynamicInputs , setDynamicInputs ] = useState < any [ ] | null > ( null )
453453 const [ dynamicOutputs , setDynamicOutputs ] = useState < any [ ] | null > ( null )
454454
455+ // Node name editing state
456+ const [ isEditingNodeName , setIsEditingNodeName ] = useState ( false )
457+ const [ editingNodeName , setEditingNodeName ] = useState ( '' )
458+
459+ const handleSaveNodeName = useCallback ( ( ) => {
460+ const trimmedName = editingNodeName . trim ( )
461+ if ( trimmedName && trimmedName !== nodeData . label ) {
462+ onUpdateNode ?.( selectedNode . id , { label : trimmedName } )
463+ }
464+ setIsEditingNodeName ( false )
465+ } , [ editingNodeName , nodeData . label , onUpdateNode , selectedNode . id ] )
466+
455467 // Debounce ref
456468 const assertPortResolution = useRef < NodeJS . Timeout | null > ( null )
457469
@@ -597,7 +609,7 @@ export function ConfigPanel({
597609 </ Button >
598610 </ div >
599611
600- { /* Component Info */ }
612+ { /* Component Info with inline Node Name editing */ }
601613 < div className = "px-4 py-3 border-b bg-muted/20" >
602614 < div className = "flex items-start gap-3" >
603615 < div className = "p-2 rounded-lg border bg-background flex-shrink-0" >
@@ -619,7 +631,62 @@ export function ConfigPanel({
619631 ) } />
620632 </ div >
621633 < div className = "flex-1 min-w-0" >
622- < h4 className = "font-medium text-sm" > { component . name } </ h4 >
634+ { /* Node Name - editable for non-entry-point nodes */ }
635+ { ! isEntryPointComponent && isEditingNodeName ? (
636+ < div className = "flex items-center gap-1" >
637+ < Input
638+ type = "text"
639+ value = { editingNodeName }
640+ onChange = { ( e ) => setEditingNodeName ( e . target . value ) }
641+ onKeyDown = { ( e ) => {
642+ if ( e . key === 'Enter' ) {
643+ e . preventDefault ( )
644+ handleSaveNodeName ( )
645+ } else if ( e . key === 'Escape' ) {
646+ setIsEditingNodeName ( false )
647+ }
648+ } }
649+ onBlur = { handleSaveNodeName }
650+ placeholder = { component . name }
651+ className = "h-6 text-sm font-medium py-0 px-1"
652+ autoFocus
653+ />
654+ < Button
655+ variant = "ghost"
656+ size = "icon"
657+ className = "h-5 w-5 flex-shrink-0"
658+ onClick = { handleSaveNodeName }
659+ >
660+ < Check className = "h-3 w-3" />
661+ </ Button >
662+ </ div >
663+ ) : (
664+ < div className = "flex items-center gap-1 group" >
665+ < h4 className = "font-medium text-sm truncate" >
666+ { nodeData . label || component . name }
667+ </ h4 >
668+ { ! isEntryPointComponent && (
669+ < Button
670+ variant = "ghost"
671+ size = "icon"
672+ className = "h-5 w-5 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
673+ onClick = { ( ) => {
674+ setEditingNodeName ( nodeData . label || component . name )
675+ setIsEditingNodeName ( true )
676+ } }
677+ title = "Rename node"
678+ >
679+ < Pencil className = "h-3 w-3" />
680+ </ Button >
681+ ) }
682+ </ div >
683+ ) }
684+ { /* Show component name as subscript if custom name is set */ }
685+ { nodeData . label && nodeData . label !== component . name && (
686+ < span className = "text-[10px] text-muted-foreground opacity-70" >
687+ { component . name }
688+ </ span >
689+ ) }
623690 < p className = "text-xs text-muted-foreground mt-0.5 leading-relaxed" >
624691 { component . description }
625692 </ p >
0 commit comments