From fb52f1368ebbc50fee31b496caaea060050e7039 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 4 Jan 2024 15:22:53 -0300 Subject: [PATCH 01/10] feat(flowManagerStore.ts): add flowManagerStore to manage nodes and edges in reactflow The flowManagerStore.ts file is added to the src/frontend/src/stores directory. This file contains the implementation of a custom store called RFState, which manages the state of nodes and edges in a reactflow component. The store provides various functions and callbacks to manipulate and update the nodes and edges. The RFState interface defines the structure of the store, including the nodes and edges arrays, as well as callbacks for handling changes to nodes and edges. The useStore hook is created using the create function from the zustand library. It initializes the store with an empty nodes and edges array, and defines callbacks for handling changes to nodes, edges, and connections. The setEdges and setNodes functions are used to update the nodes and edges arrays in the store. The deleteNode function removes a node from the nodes array and any associated edges from the edges array. The deleteEdge function removes an edge from the edges array. This store can be used in components to access and manipulate the nodes and edges in a reactflow component. --- src/frontend/src/stores/flowManagerStore.ts | 62 +++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/frontend/src/stores/flowManagerStore.ts diff --git a/src/frontend/src/stores/flowManagerStore.ts b/src/frontend/src/stores/flowManagerStore.ts new file mode 100644 index 000000000..e6fd566fc --- /dev/null +++ b/src/frontend/src/stores/flowManagerStore.ts @@ -0,0 +1,62 @@ +import { + Connection, + Edge, + EdgeChange, + Node, + NodeChange, + OnConnect, + OnEdgesChange, + OnNodesChange, + addEdge, + applyEdgeChanges, + applyNodeChanges, +} from "reactflow"; +import { create } from "zustand"; + +type RFState = { + nodes: Node[]; + edges: Edge[]; + onNodesChange: OnNodesChange; + onEdgesChange: OnEdgesChange; + onConnect: OnConnect; + setEdges: (edges: Edge[]) => void; + setNodes: (nodes: Node[]) => void; + deleteNode: (nodeId: string) => void; + deleteEdge: (edgeId: string) => void; +}; + +// this is our useStore hook that we can use in our components to get parts of the store and call actions +const useStore = create((set, get) => ({ + nodes: [], + edges: [], + onNodesChange: (changes: NodeChange[]) => { + set({ + nodes: applyNodeChanges(changes, get().nodes), + }); + }, + onEdgesChange: (changes: EdgeChange[]) => { + set({ + edges: applyEdgeChanges(changes, get().edges), + }); + }, + onConnect: (connection: Connection) => { + set({ + edges: addEdge(connection, get().edges), + }); + }, + setEdges: (edges) => set({ edges }), + setNodes: (nodes) => set({ nodes }), + deleteNode: (nodeId) => { + set({ + nodes: get().nodes.filter((node) => node.id !== nodeId), + edges: get().edges.filter((edge) => edge.source !== nodeId), + }); + }, + deleteEdge: (edgeId) => { + set({ + edges: get().edges.filter((edge) => edge.id !== edgeId), + }); + }, +})); + +export default useStore; From c32a7f9f113cc8af2836c15593280df34a6e5f93 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 4 Jan 2024 15:58:29 -0300 Subject: [PATCH 02/10] Fix formatting and remove unused imports --- .../langflow/components/llms/OllamaLLM.py | 5 +-- .../retrievers/VectaraSelfQueryRetriver.py | 45 +++++++------------ .../components/parameterComponent/index.tsx | 12 ++--- .../chatComponent/buildTrigger/index.tsx | 6 +-- .../src/components/chatComponent/index.tsx | 3 +- src/frontend/src/contexts/flowsContext.tsx | 18 ++++---- src/frontend/src/contexts/typesContext.tsx | 1 - src/frontend/src/contexts/undoRedoContext.tsx | 4 +- .../src/modals/EditNodeModal/index.tsx | 2 - src/frontend/src/modals/exportModal/index.tsx | 1 - src/frontend/src/modals/formModal/index.tsx | 6 +-- src/frontend/src/modals/shareModal/index.tsx | 1 - .../components/PageComponent/index.tsx | 33 +++++++------- .../components/nodeToolbarComponent/index.tsx | 18 ++++++-- src/frontend/src/stores/flowManagerStore.ts | 7 ++- src/frontend/src/types/chat/index.ts | 2 +- src/frontend/src/types/tabs/index.ts | 7 +-- src/frontend/src/utils/reactflowUtils.ts | 24 ++++------ tests/test_endpoints.py | 2 +- 19 files changed, 85 insertions(+), 112 deletions(-) diff --git a/src/backend/langflow/components/llms/OllamaLLM.py b/src/backend/langflow/components/llms/OllamaLLM.py index 9bf00ad5d..fae8e1917 100644 --- a/src/backend/langflow/components/llms/OllamaLLM.py +++ b/src/backend/langflow/components/llms/OllamaLLM.py @@ -150,10 +150,9 @@ class OllamaLLM(CustomComponent): "top_k": top_k, "top_p": top_p, } - - # None Value remove - llm_params = {k: v for k, v in llm_params.items() if v is not None} + # None Value remove + llm_params = {k: v for k, v in llm_params.items() if v is not None} try: llm = Ollama(**llm_params) diff --git a/src/backend/langflow/components/retrievers/VectaraSelfQueryRetriver.py b/src/backend/langflow/components/retrievers/VectaraSelfQueryRetriver.py index bc8d78909..26afd765c 100644 --- a/src/backend/langflow/components/retrievers/VectaraSelfQueryRetriver.py +++ b/src/backend/langflow/components/retrievers/VectaraSelfQueryRetriver.py @@ -15,29 +15,21 @@ class VectaraSelfQueryRetriverComponent(CustomComponent): display_name: str = "Vectara Self Query Retriever for Vectara Vector Store" description: str = "Implementation of Vectara Self Query Retriever" - documentation = ( - "https://python.langchain.com/docs/integrations/retrievers/self_query/vectara_self_query" - ) + documentation = "https://python.langchain.com/docs/integrations/retrievers/self_query/vectara_self_query" beta = True field_config = { "code": {"show": True}, - "vectorstore": { - "display_name": "Vector Store", - "info": "Input Vectara Vectore Store" - }, - "llm": { - "display_name": "LLM", - "info": "For self query retriever" - }, - "document_content_description":{ - "display_name": "Document Content Description", + "vectorstore": {"display_name": "Vector Store", "info": "Input Vectara Vectore Store"}, + "llm": {"display_name": "LLM", "info": "For self query retriever"}, + "document_content_description": { + "display_name": "Document Content Description", "info": "For self query retriever", - }, + }, "metadata_field_info": { - "display_name": "Metadata Field Info", - "info": "Each metadata field info is a string in the form of key value pair dictionary containing additional search metadata.\nExample input: {\"name\":\"speech\",\"description\":\"what name of the speech\",\"type\":\"string or list[string]\"}.\nThe keys should remain constant(name, description, type)", - }, + "display_name": "Metadata Field Info", + "info": 'Each metadata field info is a string in the form of key value pair dictionary containing additional search metadata.\nExample input: {"name":"speech","description":"what name of the speech","type":"string or list[string]"}.\nThe keys should remain constant(name, description, type)', + }, } def build( @@ -47,24 +39,19 @@ class VectaraSelfQueryRetriverComponent(CustomComponent): llm: BaseLanguageModel, metadata_field_info: List[str], ) -> BaseRetriever: - metadata_field_obj = [] for meta in metadata_field_info: meta_obj = json.loads(meta) - if 'name' not in meta_obj or 'description' not in meta_obj or 'type' not in meta_obj : - raise Exception('Incorrect metadata field info format.') + if "name" not in meta_obj or "description" not in meta_obj or "type" not in meta_obj: + raise Exception("Incorrect metadata field info format.") attribute_info = AttributeInfo( - name = meta_obj['name'], - description = meta_obj['description'], - type = meta_obj['type'], + name=meta_obj["name"], + description=meta_obj["description"], + type=meta_obj["type"], ) metadata_field_obj.append(attribute_info) return SelfQueryRetriever.from_llm( - llm, - vectorstore, - document_content_description, - metadata_field_obj, - verbose=True - ) \ No newline at end of file + llm, vectorstore, document_content_description, metadata_field_obj, verbose=True + ) diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 6208faff5..27e685c5e 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -70,13 +70,7 @@ export default function ParameterComponent({ const { setErrorData, modalContextOpen } = useContext(alertContext); const updateNodeInternals = useUpdateNodeInternals(); const [position, setPosition] = useState(0); - const { - tabId, - flows, - nodes, - edges, - setNode, - } = useContext(FlowsContext); + const { tabId, flows, nodes, edges, setNode } = useContext(FlowsContext); const flow = flows.find((flow) => flow.id === tabId)?.data?.nodes ?? null; @@ -133,8 +127,8 @@ export default function ParameterComponent({ let newNode = cloneDeep(oldNode); newNode.data = { - ...newNode.data - } + ...newNode.data, + }; newNode.data.node.template[name].value = newValue; diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index eb8f281a6..2370772ce 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -3,7 +3,6 @@ import { useContext, useState } from "react"; import Loading from "../../../components/ui/loading"; import { useSSE } from "../../../contexts/SSEContext"; import { alertContext } from "../../../contexts/alertContext"; -import { typesContext } from "../../../contexts/typesContext"; import { postBuildInit } from "../../../controllers/API"; import { FlowType } from "../../../types/flow"; @@ -36,10 +35,7 @@ export default function BuildTrigger({ if (isBuilding) { return; } - const errors = validateNodes( - nodes, - edges - ); + const errors = validateNodes(nodes, edges); if (errors.length > 0) { setErrorData({ title: "Oops! Looks like you missed something", diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 129e1f55c..dcee84ade 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -13,7 +13,8 @@ import { NodeType } from "../../types/flow"; export default function Chat({ flow }: ChatType): JSX.Element { const [open, setOpen] = useState(false); const [canOpen, setCanOpen] = useState(false); - const { tabsState, isBuilt, setIsBuilt, isPending } = useContext(FlowsContext); + const { tabsState, isBuilt, setIsBuilt, isPending } = + useContext(FlowsContext); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx index 6487ad334..e63222cd2 100644 --- a/src/frontend/src/contexts/flowsContext.tsx +++ b/src/frontend/src/contexts/flowsContext.tsx @@ -31,7 +31,7 @@ import { updateFlowInDatabase, uploadFlowsToDatabase, } from "../controllers/API"; -import { APIClassType, APITemplateType } from "../types/api"; +import { APIClassType } from "../types/api"; import { tweakType } from "../types/components"; import { FlowType, @@ -51,7 +51,6 @@ import { scapedJSONStringfy, updateEdgesHandleIds, updateIds, - updateTemplate, } from "../utils/reactflowUtils"; import { createRandomKey, @@ -153,8 +152,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const onNodesChange = useCallback( (change: NodeChange[]) => { onNodesChangeInternal(change); - if(!isPending) - setPending(true); + if (!isPending) setPending(true); }, [onNodesChangeInternal, setPending, isPending] ); @@ -162,8 +160,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const onEdgesChange = useCallback( (edges: EdgeChange[]) => { onEdgesChangeInternal(edges); - if(!isPending) - setPending(true); + if (!isPending) setPending(true); }, [onEdgesChangeInternal, setPending, isPending] ); @@ -697,7 +694,11 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const saveTimeoutId = useRef(null); - const saveCurrentFlow = (nodes: Node[], edges: Edge[], viewport: Viewport) => { + const saveCurrentFlow = ( + nodes: Node[], + edges: Edge[], + viewport: Viewport + ) => { // Clear the previous timeout if it exists. if (saveTimeoutId.current) { clearTimeout(saveTimeoutId.current); @@ -710,8 +711,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) { saveFlow({ ...currentFlow, data: { nodes, edges, viewport } }, true); } }, 300); // Delay of 300ms. - } - + }; async function saveFlow(flow?: FlowType, silent?: boolean) { let newFlow; diff --git a/src/frontend/src/contexts/typesContext.tsx b/src/frontend/src/contexts/typesContext.tsx index 5db197e20..cc4a3adfb 100644 --- a/src/frontend/src/contexts/typesContext.tsx +++ b/src/frontend/src/contexts/typesContext.tsx @@ -95,7 +95,6 @@ export function TypesProvider({ children }: { children: ReactNode }) { } } - return ( (initialValue); export function UndoRedoProvider({ children }) { - const { tabId, flows, setNodes, setEdges, nodes, edges } = useContext(FlowsContext); + const { tabId, flows, setNodes, setEdges, nodes, edges } = + useContext(FlowsContext); const [past, setPast] = useState(flows.map(() => [])); const [future, setFuture] = useState(flows.map(() => [])); diff --git a/src/frontend/src/modals/EditNodeModal/index.tsx b/src/frontend/src/modals/EditNodeModal/index.tsx index 0b6ab5a39..131db81a0 100644 --- a/src/frontend/src/modals/EditNodeModal/index.tsx +++ b/src/frontend/src/modals/EditNodeModal/index.tsx @@ -30,9 +30,7 @@ import { } from "../../constants/constants"; import { alertContext } from "../../contexts/alertContext"; import { FlowsContext } from "../../contexts/flowsContext"; -import { typesContext } from "../../contexts/typesContext"; import { NodeDataType } from "../../types/flow"; -import { FlowsState } from "../../types/tabs"; import { convertObjToArray, convertValuesToNumbers, diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx index f2d2424a4..89bd0593a 100644 --- a/src/frontend/src/modals/exportModal/index.tsx +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -6,7 +6,6 @@ import { Checkbox } from "../../components/ui/checkbox"; import { EXPORT_DIALOG_SUBTITLE } from "../../constants/constants"; import { alertContext } from "../../contexts/alertContext"; import { FlowsContext } from "../../contexts/flowsContext"; -import { typesContext } from "../../contexts/typesContext"; import { removeApiKeys } from "../../utils/reactflowUtils"; import BaseModal from "../baseModal"; diff --git a/src/frontend/src/modals/formModal/index.tsx b/src/frontend/src/modals/formModal/index.tsx index 99ea3a465..83bd3f9b7 100644 --- a/src/frontend/src/modals/formModal/index.tsx +++ b/src/frontend/src/modals/formModal/index.tsx @@ -1,6 +1,5 @@ import { useContext, useEffect, useRef, useState } from "react"; import { alertContext } from "../../contexts/alertContext"; -import { typesContext } from "../../contexts/typesContext"; import { sendAllProps } from "../../types/api"; import { ChatMessageType } from "../../types/chat"; import { FlowType } from "../../types/flow"; @@ -383,10 +382,7 @@ export default function FormModal({ }, [open]); function sendMessage(): void { - let nodeValidationErrors = validateNodes( - nodes, - edges - ); + let nodeValidationErrors = validateNodes(nodes, edges); if (nodeValidationErrors.length === 0) { setLockChat(true); let inputs = tabsState[id.current].formKeysData.input_keys; diff --git a/src/frontend/src/modals/shareModal/index.tsx b/src/frontend/src/modals/shareModal/index.tsx index 3363777d6..8724c65df 100644 --- a/src/frontend/src/modals/shareModal/index.tsx +++ b/src/frontend/src/modals/shareModal/index.tsx @@ -8,7 +8,6 @@ import { Checkbox } from "../../components/ui/checkbox"; import { alertContext } from "../../contexts/alertContext"; import { FlowsContext } from "../../contexts/flowsContext"; import { StoreContext } from "../../contexts/storeContext"; -import { typesContext } from "../../contexts/typesContext"; import { getStoreComponents, getStoreTags, diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index ce537084b..4667a9216 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -17,7 +17,6 @@ import ReactFlow, { SelectionDragHandler, addEdge, updateEdge, - useReactFlow, } from "reactflow"; import GenericNode from "../../../../CustomNodes/GenericNode"; import Chat from "../../../../components/chatComponent"; @@ -29,7 +28,6 @@ import { typesContext } from "../../../../contexts/typesContext"; import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import { APIClassType } from "../../../../types/api"; import { FlowType, NodeType, targetHandleType } from "../../../../types/flow"; -import { FlowsState } from "../../../../types/tabs"; import { generateFlow, generateNodeFromFlow, @@ -72,7 +70,17 @@ export default function Page({ const reactFlowWrapper = useRef(null); const { takeSnapshot } = useContext(undoRedoContext); - const { nodes, edges, setNodes, setEdges, onNodesChange, onEdgesChange, setPending, saveFlow, isPending } = useContext(FlowsContext); + const { + nodes, + edges, + setNodes, + setEdges, + onNodesChange, + onEdgesChange, + setPending, + saveFlow, + isPending, + } = useContext(FlowsContext); const position = useRef({ x: 0, y: 0 }); const [lastSelection, setLastSelection] = @@ -136,11 +144,7 @@ export default function Page({ document.removeEventListener("keydown", onKeyDown); document.removeEventListener("mousemove", handleMouseMove); }; - }, [ - lastCopiedSelection, - lastSelection, - takeSnapshot, - ]); + }, [lastCopiedSelection, lastSelection, takeSnapshot]); const [selectionMenuVisible, setSelectionMenuVisible] = useState(false); @@ -155,10 +159,12 @@ export default function Page({ useEffect(() => { setLoading(true); - if(reactFlowInstance){ + if (reactFlowInstance) { reactFlowInstance.setNodes(flow?.data?.nodes ?? []); reactFlowInstance.setEdges(flow?.data?.edges ?? []); - reactFlowInstance.setViewport(flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 }); + reactFlowInstance.setViewport( + flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 } + ); } // Clear the previous timeout @@ -370,8 +376,7 @@ export default function Page({ }, []); const onMove = useCallback(() => { - if(!isPending) - setPending(true); + if (!isPending) setPending(true); }, [setPending]); return ( @@ -479,9 +484,7 @@ export default function Page({ }} /> - {!view && ( - - )} + {!view && } ) : ( <> diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index 7e04211b8..83a7b7b3a 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -1,6 +1,5 @@ import { cloneDeep } from "lodash"; import { useContext, useEffect, useState } from "react"; -import { useReactFlow } from "reactflow"; import ShadTooltip from "../../../../components/ShadTooltipComponent"; import IconComponent from "../../../../components/genericIconComponent"; import { @@ -64,7 +63,16 @@ export default function NodeToolbarComponent({ const isMinimal = canMinimize(); const isGroup = data.node?.flow ? true : false; - const { paste, saveComponent, version, flows, nodes, edges, setNodes, setEdges } = useContext(FlowsContext); + const { + paste, + saveComponent, + version, + flows, + nodes, + edges, + setNodes, + setEdges, + } = useContext(FlowsContext); const { takeSnapshot } = useContext(undoRedoContext); const [showModalAdvanced, setShowModalAdvanced] = useState(false); const [showconfirmShare, setShowconfirmShare] = useState(false); @@ -155,8 +163,10 @@ export default function NodeToolbarComponent({ { x: 50, y: 10, - paneX: nodes.find((node) => node.id === data.id)?.position.x, - paneY: nodes.find((node) => node.id === data.id)?.position.y, + paneX: nodes.find((node) => node.id === data.id)?.position + .x, + paneY: nodes.find((node) => node.id === data.id)?.position + .y, } ); }} diff --git a/src/frontend/src/stores/flowManagerStore.ts b/src/frontend/src/stores/flowManagerStore.ts index e6fd566fc..cb07be88f 100644 --- a/src/frontend/src/stores/flowManagerStore.ts +++ b/src/frontend/src/stores/flowManagerStore.ts @@ -19,16 +19,17 @@ type RFState = { onNodesChange: OnNodesChange; onEdgesChange: OnEdgesChange; onConnect: OnConnect; - setEdges: (edges: Edge[]) => void; - setNodes: (nodes: Node[]) => void; deleteNode: (nodeId: string) => void; deleteEdge: (edgeId: string) => void; + isBuilt: boolean; }; // this is our useStore hook that we can use in our components to get parts of the store and call actions const useStore = create((set, get) => ({ nodes: [], edges: [], + isBuilt: false, + copiedSelection: { nodes: [], edges: [] }, onNodesChange: (changes: NodeChange[]) => { set({ nodes: applyNodeChanges(changes, get().nodes), @@ -44,8 +45,6 @@ const useStore = create((set, get) => ({ edges: addEdge(connection, get().edges), }); }, - setEdges: (edges) => set({ edges }), - setNodes: (nodes) => set({ nodes }), deleteNode: (nodeId) => { set({ nodes: get().nodes.filter((node) => node.id !== nodeId), diff --git a/src/frontend/src/types/chat/index.ts b/src/frontend/src/types/chat/index.ts index 829f612c1..62cb4eb78 100644 --- a/src/frontend/src/types/chat/index.ts +++ b/src/frontend/src/types/chat/index.ts @@ -1,6 +1,6 @@ import { FlowType } from "../flow"; -export type ChatType = { flow: FlowType; }; +export type ChatType = { flow: FlowType }; export type ChatMessageType = { message: string | Object; template?: string; diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 18619cfe3..9f9bb87ba 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -1,7 +1,6 @@ -import { XYPosition, Node, NodeChange, Edge, EdgeChange } from "reactflow"; +import { Edge, EdgeChange, Node, NodeChange, XYPosition } from "reactflow"; import { tweakType } from "../components"; import { FlowType, NodeDataType } from "../flow"; -import { Dispatch, SetStateAction } from "react"; type OnChange = (changes: ChangesType[]) => void; @@ -46,7 +45,9 @@ export type FlowsContextType = { isPending: boolean; setPending: (pending: boolean) => void; tabsState: FlowsState; - setTabsState: (update: FlowsState | ((oldState: FlowsState) => FlowsState)) => void; + setTabsState: ( + update: FlowsState | ((oldState: FlowsState) => FlowsState) + ) => void; paste: ( selection: { nodes: any; edges: any }, position: { x: number; y: number; paneX?: number; paneY?: number } diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 9a05f8d2e..44a16cf4e 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -82,7 +82,7 @@ export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) { export function isValidConnection( { source, target, sourceHandle, targetHandle }: Connection, nodes: Node[], - edges: Edge[], + edges: Edge[] ) { const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!); const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!); @@ -98,16 +98,12 @@ export function isValidConnection( ) { let targetNode = nodes.find((node) => node.id === target!)?.data?.node; if (!targetNode) { - if ( - !edges - .find((e) => e.targetHandle === targetHandle) - ) { + if (!edges.find((e) => e.targetHandle === targetHandle)) { return true; } } else if ( (!targetNode.template[targetHandleObject.fieldName].list && - !edges - .find((e) => e.targetHandle === targetHandle)) || + !edges.find((e) => e.targetHandle === targetHandle)) || targetNode.template[targetHandleObject.fieldName].list ) { return true; @@ -505,7 +501,7 @@ export function generateFlow( edges: Edge[], name: string ): generateFlowType { - const newFlowData = {nodes, edges, viewport: { zoom: 1, x: 0, y: 0 }}; + const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } }; const uid = new ShortUniqueId({ length: 5 }); /* remove edges that are not connected to selected nodes on both ends in future we can save this edges to when ungrouping reconect to the old nodes @@ -539,14 +535,10 @@ export function generateFlow( export function filterFlow( selection: OnSelectionChangeParams, setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, - setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void ) { - setNodes((nodes) => - nodes.filter((node) => !selection.nodes.includes(node)) - ); - setEdges((edges) => - edges.filter((edge) => !selection.edges.includes(edge)) - ); + setNodes((nodes) => nodes.filter((node) => !selection.nodes.includes(node))); + setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge))); } export function findLastNode({ nodes, edges }: findLastNodeType) { @@ -572,7 +564,7 @@ export function updateFlowPosition(NewPosition: XYPosition, flow: FlowType) { export function concatFlows( flow: FlowType, setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, - setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void ) { const { nodes, edges } = flow.data!; setNodes((old) => [...old, ...nodes]); diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 2e2a65b64..338224004 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -578,4 +578,4 @@ def test_async_task_processing_vector_store(client, added_vector_store, created_ # Validate that the task completed successfully and the result is as expected assert "result" in task_status_json, task_status_json assert "output" in task_status_json["result"], task_status_json["result"] - assert "Langflow" in task_status_json["result"]["output"], task_status_json["result"] \ No newline at end of file + assert "Langflow" in task_status_json["result"]["output"], task_status_json["result"] From aa44a70a0255e8b1572bd289258a45a149844b14 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 4 Jan 2024 16:36:46 -0300 Subject: [PATCH 03/10] feat(flowManagerStore.ts): add paste function to allow pasting copied nodes and edges at a specified position feat(flowManagerStore.ts): add lastCopiedSelection object to store the last copied nodes and edges for pasting --- src/frontend/src/stores/flowManagerStore.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/frontend/src/stores/flowManagerStore.ts b/src/frontend/src/stores/flowManagerStore.ts index cb07be88f..dc6342338 100644 --- a/src/frontend/src/stores/flowManagerStore.ts +++ b/src/frontend/src/stores/flowManagerStore.ts @@ -22,6 +22,11 @@ type RFState = { deleteNode: (nodeId: string) => void; deleteEdge: (edgeId: string) => void; isBuilt: boolean; + paste: ( + selection: { nodes: any; edges: any }, + position: { x: number; y: number; paneX?: number; paneY?: number } + ) => void; + lastCopiedSelection: { nodes: any; edges: any }; }; // this is our useStore hook that we can use in our components to get parts of the store and call actions @@ -56,6 +61,8 @@ const useStore = create((set, get) => ({ edges: get().edges.filter((edge) => edge.id !== edgeId), }); }, + paste: (selection, position) => {}, + lastCopiedSelection: { nodes: [], edges: [] }, })); export default useStore; From c61c67cc7c6a004e2e3824ff176c21bacf93d482 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 4 Jan 2024 18:29:35 -0300 Subject: [PATCH 04/10] fix(App.tsx): remove unused useContext import and hardReset function call refactor(App.tsx): remove hardReset function call and add comment indicating any reset function refactor(flowsContext.tsx): remove hardReset function and update comment for tabId property refactor(flowsContext.tsx): remove hardReset function from useEffect hook refactor(flowsContext.tsx): remove hardReset function and update comment for hardReset function refactor(flowManagerStore.ts): add nodeId property and incrementNodeId function refactor(tabs/index.ts): remove hardReset function from FlowsContextType --- src/frontend/src/App.tsx | 7 +------ src/frontend/src/contexts/flowsContext.tsx | 17 +---------------- src/frontend/src/stores/flowManagerStore.ts | 8 ++++++++ src/frontend/src/types/tabs/index.ts | 1 - 4 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 0898ad58d..7ccb9d1c7 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -16,7 +16,6 @@ import { FETCH_ERROR_MESSAGE, } from "./constants/constants"; import { alertContext } from "./contexts/alertContext"; -import { FlowsContext } from "./contexts/flowsContext"; import { locationContext } from "./contexts/locationContext"; import { typesContext } from "./contexts/typesContext"; import Router from "./routes"; @@ -30,7 +29,6 @@ export default function App() { setShowSideBar(true); setIsStackedOpen(true); }, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]); - const { hardReset } = useContext(FlowsContext); const { errorData, @@ -136,10 +134,7 @@ export default function App() {
{ - window.localStorage.removeItem("tabsData"); - window.localStorage.clear(); - hardReset(); - window.location.href = window.location.href; + // any reset function }} FallbackComponent={CrashErrorComponent} > diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx index e63222cd2..37182244a 100644 --- a/src/frontend/src/contexts/flowsContext.tsx +++ b/src/frontend/src/contexts/flowsContext.tsx @@ -64,6 +64,7 @@ import { typesContext } from "./typesContext"; const uid = new ShortUniqueId({ length: 5 }); const FlowsContextInitialValue: FlowsContextType = { + //Remove tab id and get current id from url tabId: "", setTabId: (index: string) => {}, isLoading: true, @@ -83,7 +84,6 @@ const FlowsContextInitialValue: FlowsContextType = { uploadFlow: async () => "", isBuilt: false, setIsBuilt: (state: boolean) => {}, - hardReset: () => {}, saveFlow: async (flow?: FlowType, silent?: boolean) => {}, lastCopiedSelection: null, setLastCopiedSelection: (selection: any) => {}, @@ -209,12 +209,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { setEdgesInternal(newChange); }; - useEffect(() => { - if (!isAuthenticated) { - hardReset(); - } - }, [isAuthenticated]); - const newNodeId = useRef(uid()); function incrementNodeId() { @@ -295,14 +289,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { setFlows(tabsData); } - function hardReset() { - newNodeId.current = uid(); - setTabId(""); - setFlows([]); - setIsLoading(true); - setId(uid()); - } - /** * Downloads the current flow as a JSON file */ @@ -799,7 +785,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { setIsBuilt, lastCopiedSelection, setLastCopiedSelection, - hardReset, tabId, setTabId, flows, diff --git a/src/frontend/src/stores/flowManagerStore.ts b/src/frontend/src/stores/flowManagerStore.ts index dc6342338..ee2bd172d 100644 --- a/src/frontend/src/stores/flowManagerStore.ts +++ b/src/frontend/src/stores/flowManagerStore.ts @@ -11,7 +11,9 @@ import { applyEdgeChanges, applyNodeChanges, } from "reactflow"; +import ShortUniqueId from "short-unique-id"; import { create } from "zustand"; +const uid = new ShortUniqueId({ length: 5 }); type RFState = { nodes: Node[]; @@ -27,6 +29,8 @@ type RFState = { position: { x: number; y: number; paneX?: number; paneY?: number } ) => void; lastCopiedSelection: { nodes: any; edges: any }; + nodeId: string; + incrementNodeId: () => void; }; // this is our useStore hook that we can use in our components to get parts of the store and call actions @@ -63,6 +67,10 @@ const useStore = create((set, get) => ({ }, paste: (selection, position) => {}, lastCopiedSelection: { nodes: [], edges: [] }, + nodeId: uid(), + incrementNodeId: () => { + set((state) => ({ nodeId: uid() })); + }, })); export default useStore; diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 9f9bb87ba..0e03e6b73 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -40,7 +40,6 @@ export type FlowsContextType = { isComponent?: boolean; position?: XYPosition; }) => Promise; - hardReset: () => void; getNodeId: (nodeType: string) => string; isPending: boolean; setPending: (pending: boolean) => void; From ca7193476d54eb4835d7d14e23250f23b1204383 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 4 Jan 2024 18:51:32 -0300 Subject: [PATCH 05/10] chore(flowManagerStore.ts): add isPending property to RFState to track pending state chore(tabs/index.ts): keep existing properties and functions in FlowsContextType for future use --- src/frontend/src/stores/flowManagerStore.ts | 2 ++ src/frontend/src/types/tabs/index.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/frontend/src/stores/flowManagerStore.ts b/src/frontend/src/stores/flowManagerStore.ts index ee2bd172d..6aba64e2a 100644 --- a/src/frontend/src/stores/flowManagerStore.ts +++ b/src/frontend/src/stores/flowManagerStore.ts @@ -31,6 +31,7 @@ type RFState = { lastCopiedSelection: { nodes: any; edges: any }; nodeId: string; incrementNodeId: () => void; + isPending: boolean; }; // this is our useStore hook that we can use in our components to get parts of the store and call actions @@ -71,6 +72,7 @@ const useStore = create((set, get) => ({ incrementNodeId: () => { set((state) => ({ nodeId: uid() })); }, + isPending: false, })); export default useStore; diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 0e03e6b73..68f7447e7 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -5,14 +5,19 @@ import { FlowType, NodeDataType } from "../flow"; type OnChange = (changes: ChangesType[]) => void; export type FlowsContextType = { + //keep saveFlow: (flow?: FlowType, silent?: boolean) => Promise; tabId: string; + //keep isLoading: boolean; setTabId: (index: string) => void; + //keep flows: Array; deleteNode: (idx: string | Array) => void; deleteEdge: (idx: string | Array) => void; + //keep removeFlow: (id: string) => void; + //keep addFlow: ( newProject: boolean, flow?: FlowType, @@ -25,7 +30,9 @@ export type FlowsContextType = { flowName: string, flowDescription?: string ) => void; + //keep downloadFlows: () => void; + //keep uploadFlows: () => void; isBuilt: boolean; setIsBuilt: (state: boolean) => void; From 17fd9652310a7465216778a6de97e885fd2f8f99 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Fri, 5 Jan 2024 09:47:11 -0300 Subject: [PATCH 06/10] Changed way of declaring Zustand functions --- .../src/components/codeTabsComponent/index.tsx | 2 +- .../src/components/headerComponent/index.tsx | 5 +---- .../src/components/tagsSelectorComponent/index.tsx | 2 -- src/frontend/src/modals/codeAreaModal/index.tsx | 2 +- src/frontend/src/stores/darkStore.ts | 12 ++++++------ src/frontend/src/stores/flowManagerStore.ts | 4 ++-- 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/frontend/src/components/codeTabsComponent/index.tsx b/src/frontend/src/components/codeTabsComponent/index.tsx index 6a9228f2d..f86fe370e 100644 --- a/src/frontend/src/components/codeTabsComponent/index.tsx +++ b/src/frontend/src/components/codeTabsComponent/index.tsx @@ -53,7 +53,7 @@ export default function CodeTabsComponent({ const [isCopied, setIsCopied] = useState(false); const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null); const [openAccordion, setOpenAccordion] = useState([]); - const dark = useDarkStore((state) => state.dark); + const {dark} = useDarkStore(); const { setNodes } = useContext(FlowsContext); const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index 16688a83d..743377378 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -31,10 +31,7 @@ export default function Header(): JSX.Element { const { hasStore } = useContext(StoreContext); const navigate = useNavigate(); - const dark = useDarkStore((state) => state.dark); - const setDark = useDarkStore((state) => state.updateDark); - const stars = useDarkStore((state) => state.stars); - const gradientIndex = useDarkStore((state) => state.gradientIndex); + const {dark, setDark, stars, gradientIndex} = useDarkStore(); useEffect(() => { if (dark) { diff --git a/src/frontend/src/components/tagsSelectorComponent/index.tsx b/src/frontend/src/components/tagsSelectorComponent/index.tsx index ce9da74ab..a6ffd953a 100644 --- a/src/frontend/src/components/tagsSelectorComponent/index.tsx +++ b/src/frontend/src/components/tagsSelectorComponent/index.tsx @@ -1,5 +1,4 @@ import { useEffect, useRef, useState } from "react"; -import { useDarkStore } from "../../stores/darkStore"; import { cn } from "../../utils/utils"; import { Badge } from "../ui/badge"; @@ -24,7 +23,6 @@ export function TagsSelector({ : selectedTags.filter((_, i) => i !== index); setSelectedTags(newArray); }; - const dark = useDarkStore((state) => state.dark); const scrollContainerRef = useRef(null); const fadeContainerRef = useRef(null); diff --git a/src/frontend/src/modals/codeAreaModal/index.tsx b/src/frontend/src/modals/codeAreaModal/index.tsx index cba033acd..880bbe4cb 100644 --- a/src/frontend/src/modals/codeAreaModal/index.tsx +++ b/src/frontend/src/modals/codeAreaModal/index.tsx @@ -26,7 +26,7 @@ export default function CodeAreaModal({ readonly = false, }: codeAreaModalPropsType): JSX.Element { const [code, setCode] = useState(value); - const dark = useDarkStore((state) => state.dark); + const {dark} = useDarkStore(); const [height, setHeight] = useState(null); const { setErrorData, setSuccessData } = useContext(alertContext); diff --git a/src/frontend/src/stores/darkStore.ts b/src/frontend/src/stores/darkStore.ts index 1acf37ead..3eb9a55ee 100644 --- a/src/frontend/src/stores/darkStore.ts +++ b/src/frontend/src/stores/darkStore.ts @@ -8,9 +8,9 @@ type State = { }; type Action = { - updateDark: (dark: State["dark"]) => void; - updateStars: (starts: State["stars"]) => void; - updateGradientIndex: (gradientIndex: State["gradientIndex"]) => void; + setDark: (dark: State["dark"]) => void; + setStars: (starts: State["stars"]) => void; + setGradientIndex: (gradientIndex: State["gradientIndex"]) => void; }; function gradientIndexInitialState() { @@ -23,9 +23,9 @@ export const useDarkStore = create((set) => ({ dark: JSON.parse(window.localStorage.getItem("isDark")!) ?? false, stars: 0, gradientIndex: gradientIndexInitialState(), - updateDark: (dark) => set(() => ({ dark: dark })), - updateStars: (starts) => set(() => ({ stars: starts })), - updateGradientIndex: (gradientIndex) => + setDark: (dark) => set(() => ({ dark: dark })), + setStars: (starts) => set(() => ({ stars: starts })), + setGradientIndex: (gradientIndex) => set(() => ({ gradientIndex: gradientIndex })), })); diff --git a/src/frontend/src/stores/flowManagerStore.ts b/src/frontend/src/stores/flowManagerStore.ts index 6aba64e2a..2d66c9a8c 100644 --- a/src/frontend/src/stores/flowManagerStore.ts +++ b/src/frontend/src/stores/flowManagerStore.ts @@ -35,7 +35,7 @@ type RFState = { }; // this is our useStore hook that we can use in our components to get parts of the store and call actions -const useStore = create((set, get) => ({ +const useFlow = create((set, get) => ({ nodes: [], edges: [], isBuilt: false, @@ -75,4 +75,4 @@ const useStore = create((set, get) => ({ isPending: false, })); -export default useStore; +export default useFlow; From df8912055d22e79b086071c833a215fc1d7f8b53 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Fri, 5 Jan 2024 10:38:33 -0300 Subject: [PATCH 07/10] Implemented Zustand State of flow manager --- src/frontend/src/contexts/flowsContext.tsx | 1 - .../components/PageComponent/index.tsx | 55 ++--- src/frontend/src/stores/flowManagerStore.ts | 188 ++++++++++++++++-- src/frontend/src/utils/reactflowUtils.ts | 14 ++ 4 files changed, 194 insertions(+), 64 deletions(-) diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx index 37182244a..0b9f1fbec 100644 --- a/src/frontend/src/contexts/flowsContext.tsx +++ b/src/frontend/src/contexts/flowsContext.tsx @@ -122,7 +122,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const [tabId, setTabId] = useState(""); const [isLoading, setIsLoading] = useState(false); const [flows, setFlows] = useState>([]); - const [id, setId] = useState(uid()); const { reactFlowInstance, setData } = useContext(typesContext); const [lastCopiedSelection, setLastCopiedSelection] = useState<{ nodes: any; diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 4667a9216..c12017947 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -31,6 +31,7 @@ import { FlowType, NodeType, targetHandleType } from "../../../../types/flow"; import { generateFlow, generateNodeFromFlow, + getNodeId, isValidConnection, scapeJSONParse, validateSelection, @@ -39,6 +40,7 @@ import { cn, getRandomName, isWrappedWithClass } from "../../../../utils/utils"; import ConnectionLineComponent from "../ConnectionLineComponent"; import SelectionMenu from "../SelectionMenuComponent"; import ExtraSidebar from "../extraSidebarComponent"; +import useFlow from "../../../../stores/flowManagerStore"; const nodeTypes = { genericNode: GenericNode, @@ -53,34 +55,23 @@ export default function Page({ }): JSX.Element { let { uploadFlow, - getNodeId, - paste, - lastCopiedSelection, - setLastCopiedSelection, - deleteNode, - deleteEdge, + saveFlow, } = useContext(FlowsContext); const { types, - reactFlowInstance, - setReactFlowInstance, templates, setFilterEdge, } = useContext(typesContext); const reactFlowWrapper = useRef(null); + const [lastCopiedSelection, setLastCopiedSelection] = useState<{ + nodes: any; + edges: any; + } | null>(null); + const { takeSnapshot } = useContext(undoRedoContext); - const { - nodes, - edges, - setNodes, - setEdges, - onNodesChange, - onEdgesChange, - setPending, - saveFlow, - isPending, - } = useContext(FlowsContext); + + const { reactFlowInstance, setReactFlowInstance, nodes, edges, onNodesChange, onEdgesChange, onConnect, setNodes, setEdges, deleteNode, deleteEdge, setPending, isPending, paste } = useFlow(); const position = useRef({ x: 0, y: 0 }); const [lastSelection, setLastSelection] = @@ -183,30 +174,10 @@ export default function Page({ }; }, [flow, reactFlowInstance]); - const onConnect = useCallback( + const onConnectMod = useCallback( (params: Connection) => { takeSnapshot(); - setEdges((eds) => - addEdge( - { - ...params, - data: { - targetHandle: scapeJSONParse(params.targetHandle!), - sourceHandle: scapeJSONParse(params.sourceHandle!), - }, - style: { stroke: "#555" }, - className: - ((scapeJSONParse(params.targetHandle!) as targetHandleType) - .type === "Text" - ? "stroke-foreground " - : "stroke-foreground ") + " stroke-connection", - animated: - (scapeJSONParse(params.targetHandle!) as targetHandleType) - .type === "Text", - }, - eds - ) - ); + onConnect(params); }, [setEdges, takeSnapshot, addEdge] ); @@ -404,7 +375,7 @@ export default function Page({ edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} - onConnect={onConnect} + onConnect={onConnectMod} disableKeyboardA11y={true} onInit={setReactFlowInstance} nodeTypes={nodeTypes} diff --git a/src/frontend/src/stores/flowManagerStore.ts b/src/frontend/src/stores/flowManagerStore.ts index 2d66c9a8c..14f583e93 100644 --- a/src/frontend/src/stores/flowManagerStore.ts +++ b/src/frontend/src/stores/flowManagerStore.ts @@ -1,3 +1,4 @@ +import { cloneDeep } from "lodash"; import { Connection, Edge, @@ -7,72 +8,217 @@ import { OnConnect, OnEdgesChange, OnNodesChange, + ReactFlowInstance, addEdge, applyEdgeChanges, applyNodeChanges, } from "reactflow"; -import ShortUniqueId from "short-unique-id"; import { create } from "zustand"; -const uid = new ShortUniqueId({ length: 5 }); +import { + NodeDataType, + NodeType, + sourceHandleType, + targetHandleType, +} from "../types/flow"; +import { + cleanEdges, + getHandleId, + getNodeId, + scapeJSONParse, + scapedJSONStringfy, +} from "../utils/reactflowUtils"; type RFState = { + reactFlowInstance: ReactFlowInstance | null; + setReactFlowInstance: (newState: ReactFlowInstance) => void; nodes: Node[]; edges: Edge[]; onNodesChange: OnNodesChange; onEdgesChange: OnEdgesChange; + setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void; + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void; onConnect: OnConnect; - deleteNode: (nodeId: string) => void; - deleteEdge: (edgeId: string) => void; + deleteNode: (nodeId: string | Array) => void; + deleteEdge: (edgeId: string | Array) => void; isBuilt: boolean; paste: ( selection: { nodes: any; edges: any }, position: { x: number; y: number; paneX?: number; paneY?: number } ) => void; - lastCopiedSelection: { nodes: any; edges: any }; - nodeId: string; - incrementNodeId: () => void; isPending: boolean; + setPending: (pending: boolean) => void; }; // this is our useStore hook that we can use in our components to get parts of the store and call actions const useFlow = create((set, get) => ({ + reactFlowInstance: null, + setReactFlowInstance: (newState) => { + set({ reactFlowInstance: newState }); + }, nodes: [], edges: [], isBuilt: false, - copiedSelection: { nodes: [], edges: [] }, onNodesChange: (changes: NodeChange[]) => { set({ nodes: applyNodeChanges(changes, get().nodes), }); + if (!get().isPending) set({ isPending: true }); }, onEdgesChange: (changes: EdgeChange[]) => { set({ edges: applyEdgeChanges(changes, get().edges), }); + if (!get().isPending) set({ isPending: true }); + }, + setNodes: (change) => { + let newChange = typeof change === "function" ? change(get().nodes) : change; + let newEdges = cleanEdges(newChange, get().edges); + + set({ edges: newEdges }); + set({ nodes: newChange }); + }, + setEdges: (change) => { + let newChange = typeof change === "function" ? change(get().edges) : change; + + set({ edges: newChange }); }, onConnect: (connection: Connection) => { set({ - edges: addEdge(connection, get().edges), + edges: addEdge( + { + ...connection, + data: { + targetHandle: scapeJSONParse(connection.targetHandle!), + sourceHandle: scapeJSONParse(connection.sourceHandle!), + }, + style: { stroke: "#555" }, + className: + ((scapeJSONParse(connection.targetHandle!) as targetHandleType) + .type === "Text" + ? "stroke-foreground " + : "stroke-foreground ") + " stroke-connection", + animated: + (scapeJSONParse(connection.targetHandle!) as targetHandleType) + .type === "Text", + }, + get().edges + ), }); }, deleteNode: (nodeId) => { - set({ - nodes: get().nodes.filter((node) => node.id !== nodeId), - edges: get().edges.filter((edge) => edge.source !== nodeId), - }); + get().setNodes( + get().nodes.filter((node) => + typeof nodeId === "string" + ? node.id !== nodeId + : !nodeId.includes(node.id) + ) + ); }, deleteEdge: (edgeId) => { - set({ - edges: get().edges.filter((edge) => edge.id !== edgeId), - }); + get().setEdges( + get().edges.filter((edge) => + typeof edgeId === "string" + ? edge.id !== edgeId + : !edgeId.includes(edge.id) + ) + ); }, - paste: (selection, position) => {}, - lastCopiedSelection: { nodes: [], edges: [] }, - nodeId: uid(), - incrementNodeId: () => { - set((state) => ({ nodeId: uid() })); + paste: (selection, position) => { + let minimumX = Infinity; + let minimumY = Infinity; + let idsMap = {}; + let newNodes: Node[] = get().nodes; + let newEdges = get().edges; + selection.nodes.forEach((node: Node) => { + if (node.position.y < minimumY) { + minimumY = node.position.y; + } + if (node.position.x < minimumX) { + minimumX = node.position.x; + } + }); + + const insidePosition = position.paneX + ? { x: position.paneX + position.x, y: position.paneY! + position.y } + : get().reactFlowInstance!.screenToFlowPosition({ + x: position.x, + y: position.y, + }); + + selection.nodes.forEach((node: NodeType) => { + // Generate a unique node ID + let newId = getNodeId(node.data.type); + idsMap[node.id] = newId; + + // Create a new node object + const newNode: NodeType = { + id: newId, + type: "genericNode", + position: { + x: insidePosition.x + node.position!.x - minimumX, + y: insidePosition.y + node.position!.y - minimumY, + }, + data: { + ...cloneDeep(node.data), + id: newId, + }, + }; + + // Add the new node to the list of nodes in state + newNodes = newNodes + .map((node) => ({ ...node, selected: false })) + .concat({ ...newNode, selected: false }); + }); + set({ nodes: newNodes }); + + selection.edges.forEach((edge: Edge) => { + let source = idsMap[edge.source]; + let target = idsMap[edge.target]; + const sourceHandleObject: sourceHandleType = scapeJSONParse( + edge.sourceHandle! + ); + let sourceHandle = scapedJSONStringfy({ + ...sourceHandleObject, + id: source, + }); + sourceHandleObject.id = source; + + edge.data.sourceHandle = sourceHandleObject; + const targetHandleObject: targetHandleType = scapeJSONParse( + edge.targetHandle! + ); + let targetHandle = scapedJSONStringfy({ + ...targetHandleObject, + id: target, + }); + targetHandleObject.id = target; + edge.data.targetHandle = targetHandleObject; + let id = getHandleId(source, sourceHandle, target, targetHandle); + newEdges = addEdge( + { + source, + target, + sourceHandle, + targetHandle, + id, + data: cloneDeep(edge.data), + style: { stroke: "#555" }, + className: + targetHandleObject.type === "Text" + ? "stroke-gray-800 " + : "stroke-gray-900 ", + animated: targetHandleObject.type === "Text", + selected: false, + }, + newEdges.map((edge) => ({ ...edge, selected: false })) + ); + }); + set({ edges: newEdges }); }, isPending: false, + setPending: (pending: boolean) => { + set({ isPending: pending }); + }, })); export default useFlow; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 44a16cf4e..91fd39c83 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -27,6 +27,7 @@ import { updateEdgesHandleIdsType, } from "../types/utils/reactflowUtils"; import { getFieldTitle, toTitleCase } from "./utils"; +const uid = new ShortUniqueId({ length: 5 }); export function cleanEdges(nodes: Node[], edges: Edge[]) { let newEdges = _.cloneDeep(edges); @@ -495,6 +496,19 @@ export function getMiddlePoint(nodes: Node[]) { return { x: averageX, y: averageY }; } +export function getNodeId(nodeType: string) { + return nodeType + "-" + uid(); +} + +export function getHandleId(source: string, sourceHandle: string, target: string, targetHandle: string){ + return "reactflow__edge-" + + source + + sourceHandle + + "-" + + target + + targetHandle; +} + export function generateFlow( selection: OnSelectionChangeParams, nodes: Node[], From 0bc74b52519ad32de156c32790fae696ca0e422c Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Fri, 5 Jan 2024 10:43:31 -0300 Subject: [PATCH 08/10] Formatting changes --- .../components/PageComponent/index.tsx | 33 +++++++++++-------- src/frontend/src/stores/flowManagerStore.ts | 4 +-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index c12017947..7a8582501 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -26,21 +26,20 @@ import { FlowsContext } from "../../../../contexts/flowsContext"; import { locationContext } from "../../../../contexts/locationContext"; import { typesContext } from "../../../../contexts/typesContext"; import { undoRedoContext } from "../../../../contexts/undoRedoContext"; +import useFlow from "../../../../stores/flowManagerStore"; import { APIClassType } from "../../../../types/api"; -import { FlowType, NodeType, targetHandleType } from "../../../../types/flow"; +import { FlowType, NodeType } from "../../../../types/flow"; import { generateFlow, generateNodeFromFlow, getNodeId, isValidConnection, - scapeJSONParse, validateSelection, } from "../../../../utils/reactflowUtils"; import { cn, getRandomName, isWrappedWithClass } from "../../../../utils/utils"; import ConnectionLineComponent from "../ConnectionLineComponent"; import SelectionMenu from "../SelectionMenuComponent"; import ExtraSidebar from "../extraSidebarComponent"; -import useFlow from "../../../../stores/flowManagerStore"; const nodeTypes = { genericNode: GenericNode, @@ -53,15 +52,8 @@ export default function Page({ flow: FlowType; view?: boolean; }): JSX.Element { - let { - uploadFlow, - saveFlow, - } = useContext(FlowsContext); - const { - types, - templates, - setFilterEdge, - } = useContext(typesContext); + let { uploadFlow, saveFlow } = useContext(FlowsContext); + const { types, templates, setFilterEdge } = useContext(typesContext); const reactFlowWrapper = useRef(null); const [lastCopiedSelection, setLastCopiedSelection] = useState<{ @@ -71,7 +63,22 @@ export default function Page({ const { takeSnapshot } = useContext(undoRedoContext); - const { reactFlowInstance, setReactFlowInstance, nodes, edges, onNodesChange, onEdgesChange, onConnect, setNodes, setEdges, deleteNode, deleteEdge, setPending, isPending, paste } = useFlow(); + const { + reactFlowInstance, + setReactFlowInstance, + nodes, + edges, + onNodesChange, + onEdgesChange, + onConnect, + setNodes, + setEdges, + deleteNode, + deleteEdge, + setPending, + isPending, + paste, + } = useFlow(); const position = useRef({ x: 0, y: 0 }); const [lastSelection, setLastSelection] = diff --git a/src/frontend/src/stores/flowManagerStore.ts b/src/frontend/src/stores/flowManagerStore.ts index 14f583e93..29404e37f 100644 --- a/src/frontend/src/stores/flowManagerStore.ts +++ b/src/frontend/src/stores/flowManagerStore.ts @@ -40,11 +40,11 @@ type RFState = { onConnect: OnConnect; deleteNode: (nodeId: string | Array) => void; deleteEdge: (edgeId: string | Array) => void; - isBuilt: boolean; paste: ( selection: { nodes: any; edges: any }, position: { x: number; y: number; paneX?: number; paneY?: number } - ) => void; + ) => void; + isBuilt: boolean; isPending: boolean; setPending: (pending: boolean) => void; }; From 75326cad27c76954dea47afcbd19526806c5c0e6 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Fri, 5 Jan 2024 11:20:02 -0300 Subject: [PATCH 09/10] Changed every flowsContext to useFlow --- .../components/parameterComponent/index.tsx | 4 +- .../src/CustomNodes/GenericNode/index.tsx | 4 +- .../chatComponent/buildTrigger/index.tsx | 4 +- .../src/components/chatComponent/index.tsx | 4 +- .../components/codeTabsComponent/index.tsx | 4 +- .../src/components/pageLayout/index.tsx | 2 +- src/frontend/src/contexts/flowsContext.tsx | 47 +------------------ src/frontend/src/contexts/undoRedoContext.tsx | 5 +- src/frontend/src/modals/ApiModal/index.tsx | 3 +- .../src/modals/EditNodeModal/index.tsx | 4 +- src/frontend/src/modals/formModal/index.tsx | 4 +- .../components/PageComponent/index.tsx | 6 +-- .../extraSidebarComponent/index.tsx | 4 +- .../sideBarDraggableComponent/index.tsx | 3 +- .../components/nodeToolbarComponent/index.tsx | 13 +++-- src/frontend/src/stores/flowManagerStore.ts | 24 ++++++++++ src/frontend/src/types/tabs/index.ts | 27 +---------- src/frontend/src/utils/reactflowUtils.ts | 1 - 18 files changed, 65 insertions(+), 98 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 27e685c5e..fc0ed31a9 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -31,6 +31,7 @@ import { FlowsContext } from "../../../../contexts/flowsContext"; import { typesContext } from "../../../../contexts/typesContext"; import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import { postCustomComponentUpdate } from "../../../../controllers/API"; +import useFlow from "../../../../stores/flowManagerStore"; import { APIClassType } from "../../../../types/api"; import { ParameterComponentType } from "../../../../types/components"; import { NodeDataType } from "../../../../types/flow"; @@ -70,7 +71,8 @@ export default function ParameterComponent({ const { setErrorData, modalContextOpen } = useContext(alertContext); const updateNodeInternals = useUpdateNodeInternals(); const [position, setPosition] = useState(0); - const { tabId, flows, nodes, edges, setNode } = useContext(FlowsContext); + const { tabId, flows } = useContext(FlowsContext); + const { nodes, edges, setNode } = useFlow(); const flow = flows.find((flow) => flow.id === tabId)?.data?.nodes ?? null; diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index eb6d27137..6d343f9cd 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -7,7 +7,6 @@ import InputComponent from "../../components/inputComponent"; import { Textarea } from "../../components/ui/textarea"; import { priorityFields } from "../../constants/constants"; import { useSSE } from "../../contexts/SSEContext"; -import { FlowsContext } from "../../contexts/flowsContext"; import { typesContext } from "../../contexts/typesContext"; import { undoRedoContext } from "../../contexts/undoRedoContext"; import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent"; @@ -17,6 +16,7 @@ import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils"; import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils"; import { classNames, cn, getFieldTitle } from "../../utils/utils"; import ParameterComponent from "./components/parameterComponent"; +import useFlow from "../../stores/flowManagerStore"; export default function GenericNode({ data, @@ -30,7 +30,7 @@ export default function GenericNode({ yPos: number; }): JSX.Element { const { types } = useContext(typesContext); - const { deleteNode, setNode } = useContext(FlowsContext); + const { deleteNode, setNode } = useFlow(); const name = nodeIconsLucide[data.type] ? data.type : types[data.type]; const [inputName, setInputName] = useState(false); const [nodeName, setNodeName] = useState(data.node!.display_name); diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index 2370772ce..65531e995 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -12,6 +12,7 @@ import { FlowsState } from "../../../types/tabs"; import { validateNodes } from "../../../utils/reactflowUtils"; import RadialProgressComponent from "../../RadialProgress"; import IconComponent from "../../genericIconComponent"; +import useFlow from "../../../stores/flowManagerStore"; export default function BuildTrigger({ open, @@ -24,7 +25,8 @@ export default function BuildTrigger({ isBuilt: boolean; }): JSX.Element { const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE(); - const { setTabsState, saveFlow, nodes, edges } = useContext(FlowsContext); + const { setTabsState, saveFlow } = useContext(FlowsContext); + const { nodes, edges } = useFlow(); const { setErrorData, setSuccessData } = useContext(alertContext); const [isIconTouched, setIsIconTouched] = useState(false); const eventClick = isBuilding ? "pointer-events-none" : ""; diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index dcee84ade..b07732a64 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -9,11 +9,13 @@ import { FlowsContext } from "../../contexts/flowsContext"; import { getBuildStatus } from "../../controllers/API"; import FormModal from "../../modals/formModal"; import { NodeType } from "../../types/flow"; +import useFlow from "../../stores/flowManagerStore"; export default function Chat({ flow }: ChatType): JSX.Element { const [open, setOpen] = useState(false); const [canOpen, setCanOpen] = useState(false); - const { tabsState, isBuilt, setIsBuilt, isPending } = + const { isBuilt, setIsBuilt, isPending } = useFlow(); + const { tabsState } = useContext(FlowsContext); useEffect(() => { diff --git a/src/frontend/src/components/codeTabsComponent/index.tsx b/src/frontend/src/components/codeTabsComponent/index.tsx index f86fe370e..e4e4bbb60 100644 --- a/src/frontend/src/components/codeTabsComponent/index.tsx +++ b/src/frontend/src/components/codeTabsComponent/index.tsx @@ -28,7 +28,6 @@ import { TabsTrigger, } from "../../components/ui/tabs"; import { LANGFLOW_SUPPORTED_TYPES } from "../../constants/constants"; -import { FlowsContext } from "../../contexts/flowsContext"; import { useDarkStore } from "../../stores/darkStore"; import { codeTabsPropsType } from "../../types/components"; import { @@ -41,6 +40,7 @@ import { classNames } from "../../utils/utils"; import DictComponent from "../dictComponent"; import IconComponent from "../genericIconComponent"; import KeypairListComponent from "../keypairListComponent"; +import useFlow from "../../stores/flowManagerStore"; export default function CodeTabsComponent({ flow, @@ -55,7 +55,7 @@ export default function CodeTabsComponent({ const [openAccordion, setOpenAccordion] = useState([]); const {dark} = useDarkStore(); - const { setNodes } = useContext(FlowsContext); + const { setNodes } = useFlow(); const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); useEffect(() => { diff --git a/src/frontend/src/components/pageLayout/index.tsx b/src/frontend/src/components/pageLayout/index.tsx index 9af894730..fb10f9550 100644 --- a/src/frontend/src/components/pageLayout/index.tsx +++ b/src/frontend/src/components/pageLayout/index.tsx @@ -12,7 +12,7 @@ export default function PageLayout({ description: string; children: React.ReactNode; button?: React.ReactNode; - betaIcon: boolean; + betaIcon?: boolean; }) { return (
diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx index 0b9f1fbec..80851d945 100644 --- a/src/frontend/src/contexts/flowsContext.tsx +++ b/src/frontend/src/contexts/flowsContext.tsx @@ -75,40 +75,16 @@ const FlowsContextInitialValue: FlowsContextType = { flowData?: FlowType, override?: boolean ) => "", - deleteNode: () => {}, - deleteEdge: () => {}, - incrementNodeId: () => uid(), downloadFlow: (flow: FlowType) => {}, downloadFlows: () => {}, uploadFlows: () => {}, uploadFlow: async () => "", - isBuilt: false, - setIsBuilt: (state: boolean) => {}, saveFlow: async (flow?: FlowType, silent?: boolean) => {}, - lastCopiedSelection: null, - setLastCopiedSelection: (selection: any) => {}, - isPending: false, - setPending: (pending: boolean) => {}, tabsState: {}, setTabsState: () => {}, - getNodeId: (nodeType: string) => "", - setTweak: (tweak: any) => {}, - getTweak: [], - paste: ( - selection: { nodes: any; edges: any }, - position: { x: number; y: number; paneX?: number; paneY?: number } - ) => {}, saveComponent: async (component: NodeDataType, override: boolean) => "", deleteComponent: (key: string) => {}, version: "", - nodes: [], - setNodes: () => {}, - setNode: () => {}, - getNode: () => undefined, - onNodesChange: () => {}, - edges: [], - setEdges: () => {}, - onEdgesChange: () => {}, }; export const FlowsContext = createContext( @@ -779,42 +755,21 @@ export function FlowsProvider({ children }: { children: ReactNode }) { {children} diff --git a/src/frontend/src/contexts/undoRedoContext.tsx b/src/frontend/src/contexts/undoRedoContext.tsx index 2c15210ce..f4b50beb0 100644 --- a/src/frontend/src/contexts/undoRedoContext.tsx +++ b/src/frontend/src/contexts/undoRedoContext.tsx @@ -13,6 +13,7 @@ import { } from "../types/typesContext"; import { isWrappedWithClass } from "../utils/utils"; import { FlowsContext } from "./flowsContext"; +import useFlow from "../stores/flowManagerStore"; const initialValue = { undo: () => {}, @@ -28,9 +29,11 @@ const defaultOptions: UseUndoRedoOptions = { export const undoRedoContext = createContext(initialValue); export function UndoRedoProvider({ children }) { - const { tabId, flows, setNodes, setEdges, nodes, edges } = + const { tabId, flows } = useContext(FlowsContext); + const {setNodes, setEdges, nodes, edges} = useFlow(); + const [past, setPast] = useState(flows.map(() => [])); const [future, setFuture] = useState(flows.map(() => [])); const [tabIndex, setTabIndex] = useState( diff --git a/src/frontend/src/modals/ApiModal/index.tsx b/src/frontend/src/modals/ApiModal/index.tsx index 93d85549d..97a476363 100644 --- a/src/frontend/src/modals/ApiModal/index.tsx +++ b/src/frontend/src/modals/ApiModal/index.tsx @@ -48,7 +48,8 @@ const ApiModal = forwardRef( const [activeTab, setActiveTab] = useState("0"); const tweak = useRef([]); const tweaksList = useRef([]); - const { setTweak, getTweak, tabsState } = useContext(FlowsContext); + const { tabsState } = useContext(FlowsContext); + const [getTweak, setTweak] = useState([]); const pythonApiCode = getPythonApiCode( flow, autoLogin, diff --git a/src/frontend/src/modals/EditNodeModal/index.tsx b/src/frontend/src/modals/EditNodeModal/index.tsx index 131db81a0..d2e4a9e77 100644 --- a/src/frontend/src/modals/EditNodeModal/index.tsx +++ b/src/frontend/src/modals/EditNodeModal/index.tsx @@ -29,7 +29,6 @@ import { limitScrollFieldsModal, } from "../../constants/constants"; import { alertContext } from "../../contexts/alertContext"; -import { FlowsContext } from "../../contexts/flowsContext"; import { NodeDataType } from "../../types/flow"; import { convertObjToArray, @@ -39,6 +38,7 @@ import { } from "../../utils/reactflowUtils"; import { classNames } from "../../utils/utils"; import BaseModal from "../baseModal"; +import useFlow from "../../stores/flowManagerStore"; const EditNodeModal = forwardRef( ( @@ -57,7 +57,7 @@ const EditNodeModal = forwardRef( ) => { const [myData, setMyData] = useState(data); - const { setPending, edges, setNode } = useContext(FlowsContext); + const { setPending, edges, setNode } = useFlow(); const { setModalContextOpen } = useContext(alertContext); function changeAdvanced(n) { diff --git a/src/frontend/src/modals/formModal/index.tsx b/src/frontend/src/modals/formModal/index.tsx index 83bd3f9b7..71115aef8 100644 --- a/src/frontend/src/modals/formModal/index.tsx +++ b/src/frontend/src/modals/formModal/index.tsx @@ -25,6 +25,7 @@ import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants"; import { AuthContext } from "../../contexts/authContext"; import { FlowsContext } from "../../contexts/flowsContext"; import { getBuildStatus } from "../../controllers/API"; +import useFlow from "../../stores/flowManagerStore"; import { FlowsState } from "../../types/tabs"; import { validateNodes } from "../../utils/reactflowUtils"; @@ -37,7 +38,8 @@ export default function FormModal({ setOpen: (open: boolean) => void; flow: FlowType; }): JSX.Element { - const { tabsState, setTabsState, nodes, edges } = useContext(FlowsContext); + const { tabsState, setTabsState } = useContext(FlowsContext); + const { nodes, edges } = useFlow(); const [chatValue, setChatValue] = useState(() => { try { const { formKeysData } = tabsState[flow.id]; diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 7a8582501..17777adcd 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -221,10 +221,6 @@ export default function Page({ if (event.dataTransfer.types.some((types) => types === "nodedata")) { takeSnapshot(); - // Get the current bounds of the ReactFlow wrapper element - const reactflowBounds = - reactFlowWrapper.current?.getBoundingClientRect(); - // Extract the data from the drag event and parse it as a JSON object let data: { type: string; node?: APIClassType } = JSON.parse( event.dataTransfer.getData("nodedata") @@ -356,7 +352,7 @@ export default function Page({ const onMove = useCallback(() => { if (!isPending) setPending(true); }, [setPending]); - + return (
{!view && } diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 909a0dcd7..5f09cfa40 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -24,12 +24,14 @@ import { } from "../../../../utils/utils"; import DisclosureComponent from "../DisclosureComponent"; import SidebarDraggableComponent from "./sideBarDraggableComponent"; +import useFlow from "../../../../stores/flowManagerStore"; export default function ExtraSidebar(): JSX.Element { const { data, templates, getFilterEdge, setFilterEdge } = useContext(typesContext); - const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, isPending } = + const { flows, tabId, uploadFlow, tabsState, saveFlow } = useContext(FlowsContext); + const { isBuilt, isPending } = useFlow(); const { hasApiKey, validApiKey, hasStore } = useContext(StoreContext); const { setErrorData } = useContext(alertContext); const [dataFilter, setFilterData] = useState(data); diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx index 07aacaefc..0d1bcdddb 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx @@ -12,6 +12,7 @@ import { APIClassType } from "../../../../../types/api"; import { createFlowComponent, downloadNode, + getNodeId, } from "../../../../../utils/reactflowUtils"; import { removeCountFromString } from "../../../../../utils/utils"; @@ -35,7 +36,7 @@ export default function SidebarDraggableComponent({ official: boolean; }) { const [open, setOpen] = useState(false); - const { getNodeId, deleteComponent, version } = useContext(FlowsContext); + const { deleteComponent, version } = useContext(FlowsContext); const { autoLogin, userData } = useContext(AuthContext); const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 }); const popoverRef = useRef(null); diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index 83a7b7b3a..26dfd0333 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -23,6 +23,7 @@ import { updateFlowPosition, } from "../../../../utils/reactflowUtils"; import { classNames } from "../../../../utils/utils"; +import useFlow from "../../../../stores/flowManagerStore"; export default function NodeToolbarComponent({ data, @@ -49,7 +50,6 @@ export default function NodeToolbarComponent({ data.node.template[templateField].type === "NestedDict") ).length ); - const { getNodeId } = useContext(FlowsContext); const { hasApiKey, validApiKey, hasStore } = useContext(StoreContext); function canMinimize() { @@ -65,13 +65,16 @@ export default function NodeToolbarComponent({ const { paste, - saveComponent, - version, - flows, nodes, edges, setNodes, setEdges, + } = useFlow(); + + const { + saveComponent, + flows, + version, } = useContext(FlowsContext); const { takeSnapshot } = useContext(undoRedoContext); const [showModalAdvanced, setShowModalAdvanced] = useState(false); @@ -120,7 +123,7 @@ export default function NodeToolbarComponent({ case "ungroup": takeSnapshot(); updateFlowPosition(position, data.node?.flow!); - expandGroupNode(data, getNodeId, nodes, edges, setNodes, setEdges); + expandGroupNode(data, nodes, edges, setNodes, setEdges); break; case "override": setShowOverrideModal(true); diff --git a/src/frontend/src/stores/flowManagerStore.ts b/src/frontend/src/stores/flowManagerStore.ts index 29404e37f..db6501460 100644 --- a/src/frontend/src/stores/flowManagerStore.ts +++ b/src/frontend/src/stores/flowManagerStore.ts @@ -37,6 +37,8 @@ type RFState = { onEdgesChange: OnEdgesChange; setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void; setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void; + setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void; + getNode: (id: string) => Node | undefined; onConnect: OnConnect; deleteNode: (nodeId: string | Array) => void; deleteEdge: (edgeId: string | Array) => void; @@ -45,6 +47,7 @@ type RFState = { position: { x: number; y: number; paneX?: number; paneY?: number } ) => void; isBuilt: boolean; + setIsBuilt: (isBuilt: boolean) => void; isPending: boolean; setPending: (pending: boolean) => void; }; @@ -58,6 +61,9 @@ const useFlow = create((set, get) => ({ nodes: [], edges: [], isBuilt: false, + setIsBuilt: (isBuilt) => { + set({ isBuilt }); + }, onNodesChange: (changes: NodeChange[]) => { set({ nodes: applyNodeChanges(changes, get().nodes), @@ -82,6 +88,24 @@ const useFlow = create((set, get) => ({ set({ edges: newChange }); }, + setNode: (id: string, change: Node | ((oldState: Node) => Node)) => { + let newChange = + typeof change === "function" + ? change(get().nodes.find((node) => node.id === id)!) + : change; + + get().setNodes((oldNodes) => + oldNodes.map((node) => { + if (node.id === id) { + return newChange; + } + return node; + }) + ); + }, + getNode: (id: string) => { + return get().nodes.find((node) => node.id === id); + }, onConnect: (connection: Connection) => { set({ edges: addEdge( diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 68f7447e7..c5c0e680b 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -12,10 +12,6 @@ export type FlowsContextType = { isLoading: boolean; setTabId: (index: string) => void; //keep - flows: Array; - deleteNode: (idx: string | Array) => void; - deleteEdge: (idx: string | Array) => void; - //keep removeFlow: (id: string) => void; //keep addFlow: ( @@ -24,7 +20,6 @@ export type FlowsContextType = { override?: boolean, position?: XYPosition ) => Promise; - incrementNodeId: () => string; downloadFlow: ( flow: FlowType, flowName: string, @@ -34,8 +29,6 @@ export type FlowsContextType = { downloadFlows: () => void; //keep uploadFlows: () => void; - isBuilt: boolean; - setIsBuilt: (state: boolean) => void; uploadFlow: ({ newProject, file, @@ -47,35 +40,17 @@ export type FlowsContextType = { isComponent?: boolean; position?: XYPosition; }) => Promise; - getNodeId: (nodeType: string) => string; - isPending: boolean; - setPending: (pending: boolean) => void; tabsState: FlowsState; setTabsState: ( update: FlowsState | ((oldState: FlowsState) => FlowsState) ) => void; - paste: ( - selection: { nodes: any; edges: any }, - position: { x: number; y: number; paneX?: number; paneY?: number } - ) => void; - lastCopiedSelection: { nodes: any; edges: any } | null; - setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void; - setTweak: (tweak: tweakType) => tweakType | void; - getTweak: tweakType; saveComponent: ( component: NodeDataType, override: boolean ) => Promise; deleteComponent: (key: string) => void; version: string; - nodes: Array; - setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void; - setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void; - getNode: (id: string) => Node | undefined; - onNodesChange: OnChange; - edges: Array; - setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void; - onEdgesChange: OnChange; + flows: Array; }; export type FlowsState = { diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 91fd39c83..a2a12ba29 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -924,7 +924,6 @@ function updateEdgesIds(edges: Edge[], idsMap: { [key: string]: string }) { export function expandGroupNode( groupNode: NodeDataType, - getNodeId: (type: string) => string, nodes: Node[], edges: Edge[], setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, From 3d720f7f798d4e0670ab2af373a545606749db5e Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Fri, 5 Jan 2024 11:31:40 -0300 Subject: [PATCH 10/10] Fixed flowsContext to work using useFlow zustand, removed unused functions --- src/frontend/src/contexts/flowsContext.tsx | 256 +----------------- src/frontend/src/contexts/typesContext.tsx | 6 - .../extraSidebarComponent/index.tsx | 5 +- src/frontend/src/types/typesContext/index.ts | 2 - src/frontend/src/utils/reactflowUtils.ts | 30 +- 5 files changed, 37 insertions(+), 262 deletions(-) diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx index 80851d945..97cdfc77b 100644 --- a/src/frontend/src/contexts/flowsContext.tsx +++ b/src/frontend/src/contexts/flowsContext.tsx @@ -46,9 +46,11 @@ import { checkOldEdgesHandles, cleanEdges, createFlowComponent, + processFlowEdges, removeFileNameFromComponents, scapeJSONParse, scapedJSONStringfy, + updateEdges, updateEdgesHandleIds, updateIds, } from "../utils/reactflowUtils"; @@ -60,6 +62,7 @@ import { import { alertContext } from "./alertContext"; import { AuthContext } from "./authContext"; import { typesContext } from "./typesContext"; +import useFlow from "../stores/flowManagerStore"; const uid = new ShortUniqueId({ length: 5 }); @@ -98,98 +101,10 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const [tabId, setTabId] = useState(""); const [isLoading, setIsLoading] = useState(false); const [flows, setFlows] = useState>([]); - const { reactFlowInstance, setData } = useContext(typesContext); - const [lastCopiedSelection, setLastCopiedSelection] = useState<{ - nodes: any; - edges: any; - } | null>(null); + const { setData } = useContext(typesContext); const [tabsState, setTabsState] = useState({}); - const [getTweak, setTweak] = useState([]); - const [nodes, setNodesInternal, onNodesChangeInternal] = useNodesState([]); - - const [edges, setEdgesInternal, onEdgesChangeInternal] = useEdgesState([]); - - const setPending = (pending: boolean) => { - setTabsState((prev: FlowsState) => { - return { - ...prev, - [tabId]: { - ...prev[tabId], - isPending: pending, - }, - }; - }); - }; - - const isPending = tabsState[tabId]?.isPending ?? false; - - const onNodesChange = useCallback( - (change: NodeChange[]) => { - onNodesChangeInternal(change); - if (!isPending) setPending(true); - }, - [onNodesChangeInternal, setPending, isPending] - ); - - const onEdgesChange = useCallback( - (edges: EdgeChange[]) => { - onEdgesChangeInternal(edges); - if (!isPending) setPending(true); - }, - [onEdgesChangeInternal, setPending, isPending] - ); - - const setNodes = (change: Node[] | ((oldState: Node[]) => Node[])) => { - let newChange = typeof change === "function" ? change(nodes) : change; - let newEdges = cleanEdges(newChange, edges); - - saveCurrentFlow( - newChange, - newEdges, - reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 } - ); - setEdgesInternal(newEdges); - setNodesInternal(newChange); - }; - - const setNode = (id: string, change: Node | ((oldState: Node) => Node)) => { - let newChange = - typeof change === "function" - ? change(nodes.find((node) => node.id === id)!) - : change; - - setNodes((oldNodes) => - oldNodes.map((node) => { - if (node.id === id) { - return newChange; - } - return node; - }) - ); - }; - - const getNode = (id: string) => { - return nodes.find((node) => node.id === id); - }; - - const setEdges = (change: Edge[] | ((oldState: Edge[]) => Edge[])) => { - let newChange = typeof change === "function" ? change(edges) : change; - - saveCurrentFlow( - nodes, - newChange, - reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 } - ); - setEdgesInternal(newChange); - }; - - const newNodeId = useRef(uid()); - - function incrementNodeId() { - newNodeId.current = uid(); - return newNodeId.current; - } + const {nodes, edges, paste, setPending, reactFlowInstance} = useFlow(); function refreshFlows() { setIsLoading(true); @@ -197,7 +112,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) { if (DbData) { try { processFlows(DbData, false); - updateStateWithDbData(DbData); + setFlows(DbData); setIsLoading(false); } catch (e) {} } @@ -247,23 +162,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { }); } - function processFlowEdges(flow: FlowType) { - if (!flow.data || !flow.data.edges) return; - if (checkOldEdgesHandles(flow.data.edges)) { - const newEdges = updateEdgesHandleIds(flow.data); - flow.data.edges = newEdges; - } - //update edges colors - flow.data.edges.forEach((edge) => { - edge.className = ""; - edge.style = { stroke: "#555" }; - }); - } - - function updateStateWithDbData(tabsData: FlowType[]) { - setFlows(tabsData); - } - /** * Downloads the current flow as a JSON file */ @@ -311,11 +209,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { link.click(); }); } - - function getNodeId(nodeType: string) { - return nodeType + "-" + incrementNodeId(); - } - /** * Creates a file input and listens to a change event to upload a JSON flow file. * If the file type is application/json, the file is read and parsed into a JSON object. @@ -424,111 +317,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { processFlows(flows.filter((flow) => flow.id !== id)); } } - /** - * Add a new flow to the list of flows. - * @param flow Optional flow to add. - */ - function paste( - selectionInstance: { nodes: Node[]; edges: Edge[] }, - position: { x: number; y: number; paneX?: number; paneY?: number } - ) { - let minimumX = Infinity; - let minimumY = Infinity; - let idsMap = {}; - let newNodes: Node[] = nodes; - let newEdges = edges; - selectionInstance.nodes.forEach((node: Node) => { - if (node.position.y < minimumY) { - minimumY = node.position.y; - } - if (node.position.x < minimumX) { - minimumX = node.position.x; - } - }); - - const insidePosition = position.paneX - ? { x: position.paneX + position.x, y: position.paneY! + position.y } - : reactFlowInstance!.screenToFlowPosition({ - x: position.x, - y: position.y, - }); - - selectionInstance.nodes.forEach((node: NodeType) => { - // Generate a unique node ID - let newId = getNodeId(node.data.type); - idsMap[node.id] = newId; - - // Create a new node object - const newNode: NodeType = { - id: newId, - type: "genericNode", - position: { - x: insidePosition.x + node.position!.x - minimumX, - y: insidePosition.y + node.position!.y - minimumY, - }, - data: { - ..._.cloneDeep(node.data), - id: newId, - }, - }; - - // Add the new node to the list of nodes in state - newNodes = newNodes - .map((node) => ({ ...node, selected: false })) - .concat({ ...newNode, selected: false }); - }); - setNodes(newNodes); - - selectionInstance.edges.forEach((edge: Edge) => { - let source = idsMap[edge.source]; - let target = idsMap[edge.target]; - const sourceHandleObject: sourceHandleType = scapeJSONParse( - edge.sourceHandle! - ); - let sourceHandle = scapedJSONStringfy({ - ...sourceHandleObject, - id: source, - }); - sourceHandleObject.id = source; - - edge.data.sourceHandle = sourceHandleObject; - const targetHandleObject: targetHandleType = scapeJSONParse( - edge.targetHandle! - ); - let targetHandle = scapedJSONStringfy({ - ...targetHandleObject, - id: target, - }); - targetHandleObject.id = target; - edge.data.targetHandle = targetHandleObject; - let id = - "reactflow__edge-" + - source + - sourceHandle + - "-" + - target + - targetHandle; - newEdges = addEdge( - { - source, - target, - sourceHandle, - targetHandle, - id, - data: cloneDeep(edge.data), - style: { stroke: "#555" }, - className: - targetHandleObject.type === "Text" - ? "stroke-gray-800 " - : "stroke-gray-900 ", - animated: targetHandleObject.type === "Text", - selected: false, - }, - newEdges.map((edge) => ({ ...edge, selected: false })) - ); - }); - setEdges(newEdges); - } const addFlow = async ( newProject: Boolean, @@ -592,26 +380,12 @@ export function FlowsProvider({ children }: { children: ReactNode }) { //add animation to text type edges updateEdges(data.edges); // updateNodes(data.nodes, data.edges); - if (refreshIds) updateIds(data, getNodeId); // Assuming updateIds is defined elsewhere + if (refreshIds) updateIds(data); // Assuming updateIds is defined elsewhere } return data; }; - const updateEdges = (edges: Edge[]) => { - if (edges) - edges.forEach((edge) => { - const targetHandleObject: targetHandleType = scapeJSONParse( - edge.targetHandle! - ); - edge.className = - (targetHandleObject.type === "Text" - ? "stroke-gray-800 " - : "stroke-gray-900 ") + " stroke-connection"; - edge.animated = targetHandleObject.type === "Text"; - }); - }; - const createNewFlow = ( flowData: ReactFlowJsonObject | null, flow: FlowType @@ -726,7 +500,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { } } - const [isBuilt, setIsBuilt] = useState(false); // Initialize state variable for the version const [version, setVersion] = useState(""); useEffect(() => { @@ -735,21 +508,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { }); }, []); - function deleteNode(idx: string | Array) { - setNodes((oldNodes) => - oldNodes.filter((node) => - typeof idx === "string" ? node.id !== idx : !idx.includes(node.id) - ) - ); - } - - function deleteEdge(idx: string | Array) { - setEdges((oldEdges) => - oldEdges.filter((edge) => - typeof idx === "string" ? edge.id !== idx : !idx.includes(edge.id) - ) - ); - } return ( {}, types: {}, setTypes: () => {}, templates: {}, @@ -34,8 +32,6 @@ export const typesContext = createContext(initialValue); export function TypesProvider({ children }: { children: ReactNode }) { const [types, setTypes] = useState({}); - const [reactFlowInstance, setReactFlowInstance] = - useState(null); const [templates, setTemplates] = useState({}); const [data, setData] = useState({}); const [fetchError, setFetchError] = useState(false); @@ -100,8 +96,6 @@ export function TypesProvider({ children }: { children: ReactNode }) { value={{ types, setTypes, - reactFlowInstance, - setReactFlowInstance, setTemplates, templates, data, diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 5f09cfa40..0ac5a14a7 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -290,10 +290,9 @@ export default function ExtraSidebar(): JSX.Element { {flow && flow.data && (