diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 86d036f5..4dfd9b2b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,14 +1,6 @@ -import React, { useEffect } from 'react'; -import { useDispatch } from 'react-redux'; -import { fetchNodeTypes } from './store/nodeTypesSlice'; +import React from 'react'; const App = () => { - const dispatch = useDispatch(); - - useEffect(() => { - dispatch(fetchNodeTypes()); - }, [dispatch]); - return ( ); diff --git a/frontend/src/components/canvas/AddNodePopoverCanvas.jsx b/frontend/src/components/canvas/AddNodePopoverCanvas.jsx index 1207133c..f8cc1c46 100644 --- a/frontend/src/components/canvas/AddNodePopoverCanvas.jsx +++ b/frontend/src/components/canvas/AddNodePopoverCanvas.jsx @@ -1,7 +1,7 @@ import { addNode, connect, deleteEdge } from '../../store/flowSlice'; import { createNode } from '../../utils/nodeFactory'; -export const addNodeWithoutConnection = (nodeType, reactFlowInstance, dispatch) => { +export const addNodeWithoutConnection = (nodeTypes, nodeType, reactFlowInstance, dispatch) => { const id = `node_${Date.now()}`; const center = reactFlowInstance.screenToFlowPosition({ x: window.innerWidth / 2, @@ -13,18 +13,18 @@ export const addNodeWithoutConnection = (nodeType, reactFlowInstance, dispatch) y: center.y, }; - const newNode = createNode(nodeType, id, position); + const newNode = createNode(nodeTypes, nodeType, id, position); dispatch(addNode({ node: newNode })); }; -export const addNodeBetweenNodes = (nodeType, sourceNode, targetNode, edgeId, reactFlowInstance, dispatch, setVisible) => { +export const addNodeBetweenNodes = (nodeTypes, nodeType, sourceNode, targetNode, edgeId, reactFlowInstance, dispatch, setVisible) => { const id = `node_${Date.now()}`; const newPosition = { x: (sourceNode.position.x + targetNode.position.x) / 2, y: (sourceNode.position.y + targetNode.position.y) / 2, }; - const newNode = createNode(nodeType, id, newPosition); + const newNode = createNode(nodeTypes, nodeType, id, newPosition); dispatch(deleteEdge({ edgeId })); dispatch(addNode({ node: newNode })); diff --git a/frontend/src/components/canvas/FlowCanvas.jsx b/frontend/src/components/canvas/FlowCanvas.jsx index 577fcf93..4a518e24 100644 --- a/frontend/src/components/canvas/FlowCanvas.jsx +++ b/frontend/src/components/canvas/FlowCanvas.jsx @@ -17,7 +17,6 @@ import NodeSidebar from '../nodes/nodeSidebar/NodeSidebar'; import { Dropdown, DropdownMenu, DropdownSection, DropdownItem } from '@nextui-org/react'; import DynamicNode from '../nodes/DynamicNode'; import { v4 as uuidv4 } from 'uuid'; -import { nodeTypes as nodeTypesConfig } from '../../constants/nodeTypes'; import { addNodeBetweenNodes } from './AddNodePopoverCanvas'; import { useKeyboardShortcuts } from '../../hooks/useKeyboardShortcuts'; // Import the new hook import CustomEdge from './edges/CustomEdge'; @@ -27,45 +26,28 @@ import useCopyPaste from '../../utils/useCopyPaste'; import { useModeStore } from '../../store/modeStore'; import { initializeFlow } from '../../store/flowSlice'; // Import the new action // Import the new API function -import { getNodeTypes } from '../../utils/api'; import InputNode from '../nodes/InputNode'; import { useSaveWorkflow } from '../../hooks/useSaveWorkflow'; -const useNodeTypes = () => { - const [nodeTypes, setNodeTypes] = useState({}); - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - const fetchNodeTypes = async () => { - setIsLoading(true); - try { - const nodeTypesConfig = await getNodeTypes(); - const dynamicNodeTypes = Object.keys(nodeTypesConfig).reduce((acc, category) => { - nodeTypesConfig[category].forEach(node => { - if (node.name === 'InputNode') { - acc[node.name] = InputNode; - } else { - acc[node.name] = (props) => { - return ; - }; - } - }); - return acc; - }, {}); - - setNodeTypes({ - ...dynamicNodeTypes - }); - } catch (error) { - console.error('Error fetching node types:', error); - } finally { - setIsLoading(false); - } - }; - - fetchNodeTypes(); - }, []); +const useNodeTypes = ({nodeTypesConfig}) => { + const nodeTypes = useMemo(() => { + if (!nodeTypesConfig) return {}; + + return Object.keys(nodeTypesConfig).reduce((acc, category) => { + nodeTypesConfig[category].forEach(node => { + if (node.name === 'InputNode') { + acc[node.name] = InputNode; + } else { + acc[node.name] = (props) => { + return ; + }; + } + }); + return acc; + }, {}); + }, [nodeTypesConfig]); + const isLoading = !nodeTypesConfig; return { nodeTypes, isLoading }; }; @@ -86,6 +68,8 @@ const FlowCanvasContent = (props) => { const dispatch = useDispatch(); + const nodeTypesConfig = useSelector((state) => state.nodeTypes.data); + useEffect(() => { if (workflowData) { // if the input node already has a schema add it to the workflowInputVariables @@ -103,12 +87,12 @@ const FlowCanvasContent = (props) => { } } } - dispatch(initializeFlow({ ...workflowData, workflowID })); + dispatch(initializeFlow({ ...workflowData, workflowID, nodeTypes: nodeTypesConfig })); } }, [dispatch, workflowData, workflowID]); - const { nodeTypes, isLoading } = useNodeTypes(); + const { nodeTypes, isLoading } = useNodeTypes({ nodeTypesConfig }); const nodes = useSelector((state) => state.flow.nodes); const edges = useSelector((state) => state.flow.edges); diff --git a/frontend/src/components/canvas/footer/operator/AddNodePopoverFooter.jsx b/frontend/src/components/canvas/footer/operator/AddNodePopoverFooter.jsx index 4c8fced0..be7a81e7 100644 --- a/frontend/src/components/canvas/footer/operator/AddNodePopoverFooter.jsx +++ b/frontend/src/components/canvas/footer/operator/AddNodePopoverFooter.jsx @@ -1,19 +1,20 @@ import React from 'react'; import { Icon } from '@iconify/react'; import { Button, Dropdown, DropdownTrigger, DropdownMenu, DropdownSection, DropdownItem } from '@nextui-org/react'; -import { nodeTypes } from '../../../../constants/nodeTypes'; -import { useDispatch } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; import { addNodeWithoutConnection } from '../../AddNodePopoverCanvas'; import { useReactFlow } from '@xyflow/react'; import TipPopup from '../../../TipPopUp'; const AddNodePopoverFooter = () => { - const reactFlowInstance = useReactFlow(); const dispatch = useDispatch(); + const nodeTypes = useSelector((state) => state.nodeTypes.data); + + const reactFlowInstance = useReactFlow(); const handleAddNode = (nodeName) => { if (reactFlowInstance) { - addNodeWithoutConnection(nodeName, reactFlowInstance, dispatch); + addNodeWithoutConnection(nodeTypes, nodeName, reactFlowInstance, dispatch); } }; diff --git a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.jsx b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.jsx index fe658915..2c34fc33 100644 --- a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.jsx +++ b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.jsx @@ -3,7 +3,6 @@ import { useDispatch, useSelector } from 'react-redux'; import { updateNodeData, selectNodeById, setSidebarWidth, setSelectedNode } from '../../../store/flowSlice'; import NumberInput from '../../NumberInput'; import CodeEditor from '../../CodeEditor'; -import { nodeTypes } from '../../../constants/nodeTypes'; import { jsonOptions } from '../../../constants/jsonOptions'; import FewShotEditor from '../../textEditor/FewShotEditor'; import PromptEditor from '../../textEditor/PromptEditor'; @@ -13,6 +12,7 @@ import NodeStatus from "../NodeStatusDisplay"; import SchemaEditor from './SchemaEditor'; const NodeSidebar = ({ nodeID }) => { const dispatch = useDispatch(); + const nodeTypes = useSelector((state) => state.nodeTypes.data); const node = useSelector((state) => selectNodeById(state, nodeID)); // Get the width from Redux store const storedWidth = useSelector((state) => state.flow.sidebarWidth); diff --git a/frontend/src/pages/index.jsx b/frontend/src/pages/index.jsx index 9e758cfc..951276c5 100644 --- a/frontend/src/pages/index.jsx +++ b/frontend/src/pages/index.jsx @@ -1,8 +1,16 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import Header from '../components/Header'; // Import the Header component +import { useDispatch } from 'react-redux'; import Dashboard from '../components/Dashboard'; +import { fetchNodeTypes } from '../store/nodeTypesSlice'; const Home = () => { + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(fetchNodeTypes()); + }, []); + return (
diff --git a/frontend/src/store/flowSlice.js b/frontend/src/store/flowSlice.js index b0811b08..f4174fbf 100644 --- a/frontend/src/store/flowSlice.js +++ b/frontend/src/store/flowSlice.js @@ -20,13 +20,14 @@ const flowSlice = createSlice({ initialState, reducers: { initializeFlow: (state, action) => { - const { workflowID, definition, name } = action.payload; + const { workflowID, definition, nodeTypes, name } = action.payload; + console.log("action", action); state.workflowID = workflowID; state.projectName = name; const { nodes, links } = definition; // Map nodes to the expected format let mappedNodes = nodes.map(node => - createNode(node.node_type, node.id, { x: node.coordinates.x, y: node.coordinates.y }, { userconfig: node.config }) + createNode(nodeTypes, node.node_type, node.id, { x: node.coordinates.x, y: node.coordinates.y }, { userconfig: node.config }) ); state.nodes = mappedNodes; diff --git a/frontend/src/store/nodeTypesSlice.js b/frontend/src/store/nodeTypesSlice.js index d1b22f2f..028d8dd2 100644 --- a/frontend/src/store/nodeTypesSlice.js +++ b/frontend/src/store/nodeTypesSlice.js @@ -10,7 +10,7 @@ export const fetchNodeTypes = createAsyncThunk('nodeTypes/fetchNodeTypes', async const nodeTypesSlice = createSlice({ name: 'nodeTypes', initialState: { - types: [], + data: [], status: 'idle', error: null, }, @@ -22,7 +22,7 @@ const nodeTypesSlice = createSlice({ }) .addCase(fetchNodeTypes.fulfilled, (state, action) => { state.status = 'succeeded'; - state.types = action.payload; + state.data = action.payload; }) .addCase(fetchNodeTypes.rejected, (state, action) => { state.status = 'failed'; diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index 6624b584..c8d4f9bf 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -1,20 +1,24 @@ -import { configureStore } from '@reduxjs/toolkit'; +import { combineReducers, configureStore } from '@reduxjs/toolkit'; import { persistStore, persistReducer } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; import flowReducer from './flowSlice'; +import nodeTypesReducer from './nodeTypesSlice'; const persistConfig = { key: 'root', storage, - whitelist: ['nodes', 'edges', 'projectName'], + whitelist: ['nodes', 'edges', 'nodeTypes'], }; -const persistedReducer = persistReducer(persistConfig, flowReducer); +const rootReducer = combineReducers({ + flow: flowReducer, + nodeTypes: nodeTypesReducer, +}); + +const persistedReducer = persistReducer(persistConfig, rootReducer); const store = configureStore({ - reducer: { - flow: persistedReducer, - }, + reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { diff --git a/frontend/src/utils/nodeFactory.js b/frontend/src/utils/nodeFactory.js index f13dfd07..f3361bdd 100644 --- a/frontend/src/utils/nodeFactory.js +++ b/frontend/src/utils/nodeFactory.js @@ -1,8 +1,7 @@ -import { nodeTypes } from '../constants/nodeTypes'; import cloneDeep from 'lodash/cloneDeep'; // Function to create a node based on its type -export const createNode = (type, id, position, additionalData = {}) => { +export const createNode = (nodeTypes, type, id, position, additionalData = {}) => { let nodeType = null; for (const category in nodeTypes) { const found = nodeTypes[category].find((node) => node.name === type);