setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
+
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onDoubleClick={handleDoubleClick}>
{data.label}
-
+
diff --git a/packages/agentflow/src/features/canvas/containers/StickyNote.tsx b/packages/agentflow/src/features/canvas/containers/StickyNote.tsx
index ab538fdc3..e60a31e27 100644
--- a/packages/agentflow/src/features/canvas/containers/StickyNote.tsx
+++ b/packages/agentflow/src/features/canvas/containers/StickyNote.tsx
@@ -3,7 +3,7 @@ import { memo, useRef, useState } from 'react'
import { Box, TextField } from '@mui/material'
import type { NodeData } from '@/core/types'
-import { useConfigContext } from '@/infrastructure/store'
+import { useAgentflowContext, useConfigContext } from '@/infrastructure/store'
import { NodeToolbarActions } from '../components/NodeToolbarActions'
import { useNodeColors } from '../hooks/useNodeColors'
@@ -18,9 +18,10 @@ export interface StickyNoteProps {
*/
function StickyNoteComponent({ data }: StickyNoteProps) {
const { isDarkMode } = useConfigContext()
+ const { updateNodeData } = useAgentflowContext()
const ref = useRef(null)
- const [inputParam] = data.inputParams || []
+ const [inputParam] = data.inputs || []
const [isHovered, setIsHovered] = useState(false)
const { stateColor, backgroundColor } = useNodeColors({
@@ -56,10 +57,14 @@ function StickyNoteComponent({ data }: StickyNoteProps) {
multiline
rows={3}
placeholder={inputParam?.placeholder || 'Add a note...'}
- value={data.inputs?.[inputParam?.name || 'note'] ?? inputParam?.default ?? ''}
+ value={data.inputValues?.[inputParam?.name || 'note'] ?? inputParam?.default ?? ''}
onChange={(e) => {
- if (data.inputs && inputParam) {
- data.inputs[inputParam.name] = e.target.value
+ if (inputParam) {
+ const updatedInputValues = {
+ ...data.inputValues,
+ [inputParam.name]: e.target.value
+ }
+ updateNodeData(data.id, { inputValues: updatedInputValues })
}
}}
sx={{
diff --git a/packages/agentflow/src/features/canvas/hooks/index.ts b/packages/agentflow/src/features/canvas/hooks/index.ts
index 28e13263e..e49295d9b 100644
--- a/packages/agentflow/src/features/canvas/hooks/index.ts
+++ b/packages/agentflow/src/features/canvas/hooks/index.ts
@@ -2,3 +2,4 @@ export { useDragAndDrop } from './useDragAndDrop'
export { useFlowHandlers } from './useFlowHandlers'
export { useFlowNodes } from './useFlowNodes'
export { useNodeColors } from './useNodeColors'
+export { useOpenNodeEditor } from './useOpenNodeEditor'
diff --git a/packages/agentflow/src/features/canvas/hooks/useOpenNodeEditor.ts b/packages/agentflow/src/features/canvas/hooks/useOpenNodeEditor.ts
new file mode 100644
index 000000000..12c45bb48
--- /dev/null
+++ b/packages/agentflow/src/features/canvas/hooks/useOpenNodeEditor.ts
@@ -0,0 +1,42 @@
+import { useCallback } from 'react'
+
+import type { NodeData } from '@/core/types'
+import { useAgentflowContext } from '@/infrastructure/store'
+
+import { useFlowNodes } from './useFlowNodes'
+
+/**
+ * Hook to open the node editor dialog
+ * Handles fetching node data and schema, then opens the edit dialog
+ */
+export function useOpenNodeEditor() {
+ const { state, openEditDialog } = useAgentflowContext()
+ const { availableNodes } = useFlowNodes()
+
+ const openNodeEditor = useCallback(
+ (nodeId: string) => {
+ // Find the node data
+ const node = state.nodes.find((n) => n.id === nodeId)
+ if (!node) return
+
+ // Find the node schema from available nodes (contains InputParam[] definitions)
+ const nodeSchema = availableNodes.find((n) => n.name === node.data.name)
+ if (!nodeSchema) return
+
+ // Get inputParams from schema (API returns 'inputs' property as InputParam[])
+ const inputParams = nodeSchema.inputs || []
+
+ // Ensure inputValues object exists for storing user input
+ const nodeDataWithInputValues: NodeData = {
+ ...node.data,
+ inputValues: node.data.inputValues || {}
+ }
+
+ // Open the dialog
+ openEditDialog(nodeId, nodeDataWithInputValues, inputParams)
+ },
+ [state.nodes, availableNodes, openEditDialog]
+ )
+
+ return { openNodeEditor }
+}
diff --git a/packages/agentflow/src/features/node-editor/EditNodeDialog.tsx b/packages/agentflow/src/features/node-editor/EditNodeDialog.tsx
index 0777bf895..0abc0f3e1 100644
--- a/packages/agentflow/src/features/node-editor/EditNodeDialog.tsx
+++ b/packages/agentflow/src/features/node-editor/EditNodeDialog.tsx
@@ -47,13 +47,13 @@ function EditNodeDialogComponent({ show, dialogProps, onCancel }: EditNodeDialog
const onCustomDataChange = ({ inputParam, newValue }: { inputParam: InputParam; newValue: unknown }) => {
if (!data) return
- const updatedInputs = {
- ...data.inputs,
+ const updatedInputValues = {
+ ...data.inputValues,
[inputParam.name]: newValue
}
- updateNodeData(data.id, { inputs: updatedInputs })
- setData({ ...data, inputs: updatedInputs })
+ updateNodeData(data.id, { inputValues: updatedInputValues })
+ setData({ ...data, inputValues: updatedInputValues })
}
useEffect(() => {
@@ -64,11 +64,6 @@ function EditNodeDialogComponent({ show, dialogProps, onCancel }: EditNodeDialog
setData(dialogProps.data)
if (dialogProps.data.label) setNodeName(dialogProps.data.label)
}
-
- return () => {
- setInputParams([])
- setData(null)
- }
}, [dialogProps])
if (!show) return null
diff --git a/packages/agentflow/src/infrastructure/store/AgentflowContext.tsx b/packages/agentflow/src/infrastructure/store/AgentflowContext.tsx
index ecf8c654c..ccf102d31 100644
--- a/packages/agentflow/src/infrastructure/store/AgentflowContext.tsx
+++ b/packages/agentflow/src/infrastructure/store/AgentflowContext.tsx
@@ -1,7 +1,7 @@
import { createContext, Dispatch, ReactNode, useCallback, useContext, useReducer } from 'react'
import type { ReactFlowInstance } from 'reactflow'
-import type { AgentflowAction, AgentflowState, FlowConfig, FlowData, FlowEdge, FlowNode } from '@/core/types'
+import type { AgentflowAction, AgentflowState, FlowConfig, FlowData, FlowEdge, FlowNode, InputParam, NodeData } from '@/core/types'
import { agentflowReducer, initialState, normalizeNodes } from './agentflowReducer'
@@ -28,6 +28,10 @@ export interface AgentflowContextValue {
// Flow operations
getFlowData: () => FlowData
reset: () => void
+
+ //Dialog operations
+ openEditDialog: (nodeId: string, data: NodeData, inputParams: InputParam[]) => void
+ closeEditDialog: () => void
}
const AgentflowContext = createContext(null)
@@ -125,6 +129,26 @@ export function AgentflowStateProvider({ children, initialFlow }: AgentflowState
[state.edges]
)
+ // Dialog operations
+ const openEditDialog = useCallback((nodeId: string, data: NodeData, inputParams: InputParam[]) => {
+ const dialogProps = {
+ inputParams: inputParams,
+ data: data,
+ disabled: false
+ }
+ dispatch({
+ type: 'OPEN_EDIT_DIALOG',
+ payload: {
+ nodeId,
+ dialogProps
+ }
+ })
+ }, [])
+
+ const closeEditDialog = useCallback(() => {
+ dispatch({ type: 'CLOSE_EDIT_DIALOG' })
+ }, [])
+
// Get flow data
const getFlowData = useCallback((): FlowData => {
const viewport = state.reactFlowInstance?.getViewport() || { x: 0, y: 0, zoom: 1 }
@@ -152,6 +176,8 @@ export function AgentflowStateProvider({ children, initialFlow }: AgentflowState
duplicateNode,
updateNodeData,
deleteEdge,
+ openEditDialog,
+ closeEditDialog,
getFlowData,
reset
}
diff --git a/packages/agentflow/src/infrastructure/store/agentflowReducer.test.ts b/packages/agentflow/src/infrastructure/store/agentflowReducer.test.ts
index df84b279b..5efe2398c 100644
--- a/packages/agentflow/src/infrastructure/store/agentflowReducer.test.ts
+++ b/packages/agentflow/src/infrastructure/store/agentflowReducer.test.ts
@@ -13,7 +13,9 @@ const initialState: AgentflowState = {
edges: [],
chatflow: null,
isDirty: false,
- reactFlowInstance: null
+ reactFlowInstance: null,
+ editingNodeId: null,
+ editDialogProps: null
}
describe('normalizeNodes', () => {
@@ -77,6 +79,39 @@ describe('agentflowReducer', () => {
expect(result.chatflow).toBeNull()
})
+ it('should open edit dialog with nodeId and dialogProps', () => {
+ const state = initialState
+ const action: AgentflowAction = {
+ type: 'OPEN_EDIT_DIALOG',
+ payload: {
+ nodeId: 'node-123',
+ dialogProps: {
+ inputParams: [],
+ data: { id: 'node-123', name: 'sendResponse', label: 'Send Response 1' },
+ disabled: false
+ }
+ }
+ }
+ const newState = agentflowReducer(state, action)
+
+ expect(newState.editingNodeId).toBe('node-123')
+ expect(newState.editDialogProps).toEqual(action.payload.dialogProps)
+ })
+
+ it('should close edit dialog and clear state', () => {
+ const state = {
+ ...initialState,
+ editingNodeId: 'node-123',
+ editDialogProps: {
+ /* ... */
+ }
+ }
+ const newState = agentflowReducer(state, { type: 'CLOSE_EDIT_DIALOG' })
+
+ expect(newState.editingNodeId).toBeNull()
+ expect(newState.editDialogProps).toBeNull()
+ })
+
it('should handle SET_DIRTY', () => {
const result = agentflowReducer(initialState, { type: 'SET_DIRTY', payload: true })
expect(result.isDirty).toBe(true)
@@ -94,7 +129,9 @@ describe('agentflowReducer', () => {
edges: [makeEdge('a', 'b')],
chatflow: { id: '1', name: 'Test' },
isDirty: true,
- reactFlowInstance: null
+ reactFlowInstance: null,
+ editingNodeId: null,
+ editDialogProps: null
}
const result = agentflowReducer(dirtyState, { type: 'RESET' })
expect(result.nodes).toEqual([])
diff --git a/packages/agentflow/src/infrastructure/store/agentflowReducer.ts b/packages/agentflow/src/infrastructure/store/agentflowReducer.ts
index 716f7b2dc..b3cc93871 100644
--- a/packages/agentflow/src/infrastructure/store/agentflowReducer.ts
+++ b/packages/agentflow/src/infrastructure/store/agentflowReducer.ts
@@ -18,7 +18,9 @@ export const initialState: AgentflowState = {
edges: [],
chatflow: null,
isDirty: false,
- reactFlowInstance: null
+ reactFlowInstance: null,
+ editingNodeId: null,
+ editDialogProps: null
}
export function agentflowReducer(state: AgentflowState, action: AgentflowAction): AgentflowState {
@@ -33,6 +35,18 @@ export function agentflowReducer(state: AgentflowState, action: AgentflowAction)
return { ...state, isDirty: action.payload }
case 'SET_REACTFLOW_INSTANCE':
return { ...state, reactFlowInstance: action.payload }
+ case 'OPEN_EDIT_DIALOG':
+ return {
+ ...state,
+ editingNodeId: action.payload.nodeId,
+ editDialogProps: action.payload.dialogProps
+ }
+ case 'CLOSE_EDIT_DIALOG':
+ return {
+ ...state,
+ editingNodeId: null,
+ editDialogProps: null
+ }
case 'RESET':
return initialState
default: