Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic node types #21

Merged
merged 4 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Home />
);
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/canvas/AddNodePopoverCanvas.jsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 }));
Expand Down
60 changes: 22 additions & 38 deletions frontend/src/components/canvas/FlowCanvas.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 <DynamicNode {...props} type={node.name} />;
};
}
});
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 <DynamicNode {...props} type={node.name} />;
};
}
});
return acc;
}, {});
}, [nodeTypesConfig]);

const isLoading = !nodeTypesConfig;
return { nodeTypes, isLoading };
};

Expand All @@ -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
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
};

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/nodes/nodeSidebar/NodeSidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/pages/index.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="App relative">
<Header activePage="home" />
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/store/flowSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/store/nodeTypesSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const fetchNodeTypes = createAsyncThunk('nodeTypes/fetchNodeTypes', async
const nodeTypesSlice = createSlice({
name: 'nodeTypes',
initialState: {
types: [],
data: [],
status: 'idle',
error: null,
},
Expand All @@ -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';
Expand Down
16 changes: 10 additions & 6 deletions frontend/src/store/store.js
Original file line number Diff line number Diff line change
@@ -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: {
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/utils/nodeFactory.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down