From 5c1c613c76e644beb730e0900e695ebded549dff Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Fri, 29 Dec 2023 23:21:53 -0300 Subject: [PATCH 01/89] Removed reactFlowInstance and changed to the interceptor function, validate connection not working --- .../components/parameterComponent/index.tsx | 15 ++-- .../src/CustomNodes/GenericNode/index.tsx | 2 +- .../chatComponent/buildTrigger/index.tsx | 7 +- .../components/codeTabsComponent/index.tsx | 5 +- src/frontend/src/contexts/flowsContext.tsx | 87 +++++++++++++++---- src/frontend/src/contexts/undoRedoContext.tsx | 22 +++-- .../src/modals/EditNodeModal/index.tsx | 5 +- .../src/modals/codeAreaModal/index.tsx | 1 - src/frontend/src/modals/exportModal/index.tsx | 1 - src/frontend/src/modals/formModal/index.tsx | 7 +- src/frontend/src/modals/shareModal/index.tsx | 3 +- .../components/PageComponent/index.tsx | 60 ++----------- .../extraSidebarComponent/index.tsx | 2 +- .../components/nodeToolbarComponent/index.tsx | 11 ++- src/frontend/src/types/chat/index.ts | 2 +- src/frontend/src/types/tabs/index.ts | 13 ++- src/frontend/src/utils/reactflowUtils.ts | 47 +++++----- 17 files changed, 149 insertions(+), 141 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 4c81e29a5..ed8930c16 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -71,7 +71,7 @@ export default function ParameterComponent({ const { setErrorData, modalContextOpen } = useContext(alertContext); const updateNodeInternals = useUpdateNodeInternals(); const [position, setPosition] = useState(0); - const { setTabsState, tabId, flows, tabsState, updateFlow } = + const { setTabsState, tabId, flows, tabsState, updateFlow, nodes, edges, setEdges } = useContext(FlowsContext); const [dataRef, setDataRef] = useState(data); @@ -96,10 +96,9 @@ export default function ParameterComponent({ setDataRef(data); }, [modalContextOpen]); - const { reactFlowInstance, setFilterEdge } = useContext(typesContext); + const { setFilterEdge } = useContext(typesContext); let disabled = - reactFlowInstance - ?.getEdges() + edges .some( (edge) => edge.targetHandle === @@ -181,14 +180,14 @@ export default function ParameterComponent({ let flow = flows.find((flow) => flow.id === tabId); setTimeout(() => { //timeout necessary because ReactFlow updates are not async - if (reactFlowInstance && flow && flow.data) { + if (flow && flow.data) { cleanEdges({ flow: { edges: flow.data!.edges, nodes: flow.data!.nodes, }, updateEdge: (edge) => { - reactFlowInstance.setEdges(edge); + setEdges(edge); updateNodeInternals(data.id); }, }); @@ -323,7 +322,7 @@ export default function ParameterComponent({ : scapedJSONStringfy(id) } isValidConnection={(connection) => - isValidConnection(connection, reactFlowInstance!) + isValidConnection(connection, nodes, edges) } className={classNames( left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ", @@ -396,7 +395,7 @@ export default function ParameterComponent({ : scapedJSONStringfy(id) } isValidConnection={(connection) => - isValidConnection(connection, reactFlowInstance!) + isValidConnection(connection, nodes, edges) } className={classNames( left ? "-ml-0.5 " : "-mr-0.5 ", diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index ca92a74cf..3e7554023 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -33,7 +33,7 @@ export default function GenericNode({ const { updateFlow, flows, tabId, saveCurrentFlow } = useContext(FlowsContext); const updateNodeInternals = useUpdateNodeInternals(); - const { types, deleteNode, reactFlowInstance, setFilterEdge, getFilterEdge } = + const { types, deleteNode } = useContext(typesContext); const name = nodeIconsLucide[data.type] ? data.type : types[data.type]; const [inputName, setInputName] = useState(false); diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index f1e374a03..4a64e04db 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -25,8 +25,7 @@ export default function BuildTrigger({ isBuilt: boolean; }): JSX.Element { const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE(); - const { reactFlowInstance } = useContext(typesContext); - const { setTabsState, saveFlow } = useContext(FlowsContext); + const { setTabsState, saveFlow, nodes, edges } = useContext(FlowsContext); const { setErrorData, setSuccessData } = useContext(alertContext); const [isIconTouched, setIsIconTouched] = useState(false); const eventClick = isBuilding ? "pointer-events-none" : ""; @@ -38,8 +37,8 @@ export default function BuildTrigger({ return; } const errors = validateNodes( - reactFlowInstance!.getNodes(), - reactFlowInstance!.getEdges() + nodes, + edges ); if (errors.length > 0) { setErrorData({ diff --git a/src/frontend/src/components/codeTabsComponent/index.tsx b/src/frontend/src/components/codeTabsComponent/index.tsx index bf9f4ff74..09b90860e 100644 --- a/src/frontend/src/components/codeTabsComponent/index.tsx +++ b/src/frontend/src/components/codeTabsComponent/index.tsx @@ -41,6 +41,7 @@ import { classNames } from "../../utils/utils"; import DictComponent from "../dictComponent"; import IconComponent from "../genericIconComponent"; import KeypairListComponent from "../keypairListComponent"; +import { FlowsContext } from "../../contexts/flowsContext"; export default function CodeTabsComponent({ flow, @@ -54,7 +55,7 @@ export default function CodeTabsComponent({ const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null); const [openAccordion, setOpenAccordion] = useState([]); const { dark } = useContext(darkContext); - const { reactFlowInstance } = useContext(typesContext); + const {setNodes} = useContext(FlowsContext); const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); useEffect(() => { @@ -68,7 +69,7 @@ export default function CodeTabsComponent({ unselectAllNodes({ data, updateNodes: (nodes) => { - reactFlowInstance?.setNodes(nodes); + setNodes(nodes); }, }); } diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx index 3978aa8bc..974ce6595 100644 --- a/src/frontend/src/contexts/flowsContext.tsx +++ b/src/frontend/src/contexts/flowsContext.tsx @@ -3,6 +3,7 @@ import _, { cloneDeep } from "lodash"; import { ReactNode, createContext, + useCallback, useContext, useEffect, useRef, @@ -10,10 +11,14 @@ import { } from "react"; import { Edge, + EdgeChange, Node, + NodeChange, ReactFlowJsonObject, XYPosition, addEdge, + useEdgesState, + useNodesState, } from "reactflow"; import ShortUniqueId from "short-unique-id"; import { @@ -93,8 +98,12 @@ const FlowsContextInitialValue: FlowsContextType = { saveComponent: async (component: NodeDataType, override: boolean) => "", deleteComponent: (key: string) => {}, version: "", - nodesOnFlow: "", - setNodesOnFlow: (nodes: string) => "", + nodes: [], + setNodes: () => {}, + onNodesChange: () => {}, + edges: [], + setEdges: () => {}, + onEdgesChange: () => {}, }; export const FlowsContext = createContext( @@ -102,18 +111,15 @@ export const FlowsContext = createContext( ); export function FlowsProvider({ children }: { children: ReactNode }) { - const { setErrorData, setNoticeData, setSuccessData } = + const { setErrorData, setSuccessData } = useContext(alertContext); const { getAuthentication, isAuthenticated } = useContext(AuthContext); const [tabId, setTabId] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [nodesOnFlow, setNodesOnFlow] = useState(""); - const [flows, setFlows] = useState>([]); const [id, setId] = useState(uid()); - const { reactFlowInstance, setData, data } = useContext(typesContext); + const { reactFlowInstance, setData } = useContext(typesContext); const [lastCopiedSelection, setLastCopiedSelection] = useState<{ nodes: any; edges: any; @@ -121,6 +127,50 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const [tabsState, setTabsState] = useState({}); const [getTweak, setTweak] = useState([]); + const [nodes, setNodesInternal, onNodesChangeInternal] = useNodesState([]); + + const [edges, setEdgesInternal, onEdgesChangeInternal] = useEdgesState([]); + + const onNodesChange = useCallback((nodes: NodeChange[]) => { + onNodesChangeInternal(nodes); + //@ts-ignore + setTabsState((prev: FlowsState) => { + return { + ...prev, + [tabId]: { + ...prev[tabId], + isPending: true, + }, + }; + }); + //SAVE FLOW IN FLOWS + }, [onNodesChangeInternal, setTabsState, tabId]); + + const onEdgesChange = useCallback( + (edges: EdgeChange[]) => { + onEdgesChangeInternal(edges); + //@ts-ignore + setTabsState((prev: FlowsState) => { + return { + ...prev, + [tabId]: { + ...prev[tabId], + isPending: true, + }, + }; + }); + }, + [onEdgesChangeInternal, setTabsState, tabId] + ); + + const setNodes = (nodes: Node[] | ((oldState: Node[]) => Node[])) => { + setNodesInternal(nodes) + } + + const setEdges = (edges: Edge[] | ((oldState: Edge[]) => Edge[])) => { + setEdgesInternal(edges); + }; + useEffect(() => { if (!isAuthenticated) { hardReset(); @@ -128,6 +178,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) { }, [isAuthenticated]); const newNodeId = useRef(uid()); + function incrementNodeId() { newNodeId.current = uid(); return newNodeId.current; @@ -425,8 +476,8 @@ export function FlowsProvider({ children }: { children: ReactNode }) { let minimumX = Infinity; let minimumY = Infinity; let idsMap = {}; - let nodes: Node[] = reactFlowInstance!.getNodes(); - let edges = reactFlowInstance!.getEdges(); + let newNodes: Node[] = nodes; + let newEdges = edges; selectionInstance.nodes.forEach((node: Node) => { if (node.position.y < minimumY) { minimumY = node.position.y; @@ -463,11 +514,11 @@ export function FlowsProvider({ children }: { children: ReactNode }) { }; // Add the new node to the list of nodes in state - nodes = nodes + newNodes = nodes .map((node) => ({ ...node, selected: false })) .concat({ ...newNode, selected: false }); }); - reactFlowInstance!.setNodes(nodes); + setNodes(newNodes); selectionInstance.edges.forEach((edge: Edge) => { let source = idsMap[edge.source]; @@ -498,7 +549,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) { "-" + target + targetHandle; - edges = addEdge( + newEdges = addEdge( { source, target, @@ -514,10 +565,10 @@ export function FlowsProvider({ children }: { children: ReactNode }) { animated: targetHandleObject.type === "Text", selected: false, }, - edges.map((edge) => ({ ...edge, selected: false })) + newEdges.map((edge) => ({ ...edge, selected: false })) ); }); - reactFlowInstance!.setEdges(edges); + setEdges(newEdges); } const addFlow = async ( @@ -744,8 +795,12 @@ export function FlowsProvider({ children }: { children: ReactNode }) { isLoading, saveComponent, deleteComponent, - nodesOnFlow, - setNodesOnFlow, + nodes, + setNodes, + onNodesChange, + edges, + setEdges, + onEdgesChange, }} > {children} diff --git a/src/frontend/src/contexts/undoRedoContext.tsx b/src/frontend/src/contexts/undoRedoContext.tsx index f02be6257..ce925206f 100644 --- a/src/frontend/src/contexts/undoRedoContext.tsx +++ b/src/frontend/src/contexts/undoRedoContext.tsx @@ -29,7 +29,7 @@ const defaultOptions: UseUndoRedoOptions = { export const undoRedoContext = createContext(initialValue); export function UndoRedoProvider({ children }) { - const { tabId, flows } = useContext(FlowsContext); + const { tabId, flows, setNodes, setEdges, nodes, edges } = useContext(FlowsContext); const [past, setPast] = useState(flows.map(() => [])); const [future, setFuture] = useState(flows.map(() => [])); @@ -48,14 +48,12 @@ export function UndoRedoProvider({ children }) { setTabIndex(flows.findIndex((flow) => flow.id === tabId)); }, [flows, tabId]); - const { setNodes, setEdges, getNodes, getEdges } = useReactFlow(); - const takeSnapshot = useCallback(() => { // push the current graph to the past state let newPast = cloneDeep(past); let newState = { - nodes: cloneDeep(getNodes()), - edges: cloneDeep(getEdges()), + nodes: cloneDeep(nodes), + edges: cloneDeep(edges), }; if ( past[tabIndex] && @@ -76,7 +74,7 @@ export function UndoRedoProvider({ children }) { newFuture[tabIndex] = []; return newFuture; }); - }, [getNodes, getEdges, past, future, flows, tabId, setPast, setFuture]); + }, [nodes, edges, past, future, flows, tabId, setPast, setFuture]); const undo = useCallback(() => { // get the last state that we want to go back to @@ -93,7 +91,7 @@ export function UndoRedoProvider({ children }) { setFuture((old) => { let newFuture = cloneDeep(old); newFuture[tabIndex] = old[tabIndex]; - newFuture[tabIndex].push({ nodes: getNodes(), edges: getEdges() }); + newFuture[tabIndex].push({ nodes: nodes, edges: edges }); return newFuture; }); // now we can set the graph to the past state @@ -103,8 +101,8 @@ export function UndoRedoProvider({ children }) { }, [ setNodes, setEdges, - getNodes, - getEdges, + nodes, + edges, future, past, setFuture, @@ -124,7 +122,7 @@ export function UndoRedoProvider({ children }) { setPast((old) => { let newPast = cloneDeep(old); newPast[tabIndex] = old[tabIndex]; - newPast[tabIndex].push({ nodes: getNodes(), edges: getEdges() }); + newPast[tabIndex].push({ nodes: nodes, edges: edges }); return newPast; }); setNodes(futureState.nodes); @@ -137,8 +135,8 @@ export function UndoRedoProvider({ children }) { setPast, setNodes, setEdges, - getNodes, - getEdges, + nodes, + edges, future, tabIndex, ]); diff --git a/src/frontend/src/modals/EditNodeModal/index.tsx b/src/frontend/src/modals/EditNodeModal/index.tsx index 26dc15fd4..b9576594b 100644 --- a/src/frontend/src/modals/EditNodeModal/index.tsx +++ b/src/frontend/src/modals/EditNodeModal/index.tsx @@ -62,8 +62,7 @@ const EditNodeModal = forwardRef( const [myData, setMyData] = useState(data); - const { setTabsState, tabId } = useContext(FlowsContext); - const { reactFlowInstance } = useContext(typesContext); + const { setTabsState, tabId, edges } = useContext(FlowsContext); const { setModalContextOpen } = useContext(alertContext); function changeAdvanced(n) { @@ -159,7 +158,7 @@ const EditNodeModal = forwardRef( fieldName: templateParam, }; let disabled = - reactFlowInstance?.getEdges().some( + edges.some( (edge) => edge.targetHandle === scapedJSONStringfy( diff --git a/src/frontend/src/modals/codeAreaModal/index.tsx b/src/frontend/src/modals/codeAreaModal/index.tsx index f488704c8..c700b701b 100644 --- a/src/frontend/src/modals/codeAreaModal/index.tsx +++ b/src/frontend/src/modals/codeAreaModal/index.tsx @@ -28,7 +28,6 @@ export default function CodeAreaModal({ }: codeAreaModalPropsType): JSX.Element { const [code, setCode] = useState(value); const { dark } = useContext(darkContext); - const { reactFlowInstance } = useContext(typesContext); const [height, setHeight] = useState(null); const { setErrorData, setSuccessData } = useContext(alertContext); const [error, setError] = useState<{ diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx index 5ea23b644..f2d2424a4 100644 --- a/src/frontend/src/modals/exportModal/index.tsx +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -13,7 +13,6 @@ import BaseModal from "../baseModal"; const ExportModal = forwardRef( (props: { children: ReactNode }, ref): JSX.Element => { const { flows, tabId, downloadFlow, version } = useContext(FlowsContext); - const { reactFlowInstance } = useContext(typesContext); const { setNoticeData } = useContext(alertContext); const [checked, setChecked] = useState(true); const flow = flows.find((f) => f.id === tabId); diff --git a/src/frontend/src/modals/formModal/index.tsx b/src/frontend/src/modals/formModal/index.tsx index 571cbca06..891a1dfa6 100644 --- a/src/frontend/src/modals/formModal/index.tsx +++ b/src/frontend/src/modals/formModal/index.tsx @@ -38,7 +38,7 @@ export default function FormModal({ setOpen: (open: boolean) => void; flow: FlowType; }): JSX.Element { - const { tabsState, setTabsState } = useContext(FlowsContext); + const { tabsState, setTabsState, nodes, edges } = useContext(FlowsContext); const [chatValue, setChatValue] = useState(() => { try { const { formKeysData } = tabsState[flow.id]; @@ -62,7 +62,6 @@ export default function FormModal({ const [chatHistory, setChatHistory] = useState([]); const template = useRef(tabsState[flow.id].formKeysData.template); - const { reactFlowInstance } = useContext(typesContext); const { accessToken } = useContext(AuthContext); const { setErrorData } = useContext(alertContext); const ws = useRef(null); @@ -385,8 +384,8 @@ export default function FormModal({ function sendMessage(): void { let nodeValidationErrors = validateNodes( - reactFlowInstance!.getNodes(), - reactFlowInstance!.getEdges() + nodes, + edges ); if (nodeValidationErrors.length === 0) { setLockChat(true); diff --git a/src/frontend/src/modals/shareModal/index.tsx b/src/frontend/src/modals/shareModal/index.tsx index 0edbe80be..3363777d6 100644 --- a/src/frontend/src/modals/shareModal/index.tsx +++ b/src/frontend/src/modals/shareModal/index.tsx @@ -40,10 +40,9 @@ export default function ShareModal({ setOpen?: (open: boolean) => void; disabled?: boolean; }): JSX.Element { - const { version, addFlow, downloadFlow } = useContext(FlowsContext); + const { version } = useContext(FlowsContext); const { hasApiKey, hasStore } = useContext(StoreContext); const { setSuccessData, setErrorData } = useContext(alertContext); - const { reactFlowInstance } = useContext(typesContext); const [internalOpen, internalSetOpen] = useState(children ? false : true); const [openConfirmationModal, setOpenConfirmationModal] = useState(false); const nameComponent = is_component ? "component" : "flow"; diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index d035812f0..ed1700be4 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -82,7 +82,7 @@ export default function Page({ const reactFlowWrapper = useRef(null); const { takeSnapshot } = useContext(undoRedoContext); - const { nodesOnFlow, setNodesOnFlow } = useContext(FlowsContext); + const { nodes, edges, setNodes, setEdges, onNodesChange, onEdgesChange } = useContext(FlowsContext); const position = useRef({ x: 0, y: 0 }); const [lastSelection, setLastSelection] = @@ -164,13 +164,7 @@ export default function Page({ const { setExtraComponent, setExtraNavigation } = useContext(locationContext); const { setErrorData } = useContext(alertContext); - const [nodes, setNodes, onNodesChange] = useNodesState( - flow.data?.nodes ?? [] - ); - const [edges, setEdges, onEdgesChange] = useEdgesState( - flow.data?.edges ?? [] - ); const { setViewport } = useReactFlow(); const edgeUpdateSuccessful = useRef(true); @@ -210,50 +204,6 @@ export default function Page({ }; }, [flow, flow.data]); - const onEdgesChangeMod = useCallback( - (change: EdgeChange[]) => { - onEdgesChange(change); - //@ts-ignore - setTabsState((prev: FlowsState) => { - return { - ...prev, - [tabId]: { - ...prev[tabId], - isPending: true, - }, - }; - }); - saveCurrentFlowTimeout(); - }, - [onEdgesChange, setNodes, setTabsState, saveCurrentFlowTimeout, tabId] - ); - - const onNodesChangeMod = useCallback( - (change: NodeChange[]) => { - const changeString = JSON.stringify(change); - if (changeString !== nodesOnFlow) { - onNodesChange(change); - updateNodeFlow(changeString); - //@ts-ignore - setTabsState((prev: FlowsState) => { - return { - ...prev, - [tabId]: { - ...prev[tabId], - isPending: true, - }, - }; - }); - saveCurrentFlowTimeout(); - } - }, - [onNodesChange, setTabsState, tabId, updateNodeFlow, saveCurrentFlowTimeout] - ); - - function updateNodeFlow(changeString: string) { - setNodesOnFlow(changeString); - } - const onConnect = useCallback( (params: Connection) => { takeSnapshot(); @@ -416,7 +366,7 @@ export default function Page({ const onEdgeUpdate = useCallback( (oldEdge: Edge, newConnection: Connection) => { - if (isValidConnection(newConnection, reactFlowInstance!)) { + if (isValidConnection(newConnection, nodes, edges)) { edgeUpdateSuccessful.current = true; setEdges((els) => updateEdge(oldEdge, newConnection, els)); } @@ -498,8 +448,8 @@ export default function Page({ nodes={nodes} onMove={onMove} edges={edges} - onNodesChange={onNodesChangeMod} - onEdgesChange={onEdgesChangeMod} + onNodesChange={onNodesChange} + onEdgesChange={onEdgesChange} onConnect={onConnect} disableKeyboardA11y={true} onInit={setReactFlowInstance} @@ -579,7 +529,7 @@ export default function Page({ /> {!view && ( - + )} ) : ( diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index a70132794..84e34a07b 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -26,7 +26,7 @@ import DisclosureComponent from "../DisclosureComponent"; import SidebarDraggableComponent from "./sideBarDraggableComponent"; export default function ExtraSidebar(): JSX.Element { - const { data, templates, getFilterEdge, setFilterEdge, reactFlowInstance } = + const { data, templates, getFilterEdge, setFilterEdge } = useContext(typesContext); const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, version } = useContext(FlowsContext); diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index 00b5e46c8..5b4d5ed38 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -65,9 +65,8 @@ export default function NodeToolbarComponent({ const isMinimal = canMinimize(); const isGroup = data.node?.flow ? true : false; - const { paste, saveComponent, version, flows } = useContext(FlowsContext); + const { paste, saveComponent, version, flows, nodes, edges, setNodes, setEdges } = useContext(FlowsContext); const { takeSnapshot } = useContext(undoRedoContext); - const reactFlowInstance = useReactFlow(); const [showModalAdvanced, setShowModalAdvanced] = useState(false); const [showconfirmShare, setShowconfirmShare] = useState(false); const [selectedValue, setSelectedValue] = useState(""); @@ -115,7 +114,7 @@ export default function NodeToolbarComponent({ case "ungroup": takeSnapshot(); updateFlowPosition(position, data.node?.flow!); - expandGroupNode(data, reactFlowInstance, getNodeId); + expandGroupNode(data, getNodeId, nodes, edges, setNodes, setEdges); break; case "override": setShowOverrideModal(true); @@ -152,14 +151,14 @@ export default function NodeToolbarComponent({ event.preventDefault(); paste( { - nodes: [reactFlowInstance.getNode(data.id)], + nodes: [nodes.find((node) => node.id === data.id)!], edges: [], }, { x: 50, y: 10, - paneX: reactFlowInstance.getNode(data.id)?.position.x, - paneY: reactFlowInstance.getNode(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/types/chat/index.ts b/src/frontend/src/types/chat/index.ts index 71a081be7..66422a968 100644 --- a/src/frontend/src/types/chat/index.ts +++ b/src/frontend/src/types/chat/index.ts @@ -1,7 +1,7 @@ import { ReactFlowInstance } from "reactflow"; import { FlowType } from "../flow"; -export type ChatType = { flow: FlowType; reactFlowInstance: ReactFlowInstance }; +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 d4e120c1c..732ddbb80 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -1,6 +1,9 @@ -import { XYPosition } from "reactflow"; +import { XYPosition, Node, NodeChange, Edge, EdgeChange } from "reactflow"; import { tweakType } from "../components"; import { FlowType, NodeDataType } from "../flow"; +import { Dispatch, SetStateAction } from "react"; + +type OnChange = (changes: ChangesType[]) => void; export type FlowsContextType = { saveFlow: (flow: FlowType, silent?: boolean) => Promise; @@ -56,8 +59,12 @@ export type FlowsContextType = { ) => Promise; deleteComponent: (key: string) => void; version: string; - nodesOnFlow: string; - setNodesOnFlow: (nodes: string) => void; + nodes: Array; + setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void; + onNodesChange: OnChange; + edges: Array; + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void; + onEdgesChange: OnChange; }; export type FlowsState = { diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index c8b7120a8..957708dcb 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -86,8 +86,10 @@ export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) { export function isValidConnection( { source, target, sourceHandle, targetHandle }: Connection, - reactFlowInstance: ReactFlowInstance + nodes: Node[], + edges: Edge[], ) { + debugger const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!); const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!); if ( @@ -100,19 +102,17 @@ export function isValidConnection( t === targetHandleObject.type ) ) { - let targetNode = reactFlowInstance?.getNode(target!)?.data?.node; + let targetNode = nodes.find((node) => node.id === target!)?.data?.node; if (!targetNode) { if ( - !reactFlowInstance - .getEdges() + edges .find((e) => e.targetHandle === targetHandle) ) { return true; } } else if ( (!targetNode.template[targetHandleObject.fieldName].list && - !reactFlowInstance - .getEdges() + edges .find((e) => e.targetHandle === targetHandle)) || targetNode.template[targetHandleObject.fieldName].list ) { @@ -543,12 +543,13 @@ export function generateFlow( export function filterFlow( selection: OnSelectionChangeParams, - reactFlowInstance: ReactFlowInstance + setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, ) { - reactFlowInstance.setNodes((nodes) => + setNodes((nodes) => nodes.filter((node) => !selection.nodes.includes(node)) ); - reactFlowInstance.setEdges((edges) => + setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge)) ); } @@ -575,11 +576,12 @@ export function updateFlowPosition(NewPosition: XYPosition, flow: FlowType) { export function concatFlows( flow: FlowType, - ReactFlowInstance: ReactFlowInstance + setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, ) { const { nodes, edges } = flow.data!; - ReactFlowInstance.addNodes(nodes); - ReactFlowInstance.addEdges(edges); + setNodes((old) => [...old, ...nodes]); + setEdges((old) => [...old, ...edges]); } export function validateSelection( @@ -921,13 +923,16 @@ function updateEdgesIds(edges: Edge[], idsMap: { [key: string]: string }) { export function expandGroupNode( groupNode: NodeDataType, - ReactFlowInstance: ReactFlowInstance, - getNodeId: (type: string) => string + getNodeId: (type: string) => string, + nodes: Node[], + edges: Edge[], + setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, + setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void ) { const { template, flow } = _.cloneDeep(groupNode.node!); const idsMap = updateIds(flow!.data!, getNodeId); updateProxyIdsOnTemplate(template, idsMap); - let flowEdges = ReactFlowInstance.getEdges(); + let flowEdges = edges; updateEdgesIds(flowEdges, idsMap); const gNodes: NodeType[] = flow?.data?.nodes!; const gEdges = flow!.data!.edges; @@ -1008,19 +1013,19 @@ export function expandGroupNode( } }); - const nodes = [ - ...ReactFlowInstance.getNodes().filter((n) => n.id !== groupNode.id), + const filteredNodes = [ + ...nodes.filter((n) => n.id !== groupNode.id), ...gNodes, ]; - const edges = [ - ...ReactFlowInstance.getEdges().filter( + const filteredEdges = [ + ...edges.filter( (e) => e.target !== groupNode.id && e.source !== groupNode.id ), ...gEdges, ...updatedEdges, ]; - ReactFlowInstance.setNodes(nodes); - ReactFlowInstance.setEdges(edges); + setNodes(filteredNodes); + setEdges(filteredEdges); } export function processFlow(FlowObject: ReactFlowJsonObject) { From 319e70863a04d63559cc8f11d374241ce72a9a1b Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sat, 30 Dec 2023 17:54:31 -0300 Subject: [PATCH 02/89] Fixed isValidConnection --- src/frontend/src/utils/reactflowUtils.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 957708dcb..ea99c12a4 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -89,7 +89,6 @@ export function isValidConnection( nodes: Node[], edges: Edge[], ) { - debugger const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!); const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!); if ( @@ -105,14 +104,14 @@ export function isValidConnection( let targetNode = nodes.find((node) => node.id === target!)?.data?.node; if (!targetNode) { if ( - edges + !edges .find((e) => e.targetHandle === targetHandle) ) { return true; } } else if ( (!targetNode.template[targetHandleObject.fieldName].list && - edges + !edges .find((e) => e.targetHandle === targetHandle)) || targetNode.template[targetHandleObject.fieldName].list ) { From 6e2673e1d54ef078314c534f85537cc6d463c2f2 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sat, 30 Dec 2023 19:31:13 -0300 Subject: [PATCH 03/89] Fix handleNodeClass and handleOnNewValue to use interceptor saving, added cleanEdges on interceptor --- .../components/parameterComponent/index.tsx | 100 ++++++++---------- src/frontend/src/contexts/flowsContext.tsx | 82 +++++++++----- .../components/PageComponent/index.tsx | 6 +- src/frontend/src/types/tabs/index.ts | 4 + .../src/types/utils/reactflowUtils.ts | 8 -- src/frontend/src/utils/reactflowUtils.ts | 8 +- 6 files changed, 108 insertions(+), 100 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index ed8930c16..54458cc01 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -35,7 +35,6 @@ import { APIClassType } from "../../../../types/api"; import { ParameterComponentType } from "../../../../types/components"; import { NodeDataType } from "../../../../types/flow"; import { - cleanEdges, convertObjToArray, convertValuesToNumbers, hasDuplicateKeys, @@ -71,8 +70,15 @@ export default function ParameterComponent({ const { setErrorData, modalContextOpen } = useContext(alertContext); const updateNodeInternals = useUpdateNodeInternals(); const [position, setPosition] = useState(0); - const { setTabsState, tabId, flows, tabsState, updateFlow, nodes, edges, setEdges } = - useContext(FlowsContext); + const { + tabId, + flows, + updateFlow, + nodes, + edges, + setEdges, + setNode, + } = useContext(FlowsContext); const [dataRef, setDataRef] = useState(data); @@ -98,12 +104,10 @@ export default function ParameterComponent({ const { setFilterEdge } = useContext(typesContext); let disabled = - edges - .some( - (edge) => - edge.targetHandle === - scapedJSONStringfy(proxy ? { ...id, proxy } : id) - ) ?? false; + edges.some( + (edge) => + edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id) + ) ?? false; const { data: myData } = useContext(typesContext); @@ -132,68 +136,50 @@ export default function ParameterComponent({ if (data.node!.template[name].value !== newValue) { takeSnapshot(); } - data.node!.template[name].value = newValue; - updateNodeInternals(data.id); + + setNode(data.id, (oldNode) => { + let newNode = cloneDeep(oldNode); + + newNode.data = { + ...newNode.data + } + + newNode.data.node.template[name].value = newValue; + + return newNode; + }); setDataRef((old) => { let newData = cloneDeep(old); newData.node!.template[name].value = newValue; return newData; }); - - // Set state to pending - //@ts-ignore - if (data.node!.template[name].value !== newValue) { - const tabs = cloneDeep(tabsState); - tabs[tabId].isPending = false; - tabs[tabId].formKeysData = tabsState[tabId].formKeysData; - setTabsState({ - ...tabs, - }); - } + renderTooltips(); }; const handleNodeClass = (newNodeClass: APIClassType, code?: string): void => { if (!data.node) return; - if (data.node!.template[name].value !== newNodeClass.template[name].value) { + if (data.node!.template[name].value !== code) { takeSnapshot(); } - data.node! = { - ...newNodeClass, - description: newNodeClass.description ?? data.node!.description, - display_name: newNodeClass.display_name ?? data.node!.display_name, - }; - data.node!.template[name].value = code; - updateNodeInternals(data.id); - // Set state to pending - //@ts-ignore - if (data.node!.template[name].value !== code) { - const tabs = cloneDeep(tabsState); - tabs[tabId].isPending = false; - tabs[tabId].formKeysData = tabsState[tabId].formKeysData; - setTabsState({ - ...tabs, - }); - } + + setNode(data.id, (oldNode) => { + let newNode = cloneDeep(oldNode); + + newNode.data = { + ...newNode.data, + node: newNodeClass, + description: newNodeClass.description ?? data.node!.description, + display_name: newNodeClass.display_name ?? data.node!.display_name, + }; + + newNode.data.node.template[name].value = code; + + return newNode; + }); + renderTooltips(); - let flow = flows.find((flow) => flow.id === tabId); - setTimeout(() => { - //timeout necessary because ReactFlow updates are not async - if (flow && flow.data) { - cleanEdges({ - flow: { - edges: flow.data!.edges, - nodes: flow.data!.nodes, - }, - updateEdge: (edge) => { - setEdges(edge); - updateNodeInternals(data.id); - }, - }); - updateFlow(flow); - } - }, 50); }; const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx index 974ce6595..955f32fb3 100644 --- a/src/frontend/src/contexts/flowsContext.tsx +++ b/src/frontend/src/contexts/flowsContext.tsx @@ -43,6 +43,7 @@ import { FlowsContextType, FlowsState } from "../types/tabs"; import { addVersionToDuplicates, checkOldEdgesHandles, + cleanEdges, createFlowComponent, removeFileNameFromComponents, scapeJSONParse, @@ -85,6 +86,8 @@ const FlowsContextInitialValue: FlowsContextType = { saveFlow: async (flow: FlowType, silent?: boolean) => {}, lastCopiedSelection: null, setLastCopiedSelection: (selection: any) => {}, + isPending: false, + setPending: (pending: boolean) => {}, tabsState: {}, setTabsState: (state: FlowsState) => {}, saveCurrentFlow: () => {}, @@ -100,6 +103,8 @@ const FlowsContextInitialValue: FlowsContextType = { version: "", nodes: [], setNodes: () => {}, + setNode: () => {}, + getNode: () => undefined, onNodesChange: () => {}, edges: [], setEdges: () => {}, @@ -111,8 +116,7 @@ export const FlowsContext = createContext( ); export function FlowsProvider({ children }: { children: ReactNode }) { - const { setErrorData, setSuccessData } = - useContext(alertContext); + const { setErrorData, setSuccessData } = useContext(alertContext); const { getAuthentication, isAuthenticated } = useContext(AuthContext); const [tabId, setTabId] = useState(""); @@ -131,41 +135,67 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const [edges, setEdgesInternal, onEdgesChangeInternal] = useEdgesState([]); - const onNodesChange = useCallback((nodes: NodeChange[]) => { - onNodesChangeInternal(nodes); - //@ts-ignore - setTabsState((prev: FlowsState) => { - return { - ...prev, - [tabId]: { - ...prev[tabId], - isPending: true, - }, - }; - }); - //SAVE FLOW IN FLOWS - }, [onNodesChangeInternal, setTabsState, tabId]); - - const onEdgesChange = useCallback( - (edges: EdgeChange[]) => { - onEdgesChangeInternal(edges); + const setPending = useCallback( + (pending: boolean) => { //@ts-ignore setTabsState((prev: FlowsState) => { return { ...prev, [tabId]: { ...prev[tabId], - isPending: true, + isPending: pending, }, }; }); }, + [setTabsState] + ); + + const isPending = tabsState[tabId]?.isPending ?? false; + + const onNodesChange = useCallback( + (nodes: NodeChange[]) => { + onNodesChangeInternal(nodes); + console.log("nodesChangou") + + setPending(true); + }, + [onNodesChangeInternal, setTabsState, tabId] + ); + + const onEdgesChange = useCallback( + (edges: EdgeChange[]) => { + onEdgesChangeInternal(edges); + console.log("edgesChangou") + setPending(true); + }, [onEdgesChangeInternal, setTabsState, tabId] ); - const setNodes = (nodes: Node[] | ((oldState: Node[]) => Node[])) => { - setNodesInternal(nodes) - } + const setNodes = (change: Node[] | ((oldState: Node[]) => Node[])) => { + let newChange = typeof change === "function" ? change(nodes) : change; + + + setEdgesInternal(cleanEdges(newChange, edges)); + 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 = (edges: Edge[] | ((oldState: Edge[]) => Edge[])) => { setEdgesInternal(edges); @@ -787,6 +817,8 @@ export function FlowsProvider({ children }: { children: ReactNode }) { uploadFlows, uploadFlow, getNodeId, + isPending, + setPending, tabsState, setTabsState, paste, @@ -797,6 +829,8 @@ export function FlowsProvider({ children }: { children: ReactNode }) { deleteComponent, nodes, setNodes, + setNode, + getNode, onNodesChange, edges, setEdges, diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index ed1700be4..d1a5a2c2d 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -228,10 +228,6 @@ export default function Page({ eds ) ); - setNodes((node) => { - let newX = _.cloneDeep(node); - return newX; - }); //@ts-ignore setTabsState((prev: FlowsState) => { return { @@ -244,7 +240,7 @@ export default function Page({ }); saveCurrentFlowTimeout(); }, - [setEdges, setNodes, takeSnapshot, addEdge] + [setEdges, takeSnapshot, addEdge] ); const onNodeDragStart: NodeDragHandler = useCallback(() => { diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 732ddbb80..41b6726ec 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -43,6 +43,8 @@ export type FlowsContextType = { }) => Promise; hardReset: () => void; getNodeId: (nodeType: string) => string; + isPending: boolean; + setPending: (pending: boolean) => void; tabsState: FlowsState; setTabsState: (state: FlowsState) => void; paste: ( @@ -61,6 +63,8 @@ export type FlowsContextType = { 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; diff --git a/src/frontend/src/types/utils/reactflowUtils.ts b/src/frontend/src/types/utils/reactflowUtils.ts index 627c5dc3a..fda6b75a8 100644 --- a/src/frontend/src/types/utils/reactflowUtils.ts +++ b/src/frontend/src/types/utils/reactflowUtils.ts @@ -1,14 +1,6 @@ import { Edge, Node } from "reactflow"; import { FlowType, NodeType } from "../flow"; -export type cleanEdgesType = { - flow: { - edges: Edge[]; - nodes: NodeType[]; - }; - updateEdge: (edge: Edge[]) => void; -}; - export type unselectAllNodesType = { updateNodes: (nodes: Node[]) => void; data: Node[]; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index ea99c12a4..4f6fb7bc6 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -22,7 +22,6 @@ import { targetHandleType, } from "../types/flow"; import { - cleanEdgesType, findLastNodeType, generateFlowType, unselectAllNodesType, @@ -30,10 +29,7 @@ import { } from "../types/utils/reactflowUtils"; import { getFieldTitle, toTitleCase } from "./utils"; -export function cleanEdges({ - flow: { edges, nodes }, - updateEdge, -}: cleanEdgesType) { +export function cleanEdges(nodes: Node[], edges: Edge[]) { let newEdges = _.cloneDeep(edges); edges.forEach((edge) => { // check if the source and target node still exists @@ -73,7 +69,7 @@ export function cleanEdges({ } } }); - updateEdge(newEdges); + return newEdges; } export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) { From f6bd5a4e4a6f790089ee89b6cfa2d027711db955 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sat, 30 Dec 2023 20:50:51 -0300 Subject: [PATCH 04/89] Removed all reactFlowInstance nodes and edges settings. Centralized flow saving. --- .../components/parameterComponent/index.tsx | 4 +- .../src/CustomNodes/GenericNode/index.tsx | 8 +- .../src/components/chatComponent/index.tsx | 6 +- src/frontend/src/contexts/flowsContext.tsx | 103 +++++++++++------- src/frontend/src/contexts/typesContext.tsx | 25 ----- .../src/modals/EditNodeModal/index.tsx | 10 +- .../components/PageComponent/index.tsx | 87 ++++----------- .../extraSidebarComponent/index.tsx | 5 +- src/frontend/src/types/chat/index.ts | 1 - src/frontend/src/types/tabs/index.ts | 6 +- src/frontend/src/types/typesContext/index.ts | 2 - src/frontend/src/utils/reactflowUtils.ts | 6 +- 12 files changed, 96 insertions(+), 167 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 54458cc01..ba211897e 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -73,10 +73,8 @@ export default function ParameterComponent({ const { tabId, flows, - updateFlow, nodes, edges, - setEdges, setNode, } = useContext(FlowsContext); @@ -154,7 +152,7 @@ export default function ParameterComponent({ newData.node!.template[name].value = newValue; return newData; }); - + renderTooltips(); }; diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 3e7554023..e4ff8a692 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -30,11 +30,9 @@ export default function GenericNode({ xPos: number; yPos: number; }): JSX.Element { - const { updateFlow, flows, tabId, saveCurrentFlow } = - useContext(FlowsContext); const updateNodeInternals = useUpdateNodeInternals(); - const { types, deleteNode } = - useContext(typesContext); + const { types } = useContext(typesContext); + const { deleteNode } = useContext(FlowsContext); const name = nodeIconsLucide[data.type] ? data.type : types[data.type]; const [inputName, setInputName] = useState(false); const [nodeName, setNodeName] = useState(data.node!.display_name); @@ -46,7 +44,6 @@ export default function GenericNode({ useState(null); const [handles, setHandles] = useState([]); let numberOfInputs: boolean[] = []; - const { modalContextOpen } = useContext(alertContext); const { takeSnapshot } = useContext(undoRedoContext); @@ -118,7 +115,6 @@ export default function GenericNode({ deleteNode={(id) => { takeSnapshot(); deleteNode(id); - saveCurrentFlow(); }} setShowNode={(show: boolean) => { data.showNode = show; diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 97497cb5d..129e1f55c 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -13,7 +13,7 @@ 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 } = useContext(FlowsContext); + const { tabsState, isBuilt, setIsBuilt, isPending } = useContext(FlowsContext); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { @@ -51,9 +51,7 @@ export default function Chat({ flow }: ChatType): JSX.Element { _.cloneDeep(node.data.node?.template) ); if ( - tabsState && - tabsState[flow.id] && - tabsState[flow.id].isPending && + isPending && JSON.stringify(prevNodes) !== JSON.stringify(currentNodes) ) { setIsBuilt(false); diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx index 955f32fb3..3902b382b 100644 --- a/src/frontend/src/contexts/flowsContext.tsx +++ b/src/frontend/src/contexts/flowsContext.tsx @@ -15,6 +15,7 @@ import { Node, NodeChange, ReactFlowJsonObject, + Viewport, XYPosition, addEdge, useEdgesState, @@ -74,7 +75,8 @@ const FlowsContextInitialValue: FlowsContextType = { flowData?: FlowType, override?: boolean ) => "", - updateFlow: (newFlow: FlowType) => {}, + deleteNode: () => {}, + deleteEdge: () => {}, incrementNodeId: () => uid(), downloadFlow: (flow: FlowType) => {}, downloadFlows: () => {}, @@ -83,14 +85,13 @@ const FlowsContextInitialValue: FlowsContextType = { isBuilt: false, setIsBuilt: (state: boolean) => {}, hardReset: () => {}, - saveFlow: async (flow: FlowType, silent?: boolean) => {}, + saveFlow: async (flow?: FlowType, silent?: boolean) => {}, lastCopiedSelection: null, setLastCopiedSelection: (selection: any) => {}, isPending: false, setPending: (pending: boolean) => {}, tabsState: {}, setTabsState: (state: FlowsState) => {}, - saveCurrentFlow: () => {}, getNodeId: (nodeType: string) => "", setTweak: (tweak: any) => {}, getTweak: [], @@ -135,7 +136,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const [edges, setEdgesInternal, onEdgesChangeInternal] = useEdgesState([]); - const setPending = useCallback( + const setPending = (pending: boolean) => { //@ts-ignore setTabsState((prev: FlowsState) => { @@ -147,17 +148,13 @@ export function FlowsProvider({ children }: { children: ReactNode }) { }, }; }); - }, - [setTabsState] - ); + } const isPending = tabsState[tabId]?.isPending ?? false; const onNodesChange = useCallback( - (nodes: NodeChange[]) => { - onNodesChangeInternal(nodes); - console.log("nodesChangou") - + (change: NodeChange[]) => { + onNodesChangeInternal(change); setPending(true); }, [onNodesChangeInternal, setTabsState, tabId] @@ -166,7 +163,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const onEdgesChange = useCallback( (edges: EdgeChange[]) => { onEdgesChangeInternal(edges); - console.log("edgesChangou") setPending(true); }, [onEdgesChangeInternal, setTabsState, tabId] @@ -174,14 +170,18 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const setNodes = (change: Node[] | ((oldState: Node[]) => Node[])) => { let newChange = typeof change === "function" ? change(nodes) : change; + let newEdges = cleanEdges(newChange, edges); - - setEdgesInternal(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; + let newChange = + typeof change === "function" + ? change(nodes.find((node) => node.id === id)!) + : change; setNodes((oldNodes) => oldNodes.map((node) => { @@ -197,8 +197,11 @@ export function FlowsProvider({ children }: { children: ReactNode }) { return nodes.find((node) => node.id === id); }; - const setEdges = (edges: Edge[] | ((oldState: Edge[]) => Edge[])) => { - setEdgesInternal(edges); + 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); }; useEffect(() => { @@ -724,14 +727,22 @@ export function FlowsProvider({ children }: { children: ReactNode }) { }); } - function saveCurrentFlow() { + function saveCurrentFlow(nodes: Node[], edges: Edge[], viewport: Viewport) { const currentFlow = flows.find((flow) => flow.id === tabId); - if (currentFlow && reactFlowInstance && currentFlow.data) { - updateFlow({ ...currentFlow, data: reactFlowInstance?.toObject()! }); + if (currentFlow) { + saveFlow({ ...currentFlow, data: { nodes, edges, viewport } }, true); } } - async function saveFlow(newFlow: FlowType, silent?: boolean) { + async function saveFlow(flow?: FlowType, silent?: boolean) { + let newFlow; + if (!flow) { + const currentFlow = flows.find((flow) => flow.id === tabId)!; + newFlow = { ...currentFlow, data: { nodes, edges, viewport: reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 } } } + } else { + newFlow = flow; + } + try { // updates flow in db const updatedFlow = await updateFlowInDatabase(newFlow); @@ -740,26 +751,9 @@ export function FlowsProvider({ children }: { children: ReactNode }) { if (!silent) { setSuccessData({ title: "Changes saved successfully" }); } - setFlows((prevState) => { - const newFlows = [...prevState]; - const index = newFlows.findIndex((flow) => flow.id === newFlow.id); - if (index !== -1) { - newFlows[index].description = newFlow.description ?? ""; - newFlows[index].data = newFlow.data; - newFlows[index].name = newFlow.name; - } - return newFlows; - }); + updateFlow(newFlow); //update tabs state - setTabsState((prev) => { - return { - ...prev, - [tabId]: { - ...prev[tabId], - isPending: false, - }, - }; - }); + setPending(false); } } catch (err) { setErrorData({ @@ -794,6 +788,31 @@ export function FlowsProvider({ children }: { children: ReactNode }) { }); }, []); + function deleteNode(idx: string | Array) { + + setEdges((oldEdges) => + oldEdges.filter((edge) => + typeof idx === "string" + ? edge.source !== idx && edge.target !== idx + : !idx.includes(edge.source) && !idx.includes(edge.target) + ) + ); + + 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 ( {}, - deleteNode: () => {}, types: {}, setTypes: () => {}, templates: {}, @@ -29,7 +28,6 @@ const initialValue: typesContextType = { fetchError: false, setFilterEdge: (filter) => {}, getFilterEdge: [], - deleteEdge: () => {}, }; export const typesContext = createContext(initialValue); @@ -97,37 +95,14 @@ export function TypesProvider({ children }: { children: ReactNode }) { } } - function deleteNode(idx: string | Array) { - if (reactFlowInstance === null) return; - const edges = reactFlowInstance! - .getEdges() - .filter((edge) => - typeof idx === "string" - ? edge.source == idx || edge.target == idx - : idx.includes(edge.source) || idx.includes(edge.target) - ); - reactFlowInstance!.deleteElements({ - nodes: - typeof idx === "string" ? [{ id: idx }] : idx.map((id) => ({ id })), - edges, - }); - } - function deleteEdge(idx: string | Array) { - reactFlowInstance!.deleteElements({ - edges: - typeof idx === "string" ? [{ id: idx }] : idx.map((id) => ({ id })), - }); - } return ( { data.node = myData.node; //@ts-ignore - setTabsState((prev: FlowsState) => { - return { - ...prev, - [tabId]: { - ...prev[tabId], - isPending: true, - }, - }; - }); + setPending(true); setOpen(false); }} type="submit" diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index d1a5a2c2d..4b5df623f 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -12,15 +12,11 @@ import ReactFlow, { Connection, Controls, Edge, - EdgeChange, - NodeChange, NodeDragHandler, OnSelectionChangeParams, SelectionDragHandler, addEdge, updateEdge, - useEdgesState, - useNodesState, useReactFlow, } from "reactflow"; import GenericNode from "../../../../CustomNodes/GenericNode"; @@ -58,17 +54,13 @@ export default function Page({ view?: boolean; }): JSX.Element { let { - updateFlow, uploadFlow, getNodeId, paste, lastCopiedSelection, setLastCopiedSelection, - tabsState, - saveFlow, - setTabsState, - tabId, - saveCurrentFlow, + deleteNode, + deleteEdge, } = useContext(FlowsContext); const { types, @@ -76,24 +68,16 @@ export default function Page({ setReactFlowInstance, templates, setFilterEdge, - deleteNode, - deleteEdge, } = useContext(typesContext); const reactFlowWrapper = useRef(null); const { takeSnapshot } = useContext(undoRedoContext); - const { nodes, edges, setNodes, setEdges, onNodesChange, onEdgesChange } = useContext(FlowsContext); + const { nodes, edges, setNodes, setEdges, onNodesChange, onEdgesChange, setPending, saveFlow } = useContext(FlowsContext); const position = useRef({ x: 0, y: 0 }); const [lastSelection, setLastSelection] = useState(null); - const saveCurrentFlowTimeout = () => { - setTimeout(() => { - saveCurrentFlow(); - }, 500); // need to do this because ReactFlow is not asynchronous. - }; - useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { if ( @@ -137,7 +121,6 @@ export default function Page({ takeSnapshot(); deleteNode(lastSelection.nodes.map((node) => node.id)); deleteEdge(lastSelection.edges.map((edge) => edge.id)); - saveCurrentFlowTimeout(); } } }; @@ -157,7 +140,6 @@ export default function Page({ lastCopiedSelection, lastSelection, takeSnapshot, - saveCurrentFlowTimeout, ]); const [selectionMenuVisible, setSelectionMenuVisible] = useState(false); @@ -165,7 +147,6 @@ export default function Page({ const { setExtraComponent, setExtraNavigation } = useContext(locationContext); const { setErrorData } = useContext(alertContext); - const { setViewport } = useReactFlow(); const edgeUpdateSuccessful = useRef(true); const [loading, setLoading] = useState(true); @@ -174,9 +155,11 @@ export default function Page({ useEffect(() => { setLoading(true); - setNodes(flow?.data?.nodes ?? []); - setEdges(flow?.data?.edges ?? []); - setViewport(flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 }); + if(reactFlowInstance){ + reactFlowInstance.setNodes(flow?.data?.nodes ?? []); + reactFlowInstance.setEdges(flow?.data?.edges ?? []); + reactFlowInstance.setViewport(flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 }); + } // Clear the previous timeout if (timeoutRef.current) { @@ -194,16 +177,6 @@ export default function Page({ }; }, [flow, reactFlowInstance]); - useEffect(() => { - const interval = setInterval(() => { - saveFlow(flow, true); - }, 30000); - - return () => { - clearInterval(interval); - }; - }, [flow, flow.data]); - const onConnect = useCallback( (params: Connection) => { takeSnapshot(); @@ -228,17 +201,6 @@ export default function Page({ eds ) ); - //@ts-ignore - setTabsState((prev: FlowsState) => { - return { - ...prev, - [tabId]: { - ...prev[tabId], - isPending: true, - }, - }; - }); - saveCurrentFlowTimeout(); }, [setEdges, takeSnapshot, addEdge] ); @@ -249,6 +211,12 @@ export default function Page({ // 👉 you can place your event handlers here }, [takeSnapshot]); + const onNodeDragStop: NodeDragHandler = useCallback(() => { + // 👇 make dragging a node undoable + saveFlow(); + // 👉 you can place your event handlers here + }, [takeSnapshot]); + const onSelectionDragStart: SelectionDragHandler = useCallback(() => { // 👇 make dragging a selection undoable takeSnapshot(); @@ -342,18 +310,12 @@ export default function Page({ } }, // Specify dependencies for useCallback - [getNodeId, reactFlowInstance, setNodes, takeSnapshot] + [getNodeId, setNodes, takeSnapshot] ); useEffect(() => { setExtraComponent(); setExtraNavigation({ title: "Components" }); - - return () => { - if (tabsState && tabsState[flow.id]?.isPending) { - saveFlow(flow); - } - }; }, []); const onEdgeUpdateStart = useCallback(() => { @@ -367,7 +329,7 @@ export default function Page({ setEdges((els) => updateEdge(oldEdge, newConnection, els)); } }, - [reactFlowInstance, setEdges] + [setEdges] ); const onEdgeUpdateEnd = useCallback((_, edge: Edge): void => { @@ -408,18 +370,9 @@ export default function Page({ }, []); const onMove = useCallback(() => { - saveCurrentFlowTimeout(); //@ts-ignore - setTabsState((prev: FlowsState) => { - return { - ...prev, - [tabId]: { - ...prev[tabId], - isPending: true, - }, - }; - }); - }, [setTabsState, saveCurrentFlowTimeout]); + setPending(true); + }, [setPending]); return (
@@ -454,6 +407,7 @@ export default function Page({ onEdgeUpdateStart={onEdgeUpdateStart} onEdgeUpdateEnd={onEdgeUpdateEnd} onNodeDragStart={onNodeDragStart} + onNodeDragStop={onNodeDragStop} onSelectionDragStart={onSelectionDragStart} onSelectionEnd={onSelectionEnd} onSelectionStart={onSelectionStart} @@ -488,7 +442,8 @@ export default function Page({ ) { const { newFlow } = generateFlow( lastSelection!, - reactFlowInstance!, + nodes, + edges, getRandomName() ); const newGroupNode = generateNodeFromFlow( diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 84e34a07b..909a0dcd7 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -28,13 +28,12 @@ import SidebarDraggableComponent from "./sideBarDraggableComponent"; export default function ExtraSidebar(): JSX.Element { const { data, templates, getFilterEdge, setFilterEdge } = useContext(typesContext); - const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, version } = + const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, isPending } = useContext(FlowsContext); const { hasApiKey, validApiKey, hasStore } = useContext(StoreContext); const { setErrorData } = useContext(alertContext); const [dataFilter, setFilterData] = useState(data); const [search, setSearch] = useState(""); - const isPending = tabsState[tabId]?.isPending; function onDragStart( event: React.DragEvent, data: { type: string; node?: APIClassType } @@ -297,7 +296,7 @@ export default function ExtraSidebar(): JSX.Element { : "button-disable") } onClick={(event) => { - saveFlow(flow!); + saveFlow(); }} > = (changes: ChangesType[]) => void; export type FlowsContextType = { - saveFlow: (flow: FlowType, silent?: boolean) => Promise; + saveFlow: (flow?: FlowType, silent?: boolean) => Promise; tabId: string; isLoading: boolean; setTabId: (index: string) => void; flows: Array; + deleteNode: (idx: string | Array) => void; + deleteEdge: (idx: string | Array) => void; removeFlow: (id: string) => void; addFlow: ( newProject: boolean, @@ -18,7 +20,6 @@ export type FlowsContextType = { override?: boolean, position?: XYPosition ) => Promise; - updateFlow: (newFlow: FlowType) => void; incrementNodeId: () => string; downloadFlow: ( flow: FlowType, @@ -29,7 +30,6 @@ export type FlowsContextType = { uploadFlows: () => void; isBuilt: boolean; setIsBuilt: (state: boolean) => void; - saveCurrentFlow: () => void; uploadFlow: ({ newProject, file, diff --git a/src/frontend/src/types/typesContext/index.ts b/src/frontend/src/types/typesContext/index.ts index 6b0fbbe2c..cfe206694 100644 --- a/src/frontend/src/types/typesContext/index.ts +++ b/src/frontend/src/types/typesContext/index.ts @@ -9,7 +9,6 @@ const data: { [char: string]: string } = {}; export type typesContextType = { reactFlowInstance: ReactFlowInstance | null; setReactFlowInstance: (newState: ReactFlowInstance) => void; - deleteNode: (idx: string | Array) => void; types: typeof types; setTypes: (newState: {}) => void; templates: typeof template; @@ -20,7 +19,6 @@ export type typesContextType = { setFetchError: (newState: boolean) => void; setFilterEdge: (newState) => void; getFilterEdge: any[]; - deleteEdge: (idx: string | Array) => void; }; export type alertContextType = { diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 4f6fb7bc6..9a05f8d2e 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -4,7 +4,6 @@ import { Edge, Node, OnSelectionChangeParams, - ReactFlowInstance, ReactFlowJsonObject, XYPosition, } from "reactflow"; @@ -502,10 +501,11 @@ export function getMiddlePoint(nodes: Node[]) { export function generateFlow( selection: OnSelectionChangeParams, - reactFlowInstance: ReactFlowInstance, + nodes: Node[], + edges: Edge[], name: string ): generateFlowType { - const newFlowData = reactFlowInstance.toObject(); + 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 From 3241c795adf18933b260826614a52b508f172375 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sat, 30 Dec 2023 21:02:13 -0300 Subject: [PATCH 05/89] Fixed types of tabsState --- .../chatComponent/buildTrigger/index.tsx | 1 - src/frontend/src/contexts/flowsContext.tsx | 58 ++++++++++--------- .../src/modals/EditNodeModal/index.tsx | 3 +- src/frontend/src/modals/formModal/index.tsx | 3 - .../src/modals/genericModal/index.tsx | 5 +- .../components/PageComponent/index.tsx | 1 - src/frontend/src/types/tabs/index.ts | 2 +- 7 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index 4a64e04db..eb8f281a6 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -98,7 +98,6 @@ export default function BuildTrigger({ // If the event is a log, log it setSuccessData({ title: parsedData.log }); } else if (parsedData.input_keys !== undefined) { - //@ts-ignore setTabsState((old: FlowsState) => { return { ...old, diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx index 3902b382b..ad0e946a1 100644 --- a/src/frontend/src/contexts/flowsContext.tsx +++ b/src/frontend/src/contexts/flowsContext.tsx @@ -91,7 +91,7 @@ const FlowsContextInitialValue: FlowsContextType = { isPending: false, setPending: (pending: boolean) => {}, tabsState: {}, - setTabsState: (state: FlowsState) => {}, + setTabsState: () => {}, getNodeId: (nodeType: string) => "", setTweak: (tweak: any) => {}, getTweak: [], @@ -136,19 +136,17 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const [edges, setEdgesInternal, onEdgesChangeInternal] = useEdgesState([]); - const setPending = - (pending: boolean) => { - //@ts-ignore - setTabsState((prev: FlowsState) => { - return { - ...prev, - [tabId]: { - ...prev[tabId], - isPending: pending, - }, - }; - }); - } + const setPending = (pending: boolean) => { + setTabsState((prev: FlowsState) => { + return { + ...prev, + [tabId]: { + ...prev[tabId], + isPending: pending, + }, + }; + }); + }; const isPending = tabsState[tabId]?.isPending ?? false; @@ -172,7 +170,11 @@ export function FlowsProvider({ children }: { children: ReactNode }) { let newChange = typeof change === "function" ? change(nodes) : change; let newEdges = cleanEdges(newChange, edges); - saveCurrentFlow(newChange, newEdges, reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 }); + saveCurrentFlow( + newChange, + newEdges, + reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 } + ); setEdgesInternal(newEdges); setNodesInternal(newChange); }; @@ -200,7 +202,11 @@ export function FlowsProvider({ children }: { children: ReactNode }) { 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 }); + saveCurrentFlow( + nodes, + newChange, + reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 } + ); setEdgesInternal(newChange); }; @@ -738,7 +744,14 @@ export function FlowsProvider({ children }: { children: ReactNode }) { let newFlow; if (!flow) { const currentFlow = flows.find((flow) => flow.id === tabId)!; - newFlow = { ...currentFlow, data: { nodes, edges, viewport: reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 } } } + newFlow = { + ...currentFlow, + data: { + nodes, + edges, + viewport: reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 }, + }, + }; } else { newFlow = flow; } @@ -789,22 +802,13 @@ export function FlowsProvider({ children }: { children: ReactNode }) { }, []); function deleteNode(idx: string | Array) { - - setEdges((oldEdges) => - oldEdges.filter((edge) => - typeof idx === "string" - ? edge.source !== idx && edge.target !== idx - : !idx.includes(edge.source) && !idx.includes(edge.target) - ) - ); - 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) => diff --git a/src/frontend/src/modals/EditNodeModal/index.tsx b/src/frontend/src/modals/EditNodeModal/index.tsx index fe9df7d46..fea51b765 100644 --- a/src/frontend/src/modals/EditNodeModal/index.tsx +++ b/src/frontend/src/modals/EditNodeModal/index.tsx @@ -62,7 +62,7 @@ const EditNodeModal = forwardRef( const [myData, setMyData] = useState(data); - const { setTabsState, tabId, edges } = useContext(FlowsContext); + const { setPending, tabId, edges } = useContext(FlowsContext); const { setModalContextOpen } = useContext(alertContext); function changeAdvanced(n) { @@ -535,7 +535,6 @@ const EditNodeModal = forwardRef( className="mt-3" onClick={() => { data.node = myData.node; - //@ts-ignore setPending(true); setOpen(false); }} diff --git a/src/frontend/src/modals/formModal/index.tsx b/src/frontend/src/modals/formModal/index.tsx index 891a1dfa6..99ea3a465 100644 --- a/src/frontend/src/modals/formModal/index.tsx +++ b/src/frontend/src/modals/formModal/index.tsx @@ -401,7 +401,6 @@ export default function FormModal({ description: flow.description, chatKey: chatKey!, }); - //@ts-ignore setTabsState((old: FlowsState) => { if (!chatKey) return old; let newTabsState = _.cloneDeep(old); @@ -522,7 +521,6 @@ export default function FormModal({ ] } onChange={(e) => { - //@ts-ignore setTabsState((old: FlowsState) => { let newTabsState = _.cloneDeep(old); newTabsState[ @@ -634,7 +632,6 @@ export default function FormModal({ sendMessage={sendMessage} setChatValue={(value) => { setChatValue(value); - //@ts-ignore setTabsState((old: FlowsState) => { let newTabsState = _.cloneDeep(old); newTabsState[id.current].formKeysData.input_keys![ diff --git a/src/frontend/src/modals/genericModal/index.tsx b/src/frontend/src/modals/genericModal/index.tsx index 064ae87b4..dbc955802 100644 --- a/src/frontend/src/modals/genericModal/index.tsx +++ b/src/frontend/src/modals/genericModal/index.tsx @@ -42,7 +42,7 @@ export default function GenericModal({ const [wordsHighlight, setWordsHighlight] = useState([]); const { setErrorData, setSuccessData, setNoticeData, setModalContextOpen } = useContext(alertContext); - const ref = useRef(); + const textRef = useRef(null); const divRef = useRef(null); const divRefPrompt = useRef(null); @@ -228,8 +228,7 @@ export default function GenericModal({ /> ) : type !== TypeModal.PROMPT ? (
- } - key={index} - keyValue={key} - > -
- {tabsState[id.current].formKeysData.handle_keys!.some( - (t) => t === key - ) && ( -
- Source: Component -
- )} - -
- - - )) + + + ) + ) : null} - {tabsState[id.current].formKeysData.memory_keys!.map( - (key, index) => ( -
- - - {key} - -
- {}} - size="small" - disabled={true} - /> -
-
- } - key={index} - keyValue={key} - > -
-
- Source: Memory + {currentFlowState?.formKeysData.memory_keys!.map((key, index) => ( +
+ + + {key} + +
+ {}} + size="small" + disabled={true} + />
- -
- ) - )} + } + key={index} + keyValue={key} + > +
+
+ Source: Memory +
+
+ +
+ ))}
@@ -631,13 +628,17 @@ export default function FormModal({ sendMessage={sendMessage} setChatValue={(value) => { setChatValue(value); - setTabsState((old: FlowsState) => { - let newTabsState = _.cloneDeep(old); - newTabsState[id.current].formKeysData.input_keys![ - chatKey! - ] = value; - return newTabsState; - }); + if (currentFlowState && chatKey) { + setCurrentFlowState( + (old: FlowState | undefined) => { + let newFlowState = cloneDeep(old!); + newFlowState.formKeysData.input_keys![ + chatKey + ] = value; + return newFlowState; + } + ); + } }} inputRef={ref} /> diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index e61d85548..bc0d20d3c 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -1,20 +1,29 @@ import { create } from "zustand"; -import { FlowsManagerStoreType } from "../types/zustand/flowsManager"; import { FlowState } from "../types/tabs"; +import { FlowsManagerStoreType } from "../types/zustand/flowsManager"; const useFlowsManagerStore = create((set, get) => ({ currentFlowId: "", setCurrentFlowId: (currentFlowId: string) => { - set({ currentFlowId, currentFlow: get().flows.find((flow) => flow.id === currentFlowId) }); -}, + set((state) => ({ + currentFlowId, + currentFlowState: state.flowsState[state.currentFlowId], + currentFlow: state.flows.find((flow) => flow.id === currentFlowId), + })); + }, flows: [], currentFlow: undefined, isLoading: true, setIsLoading: (isLoading: boolean) => set({ isLoading }), flowsState: {}, currentFlowState: undefined, - setCurrentFlowState: (flowState: FlowState | ((oldState: FlowState | undefined) => FlowState)) => { - const newFlowState = typeof flowState === "function" ? flowState(get().currentFlowState) : flowState; + setCurrentFlowState: ( + flowState: FlowState | ((oldState: FlowState | undefined) => FlowState) + ) => { + const newFlowState = + typeof flowState === "function" + ? flowState(get().currentFlowState) + : flowState; set((state) => ({ flowsState: { ...state.flowsState, @@ -23,7 +32,6 @@ const useFlowsManagerStore = create((set, get) => ({ currentFlowState: newFlowState, })); }, - })); export default useFlowsManagerStore; diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 4196cecdc..f5815c62c 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -59,7 +59,6 @@ export type FlowsState = { }; export type FlowState = { - isPending: boolean; formKeysData: { template?: string; input_keys?: Object; diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index 478028bca..006d29435 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -13,7 +13,7 @@ import { tweakType, } from "../types/components"; import { FlowType, NodeType } from "../types/flow"; -import { FlowsState } from "../types/tabs"; +import { FlowState, FlowsState } from "../types/tabs"; import { buildTweaks } from "./reactflowUtils"; export function classNames(...classes: Array): string { @@ -217,13 +217,12 @@ export function groupByFamily( })); } -export function buildInputs(tabsState: FlowsState, id: string): string { - return tabsState && - tabsState[id] && - tabsState[id].formKeysData && - tabsState[id].formKeysData.input_keys && - Object.keys(tabsState[id].formKeysData.input_keys!).length > 0 - ? JSON.stringify(tabsState[id].formKeysData.input_keys) +export function buildInputs(currentFlowState?: FlowState): string { + return currentFlowState && + currentFlowState.formKeysData && + currentFlowState.formKeysData.input_keys && + Object.keys(currentFlowState.formKeysData.input_keys!).length > 0 + ? JSON.stringify(currentFlowState.formKeysData.input_keys) : '{"input": "message"}'; } @@ -298,17 +297,16 @@ export function buildTweakObject(tweak: tweakType) { * @param {FlowsState} tabsState - The current tabs state. * @returns {string} - The chat input field */ -export function getChatInputField(flow: FlowType, tabsState?: FlowsState) { +export function getChatInputField(flow: FlowType, currentFlowState?: FlowState) { let chat_input_field = "text"; if ( - tabsState && - tabsState[flow.id] && - tabsState[flow.id].formKeysData && - tabsState[flow.id].formKeysData.input_keys + currentFlowState && + currentFlowState.formKeysData && + currentFlowState.formKeysData.input_keys ) { chat_input_field = Object.keys( - tabsState[flow.id].formKeysData.input_keys! + currentFlowState.formKeysData.input_keys! )[0]; } return chat_input_field; @@ -323,7 +321,7 @@ export function getPythonApiCode( flow: FlowType, isAuth: boolean, tweak?: any[], - tabsState?: FlowsState + currentFlowState?: FlowState ): string { const flowId = flow.id; @@ -332,7 +330,7 @@ export function getPythonApiCode( // node.data.id // } const tweaks = buildTweaks(flow); - const inputs = buildInputs(tabsState!, flow.id); + const inputs = buildInputs(currentFlowState); return `import requests from typing import Optional @@ -387,11 +385,11 @@ export function getCurlCode( flow: FlowType, isAuth: boolean, tweak?: any[], - tabsState?: FlowsState + currentFlowState?: FlowState ): string { const flowId = flow.id; const tweaks = buildTweaks(flow); - const inputs = buildInputs(tabsState!, flow.id); + const inputs = buildInputs(currentFlowState); return `curl -X POST \\ ${window.location.protocol}//${ @@ -415,11 +413,11 @@ export function getCurlCode( export function getPythonCode( flow: FlowType, tweak?: any[], - tabsState?: FlowsState + currentFlowState?: FlowState ): string { const flowName = flow.name; const tweaks = buildTweaks(flow); - const inputs = buildInputs(tabsState!, flow.id); + const inputs = buildInputs(currentFlowState); return `from langflow import load_flow_from_json TWEAKS = ${ tweak && tweak.length > 0 @@ -440,12 +438,12 @@ flow(inputs)`; export function getWidgetCode( flow: FlowType, isAuth: boolean, - tabsState?: FlowsState + currentFlowState?: FlowState ): string { const flowId = flow.id; const flowName = flow.name; - const inputs = buildInputs(tabsState!, flow.id); - let chat_input_field = getChatInputField(flow, tabsState); + const inputs = buildInputs(currentFlowState); + let chat_input_field = getChatInputField(flow, currentFlowState); return ` @@ -456,7 +454,7 @@ chat_input_field: Input key that you want the chat to send the user message with window_title="${flowName}" flow_id="${flowId}" ${ - tabsState![flow.id] && tabsState![flow.id].formKeysData + currentFlowState && currentFlowState.formKeysData ? `chat_inputs='${inputs}' chat_input_field="${chat_input_field}" ` From e3c3b18bdc22a121af1482597aae6cb742f0bc59 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Fri, 5 Jan 2024 23:48:55 -0300 Subject: [PATCH 51/89] Added refresh flows function --- src/frontend/src/App.tsx | 3 +- src/frontend/src/stores/flowsManagerStore.ts | 54 +++++++++++++++++++ .../src/types/zustand/flowsManager/index.ts | 1 + src/frontend/src/utils/reactflowUtils.ts | 13 +++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index bfd704d78..ab4ea80ca 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -23,6 +23,7 @@ import Router from "./routes"; import useAlertStore from "./stores/alertStore"; import { useTypesStore } from "./stores/typesStore"; import { useDarkStore } from "./stores/darkStore"; +import useFlowsManagerStore from "./stores/flowsManagerStore"; export default function App() { let { setCurrent, setShowSideBar, setIsStackedOpen } = @@ -131,7 +132,7 @@ export default function App() { }; const { isAuthenticated } = useContext(AuthContext); - const { refreshFlows } = useContext(FlowsContext); + const refreshFlows = useFlowsManagerStore((state) => state.refreshFlows); const setVersion = useDarkStore((state) => state.setVersion); const getTypes = useTypesStore((state) => state.getTypes); diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index bc0d20d3c..4f7cf8424 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -1,6 +1,40 @@ +import { cloneDeep } from "lodash"; +import ShortUniqueId from "short-unique-id"; import { create } from "zustand"; +import { readFlowsFromDatabase } from "../controllers/API"; +import { APIClassType } from "../types/api"; +import { FlowType, NodeDataType } from "../types/flow"; import { FlowState } from "../types/tabs"; import { FlowsManagerStoreType } from "../types/zustand/flowsManager"; +import { processDataFromFlow } from "../utils/reactflowUtils"; +import { createRandomKey } from "../utils/utils"; +import { useTypesStore } from "./typesStore"; +import useAlertStore from "./alertStore"; + +const uid = new ShortUniqueId({ length: 5 }); + +const processFlows = (DbData: FlowType[], skipUpdate = true) => { + let savedComponents: { [key: string]: APIClassType } = {}; + DbData.forEach((flow: FlowType) => { + try { + if (!flow.data) { + return; + } + if (flow.data && flow.is_component) { + (flow.data.nodes[0].data as NodeDataType).node!.display_name = + flow.name; + savedComponents[ + createRandomKey((flow.data.nodes[0].data as NodeDataType).type, uid()) + ] = cloneDeep((flow.data.nodes[0].data as NodeDataType).node!); + return; + } + if (!skipUpdate) processDataFromFlow(flow, false); + } catch (e) { + console.log(e); + } + }); + return { data: savedComponents, flows: DbData }; +}; const useFlowsManagerStore = create((set, get) => ({ currentFlowId: "", @@ -32,6 +66,26 @@ const useFlowsManagerStore = create((set, get) => ({ currentFlowState: newFlowState, })); }, + refreshFlows: () => { + return new Promise((resolve, reject) => { + set({ isLoading: true }); + readFlowsFromDatabase().then((dbData) => { + if (dbData) { + const { data, flows } = processFlows(dbData, false); + set({ flows, isLoading: false }); + useTypesStore.setState((state) => ({ + data: { ...state.data, ["saved_components"]: data }, + })); + resolve(); + } + }).catch((e) => { + useAlertStore.getState().setErrorData({ + title: "Could not load flows from database", + }); + reject(e); + }); + }); + }, })); export default useFlowsManagerStore; diff --git a/src/frontend/src/types/zustand/flowsManager/index.ts b/src/frontend/src/types/zustand/flowsManager/index.ts index 3fb239091..5a4532403 100644 --- a/src/frontend/src/types/zustand/flowsManager/index.ts +++ b/src/frontend/src/types/zustand/flowsManager/index.ts @@ -11,4 +11,5 @@ export type FlowsManagerStoreType = { flowsState: FlowsState; currentFlowState: FlowState | undefined; setCurrentFlowState: (state: FlowState | ((oldState: FlowState | undefined) => FlowState)) => void; + refreshFlows: () => Promise; }; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index d9dcfe2ec..6686614e2 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -153,6 +153,19 @@ export function updateTemplate( return clonedObject; } +export const processDataFromFlow = (flow: FlowType, refreshIds = true) => { + let data = flow?.data ? flow.data : null; + if (data) { + processFlowEdges(flow); + //prevent node update for now + // processFlowNodes(flow); + //add animation to text type edges + updateEdges(data.edges); + // updateNodes(data.nodes, data.edges); + if (refreshIds) updateIds(data); // Assuming updateIds is defined elsewhere + } +}; + export function updateIds(newFlow: ReactFlowJsonObject) { let idsMap = {}; From d44648d6c1404e57fbd4b15a6c0d41db8fe32704 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sat, 6 Jan 2024 00:27:36 -0300 Subject: [PATCH 52/89] added saveFlow function to Zustand state and implemented it on the project --- src/frontend/src/App.tsx | 1 - .../components/parameterComponent/index.tsx | 1 - .../chatComponent/buildTrigger/index.tsx | 2 +- .../src/components/chatComponent/index.tsx | 1 - src/frontend/src/contexts/undoRedoContext.tsx | 1 - .../src/modals/flowSettingsModal/index.tsx | 2 +- src/frontend/src/modals/shareModal/index.tsx | 3 +- src/frontend/src/pages/AdminPage/index.tsx | 1 - .../components/PageComponent/index.tsx | 6 +- .../extraSidebarComponent/index.tsx | 6 +- .../components/nodeToolbarComponent/index.tsx | 4 +- .../MainPage/components/components/index.tsx | 4 +- src/frontend/src/stores/flowStore.ts | 17 +++ src/frontend/src/stores/flowsManagerStore.ts | 123 +++++++++++------- .../src/types/zustand/flowsManager/index.ts | 3 + src/frontend/src/utils/reactflowUtils.ts | 28 +++- 16 files changed, 141 insertions(+), 62 deletions(-) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index ab4ea80ca..f4acd7cd7 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 { AuthContext } from "./contexts/authContext"; -import { FlowsContext } from "./contexts/flowsContext"; import { locationContext } from "./contexts/locationContext"; import { getHealth, getVersion } from "./controllers/API"; import Router from "./routes"; diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 83425e4ec..959865766 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -26,7 +26,6 @@ import { LANGFLOW_SUPPORTED_TYPES, TOOLTIP_EMPTY, } from "../../../../constants/constants"; -import { FlowsContext } from "../../../../contexts/flowsContext"; import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import { postCustomComponentUpdate } from "../../../../controllers/API"; import useAlertStore from "../../../../stores/alertStore"; diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index 6cee73615..534036221 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -26,7 +26,6 @@ export default function BuildTrigger({ isBuilt: boolean; }): JSX.Element { const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE(); - const { saveFlow } = useContext(FlowsContext); const nodes = useFlowStore((state) => state.nodes); const edges = useFlowStore((state) => state.edges); const setErrorData = useAlertStore((state) => state.setErrorData); @@ -34,6 +33,7 @@ export default function BuildTrigger({ const setCurrentFlowState = useFlowsManagerStore( (state) => state.setCurrentFlowState ); + const saveFlow = useFlowsManagerStore((state) => state.saveFlow); 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 22362bca5..b975e2b9f 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -5,7 +5,6 @@ import BuildTrigger from "./buildTrigger"; import ChatTrigger from "./chatTrigger"; import * as _ from "lodash"; -import { FlowsContext } from "../../contexts/flowsContext"; import { getBuildStatus } from "../../controllers/API"; import FormModal from "../../modals/formModal"; import useFlowStore from "../../stores/flowStore"; diff --git a/src/frontend/src/contexts/undoRedoContext.tsx b/src/frontend/src/contexts/undoRedoContext.tsx index 679e6cf9d..095878b8c 100644 --- a/src/frontend/src/contexts/undoRedoContext.tsx +++ b/src/frontend/src/contexts/undoRedoContext.tsx @@ -13,7 +13,6 @@ import { undoRedoContextType, } from "../types/typesContext"; import { isWrappedWithClass } from "../utils/utils"; -import { FlowsContext } from "./flowsContext"; import useFlowsManagerStore from "../stores/flowsManagerStore"; const initialValue = { diff --git a/src/frontend/src/modals/flowSettingsModal/index.tsx b/src/frontend/src/modals/flowSettingsModal/index.tsx index 2598c7c16..c892c173f 100644 --- a/src/frontend/src/modals/flowSettingsModal/index.tsx +++ b/src/frontend/src/modals/flowSettingsModal/index.tsx @@ -13,7 +13,7 @@ export default function FlowSettingsModal({ open, setOpen, }: FlowSettingsPropsType): JSX.Element { - const { saveFlow } = useContext(FlowsContext); + const saveFlow = useFlowsManagerStore((state) => state.saveFlow); const currentFlow = useFlowsManagerStore((state) => state.currentFlow); const flows = useFlowsManagerStore((state) => state.flows); useEffect(() => { diff --git a/src/frontend/src/modals/shareModal/index.tsx b/src/frontend/src/modals/shareModal/index.tsx index 3992fb89a..52833d5ce 100644 --- a/src/frontend/src/modals/shareModal/index.tsx +++ b/src/frontend/src/modals/shareModal/index.tsx @@ -24,6 +24,7 @@ import { getTagsIds } from "../../utils/storeUtils"; import ConfirmationModal from "../ConfirmationModal"; import BaseModal from "../baseModal"; import { useDarkStore } from "../../stores/darkStore"; +import useFlowsManagerStore from "../../stores/flowsManagerStore"; export default function ShareModal({ component, @@ -57,7 +58,7 @@ export default function ShareModal({ const [unavaliableNames, setUnavaliableNames] = useState< { id: string; name: string }[] >([]); - const { saveFlow } = useContext(FlowsContext); + const saveFlow = useFlowsManagerStore((state) => state.saveFlow); const [loadingNames, setLoadingNames] = useState(false); diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index 7434c3369..71cca7bd7 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -21,7 +21,6 @@ import { ADMIN_HEADER_TITLE, } from "../../constants/constants"; import { AuthContext } from "../../contexts/authContext"; -import { FlowsContext } from "../../contexts/flowsContext"; import { addUser, deleteUser, diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index d5d9f0f26..38e8b3422 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -42,6 +42,7 @@ import ConnectionLineComponent from "../ConnectionLineComponent"; import SelectionMenu from "../SelectionMenuComponent"; import ExtraSidebar from "../extraSidebarComponent"; import { useTypesStore } from "../../../../stores/typesStore"; +import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; const nodeTypes = { genericNode: GenericNode, @@ -54,7 +55,8 @@ export default function Page({ flow: FlowType; view?: boolean; }): JSX.Element { - let { uploadFlow, saveFlow } = useContext(FlowsContext); + let { uploadFlow } = useContext(FlowsContext); + const autoSaveCurrentFlow = useFlowsManagerStore((state) => state.autoSaveCurrentFlow); const types = useTypesStore((state) => state.types); const templates = useTypesStore((state) => state.templates); const setFilterEdge = useTypesStore((state) => state.setFilterEdge); @@ -202,7 +204,7 @@ export default function Page({ const onNodeDragStop: NodeDragHandler = useCallback(() => { // 👇 make dragging a node undoable - saveFlow(undefined, true); + autoSaveCurrentFlow(nodes, edges, reactFlowInstance?.getViewport()!); // 👉 you can place your event handlers here }, [takeSnapshot]); diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 019956192..bc75c0e84 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -32,7 +32,9 @@ export default function ExtraSidebar(): JSX.Element { const templates = useTypesStore((state) => state.templates); const getFilterEdge = useTypesStore((state) => state.getFilterEdge); const setFilterEdge = useTypesStore((state) => state.setFilterEdge); - const { uploadFlow, saveFlow } = useContext(FlowsContext); + const { uploadFlow } = useContext(FlowsContext); + const saveFlow = useFlowsManagerStore((state) => state.saveFlow); + const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance); const currentFlow = useFlowsManagerStore((state) => state.currentFlow); const hasStore = useStoreStore((state) => state.hasStore); const hasApiKey = useStoreStore((state) => state.hasApiKey); @@ -302,7 +304,7 @@ export default function ExtraSidebar(): JSX.Element { (isPending ? "" : "button-disable") } onClick={(event) => { - saveFlow(); + saveFlow({...currentFlow, data: {...currentFlow.data!, viewport: reactFlowInstance?.getViewport()!} }, true); }} > state.setNodes); const setEdges = useFlowStore((state) => state.setEdges); - const { saveComponent, flows } = useContext(FlowsContext); + const { saveComponent } = useContext(FlowsContext); + const flows = useFlowsManagerStore((state) => state.flows); const version = useDarkStore((state) => state.version); const { takeSnapshot } = useContext(undoRedoContext); const [showModalAdvanced, setShowModalAdvanced] = useState(false); diff --git a/src/frontend/src/pages/MainPage/components/components/index.tsx b/src/frontend/src/pages/MainPage/components/components/index.tsx index 5f65609be..c44301714 100644 --- a/src/frontend/src/pages/MainPage/components/components/index.tsx +++ b/src/frontend/src/pages/MainPage/components/components/index.tsx @@ -9,14 +9,16 @@ import { Button } from "../../../../components/ui/button"; import { FlowsContext } from "../../../../contexts/flowsContext"; import useAlertStore from "../../../../stores/alertStore"; import { FlowType } from "../../../../types/flow"; +import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; export default function ComponentsComponent({ is_component = true, }: { is_component?: boolean; }) { - const { flows, removeFlow, uploadFlow, addFlow, isLoading } = + const { removeFlow, uploadFlow, addFlow, isLoading } = useContext(FlowsContext); + const flows = useFlowsManagerStore((state) => state.flows); const setSuccessData = useAlertStore((state) => state.setSuccessData); const setErrorData = useAlertStore((state) => state.setErrorData); const [pageSize, setPageSize] = useState(10); diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts index 16d7985e4..8de906c90 100644 --- a/src/frontend/src/stores/flowStore.ts +++ b/src/frontend/src/stores/flowStore.ts @@ -24,6 +24,7 @@ import { scapeJSONParse, scapedJSONStringfy, } from "../utils/reactflowUtils"; +import useFlowsManagerStore from "./flowsManagerStore"; // this is our useStore hook that we can use in our components to get parts of the store and call actions const useFlowStore = create((set, get) => ({ @@ -55,11 +56,27 @@ const useFlowStore = create((set, get) => ({ set({ edges: newEdges }); set({ nodes: newChange }); + + useFlowsManagerStore + .getState() + .autoSaveCurrentFlow( + newChange, + newEdges, + get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 } + ); }, setEdges: (change) => { let newChange = typeof change === "function" ? change(get().edges) : change; set({ edges: newChange }); + + useFlowsManagerStore + .getState() + .autoSaveCurrentFlow( + get().nodes, + newChange, + get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 } + ); }, setNode: (id: string, change: Node | ((oldState: Node) => Node)) => { let newChange = diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index 4f7cf8424..817c49a40 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -1,40 +1,19 @@ -import { cloneDeep } from "lodash"; -import ShortUniqueId from "short-unique-id"; +import { AxiosError } from "axios"; +import { Edge, Node, Viewport } from "reactflow"; import { create } from "zustand"; -import { readFlowsFromDatabase } from "../controllers/API"; -import { APIClassType } from "../types/api"; -import { FlowType, NodeDataType } from "../types/flow"; +import { + readFlowsFromDatabase, + updateFlowInDatabase, +} from "../controllers/API"; +import { FlowType } from "../types/flow"; import { FlowState } from "../types/tabs"; import { FlowsManagerStoreType } from "../types/zustand/flowsManager"; -import { processDataFromFlow } from "../utils/reactflowUtils"; -import { createRandomKey } from "../utils/utils"; -import { useTypesStore } from "./typesStore"; +import { processFlows } from "../utils/reactflowUtils"; import useAlertStore from "./alertStore"; +import useFlowStore from "./flowStore"; +import { useTypesStore } from "./typesStore"; -const uid = new ShortUniqueId({ length: 5 }); - -const processFlows = (DbData: FlowType[], skipUpdate = true) => { - let savedComponents: { [key: string]: APIClassType } = {}; - DbData.forEach((flow: FlowType) => { - try { - if (!flow.data) { - return; - } - if (flow.data && flow.is_component) { - (flow.data.nodes[0].data as NodeDataType).node!.display_name = - flow.name; - savedComponents[ - createRandomKey((flow.data.nodes[0].data as NodeDataType).type, uid()) - ] = cloneDeep((flow.data.nodes[0].data as NodeDataType).node!); - return; - } - if (!skipUpdate) processDataFromFlow(flow, false); - } catch (e) { - console.log(e); - } - }); - return { data: savedComponents, flows: DbData }; -}; +let saveTimeoutId: NodeJS.Timeout | null = null; const useFlowsManagerStore = create((set, get) => ({ currentFlowId: "", @@ -69,21 +48,73 @@ const useFlowsManagerStore = create((set, get) => ({ refreshFlows: () => { return new Promise((resolve, reject) => { set({ isLoading: true }); - readFlowsFromDatabase().then((dbData) => { - if (dbData) { - const { data, flows } = processFlows(dbData, false); - set({ flows, isLoading: false }); - useTypesStore.setState((state) => ({ - data: { ...state.data, ["saved_components"]: data }, - })); - resolve(); - } - }).catch((e) => { - useAlertStore.getState().setErrorData({ - title: "Could not load flows from database", + readFlowsFromDatabase() + .then((dbData) => { + if (dbData) { + const { data, flows } = processFlows(dbData, false); + set({ flows, isLoading: false }); + useTypesStore.setState((state) => ({ + data: { ...state.data, ["saved_components"]: data }, + })); + resolve(); + } + }) + .catch((e) => { + useAlertStore.getState().setErrorData({ + title: "Could not load flows from database", + }); + reject(e); + }); + }); + }, + autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => { + // Clear the previous timeout if it exists. + if (saveTimeoutId) { + clearTimeout(saveTimeoutId); + } + + // Set up a new timeout. + saveTimeoutId = setTimeout(() => { + if (get().currentFlow) { + get().saveFlow( + { ...get().currentFlow!, data: { nodes, edges, viewport } }, + true + ); + } + }, 300); // Delay of 300ms. + }, + saveFlow: (flow: FlowType, silent?: boolean) => { + return new Promise((resolve, reject) => { + updateFlowInDatabase(flow) + .then((updatedFlow) => { + if (updatedFlow) { + // updates flow in state + if (!silent) { + useAlertStore + .getState() + .setSuccessData({ title: "Changes saved successfully" }); + } + set((oldState) => ({ + flows: oldState.flows.map((flow) => { + if (flow.id === updatedFlow.id) { + return updatedFlow; + } + return flow; + }), + })); + //update tabs state + + useFlowStore.setState({ isPending: false }); + resolve(); + } + }) + .catch((err) => { + useAlertStore.getState().setErrorData({ + title: "Error while saving changes", + list: [(err as AxiosError).message], + }); + reject(err); }); - reject(e); - }); }); }, })); diff --git a/src/frontend/src/types/zustand/flowsManager/index.ts b/src/frontend/src/types/zustand/flowsManager/index.ts index 5a4532403..dad1356ab 100644 --- a/src/frontend/src/types/zustand/flowsManager/index.ts +++ b/src/frontend/src/types/zustand/flowsManager/index.ts @@ -1,3 +1,4 @@ +import { Node, Edge, Viewport } from "reactflow"; import { FlowType } from "../../flow"; import { FlowState, FlowsState } from "../../tabs"; @@ -12,4 +13,6 @@ export type FlowsManagerStoreType = { currentFlowState: FlowState | undefined; setCurrentFlowState: (state: FlowState | ((oldState: FlowState | undefined) => FlowState)) => void; refreshFlows: () => Promise; + saveFlow: (flow: FlowType, silent?: boolean) => Promise; + autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => void; }; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 6686614e2..039be107b 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -1,4 +1,4 @@ -import _ from "lodash"; +import _, { cloneDeep } from "lodash"; import { Connection, Edge, @@ -13,6 +13,7 @@ import { specialCharsRegex, } from "../constants/constants"; import { + APIClassType, APIKindType, APIObjectType, APITemplateType, @@ -31,7 +32,7 @@ import { unselectAllNodesType, updateEdgesHandleIdsType, } from "../types/utils/reactflowUtils"; -import { getFieldTitle, toTitleCase } from "./utils"; +import { createRandomKey, getFieldTitle, toTitleCase } from "./utils"; const uid = new ShortUniqueId({ length: 5 }); export function cleanEdges(nodes: Node[], edges: Edge[]) { @@ -153,6 +154,29 @@ export function updateTemplate( return clonedObject; } +export const processFlows = (DbData: FlowType[], skipUpdate = true) => { + let savedComponents: { [key: string]: APIClassType } = {}; + DbData.forEach((flow: FlowType) => { + try { + if (!flow.data) { + return; + } + if (flow.data && flow.is_component) { + (flow.data.nodes[0].data as NodeDataType).node!.display_name = + flow.name; + savedComponents[ + createRandomKey((flow.data.nodes[0].data as NodeDataType).type, uid()) + ] = cloneDeep((flow.data.nodes[0].data as NodeDataType).node!); + return; + } + if (!skipUpdate) processDataFromFlow(flow, false); + } catch (e) { + console.log(e); + } + }); + return { data: savedComponents, flows: DbData }; +}; + export const processDataFromFlow = (flow: FlowType, refreshIds = true) => { let data = flow?.data ? flow.data : null; if (data) { From 616a4f00314154af8745b3d00a5a3c4a9b58502c Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sat, 6 Jan 2024 01:00:59 -0300 Subject: [PATCH 53/89] added upload and download flows on zustand and utils --- .../chatComponent/buildTrigger/index.tsx | 1 - .../src/components/headerComponent/index.tsx | 2 - .../components/inputFileComponent/index.tsx | 1 - src/frontend/src/modals/exportModal/index.tsx | 4 +- .../src/modals/flowSettingsModal/index.tsx | 1 - src/frontend/src/modals/shareModal/index.tsx | 1 - .../components/PageComponent/index.tsx | 20 ++++---- .../extraSidebarComponent/index.tsx | 4 +- .../MainPage/components/components/index.tsx | 5 +- src/frontend/src/pages/MainPage/index.tsx | 6 ++- .../src/pages/ProfileSettingsPage/index.tsx | 1 - src/frontend/src/pages/StorePage/index.tsx | 1 - src/frontend/src/stores/flowsManagerStore.ts | 47 +++++++++++++++++-- .../src/types/zustand/flowsManager/index.ts | 2 + src/frontend/src/utils/reactflowUtils.ts | 46 ++++++++++++++++++ 15 files changed, 114 insertions(+), 28 deletions(-) diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index 534036221..1dcc327ee 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -5,7 +5,6 @@ import { useSSE } from "../../../contexts/SSEContext"; import { postBuildInit } from "../../../controllers/API"; import { FlowType } from "../../../types/flow"; -import { FlowsContext } from "../../../contexts/flowsContext"; import useAlertStore from "../../../stores/alertStore"; import useFlowStore from "../../../stores/flowStore"; import useFlowsManagerStore from "../../../stores/flowsManagerStore"; diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index a69ca1946..6654760b9 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -5,7 +5,6 @@ import AlertDropdown from "../../alerts/alertDropDown"; import { USER_PROJECTS_HEADER } from "../../constants/constants"; import { AuthContext } from "../../contexts/authContext"; -import { FlowsContext } from "../../contexts/flowsContext"; import useAlertStore from "../../stores/alertStore"; import { useDarkStore } from "../../stores/darkStore"; import { useStoreStore } from "../../stores/storeStore"; @@ -22,7 +21,6 @@ import { } from "../ui/dropdown-menu"; import { Separator } from "../ui/separator"; import MenuBar from "./components/menuBar"; -import useFlowsManagerStore from "../../stores/flowsManagerStore"; export default function Header(): JSX.Element { const notificationCenter = useAlertStore((state) => state.notificationCenter); diff --git a/src/frontend/src/components/inputFileComponent/index.tsx b/src/frontend/src/components/inputFileComponent/index.tsx index f9d8a79ef..a365b188c 100644 --- a/src/frontend/src/components/inputFileComponent/index.tsx +++ b/src/frontend/src/components/inputFileComponent/index.tsx @@ -1,5 +1,4 @@ import { useContext, useEffect, useState } from "react"; -import { FlowsContext } from "../../contexts/flowsContext"; import { uploadFile } from "../../controllers/API"; import useAlertStore from "../../stores/alertStore"; import { FileComponentType } from "../../types/components"; diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx index fa7a688dd..358f5225c 100644 --- a/src/frontend/src/modals/exportModal/index.tsx +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -4,16 +4,14 @@ import IconComponent from "../../components/genericIconComponent"; import { Button } from "../../components/ui/button"; import { Checkbox } from "../../components/ui/checkbox"; import { EXPORT_DIALOG_SUBTITLE } from "../../constants/constants"; -import { FlowsContext } from "../../contexts/flowsContext"; import useAlertStore from "../../stores/alertStore"; -import { removeApiKeys } from "../../utils/reactflowUtils"; +import { downloadFlow, removeApiKeys } from "../../utils/reactflowUtils"; import BaseModal from "../baseModal"; import { useDarkStore } from "../../stores/darkStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; const ExportModal = forwardRef( (props: { children: ReactNode }, ref): JSX.Element => { - const { downloadFlow } = useContext(FlowsContext); const version = useDarkStore((state) => state.version); const setNoticeData = useAlertStore((state) => state.setNoticeData); const [checked, setChecked] = useState(true); diff --git a/src/frontend/src/modals/flowSettingsModal/index.tsx b/src/frontend/src/modals/flowSettingsModal/index.tsx index c892c173f..09c1f1ed5 100644 --- a/src/frontend/src/modals/flowSettingsModal/index.tsx +++ b/src/frontend/src/modals/flowSettingsModal/index.tsx @@ -3,7 +3,6 @@ import EditFlowSettings from "../../components/EditFlowSettingsComponent"; import IconComponent from "../../components/genericIconComponent"; import { Button } from "../../components/ui/button"; import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants"; -import { FlowsContext } from "../../contexts/flowsContext"; import { FlowSettingsPropsType } from "../../types/components"; import { FlowType } from "../../types/flow"; import BaseModal from "../baseModal"; diff --git a/src/frontend/src/modals/shareModal/index.tsx b/src/frontend/src/modals/shareModal/index.tsx index 52833d5ce..8a8f864af 100644 --- a/src/frontend/src/modals/shareModal/index.tsx +++ b/src/frontend/src/modals/shareModal/index.tsx @@ -5,7 +5,6 @@ import IconComponent from "../../components/genericIconComponent"; import { TagsSelector } from "../../components/tagsSelectorComponent"; import { Button } from "../../components/ui/button"; import { Checkbox } from "../../components/ui/checkbox"; -import { FlowsContext } from "../../contexts/flowsContext"; 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 38e8b3422..093e6d43b 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -17,8 +17,6 @@ import ReactFlow, { SelectionDragHandler, addEdge, updateEdge, - useEdgesState, - useNodesState, } from "reactflow"; import GenericNode from "../../../../CustomNodes/GenericNode"; import Chat from "../../../../components/chatComponent"; @@ -28,6 +26,8 @@ import { locationContext } from "../../../../contexts/locationContext"; import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import useAlertStore from "../../../../stores/alertStore"; import useFlowStore from "../../../../stores/flowStore"; +import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; +import { useTypesStore } from "../../../../stores/typesStore"; import { APIClassType } from "../../../../types/api"; import { FlowType, NodeType } from "../../../../types/flow"; import { @@ -41,8 +41,6 @@ import { cn, getRandomName, isWrappedWithClass } from "../../../../utils/utils"; import ConnectionLineComponent from "../ConnectionLineComponent"; import SelectionMenu from "../SelectionMenuComponent"; import ExtraSidebar from "../extraSidebarComponent"; -import { useTypesStore } from "../../../../stores/typesStore"; -import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; const nodeTypes = { genericNode: GenericNode, @@ -56,7 +54,9 @@ export default function Page({ view?: boolean; }): JSX.Element { let { uploadFlow } = useContext(FlowsContext); - const autoSaveCurrentFlow = useFlowsManagerStore((state) => state.autoSaveCurrentFlow); + const autoSaveCurrentFlow = useFlowsManagerStore( + (state) => state.autoSaveCurrentFlow + ); const types = useTypesStore((state) => state.types); const templates = useTypesStore((state) => state.templates); const setFilterEdge = useTypesStore((state) => state.setFilterEdge); @@ -160,13 +160,17 @@ export default function Page({ const [loading, setLoading] = useState(true); + const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); + const timeoutRef = useRef(); useEffect(() => { setLoading(true); if (reactFlowInstance) { - reactFlowInstance.setNodes(flow?.data?.nodes ?? []); - reactFlowInstance.setEdges(flow?.data?.edges ?? []); + useFlowStore.setState({ + nodes: flow?.data?.nodes ?? [], + edges: flow?.data?.edges ?? [], + }); reactFlowInstance.setViewport( flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 } ); @@ -186,7 +190,7 @@ export default function Page({ return () => { clearTimeout(timeoutRef.current); }; - }, [flow, reactFlowInstance]); + }, [currentFlowId, reactFlowInstance]); const onConnectMod = useCallback( (params: Connection) => { diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index bc75c0e84..dafd15682 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -35,6 +35,8 @@ export default function ExtraSidebar(): JSX.Element { const { uploadFlow } = useContext(FlowsContext); const saveFlow = useFlowsManagerStore((state) => state.saveFlow); const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance); + const nodes = useFlowStore((state) => state.nodes); + const edges = useFlowStore((state) => state.edges); const currentFlow = useFlowsManagerStore((state) => state.currentFlow); const hasStore = useStoreStore((state) => state.hasStore); const hasApiKey = useStoreStore((state) => state.hasApiKey); @@ -304,7 +306,7 @@ export default function ExtraSidebar(): JSX.Element { (isPending ? "" : "button-disable") } onClick={(event) => { - saveFlow({...currentFlow, data: {...currentFlow.data!, viewport: reactFlowInstance?.getViewport()!} }, true); + saveFlow({...currentFlow, data: {nodes, edges, viewport: reactFlowInstance?.getViewport()!} }, true); }} > state.isLoading); const flows = useFlowsManagerStore((state) => state.flows); const setSuccessData = useAlertStore((state) => state.setSuccessData); const setErrorData = useAlertStore((state) => state.setErrorData); @@ -51,7 +52,7 @@ export default function ComponentsComponent({ const start = (pageIndex - 1) * pageSize; const end = start + pageSize; setData(all.slice(start, end)); - }, [flows, pageIndex, pageSize]); + }, [flows, isLoading, pageIndex, pageSize]); const [data, setData] = useState([]); diff --git a/src/frontend/src/pages/MainPage/index.tsx b/src/frontend/src/pages/MainPage/index.tsx index 7531ad4fb..17ef52c59 100644 --- a/src/frontend/src/pages/MainPage/index.tsx +++ b/src/frontend/src/pages/MainPage/index.tsx @@ -10,12 +10,16 @@ import { USER_PROJECTS_HEADER } from "../../constants/constants"; import { FlowsContext } from "../../contexts/flowsContext"; import useAlertStore from "../../stores/alertStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; +import { downloadFlows } from "../../utils/reactflowUtils"; export default function HomePage(): JSX.Element { - const { downloadFlows, uploadFlows, addFlow, uploadFlow } = + const { addFlow, uploadFlow } = useContext(FlowsContext); const setCurrentFlowId = useFlowsManagerStore( (state) => state.setCurrentFlowId ); + const uploadFlows = useFlowsManagerStore( + (state) => state.uploadFlows + ); const setSuccessData = useAlertStore((state) => state.setSuccessData); const setErrorData = useAlertStore((state) => state.setErrorData); const location = useLocation(); diff --git a/src/frontend/src/pages/ProfileSettingsPage/index.tsx b/src/frontend/src/pages/ProfileSettingsPage/index.tsx index 8c2d5136b..ca9b30d34 100644 --- a/src/frontend/src/pages/ProfileSettingsPage/index.tsx +++ b/src/frontend/src/pages/ProfileSettingsPage/index.tsx @@ -8,7 +8,6 @@ import InputComponent from "../../components/inputComponent"; import { Button } from "../../components/ui/button"; import { CONTROL_PATCH_USER_STATE } from "../../constants/constants"; import { AuthContext } from "../../contexts/authContext"; -import { FlowsContext } from "../../contexts/flowsContext"; import { resetPassword, updateUser } from "../../controllers/API"; import useAlertStore from "../../stores/alertStore"; import { diff --git a/src/frontend/src/pages/StorePage/index.tsx b/src/frontend/src/pages/StorePage/index.tsx index 1cf5c5f13..c54893033 100644 --- a/src/frontend/src/pages/StorePage/index.tsx +++ b/src/frontend/src/pages/StorePage/index.tsx @@ -21,7 +21,6 @@ import { SelectValue, } from "../../components/ui/select"; import { AuthContext } from "../../contexts/authContext"; -import { FlowsContext } from "../../contexts/flowsContext"; import { checkHasApiKey, getStoreComponents, diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index 817c49a40..491ab25f3 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -4,6 +4,7 @@ import { create } from "zustand"; import { readFlowsFromDatabase, updateFlowInDatabase, + uploadFlowsToDatabase, } from "../controllers/API"; import { FlowType } from "../types/flow"; import { FlowState } from "../types/tabs"; @@ -25,6 +26,12 @@ const useFlowsManagerStore = create((set, get) => ({ })); }, flows: [], + setFlows: (flows: FlowType[]) => { + set({ + flows, + currentFlow: flows.find((flow) => flow.id === get().currentFlowId), + }); + }, currentFlow: undefined, isLoading: true, setIsLoading: (isLoading: boolean) => set({ isLoading }), @@ -52,7 +59,8 @@ const useFlowsManagerStore = create((set, get) => ({ .then((dbData) => { if (dbData) { const { data, flows } = processFlows(dbData, false); - set({ flows, isLoading: false }); + get().setFlows(flows); + set({ isLoading: false }); useTypesStore.setState((state) => ({ data: { ...state.data, ["saved_components"]: data }, })); @@ -94,14 +102,14 @@ const useFlowsManagerStore = create((set, get) => ({ .getState() .setSuccessData({ title: "Changes saved successfully" }); } - set((oldState) => ({ - flows: oldState.flows.map((flow) => { + get().setFlows( + get().flows.map((flow) => { if (flow.id === updatedFlow.id) { return updatedFlow; } return flow; - }), - })); + }) + ); //update tabs state useFlowStore.setState({ isPending: false }); @@ -117,6 +125,35 @@ const useFlowsManagerStore = create((set, get) => ({ }); }); }, + uploadFlows: () => { + return new Promise((resolve) => { + const input = document.createElement("input"); + input.type = "file"; + // add a change event listener to the file input + input.onchange = (event: Event) => { + // check if the file type is application/json + if ( + (event.target as HTMLInputElement).files![0].type === + "application/json" + ) { + // get the file from the file input + const file = (event.target as HTMLInputElement).files![0]; + // read the file as text + const formData = new FormData(); + formData.append("file", file); + uploadFlowsToDatabase(formData).then(() => { + get() + .refreshFlows() + .then(() => { + resolve(); + }); + }); + } + }; + // trigger the file input click event to open the file dialog + input.click(); + }); + }, })); export default useFlowsManagerStore; diff --git a/src/frontend/src/types/zustand/flowsManager/index.ts b/src/frontend/src/types/zustand/flowsManager/index.ts index dad1356ab..2e7e56a71 100644 --- a/src/frontend/src/types/zustand/flowsManager/index.ts +++ b/src/frontend/src/types/zustand/flowsManager/index.ts @@ -4,6 +4,7 @@ import { FlowState, FlowsState } from "../../tabs"; export type FlowsManagerStoreType = { flows: Array; + setFlows: (flows: FlowType[]) => void; currentFlow: FlowType | undefined; currentFlowId: string; setCurrentFlowId: (currentFlowId: string) => void; @@ -15,4 +16,5 @@ export type FlowsManagerStoreType = { refreshFlows: () => Promise; saveFlow: (flow: FlowType, silent?: boolean) => Promise; autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => void; + uploadFlows: () => Promise; }; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 039be107b..727750900 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -33,6 +33,7 @@ import { updateEdgesHandleIdsType, } from "../types/utils/reactflowUtils"; import { createRandomKey, getFieldTitle, toTitleCase } from "./utils"; +import { downloadFlowsFromDatabase } from "../controllers/API"; const uid = new ShortUniqueId({ length: 5 }); export function cleanEdges(nodes: Node[], edges: Edge[]) { @@ -1205,3 +1206,48 @@ export function templatesGenerator(data: APIObjectType) { return acc; }, {}); } + +export function downloadFlow( + flow: FlowType, + flowName: string, + flowDescription?: string +) { + let clonedFlow = cloneDeep(flow); + removeFileNameFromComponents(clonedFlow); + // create a data URI with the current flow data + const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( + JSON.stringify({ + ...clonedFlow, + name: flowName, + description: flowDescription, + }) + )}`; + + // create a link element and set its properties + const link = document.createElement("a"); + link.href = jsonString; + link.download = `${ + flowName && flowName != "" + ? flowName + : flow.name + }.json`; + + // simulate a click on the link element to trigger the download + link.click(); +} + +export function downloadFlows() { + downloadFlowsFromDatabase().then((flows) => { + const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( + JSON.stringify(flows) + )}`; + + // create a link element and set its properties + const link = document.createElement("a"); + link.href = jsonString; + link.download = `flows.json`; + + // simulate a click on the link element to trigger the download + link.click(); + }); +} \ No newline at end of file From 882f365ff631a3ee2a5031f1c109495e41c7d871 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sat, 6 Jan 2024 01:27:54 -0300 Subject: [PATCH 54/89] Passed the rest of the flowsContext functions to zustand, removed flowsContext --- .../src/components/cardComponent/index.tsx | 4 +- .../components/menuBar/index.tsx | 3 +- src/frontend/src/contexts/flowsContext.tsx | 500 ------------------ src/frontend/src/contexts/index.tsx | 5 +- .../components/PageComponent/index.tsx | 3 +- .../extraSidebarComponent/index.tsx | 3 +- .../sideBarDraggableComponent/index.tsx | 6 +- .../components/nodeToolbarComponent/index.tsx | 3 +- .../MainPage/components/components/index.tsx | 6 +- src/frontend/src/pages/MainPage/index.tsx | 5 +- src/frontend/src/stores/flowsManagerStore.ts | 182 ++++++- .../src/types/zustand/flowsManager/index.ts | 7 +- src/frontend/src/utils/reactflowUtils.ts | 32 +- 13 files changed, 225 insertions(+), 534 deletions(-) delete mode 100644 src/frontend/src/contexts/flowsContext.tsx diff --git a/src/frontend/src/components/cardComponent/index.tsx b/src/frontend/src/components/cardComponent/index.tsx index f4afea46b..d87419fe2 100644 --- a/src/frontend/src/components/cardComponent/index.tsx +++ b/src/frontend/src/components/cardComponent/index.tsx @@ -1,5 +1,4 @@ import { useContext, useEffect, useState } from "react"; -import { FlowsContext } from "../../contexts/flowsContext"; import { getComponent, postLikeComponent } from "../../controllers/API"; import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal"; import useAlertStore from "../../stores/alertStore"; @@ -18,6 +17,7 @@ import { CardHeader, CardTitle, } from "../ui/card"; +import useFlowsManagerStore from "../../stores/flowsManagerStore"; export default function CollectionCardComponent({ data, @@ -32,7 +32,7 @@ export default function CollectionCardComponent({ button?: JSX.Element; onDelete?: () => void; }) { - const { addFlow } = useContext(FlowsContext); + const addFlow = useFlowsManagerStore((state) => state.addFlow); const setSuccessData = useAlertStore((state) => state.setSuccessData); const setErrorData = useAlertStore((state) => state.setErrorData); const setValidApiKey = useStoreStore((state) => state.updateValidApiKey); diff --git a/src/frontend/src/components/headerComponent/components/menuBar/index.tsx b/src/frontend/src/components/headerComponent/components/menuBar/index.tsx index e7cf80587..e2fd345c5 100644 --- a/src/frontend/src/components/headerComponent/components/menuBar/index.tsx +++ b/src/frontend/src/components/headerComponent/components/menuBar/index.tsx @@ -1,5 +1,4 @@ import { useContext, useState } from "react"; -import { FlowsContext } from "../../../../contexts/flowsContext"; import { DropdownMenu, DropdownMenuContent, @@ -17,7 +16,7 @@ import IconComponent from "../../../genericIconComponent"; import { Button } from "../../../ui/button"; export const MenuBar = (): JSX.Element => { - const { addFlow } = useContext(FlowsContext); + const addFlow = useFlowsManagerStore((state) => state.addFlow); const currentFlow = useFlowsManagerStore((state) => state.currentFlow); const setErrorData = useAlertStore((state) => state.setErrorData); const { undo, redo } = useContext(undoRedoContext); diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx deleted file mode 100644 index 4c1750fce..000000000 --- a/src/frontend/src/contexts/flowsContext.tsx +++ /dev/null @@ -1,500 +0,0 @@ -import { AxiosError } from "axios"; -import _, { cloneDeep } from "lodash"; -import { ReactNode, createContext, useContext, useRef, useState } from "react"; -import { - Edge, - Node, - ReactFlowJsonObject, - Viewport, - XYPosition, -} from "reactflow"; -import ShortUniqueId from "short-unique-id"; -import { - deleteFlowFromDatabase, - downloadFlowsFromDatabase, - readFlowsFromDatabase, - saveFlowToDatabase, - updateFlowInDatabase, - uploadFlowsToDatabase, -} from "../controllers/API"; -import useAlertStore from "../stores/alertStore"; -import useFlowStore from "../stores/flowStore"; -import { APIClassType } from "../types/api"; -import { FlowType, NodeDataType } from "../types/flow"; -import { FlowsContextType, FlowsState } from "../types/tabs"; -import { - addVersionToDuplicates, - createFlowComponent, - processFlowEdges, - removeFileNameFromComponents, - updateEdges, - updateIds, -} from "../utils/reactflowUtils"; -import { - createRandomKey, - getRandomDescription, - getRandomName, -} from "../utils/utils"; -import { useTypesStore } from "../stores/typesStore"; - -const uid = new ShortUniqueId({ length: 5 }); - -const FlowsContextInitialValue: FlowsContextType = { - //Remove tab id and get current id from url - tabId: "", - setTabId: (index: string) => {}, - isLoading: true, - flows: [], - setVersion: () => {}, - removeFlow: (id: string) => {}, - addFlow: async ( - newProject: boolean, - flowData?: FlowType, - override?: boolean - ) => "", - downloadFlow: (flow: FlowType) => {}, - downloadFlows: () => {}, - uploadFlows: () => {}, - uploadFlow: async () => "", - saveFlow: async (flow?: FlowType, silent?: boolean) => {}, - tabsState: {}, - setTabsState: () => {}, - saveComponent: async (component: NodeDataType, override: boolean) => "", - deleteComponent: (key: string) => {}, - version: "", - refreshFlows: () => {}, -}; - -export const FlowsContext = createContext( - FlowsContextInitialValue -); - -export function FlowsProvider({ children }: { children: ReactNode }) { - const setSuccessData = useAlertStore((state) => state.setSuccessData); - const setErrorData = useAlertStore((state) => state.setErrorData); - const [tabId, setTabId] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [flows, setFlows] = useState>([]); - const [tabsState, setTabsState] = useState({}); - const setData = useTypesStore((state) => state.setData); - const nodes = useFlowStore((state) => state.nodes); - const edges = useFlowStore((state) => state.edges); - const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance); - const setPending = useFlowStore((state) => state.setPending); - const paste = useFlowStore((state) => state.paste); - - function refreshFlows() { - setIsLoading(true); - getTabsDataFromDB().then((DbData) => { - if (DbData) { - try { - processFlows(DbData, false); - setFlows(DbData); - setIsLoading(false); - } catch (e) {} - } - }); - } - - function getTabsDataFromDB() { - //get tabs from db - return readFlowsFromDatabase(); - } - - function processFlows(DbData: FlowType[], skipUpdate = true) { - let savedComponents: { [key: string]: APIClassType } = {}; - DbData.forEach((flow: FlowType) => { - try { - if (!flow.data) { - return; - } - if (flow.data && flow.is_component) { - (flow.data.nodes[0].data as NodeDataType).node!.display_name = - flow.name; - savedComponents[ - createRandomKey( - (flow.data.nodes[0].data as NodeDataType).type, - uid() - ) - ] = _.cloneDeep((flow.data.nodes[0].data as NodeDataType).node!); - return; - } - if (!skipUpdate) processDataFromFlow(flow, false); - } catch (e) { - console.log(e); - } - }); - setData((prev) => { - let newData = cloneDeep(prev); - newData["saved_components"] = cloneDeep(savedComponents); - return newData; - }); - } - - /** - * Downloads the current flow as a JSON file - */ - function downloadFlow( - flow: FlowType, - flowName: string, - flowDescription?: string - ) { - let clonedFlow = cloneDeep(flow); - removeFileNameFromComponents(clonedFlow); - // create a data URI with the current flow data - const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( - JSON.stringify({ - ...clonedFlow, - name: flowName, - description: flowDescription, - }) - )}`; - - // create a link element and set its properties - const link = document.createElement("a"); - link.href = jsonString; - link.download = `${ - flowName && flowName != "" - ? flowName - : flows.find((f) => f.id === tabId)!.name - }.json`; - - // simulate a click on the link element to trigger the download - link.click(); - } - - function downloadFlows() { - downloadFlowsFromDatabase().then((flows) => { - const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( - JSON.stringify(flows) - )}`; - - // create a link element and set its properties - const link = document.createElement("a"); - link.href = jsonString; - link.download = `flows.json`; - - // simulate a click on the link element to trigger the download - link.click(); - }); - } - /** - * 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. - * The resulting JSON object is passed to the addFlow function. - */ - async function uploadFlow({ - newProject, - file, - isComponent = false, - position = { x: 10, y: 10 }, - }: { - newProject: boolean; - file?: File; - isComponent?: boolean; - position?: XYPosition; - }): Promise { - return new Promise(async (resolve, reject) => { - let id; - if (file) { - let text = await file.text(); - let fileData = JSON.parse(text); - if ( - newProject && - ((!fileData.is_component && isComponent === true) || - (fileData.is_component !== undefined && - fileData.is_component !== isComponent)) - ) { - reject("You cannot upload a component as a flow or vice versa"); - } else { - if (fileData.flows) { - fileData.flows.forEach((flow: FlowType) => { - id = addFlow(newProject, flow, undefined, position); - }); - resolve(""); - } else { - id = await addFlow(newProject, fileData, undefined, position); - resolve(id); - } - } - } else { - // create a file input - const input = document.createElement("input"); - input.type = "file"; - input.accept = ".json"; - // add a change event listener to the file input - input.onchange = async (e: Event) => { - if ( - (e.target as HTMLInputElement).files![0].type === "application/json" - ) { - const currentfile = (e.target as HTMLInputElement).files![0]; - let text = await currentfile.text(); - let fileData: FlowType = await JSON.parse(text); - - if ( - (!fileData.is_component && isComponent === true) || - (fileData.is_component !== undefined && - fileData.is_component !== isComponent) - ) { - reject("You cannot upload a component as a flow or vice versa"); - } else { - id = await addFlow(newProject, fileData); - resolve(id); - } - } - }; - // trigger the file input click event to open the file dialog - input.click(); - } - }); - } - - function uploadFlows() { - // create a file input - const input = document.createElement("input"); - input.type = "file"; - // add a change event listener to the file input - input.onchange = (event: Event) => { - // check if the file type is application/json - if ( - (event.target as HTMLInputElement).files![0].type === "application/json" - ) { - // get the file from the file input - const file = (event.target as HTMLInputElement).files![0]; - // read the file as text - const formData = new FormData(); - formData.append("file", file); - uploadFlowsToDatabase(formData).then(() => { - refreshFlows(); - }); - } - }; - // trigger the file input click event to open the file dialog - input.click(); - } - /** - * Removes a flow from an array of flows based on its id. - * Updates the state of flows and tabIndex using setFlows and setTabIndex hooks. - * @param {string} id - The id of the flow to remove. - */ - async function removeFlow(id: string) { - const index = flows.findIndex((flow) => flow.id === id); - if (index >= 0) { - await deleteFlowFromDatabase(id); - //removes component from data if there is any - setFlows(flows.filter((flow) => flow.id !== id)); - processFlows(flows.filter((flow) => flow.id !== id)); - } - } - - const addFlow = async ( - newProject: Boolean, - flow?: FlowType, - override?: boolean, - position?: XYPosition - ): Promise => { - if (newProject) { - let flowData = flow - ? processDataFromFlow(flow) - : { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } }; - - // Create a new flow with a default name if no flow is provided. - - if (override) { - deleteComponent(flow!.name); - const newFlow = createNewFlow(flowData, flow!); - const { id } = await saveFlowToDatabase(newFlow); - newFlow.id = id; - //setTimeout to prevent update state with wrong state - setTimeout(() => { - addFlowToLocalState(newFlow); - }, 200); - // addFlowToLocalState(newFlow); - return; - } - - const newFlow = createNewFlow(flowData, flow!); - - const newName = addVersionToDuplicates(newFlow, flows); - - newFlow.name = newName; - try { - const { id } = await saveFlowToDatabase(newFlow); - // Change the id to the new id. - newFlow.id = id; - - // Add the new flow to the list of flows. - addFlowToLocalState(newFlow); - - // Return the id - return id; - } catch (error) { - // Handle the error if needed - throw error; // Re-throw the error so the caller can handle it if needed - } - } else { - paste( - { nodes: flow!.data!.nodes, edges: flow!.data!.edges }, - position ?? { x: 10, y: 10 } - ); - } - }; - - const processDataFromFlow = (flow: FlowType, refreshIds = true) => { - let data = flow?.data ? flow.data : null; - if (data) { - processFlowEdges(flow); - //prevent node update for now - // processFlowNodes(flow); - //add animation to text type edges - updateEdges(data.edges); - // updateNodes(data.nodes, data.edges); - if (refreshIds) updateIds(data); // Assuming updateIds is defined elsewhere - } - return data; - }; - - const createNewFlow = ( - flowData: ReactFlowJsonObject | null, - flow: FlowType - ) => ({ - description: flow?.description ?? getRandomDescription(), - name: flow?.name ?? getRandomName(), - data: flowData, - id: "", - is_component: flow?.is_component ?? false, - }); - - const addFlowToLocalState = (newFlow: FlowType) => { - let newFlows: FlowType[] = []; - setFlows((prevState) => { - newFlows = newFlows.concat(prevState); - newFlows.push(newFlow); - return [...prevState, newFlow]; - }); - processFlows(newFlows); - }; - - /** - * Updates an existing flow with new data - * @param newFlow - The new flow object containing the updated data - */ - function updateFlow(newFlow: FlowType) { - setFlows((prevState) => { - const newFlows = [...prevState]; - const index = newFlows.findIndex((flow) => flow.id === newFlow.id); - if (index !== -1) { - newFlows[index].description = newFlow.description ?? ""; - newFlows[index].data = newFlow.data; - newFlows[index].name = newFlow.name; - } - newFlow = { - ...newFlow, - }; - return newFlows; - }); - } - - const saveTimeoutId = useRef(null); - - const saveCurrentFlow = ( - nodes: Node[], - edges: Edge[], - viewport: Viewport - ) => { - // Clear the previous timeout if it exists. - if (saveTimeoutId.current) { - clearTimeout(saveTimeoutId.current); - } - - // Set up a new timeout. - saveTimeoutId.current = setTimeout(() => { - const currentFlow = flows.find((flow: FlowType) => flow.id === tabId); - if (currentFlow) { - saveFlow({ ...currentFlow, data: { nodes, edges, viewport } }, true); - } - }, 300); // Delay of 300ms. - }; - - async function saveFlow(flow?: FlowType, silent?: boolean) { - let newFlow; - if (!flow) { - const currentFlow = flows.find((flow) => flow.id === tabId)!; - newFlow = { - ...currentFlow, - data: { - nodes, - edges, - viewport: reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 }, - }, - }; - } else { - newFlow = flow; - } - - try { - // updates flow in db - const updatedFlow = await updateFlowInDatabase(newFlow); - if (updatedFlow) { - // updates flow in state - if (!silent) { - setSuccessData({ title: "Changes saved successfully" }); - } - updateFlow(newFlow); - //update tabs state - setPending(false); - } - } catch (err) { - setErrorData({ - title: "Error while saving changes", - list: [(err as AxiosError).message], - }); - } - } - - function saveComponent(component: NodeDataType, override: boolean) { - component.node!.official = false; - return addFlow(true, createFlowComponent(component, version), override); - } - - function deleteComponent(key: string) { - let componentFlow = flows.find( - (componentFlow) => - componentFlow.is_component && componentFlow.name === key - ); - - if (componentFlow) { - removeFlow(componentFlow.id); - } - } - - // Initialize state variable for the version - const [version, setVersion] = useState(""); - - return ( - - {children} - - ); -} diff --git a/src/frontend/src/contexts/index.tsx b/src/frontend/src/contexts/index.tsx index 0bc1913fb..49b8106e6 100644 --- a/src/frontend/src/contexts/index.tsx +++ b/src/frontend/src/contexts/index.tsx @@ -5,7 +5,6 @@ import { TooltipProvider } from "../components/ui/tooltip"; import { ApiInterceptor } from "../controllers/API/api"; import { SSEProvider } from "./SSEContext"; import { AuthProvider } from "./authContext"; -import { FlowsProvider } from "./flowsContext"; import { LocationProvider } from "./locationContext"; import { UndoRedoProvider } from "./undoRedoContext"; @@ -21,9 +20,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) { - - {children} - + {children} diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 093e6d43b..f3dd8f6bd 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -21,7 +21,6 @@ import ReactFlow, { import GenericNode from "../../../../CustomNodes/GenericNode"; import Chat from "../../../../components/chatComponent"; import Loading from "../../../../components/ui/loading"; -import { FlowsContext } from "../../../../contexts/flowsContext"; import { locationContext } from "../../../../contexts/locationContext"; import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import useAlertStore from "../../../../stores/alertStore"; @@ -53,7 +52,7 @@ export default function Page({ flow: FlowType; view?: boolean; }): JSX.Element { - let { uploadFlow } = useContext(FlowsContext); + const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow); const autoSaveCurrentFlow = useFlowsManagerStore( (state) => state.autoSaveCurrentFlow ); diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index dafd15682..6f53cbd02 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -4,7 +4,6 @@ import ShadTooltip from "../../../../components/ShadTooltipComponent"; import IconComponent from "../../../../components/genericIconComponent"; import { Input } from "../../../../components/ui/input"; import { Separator } from "../../../../components/ui/separator"; -import { FlowsContext } from "../../../../contexts/flowsContext"; import ApiModal from "../../../../modals/ApiModal"; import ExportModal from "../../../../modals/exportModal"; import ShareModal from "../../../../modals/shareModal"; @@ -32,7 +31,7 @@ export default function ExtraSidebar(): JSX.Element { const templates = useTypesStore((state) => state.templates); const getFilterEdge = useTypesStore((state) => state.getFilterEdge); const setFilterEdge = useTypesStore((state) => state.setFilterEdge); - const { uploadFlow } = useContext(FlowsContext); + const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow); const saveFlow = useFlowsManagerStore((state) => state.saveFlow); const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance); const nodes = useFlowStore((state) => state.nodes); 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 41d9ebd34..0e817852b 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx @@ -7,7 +7,6 @@ import { SelectTrigger, } from "../../../../../components/ui/select-custom"; import { AuthContext } from "../../../../../contexts/authContext"; -import { FlowsContext } from "../../../../../contexts/flowsContext"; import { APIClassType } from "../../../../../types/api"; import { createFlowComponent, @@ -16,6 +15,7 @@ import { } from "../../../../../utils/reactflowUtils"; import { removeCountFromString } from "../../../../../utils/utils"; import { useDarkStore } from "../../../../../stores/darkStore"; +import useFlowsManagerStore from "../../../../../stores/flowsManagerStore"; export default function SidebarDraggableComponent({ sectionName, @@ -37,7 +37,9 @@ export default function SidebarDraggableComponent({ official: boolean; }) { const [open, setOpen] = useState(false); - const { deleteComponent } = useContext(FlowsContext); + const deleteComponent = useFlowsManagerStore( + (state) => state.deleteComponent + ); const version = useDarkStore((state) => state.version); 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 c97a635cf..84b00208a 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -8,7 +8,6 @@ import { SelectItem, SelectTrigger, } from "../../../../components/ui/select-custom"; -import { FlowsContext } from "../../../../contexts/flowsContext"; import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import ConfirmationModal from "../../../../modals/ConfirmationModal"; import EditNodeModal from "../../../../modals/EditNodeModal"; @@ -74,7 +73,7 @@ export default function NodeToolbarComponent({ const setNodes = useFlowStore((state) => state.setNodes); const setEdges = useFlowStore((state) => state.setEdges); - const { saveComponent } = useContext(FlowsContext); + const saveComponent = useFlowsManagerStore((state) => state.saveComponent); const flows = useFlowsManagerStore((state) => state.flows); const version = useDarkStore((state) => state.version); const { takeSnapshot } = useContext(undoRedoContext); diff --git a/src/frontend/src/pages/MainPage/components/components/index.tsx b/src/frontend/src/pages/MainPage/components/components/index.tsx index 697c9615c..5ee0821b2 100644 --- a/src/frontend/src/pages/MainPage/components/components/index.tsx +++ b/src/frontend/src/pages/MainPage/components/components/index.tsx @@ -6,7 +6,6 @@ import CardsWrapComponent from "../../../../components/cardsWrapComponent"; import IconComponent from "../../../../components/genericIconComponent"; import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent"; import { Button } from "../../../../components/ui/button"; -import { FlowsContext } from "../../../../contexts/flowsContext"; import useAlertStore from "../../../../stores/alertStore"; import { FlowType } from "../../../../types/flow"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; @@ -16,8 +15,9 @@ export default function ComponentsComponent({ }: { is_component?: boolean; }) { - const { removeFlow, uploadFlow, addFlow } = - useContext(FlowsContext); + const addFlow = useFlowsManagerStore((state) => state.addFlow); + const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow); + const removeFlow = useFlowsManagerStore((state) => state.removeFlow); const isLoading = useFlowsManagerStore((state) => state.isLoading); const flows = useFlowsManagerStore((state) => state.flows); const setSuccessData = useAlertStore((state) => state.setSuccessData); diff --git a/src/frontend/src/pages/MainPage/index.tsx b/src/frontend/src/pages/MainPage/index.tsx index 17ef52c59..74b3e224c 100644 --- a/src/frontend/src/pages/MainPage/index.tsx +++ b/src/frontend/src/pages/MainPage/index.tsx @@ -7,13 +7,12 @@ import PageLayout from "../../components/pageLayout"; import SidebarNav from "../../components/sidebarComponent"; import { Button } from "../../components/ui/button"; import { USER_PROJECTS_HEADER } from "../../constants/constants"; -import { FlowsContext } from "../../contexts/flowsContext"; import useAlertStore from "../../stores/alertStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { downloadFlows } from "../../utils/reactflowUtils"; export default function HomePage(): JSX.Element { - const { addFlow, uploadFlow } = - useContext(FlowsContext); + const addFlow = useFlowsManagerStore((state) => state.addFlow); + const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow); const setCurrentFlowId = useFlowsManagerStore( (state) => state.setCurrentFlowId ); diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index 491ab25f3..6b1ffa974 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -1,18 +1,27 @@ import { AxiosError } from "axios"; -import { Edge, Node, Viewport } from "reactflow"; +import { Edge, Node, Viewport, XYPosition } from "reactflow"; import { create } from "zustand"; import { + deleteFlowFromDatabase, readFlowsFromDatabase, + saveFlowToDatabase, updateFlowInDatabase, uploadFlowsToDatabase, } from "../controllers/API"; -import { FlowType } from "../types/flow"; +import { FlowType, NodeDataType } from "../types/flow"; import { FlowState } from "../types/tabs"; import { FlowsManagerStoreType } from "../types/zustand/flowsManager"; -import { processFlows } from "../utils/reactflowUtils"; +import { + addVersionToDuplicates, + createFlowComponent, + createNewFlow, + processDataFromFlow, + processFlows, +} from "../utils/reactflowUtils"; import useAlertStore from "./alertStore"; import useFlowStore from "./flowStore"; import { useTypesStore } from "./typesStore"; +import { useDarkStore } from "./darkStore"; let saveTimeoutId: NodeJS.Timeout | null = null; @@ -154,6 +163,173 @@ const useFlowsManagerStore = create((set, get) => ({ input.click(); }); }, + addFlow: async ( + newProject: Boolean, + flow?: FlowType, + override?: boolean, + position?: XYPosition + ): Promise => { + if (newProject) { + let flowData = flow + ? processDataFromFlow(flow) + : { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } }; + + // Create a new flow with a default name if no flow is provided. + + if (override) { + get().deleteComponent(flow!.name); + const newFlow = createNewFlow(flowData!, flow!); + const { id } = await saveFlowToDatabase(newFlow); + newFlow.id = id; + //setTimeout to prevent update state with wrong state + setTimeout(() => { + const { data, flows } = processFlows([newFlow, ...get().flows]); + get().setFlows(flows); + set({ isLoading: false }); + useTypesStore.setState((state) => ({ + data: { ...state.data, ["saved_components"]: data }, + })); + }, 200); + // addFlowToLocalState(newFlow); + return; + } + + const newFlow = createNewFlow(flowData!, flow!); + + const newName = addVersionToDuplicates(newFlow, get().flows); + + newFlow.name = newName; + try { + const { id } = await saveFlowToDatabase(newFlow); + // Change the id to the new id. + newFlow.id = id; + + // Add the new flow to the list of flows. + const { data, flows } = processFlows([newFlow, ...get().flows]); + get().setFlows(flows); + set({ isLoading: false }); + useTypesStore.setState((state) => ({ + data: { ...state.data, ["saved_components"]: data }, + })); + + // Return the id + return id; + } catch (error) { + // Handle the error if needed + throw error; // Re-throw the error so the caller can handle it if needed + } + } else { + useFlowStore + .getState() + .paste( + { nodes: flow!.data!.nodes, edges: flow!.data!.edges }, + position ?? { x: 10, y: 10 } + ); + } + }, + removeFlow: async (id: string) => { + return new Promise((resolve) => { + const index = get().flows.findIndex((flow) => flow.id === id); + if (index >= 0) { + deleteFlowFromDatabase(id).then(() => { + const { data, flows } = processFlows( + get().flows.filter((flow) => flow.id !== id) + ); + get().setFlows(flows); + set({ isLoading: false }); + useTypesStore.setState((state) => ({ + data: { ...state.data, ["saved_components"]: data }, + })); + resolve(); + }); + } + }); + }, + deleteComponent: async (key: string) => { + return new Promise((resolve) => { + let componentFlow = get().flows.find( + (componentFlow) => + componentFlow.is_component && componentFlow.name === key + ); + + if (componentFlow) { + get() + .removeFlow(componentFlow.id) + .then(() => { + resolve(); + }); + } + }); + }, + uploadFlow: async ({ + newProject, + file, + isComponent = false, + position = { x: 10, y: 10 }, + }: { + newProject: boolean; + file?: File; + isComponent?: boolean; + position?: XYPosition; + }): Promise => { + return new Promise(async (resolve, reject) => { + let id; + if (file) { + let text = await file.text(); + let fileData = JSON.parse(text); + if ( + newProject && + ((!fileData.is_component && isComponent === true) || + (fileData.is_component !== undefined && + fileData.is_component !== isComponent)) + ) { + reject("You cannot upload a component as a flow or vice versa"); + } else { + if (fileData.flows) { + fileData.flows.forEach((flow: FlowType) => { + id = get().addFlow(newProject, flow, undefined, position); + }); + resolve(""); + } else { + id = await get().addFlow(newProject, fileData, undefined, position); + resolve(id); + } + } + } else { + // create a file input + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".json"; + // add a change event listener to the file input + input.onchange = async (e: Event) => { + if ( + (e.target as HTMLInputElement).files![0].type === "application/json" + ) { + const currentfile = (e.target as HTMLInputElement).files![0]; + let text = await currentfile.text(); + let fileData: FlowType = await JSON.parse(text); + + if ( + (!fileData.is_component && isComponent === true) || + (fileData.is_component !== undefined && + fileData.is_component !== isComponent) + ) { + reject("You cannot upload a component as a flow or vice versa"); + } else { + id = await get().addFlow(newProject, fileData); + resolve(id); + } + } + }; + // trigger the file input click event to open the file dialog + input.click(); + } + }); + }, + saveComponent: (component: NodeDataType, override: boolean) => { + component.node!.official = false; + return get().addFlow(true, createFlowComponent(component, useDarkStore.getState().version), override); + }, })); export default useFlowsManagerStore; diff --git a/src/frontend/src/types/zustand/flowsManager/index.ts b/src/frontend/src/types/zustand/flowsManager/index.ts index 2e7e56a71..5a3c01350 100644 --- a/src/frontend/src/types/zustand/flowsManager/index.ts +++ b/src/frontend/src/types/zustand/flowsManager/index.ts @@ -1,4 +1,4 @@ -import { Node, Edge, Viewport } from "reactflow"; +import { Node, Edge, Viewport, XYPosition } from "reactflow"; import { FlowType } from "../../flow"; import { FlowState, FlowsState } from "../../tabs"; @@ -17,4 +17,9 @@ export type FlowsManagerStoreType = { saveFlow: (flow: FlowType, silent?: boolean) => Promise; autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => void; uploadFlows: () => Promise; + uploadFlow: ({newProject, file, isComponent, position}: {newProject: boolean, file?: File, isComponent?: boolean, position?: XYPosition}) => Promise; + addFlow: (newProject: boolean, flow?: FlowType, override?: boolean, position?: XYPosition) => Promise; + deleteComponent: (key: string) => Promise; + removeFlow: (id: string) => Promise; + saveComponent: (component: any, override: boolean) => Promise; }; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 727750900..e4b6b2c85 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -12,6 +12,7 @@ import { LANGFLOW_SUPPORTED_TYPES, specialCharsRegex, } from "../constants/constants"; +import { downloadFlowsFromDatabase } from "../controllers/API"; import { APIClassType, APIKindType, @@ -32,8 +33,13 @@ import { unselectAllNodesType, updateEdgesHandleIdsType, } from "../types/utils/reactflowUtils"; -import { createRandomKey, getFieldTitle, toTitleCase } from "./utils"; -import { downloadFlowsFromDatabase } from "../controllers/API"; +import { + createRandomKey, + getFieldTitle, + getRandomDescription, + getRandomName, + toTitleCase, +} from "./utils"; const uid = new ShortUniqueId({ length: 5 }); export function cleanEdges(nodes: Node[], edges: Edge[]) { @@ -189,6 +195,7 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => { // updateNodes(data.nodes, data.edges); if (refreshIds) updateIds(data); // Assuming updateIds is defined elsewhere } + return data; }; export function updateIds(newFlow: ReactFlowJsonObject) { @@ -1226,11 +1233,7 @@ export function downloadFlow( // create a link element and set its properties const link = document.createElement("a"); link.href = jsonString; - link.download = `${ - flowName && flowName != "" - ? flowName - : flow.name - }.json`; + link.download = `${flowName && flowName != "" ? flowName : flow.name}.json`; // simulate a click on the link element to trigger the download link.click(); @@ -1250,4 +1253,17 @@ export function downloadFlows() { // simulate a click on the link element to trigger the download link.click(); }); -} \ No newline at end of file +} + +export const createNewFlow = ( + flowData: ReactFlowJsonObject, + flow: FlowType +) => { + return { + description: flow?.description ?? getRandomDescription(), + name: flow?.name ?? getRandomName(), + data: flowData, + id: "", + is_component: flow?.is_component ?? false, + }; +}; From b4f7285b335e77ce5519cff1b7911e00799df7d4 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sat, 6 Jan 2024 11:34:36 -0300 Subject: [PATCH 55/89] Implemented undoRedo on Zustand --- .../components/parameterComponent/index.tsx | 4 +- .../src/CustomNodes/GenericNode/index.tsx | 4 +- .../components/menuBar/index.tsx | 4 +- .../src/components/inputComponent/index.tsx | 3 - .../components/inputFileComponent/index.tsx | 2 +- .../components/inputListComponent/index.tsx | 6 - .../src/components/intComponent/index.tsx | 2 +- .../components/keypairListComponent/index.tsx | 6 - .../src/components/promptComponent/index.tsx | 2 +- src/frontend/src/contexts/index.tsx | 4 +- src/frontend/src/contexts/undoRedoContext.tsx | 192 ------------------ .../components/PageComponent/index.tsx | 25 ++- .../components/nodeToolbarComponent/index.tsx | 8 +- src/frontend/src/stores/flowsManagerStore.ts | 69 ++++++- .../src/types/zustand/flowsManager/index.ts | 3 + 15 files changed, 100 insertions(+), 234 deletions(-) delete mode 100644 src/frontend/src/contexts/undoRedoContext.tsx diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 959865766..c0a462571 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -26,7 +26,6 @@ import { LANGFLOW_SUPPORTED_TYPES, TOOLTIP_EMPTY, } from "../../../../constants/constants"; -import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import { postCustomComponentUpdate } from "../../../../controllers/API"; import useAlertStore from "../../../../stores/alertStore"; import useFlowStore from "../../../../stores/flowStore"; @@ -88,7 +87,7 @@ export default function ParameterComponent({ const myData = useTypesStore((state) => state.data); - const { takeSnapshot } = useContext(undoRedoContext); + const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); const handleUpdateValues = async (name: string, data: NodeDataType) => { const code = data.node?.template["code"]?.value; @@ -528,7 +527,6 @@ export default function ParameterComponent({ duplicateKey={errorDuplicateKey} onChange={(newValue) => { const valueToNumbers = convertValuesToNumbers(newValue); - data.node!.template[name].value = valueToNumbers; setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers)); handleOnNewValue(valueToNumbers); }} diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 3a90c9ce4..339eb73ac 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 { undoRedoContext } from "../../contexts/undoRedoContext"; import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent"; import useFlowStore from "../../stores/flowStore"; import { validationStatusType } from "../../types/components"; @@ -17,6 +16,7 @@ import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils"; import { classNames, cn, getFieldTitle } from "../../utils/utils"; import ParameterComponent from "./components/parameterComponent"; import { useTypesStore } from "../../stores/typesStore"; +import useFlowsManagerStore from "../../stores/flowsManagerStore"; export default function GenericNode({ data, @@ -44,7 +44,7 @@ export default function GenericNode({ const [handles, setHandles] = useState([]); let numberOfInputs: boolean[] = []; - const { takeSnapshot } = useContext(undoRedoContext); + const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); function countHandles(): void { numberOfInputs = Object.keys(data.node!.template) diff --git a/src/frontend/src/components/headerComponent/components/menuBar/index.tsx b/src/frontend/src/components/headerComponent/components/menuBar/index.tsx index e2fd345c5..20ac20f04 100644 --- a/src/frontend/src/components/headerComponent/components/menuBar/index.tsx +++ b/src/frontend/src/components/headerComponent/components/menuBar/index.tsx @@ -8,7 +8,6 @@ import { } from "../../../ui/dropdown-menu"; import { useNavigate } from "react-router-dom"; -import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import FlowSettingsModal from "../../../../modals/flowSettingsModal"; import useAlertStore from "../../../../stores/alertStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; @@ -19,7 +18,8 @@ export const MenuBar = (): JSX.Element => { const addFlow = useFlowsManagerStore((state) => state.addFlow); const currentFlow = useFlowsManagerStore((state) => state.currentFlow); const setErrorData = useAlertStore((state) => state.setErrorData); - const { undo, redo } = useContext(undoRedoContext); + const undo = useFlowsManagerStore((state) => state.undo); + const redo = useFlowsManagerStore((state) => state.redo); const [openSettings, setOpenSettings] = useState(false); const navigate = useNavigate(); diff --git a/src/frontend/src/components/inputComponent/index.tsx b/src/frontend/src/components/inputComponent/index.tsx index 0efd29c0d..8017c5458 100644 --- a/src/frontend/src/components/inputComponent/index.tsx +++ b/src/frontend/src/components/inputComponent/index.tsx @@ -59,9 +59,6 @@ export default function InputComponent({ e.preventDefault(); }} onKeyDown={(e) => { - if (e.ctrlKey && e.key === "c") { - // Perform any actions you need when Ctrl+C is detected - } handleKeyDown(e, value, ""); if (blurOnEnter && e.key === "Enter") refInput.current?.blur(); }} diff --git a/src/frontend/src/components/inputFileComponent/index.tsx b/src/frontend/src/components/inputFileComponent/index.tsx index a365b188c..0d7b180d2 100644 --- a/src/frontend/src/components/inputFileComponent/index.tsx +++ b/src/frontend/src/components/inputFileComponent/index.tsx @@ -20,7 +20,7 @@ export default function InputFileComponent({ // Clear component state useEffect(() => { - if (disabled) { + if (disabled && value !== "") { setMyValue(""); onChange(""); onFileChange(""); diff --git a/src/frontend/src/components/inputListComponent/index.tsx b/src/frontend/src/components/inputListComponent/index.tsx index ae154484d..e98bba304 100644 --- a/src/frontend/src/components/inputListComponent/index.tsx +++ b/src/frontend/src/components/inputListComponent/index.tsx @@ -44,12 +44,6 @@ export default function InputListComponent({ newInputList[idx] = event.target.value; onChange(newInputList); }} - onKeyDown={(e) => { - if (e.ctrlKey && e.key === "Backspace") { - e.preventDefault(); - e.stopPropagation(); - } - }} /> {idx === value.length - 1 ? (
diff --git a/src/frontend/src/components/intComponent/index.tsx b/src/frontend/src/components/intComponent/index.tsx index d9d3504ae..25ac37369 100644 --- a/src/frontend/src/components/intComponent/index.tsx +++ b/src/frontend/src/components/intComponent/index.tsx @@ -25,11 +25,11 @@ export default function IntComponent({ id={id} onKeyDown={(event) => { handleOnlyIntegerInput(event); - handleKeyDown(event, value, "0"); + handleKeyDown(event, value, ""); }} type="number" step="1" - min={min} + min={0} onInput={(event: React.ChangeEvent) => { if (Number(event.target.value) < min) { event.target.value = min.toString(); diff --git a/src/frontend/src/modals/ConfirmationModal/index.tsx b/src/frontend/src/modals/ConfirmationModal/index.tsx index 11d711912..e5b8c629f 100644 --- a/src/frontend/src/modals/ConfirmationModal/index.tsx +++ b/src/frontend/src/modals/ConfirmationModal/index.tsx @@ -1,29 +1,29 @@ import React, { useEffect, useState } from "react"; import ShadTooltip from "../../components/ShadTooltipComponent"; import { Button } from "../../components/ui/button"; -import { ConfirmationModalType, ContentProps } from "../../types/components"; +import { ConfirmationModalType, ContentProps, TriggerProps } from "../../types/components"; import { nodeIconsLucide } from "../../utils/styleUtils"; import BaseModal from "../baseModal"; const Content: React.FC = ({ children }) => { return
{children}
; }; -const Trigger: React.FC = ({ +const Trigger: React.FC = ({ children, - tolltipContent, + tooltipContent, side, + asChild, }) => { - return tolltipContent ? ( - + return asChild ? children : (tooltipContent ? ( +
{children}
) : (
{children}
- ); + )); }; function ConfirmationModal({ title, - asChild, titleHeader, modalContentTitle, cancelText, @@ -49,6 +49,7 @@ function ConfirmationModal({ useEffect(() => { if (onClose) onClose!(modalOpen); }, [modalOpen]); + const triggerChild = React.Children.toArray(children).find( (child) => (child as React.ReactElement).type === Trigger ); @@ -58,7 +59,13 @@ function ConfirmationModal({ return ( - {triggerChild} + <> + {triggerChild && ( + + {triggerChild} + + )} + {title} = ({ children, asChild, disable }) => {
diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts index 8de906c90..273ee2533 100644 --- a/src/frontend/src/stores/flowStore.ts +++ b/src/frontend/src/stores/flowStore.ts @@ -42,13 +42,11 @@ const useFlowStore = create((set, get) => ({ 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; @@ -96,29 +94,6 @@ const useFlowStore = create((set, get) => ({ getNode: (id: string) => { return get().nodes.find((node) => node.id === id); }, - onConnect: (connection: Connection) => { - set({ - 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) => { get().setNodes( get().nodes.filter((node) => @@ -229,10 +204,6 @@ const useFlowStore = create((set, get) => ({ }); set({ edges: newEdges }); }, - isPending: false, - setPending: (pending: boolean) => { - set({ isPending: pending }); - }, })); export default useFlowStore; diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index 210987909..b076f75e4 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -130,7 +130,6 @@ const useFlowsManagerStore = create((set, get) => ({ ); //update tabs state - useFlowStore.setState({ isPending: false }); resolve(); } }) diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts index e2d875f87..cacc7f909 100644 --- a/src/frontend/src/types/zustand/flow/index.ts +++ b/src/frontend/src/types/zustand/flow/index.ts @@ -18,7 +18,6 @@ export type FlowStoreType = { 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; paste: ( @@ -27,6 +26,4 @@ export type FlowStoreType = { ) => void; isBuilt: boolean; setIsBuilt: (isBuilt: boolean) => void; - isPending: boolean; - setPending: (pending: boolean) => void; }; From a76263097ab300c155a2c7f4341aa57c4a9827ef Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Fri, 5 Jan 2024 20:32:23 -0300 Subject: [PATCH 67/89] Add cookies for access and refresh tokens --- src/backend/langflow/api/v1/login.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/backend/langflow/api/v1/login.py b/src/backend/langflow/api/v1/login.py index 9021b40b6..2ff2858a4 100644 --- a/src/backend/langflow/api/v1/login.py +++ b/src/backend/langflow/api/v1/login.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import Response, APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from sqlmodel import Session @@ -16,6 +16,7 @@ router = APIRouter(tags=["Login"]) @router.post("/login", response_model=Token) async def login_to_get_access_token( + response: Response, form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_session), # _: Session = Depends(get_current_active_user) @@ -31,7 +32,10 @@ async def login_to_get_access_token( ) from exc if user: - return create_user_tokens(user_id=user.id, db=db, update_last_login=True) + tokens = create_user_tokens(user_id=user.id, db=db, update_last_login=True) + response.set_cookie("refresh_token_lf", tokens["refresh_token"], httponly=True, secure=True, samesite="strict") + response.set_cookie("access_token_lf", tokens["access_token"], httponly=False, secure=True, samesite="strict") + return tokens else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -55,9 +59,12 @@ async def auto_login(db: Session = Depends(get_session), settings_service=Depend @router.post("/refresh") -async def refresh_token(token: str): +async def refresh_token(response: Response, token: str): if token: - return create_refresh_token(token) + tokens = create_refresh_token(token) + response.set_cookie("refresh_token_lf", tokens["refresh_token"], httponly=True, secure=True, samesite="strict") + response.set_cookie("access_token_lf", tokens["access_token"], httponly=False, secure=True, samesite="strict") + return tokens else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, From f8f71fb60311e6caeb23eec9ce07c11fe60e26e5 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Fri, 5 Jan 2024 20:33:12 -0300 Subject: [PATCH 68/89] Refactor code and fix formatting issues --- .../components/parameterComponent/index.tsx | 2 +- .../src/components/authAdminGuard/index.tsx | 11 +-- .../src/components/authGuard/index.tsx | 3 +- src/frontend/src/contexts/authContext.tsx | 7 +- src/frontend/src/contexts/typesContext.tsx | 98 +++++++++++++++++++ .../src/pages/AdminPage/LoginPage/index.tsx | 2 +- src/frontend/src/pages/loginPage/index.tsx | 2 +- 7 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 src/frontend/src/contexts/typesContext.tsx diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index b688fdb7b..06f9d0b0a 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -300,7 +300,7 @@ export default function ParameterComponent({ ) : (
<>
{ - const { - isAdmin, - isAuthenticated, - logout, - userData, - autoLogin, - } = useContext(AuthContext); + const { isAdmin, isAuthenticated, logout, userData, autoLogin } = + useContext(AuthContext); if (!isAuthenticated) { logout(); diff --git a/src/frontend/src/components/authGuard/index.tsx b/src/frontend/src/components/authGuard/index.tsx index 713862e00..93735e977 100644 --- a/src/frontend/src/components/authGuard/index.tsx +++ b/src/frontend/src/components/authGuard/index.tsx @@ -3,8 +3,7 @@ import { Navigate } from "react-router-dom"; import { AuthContext } from "../../contexts/authContext"; export const ProtectedRoute = ({ children }) => { - const { isAuthenticated, logout} = - useContext(AuthContext); + const { isAuthenticated, logout } = useContext(AuthContext); if (!isAuthenticated) { logout(); return ; diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index e00ac93b6..74969d9f2 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -81,7 +81,7 @@ export function AuthProvider({ children }): React.ReactElement { }); }, [setUserData, setLoading, autoLogin, setIsAdmin]); - function getUser(){ + function getUser() { getLoggedUser() .then((user) => { setUserData(user); @@ -101,8 +101,9 @@ export function AuthProvider({ children }): React.ReactElement { setAccessToken(newAccessToken); setRefreshToken(refreshToken); setIsAuthenticated(true); - setTimeout(() => {getUser();}, 500) - + setTimeout(() => { + getUser(); + }, 500); } function logout() { diff --git a/src/frontend/src/contexts/typesContext.tsx b/src/frontend/src/contexts/typesContext.tsx new file mode 100644 index 000000000..c785495aa --- /dev/null +++ b/src/frontend/src/contexts/typesContext.tsx @@ -0,0 +1,98 @@ +import _ from "lodash"; +import { createContext, ReactNode, useState } from "react"; +import { getAll, getHealth } from "../controllers/API"; +import useAlertStore from "../stores/alertStore"; +import { APIKindType } from "../types/api"; +import { typesContextType } from "../types/typesContext"; + +//context to share types adn functions from nodes to flow + +const initialValue: typesContextType = { + types: {}, + setTypes: () => {}, + templates: {}, + setTemplates: () => {}, + data: {}, + setData: () => {}, + getTypes: () => {}, + setFetchError: () => {}, + fetchError: false, + setFilterEdge: (filter) => {}, + getFilterEdge: [], +}; + +export const typesContext = createContext(initialValue); + +export function TypesProvider({ children }: { children: ReactNode }) { + const [types, setTypes] = useState({}); + const [templates, setTemplates] = useState({}); + const [data, setData] = useState({}); + const [fetchError, setFetchError] = useState(false); + const setLoading = useAlertStore((state) => state.setLoading); + const [getFilterEdge, setFilterEdge] = useState([]); + + async function getTypes(): Promise { + // We will keep a flag to handle the case where the component is unmounted before the API call resolves. + let isMounted = true; + try { + const result = await getAll(); + // Make sure to only update the state if the component is still mounted. + if (isMounted && result?.status === 200) { + setLoading(false); + let { data } = _.cloneDeep(result); + setData((old) => ({ ...old, ...data })); + setTemplates( + Object.keys(data).reduce((acc, curr) => { + Object.keys(data[curr]).forEach((c: keyof APIKindType) => { + //prevent wrong overwriting of the component template by a group of the same type + if (!data[curr][c].flow) acc[c] = data[curr][c]; + }); + return acc; + }, {}) + ); + // Set the types by reducing over the keys of the result data and updating the accumulator. + setTypes( + // Reverse the keys so the tool world does not overlap + Object.keys(data) + .reverse() + .reduce((acc, curr) => { + Object.keys(data[curr]).forEach((c: keyof APIKindType) => { + acc[c] = curr; + // Add the base classes to the accumulator as well. + data[curr][c].base_classes?.forEach((b) => { + acc[b] = curr; + }); + }); + return acc; + }, {}) + ); + } + } catch (error) { + console.error("An error has occurred while fetching types."); + console.log(error); + await getHealth().catch((e) => { + setFetchError(true); + }); + } + } + + return ( + + {children} + + ); +} diff --git a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx index 3d0530932..765567ca9 100644 --- a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx @@ -4,7 +4,7 @@ import { Button } from "../../../components/ui/button"; import { Input } from "../../../components/ui/input"; import { CONTROL_LOGIN_STATE } from "../../../constants/constants"; import { AuthContext } from "../../../contexts/authContext"; -import { getLoggedUser, onLogin } from "../../../controllers/API"; +import { onLogin } from "../../../controllers/API"; import useAlertStore from "../../../stores/alertStore"; import { LoginType } from "../../../types/api"; import { diff --git a/src/frontend/src/pages/loginPage/index.tsx b/src/frontend/src/pages/loginPage/index.tsx index c8dc0815c..1c005da58 100644 --- a/src/frontend/src/pages/loginPage/index.tsx +++ b/src/frontend/src/pages/loginPage/index.tsx @@ -6,7 +6,7 @@ import { Button } from "../../components/ui/button"; import { Input } from "../../components/ui/input"; import { CONTROL_LOGIN_STATE } from "../../constants/constants"; import { AuthContext } from "../../contexts/authContext"; -import { getLoggedUser, onLogin } from "../../controllers/API"; +import { onLogin } from "../../controllers/API"; import useAlertStore from "../../stores/alertStore"; import { LoginType } from "../../types/api"; import { From c7435c0c498c6144bdacf1d48ce8e5bda8915352 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 8 Jan 2024 13:38:36 -0300 Subject: [PATCH 69/89] Revert "Refactor code and fix formatting issues" This reverts commit 334ec24addf783990850b835d911ac778dfce6a2. --- .../components/parameterComponent/index.tsx | 2 +- src/frontend/src/components/authAdminGuard/index.tsx | 11 ++++++++--- src/frontend/src/components/authGuard/index.tsx | 3 ++- src/frontend/src/contexts/authContext.tsx | 7 +++---- src/frontend/src/contexts/typesContext.tsx | 9 ++++++++- src/frontend/src/pages/AdminPage/LoginPage/index.tsx | 2 +- .../pages/FlowPage/components/PageComponent/index.tsx | 2 ++ src/frontend/src/pages/loginPage/index.tsx | 2 +- 8 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 06f9d0b0a..b688fdb7b 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -300,7 +300,7 @@ export default function ParameterComponent({ ) : (
<>
{ - const { isAdmin, isAuthenticated, logout, userData, autoLogin } = - useContext(AuthContext); + const { + isAdmin, + isAuthenticated, + logout, + userData, + autoLogin, + } = useContext(AuthContext); if (!isAuthenticated) { logout(); diff --git a/src/frontend/src/components/authGuard/index.tsx b/src/frontend/src/components/authGuard/index.tsx index 93735e977..713862e00 100644 --- a/src/frontend/src/components/authGuard/index.tsx +++ b/src/frontend/src/components/authGuard/index.tsx @@ -3,7 +3,8 @@ import { Navigate } from "react-router-dom"; import { AuthContext } from "../../contexts/authContext"; export const ProtectedRoute = ({ children }) => { - const { isAuthenticated, logout } = useContext(AuthContext); + const { isAuthenticated, logout} = + useContext(AuthContext); if (!isAuthenticated) { logout(); return ; diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index 74969d9f2..e00ac93b6 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -81,7 +81,7 @@ export function AuthProvider({ children }): React.ReactElement { }); }, [setUserData, setLoading, autoLogin, setIsAdmin]); - function getUser() { + function getUser(){ getLoggedUser() .then((user) => { setUserData(user); @@ -101,9 +101,8 @@ export function AuthProvider({ children }): React.ReactElement { setAccessToken(newAccessToken); setRefreshToken(refreshToken); setIsAuthenticated(true); - setTimeout(() => { - getUser(); - }, 500); + setTimeout(() => {getUser();}, 500) + } function logout() { diff --git a/src/frontend/src/contexts/typesContext.tsx b/src/frontend/src/contexts/typesContext.tsx index c785495aa..16deecf00 100644 --- a/src/frontend/src/contexts/typesContext.tsx +++ b/src/frontend/src/contexts/typesContext.tsx @@ -1,9 +1,16 @@ import _ from "lodash"; -import { createContext, ReactNode, useState } from "react"; +import { + createContext, + ReactNode, + useContext, + useEffect, + useState, +} from "react"; import { getAll, getHealth } from "../controllers/API"; import useAlertStore from "../stores/alertStore"; import { APIKindType } from "../types/api"; import { typesContextType } from "../types/typesContext"; +import { AuthContext } from "./authContext"; //context to share types adn functions from nodes to flow diff --git a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx index 765567ca9..3d0530932 100644 --- a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx @@ -4,7 +4,7 @@ import { Button } from "../../../components/ui/button"; import { Input } from "../../../components/ui/input"; import { CONTROL_LOGIN_STATE } from "../../../constants/constants"; import { AuthContext } from "../../../contexts/authContext"; -import { onLogin } from "../../../controllers/API"; +import { getLoggedUser, onLogin } from "../../../controllers/API"; import useAlertStore from "../../../stores/alertStore"; import { LoginType } from "../../../types/api"; import { diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 9f94871f6..d4b2b8e3b 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -18,6 +18,8 @@ import ReactFlow, { SelectionDragHandler, addEdge, updateEdge, + useEdgesState, + useNodesState, } from "reactflow"; import GenericNode from "../../../../CustomNodes/GenericNode"; import Chat from "../../../../components/chatComponent"; diff --git a/src/frontend/src/pages/loginPage/index.tsx b/src/frontend/src/pages/loginPage/index.tsx index 1c005da58..c8dc0815c 100644 --- a/src/frontend/src/pages/loginPage/index.tsx +++ b/src/frontend/src/pages/loginPage/index.tsx @@ -6,7 +6,7 @@ import { Button } from "../../components/ui/button"; import { Input } from "../../components/ui/input"; import { CONTROL_LOGIN_STATE } from "../../constants/constants"; import { AuthContext } from "../../contexts/authContext"; -import { onLogin } from "../../controllers/API"; +import { getLoggedUser, onLogin } from "../../controllers/API"; import useAlertStore from "../../stores/alertStore"; import { LoginType } from "../../types/api"; import { From 3d4200fc82c1d04d1369de684935ee6830d565db Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 8 Jan 2024 13:50:34 -0300 Subject: [PATCH 70/89] Add logout functionality to login API --- src/backend/langflow/api/v1/login.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/backend/langflow/api/v1/login.py b/src/backend/langflow/api/v1/login.py index 2ff2858a4..ff8ba14c9 100644 --- a/src/backend/langflow/api/v1/login.py +++ b/src/backend/langflow/api/v1/login.py @@ -71,3 +71,10 @@ async def refresh_token(response: Response, token: str): detail="Invalid refresh token", headers={"WWW-Authenticate": "Bearer"}, ) + + +@router.post("/logout") +async def logout(response: Response): + response.delete_cookie("refresh_token_lf") + response.delete_cookie("access_token_lf") + return {"message": "Logout successful"} From 8a0b240a7f6409bc6509101c435c2882f4c5c519 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 8 Jan 2024 13:51:22 -0300 Subject: [PATCH 71/89] code format --- src/frontend/src/App.tsx | 5 +-- .../components/parameterComponent/index.tsx | 16 +++----- .../src/CustomNodes/GenericNode/index.tsx | 6 +-- .../src/components/authAdminGuard/index.tsx | 11 ++---- .../src/components/authGuard/index.tsx | 3 +- .../src/components/cardComponent/index.tsx | 4 +- .../chatComponent/buildTrigger/index.tsx | 3 +- .../src/components/chatComponent/index.tsx | 12 +++--- .../components/menuBar/index.tsx | 6 +-- .../src/components/headerComponent/index.tsx | 5 ++- .../components/inputFileComponent/index.tsx | 4 +- .../src/components/intComponent/index.tsx | 5 ++- src/frontend/src/components/ui/checkbox.tsx | 10 ++++- src/frontend/src/contexts/authContext.tsx | 9 +++-- src/frontend/src/contexts/index.tsx | 4 +- src/frontend/src/contexts/typesContext.tsx | 9 +---- src/frontend/src/modals/ApiModal/index.tsx | 14 ++++++- src/frontend/src/modals/exportModal/index.tsx | 6 +-- .../src/modals/flowSettingsModal/index.tsx | 4 +- src/frontend/src/modals/formModal/index.tsx | 17 ++++----- src/frontend/src/modals/shareModal/index.tsx | 6 +-- .../src/pages/AdminPage/LoginPage/index.tsx | 2 +- src/frontend/src/pages/AdminPage/index.tsx | 14 +++---- .../components/PageComponent/index.tsx | 14 +++---- .../components/nodeToolbarComponent/index.tsx | 37 +++++++++---------- .../MainPage/components/components/index.tsx | 4 +- src/frontend/src/pages/MainPage/index.tsx | 6 +-- .../src/pages/ProfileSettingsPage/index.tsx | 6 ++- src/frontend/src/pages/StorePage/index.tsx | 6 ++- src/frontend/src/pages/ViewPage/index.tsx | 10 ++--- src/frontend/src/pages/loginPage/index.tsx | 2 +- src/frontend/src/stores/flowStore.ts | 1 - src/frontend/src/stores/flowsManagerStore.ts | 22 +++++++---- src/frontend/src/types/components/index.ts | 9 ++--- src/frontend/src/types/zustand/flow/index.ts | 1 - .../src/types/zustand/flowsManager/index.ts | 36 +++++++++++++++--- src/frontend/src/types/zustand/types/index.ts | 20 +++++----- src/frontend/src/utils/reactflowUtils.ts | 4 +- src/frontend/src/utils/utils.ts | 5 ++- 39 files changed, 191 insertions(+), 167 deletions(-) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 7e4d80f4d..5b00268b7 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -17,12 +17,12 @@ import { } from "./constants/constants"; import { AuthContext } from "./contexts/authContext"; import { locationContext } from "./contexts/locationContext"; -import { getHealth, getRepoStars, getVersion } from "./controllers/API"; +import { getHealth } from "./controllers/API"; import Router from "./routes"; import useAlertStore from "./stores/alertStore"; -import { useTypesStore } from "./stores/typesStore"; import { useDarkStore } from "./stores/darkStore"; import useFlowsManagerStore from "./stores/flowsManagerStore"; +import { useTypesStore } from "./stores/typesStore"; export default function App() { let { setCurrent, setShowSideBar, setIsStackedOpen } = @@ -147,7 +147,6 @@ export default function App() { refreshFlows(); }); } - }, [isAuthenticated]); useEffect(() => { diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index b688fdb7b..a76a15d17 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -1,11 +1,5 @@ import { cloneDeep } from "lodash"; -import React, { - ReactNode, - useContext, - useEffect, - useRef, - useState, -} from "react"; +import React, { ReactNode, useEffect, useRef, useState } from "react"; import { Handle, Position } from "reactflow"; import ShadTooltip from "../../../../components/ShadTooltipComponent"; import CodeAreaComponent from "../../../../components/codeAreaComponent"; @@ -29,6 +23,8 @@ import { import { postCustomComponentUpdate } from "../../../../controllers/API"; import useAlertStore from "../../../../stores/alertStore"; import useFlowStore from "../../../../stores/flowStore"; +import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; +import { useTypesStore } from "../../../../stores/typesStore"; import { APIClassType } from "../../../../types/api"; import { ParameterComponentType } from "../../../../types/components"; import { NodeDataType } from "../../../../types/flow"; @@ -45,8 +41,6 @@ import { nodeNames, } from "../../../../utils/styleUtils"; import { classNames, groupByFamily } from "../../../../utils/utils"; -import { useTypesStore } from "../../../../stores/typesStore"; -import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; export default function ParameterComponent({ left, @@ -112,7 +106,7 @@ export default function ParameterComponent({ if (data.node!.template[name].value !== newValue) { takeSnapshot(); } - + data.node!.template[name].value = newValue; // necessary to enable ctrl+z inside the input setNode(data.id, (oldNode) => { @@ -300,7 +294,7 @@ export default function ParameterComponent({ ) : (
<>
{ - const { - isAdmin, - isAuthenticated, - logout, - userData, - autoLogin, - } = useContext(AuthContext); + const { isAdmin, isAuthenticated, logout, userData, autoLogin } = + useContext(AuthContext); if (!isAuthenticated) { logout(); diff --git a/src/frontend/src/components/authGuard/index.tsx b/src/frontend/src/components/authGuard/index.tsx index 713862e00..93735e977 100644 --- a/src/frontend/src/components/authGuard/index.tsx +++ b/src/frontend/src/components/authGuard/index.tsx @@ -3,8 +3,7 @@ import { Navigate } from "react-router-dom"; import { AuthContext } from "../../contexts/authContext"; export const ProtectedRoute = ({ children }) => { - const { isAuthenticated, logout} = - useContext(AuthContext); + const { isAuthenticated, logout } = useContext(AuthContext); if (!isAuthenticated) { logout(); return ; diff --git a/src/frontend/src/components/cardComponent/index.tsx b/src/frontend/src/components/cardComponent/index.tsx index d87419fe2..a253f1f5e 100644 --- a/src/frontend/src/components/cardComponent/index.tsx +++ b/src/frontend/src/components/cardComponent/index.tsx @@ -1,7 +1,8 @@ -import { useContext, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { getComponent, postLikeComponent } from "../../controllers/API"; import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal"; import useAlertStore from "../../stores/alertStore"; +import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { useStoreStore } from "../../stores/storeStore"; import { storeComponent } from "../../types/store"; import cloneFLowWithParent from "../../utils/storeUtils"; @@ -17,7 +18,6 @@ import { CardHeader, CardTitle, } from "../ui/card"; -import useFlowsManagerStore from "../../stores/flowsManagerStore"; export default function CollectionCardComponent({ data, diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index 1dcc327ee..e965397a3 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -1,5 +1,5 @@ import { Transition } from "@headlessui/react"; -import { useContext, useState } from "react"; +import { useState } from "react"; import Loading from "../../../components/ui/loading"; import { useSSE } from "../../../contexts/SSEContext"; import { postBuildInit } from "../../../controllers/API"; @@ -9,7 +9,6 @@ import useAlertStore from "../../../stores/alertStore"; import useFlowStore from "../../../stores/flowStore"; import useFlowsManagerStore from "../../../stores/flowsManagerStore"; import { parsedDataType } from "../../../types/components"; -import { FlowState } from "../../../types/tabs"; import { validateNodes } from "../../../utils/reactflowUtils"; import RadialProgressComponent from "../../RadialProgress"; import IconComponent from "../../genericIconComponent"; diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 80d6fb991..9e0026898 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useNodes } from "reactflow"; import { ChatType } from "../../types/chat"; import BuildTrigger from "./buildTrigger"; @@ -8,15 +8,17 @@ import * as _ from "lodash"; import { getBuildStatus } from "../../controllers/API"; import FormModal from "../../modals/formModal"; import useFlowStore from "../../stores/flowStore"; -import { NodeType } from "../../types/flow"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; +import { NodeType } from "../../types/flow"; export default function Chat({ flow }: ChatType): JSX.Element { const [open, setOpen] = useState(false); const [canOpen, setCanOpen] = useState(false); const isBuilt = useFlowStore((state) => state.isBuilt); const setIsBuilt = useFlowStore((state) => state.setIsBuilt); - const currentFlowState = useFlowsManagerStore((state) => state.currentFlowState); + const currentFlowState = useFlowsManagerStore( + (state) => state.currentFlowState + ); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { @@ -53,9 +55,7 @@ export default function Chat({ flow }: ChatType): JSX.Element { const currentNodes = nodes.map((node: NodeType) => _.cloneDeep(node.data.node?.template) ); - if ( - JSON.stringify(prevNodes) !== JSON.stringify(currentNodes) - ) { + if (JSON.stringify(prevNodes) !== JSON.stringify(currentNodes)) { setIsBuilt(false); } if ( diff --git a/src/frontend/src/components/headerComponent/components/menuBar/index.tsx b/src/frontend/src/components/headerComponent/components/menuBar/index.tsx index 20ac20f04..a4ff37fae 100644 --- a/src/frontend/src/components/headerComponent/components/menuBar/index.tsx +++ b/src/frontend/src/components/headerComponent/components/menuBar/index.tsx @@ -1,4 +1,4 @@ -import { useContext, useState } from "react"; +import { useState } from "react"; import { DropdownMenu, DropdownMenuContent, @@ -49,9 +49,7 @@ export const MenuBar = (): JSX.Element => { diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index b4a6a9fff..111e1d046 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -166,7 +166,10 @@ export default function Header(): JSX.Element {
+ )) : null} {flowState?.memory_keys!.map((key, index) => (
@@ -628,15 +614,11 @@ export default function FormModal({ setChatValue={(value) => { setChatValue(value); if (flowState && chatKey) { - setFlowState( - (old: FlowState | undefined) => { - let newFlowState = cloneDeep(old!); - newFlowState.input_keys![ - chatKey - ] = value; - return newFlowState; - } - ); + setFlowState((old: FlowState | undefined) => { + let newFlowState = cloneDeep(old!); + newFlowState.input_keys![chatKey] = value; + return newFlowState; + }); } }} inputRef={ref} diff --git a/src/frontend/src/modals/shareModal/index.tsx b/src/frontend/src/modals/shareModal/index.tsx index 438b1247c..a5697f2f3 100644 --- a/src/frontend/src/modals/shareModal/index.tsx +++ b/src/frontend/src/modals/shareModal/index.tsx @@ -1,5 +1,5 @@ import { Loader2 } from "lucide-react"; -import { ReactNode, useContext, useEffect, useMemo, useState } from "react"; +import { ReactNode, useEffect, useMemo, useState } from "react"; import EditFlowSettings from "../../components/EditFlowSettingsComponent"; import IconComponent from "../../components/genericIconComponent"; import { TagsSelector } from "../../components/tagsSelectorComponent"; @@ -12,6 +12,8 @@ import { updateFlowStore, } from "../../controllers/API"; import useAlertStore from "../../stores/alertStore"; +import { useDarkStore } from "../../stores/darkStore"; +import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { useStoreStore } from "../../stores/storeStore"; import { FlowType } from "../../types/flow"; import { @@ -22,8 +24,6 @@ import { import { getTagsIds } from "../../utils/storeUtils"; import ConfirmationModal from "../ConfirmationModal"; import BaseModal from "../baseModal"; -import { useDarkStore } from "../../stores/darkStore"; -import useFlowsManagerStore from "../../stores/flowsManagerStore"; export default function ShareModal({ component, diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index e206c0b5d..15537d5a6 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -30,9 +30,9 @@ import { import ConfirmationModal from "../../modals/ConfirmationModal"; import UserManagementModal from "../../modals/UserManagementModal"; import useAlertStore from "../../stores/alertStore"; +import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { Users } from "../../types/api"; import { UserInputType } from "../../types/components"; -import useFlowsManagerStore from "../../stores/flowsManagerStore"; export default function AdminPage() { const [inputValue, setInputValue] = useState(""); @@ -44,7 +44,9 @@ export default function AdminPage() { const setErrorData = useAlertStore((state) => state.setErrorData); const { userData } = useContext(AuthContext); const [totalRowsCount, setTotalRowsCount] = useState(0); - const setCurrentFlowId = useFlowsManagerStore((state) => state.setCurrentFlowId); + const setCurrentFlowId = useFlowsManagerStore( + (state) => state.setCurrentFlowId + ); // set null id useEffect(() => { @@ -333,9 +335,7 @@ export default function AdminPage() {
- +
@@ -367,9 +367,7 @@ export default function AdminPage() {
- +
diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index cd2a5214c..82c2c426a 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -18,6 +18,7 @@ import Loading from "../../../../components/ui/loading"; import useAlertStore from "../../../../stores/alertStore"; import useFlowStore from "../../../../stores/flowStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; +import { useLocationStore } from "../../../../stores/locationStore"; import { useTypesStore } from "../../../../stores/typesStore"; import { APIClassType } from "../../../../types/api"; import { FlowType, NodeType, targetHandleType } from "../../../../types/flow"; @@ -33,7 +34,6 @@ import { cn, getRandomName, isWrappedWithClass } from "../../../../utils/utils"; import ConnectionLineComponent from "../ConnectionLineComponent"; import SelectionMenu from "../SelectionMenuComponent"; import ExtraSidebar from "../extraSidebarComponent"; -import { useLocationStore } from "../../../../stores/locationStore"; const nodeTypes = { genericNode: GenericNode, @@ -179,7 +179,7 @@ export default function Page({ nodes: flow?.data?.nodes ?? [], edges: flow?.data?.edges ?? [], viewport: flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 }, - }) + }); } // Clear the previous timeout diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index 97aaca9f3..10c984229 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -1,5 +1,5 @@ import { cloneDeep } from "lodash"; -import { useContext, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import ShadTooltip from "../../../../components/ShadTooltipComponent"; import IconComponent from "../../../../components/genericIconComponent"; import { @@ -11,7 +11,9 @@ import { import ConfirmationModal from "../../../../modals/ConfirmationModal"; import EditNodeModal from "../../../../modals/EditNodeModal"; import ShareModal from "../../../../modals/shareModal"; +import { useDarkStore } from "../../../../stores/darkStore"; import useFlowStore from "../../../../stores/flowStore"; +import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import { useStoreStore } from "../../../../stores/storeStore"; import { nodeToolbarPropsType } from "../../../../types/components"; import { FlowType } from "../../../../types/flow"; @@ -22,8 +24,6 @@ import { updateFlowPosition, } from "../../../../utils/reactflowUtils"; import { classNames } from "../../../../utils/utils"; -import { useDarkStore } from "../../../../stores/darkStore"; -import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; export default function NodeToolbarComponent({ data, @@ -33,22 +33,21 @@ export default function NodeToolbarComponent({ numberOfHandles, showNode, }: nodeToolbarPropsType): JSX.Element { - const nodeLength = - Object.keys(data.node!.template).filter( - (templateField) => - templateField.charAt(0) !== "_" && - data.node?.template[templateField].show && - (data.node.template[templateField].type === "str" || - data.node.template[templateField].type === "bool" || - data.node.template[templateField].type === "float" || - data.node.template[templateField].type === "code" || - data.node.template[templateField].type === "prompt" || - data.node.template[templateField].type === "file" || - data.node.template[templateField].type === "Any" || - data.node.template[templateField].type === "int" || - data.node.template[templateField].type === "dict" || - data.node.template[templateField].type === "NestedDict") - ).length; + const nodeLength = Object.keys(data.node!.template).filter( + (templateField) => + templateField.charAt(0) !== "_" && + data.node?.template[templateField].show && + (data.node.template[templateField].type === "str" || + data.node.template[templateField].type === "bool" || + data.node.template[templateField].type === "float" || + data.node.template[templateField].type === "code" || + data.node.template[templateField].type === "prompt" || + data.node.template[templateField].type === "file" || + data.node.template[templateField].type === "Any" || + data.node.template[templateField].type === "int" || + data.node.template[templateField].type === "dict" || + data.node.template[templateField].type === "NestedDict") + ).length; const hasStore = useStoreStore((state) => state.hasStore); const hasApiKey = useStoreStore((state) => state.hasApiKey); diff --git a/src/frontend/src/pages/MainPage/components/components/index.tsx b/src/frontend/src/pages/MainPage/components/components/index.tsx index 5ee0821b2..6c48cc80e 100644 --- a/src/frontend/src/pages/MainPage/components/components/index.tsx +++ b/src/frontend/src/pages/MainPage/components/components/index.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import PaginatorComponent from "../../../../components/PaginatorComponent"; import CollectionCardComponent from "../../../../components/cardComponent"; @@ -7,8 +7,8 @@ import IconComponent from "../../../../components/genericIconComponent"; import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent"; import { Button } from "../../../../components/ui/button"; import useAlertStore from "../../../../stores/alertStore"; -import { FlowType } from "../../../../types/flow"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; +import { FlowType } from "../../../../types/flow"; export default function ComponentsComponent({ is_component = true, diff --git a/src/frontend/src/pages/MainPage/index.tsx b/src/frontend/src/pages/MainPage/index.tsx index 74b3e224c..6e0c5cb55 100644 --- a/src/frontend/src/pages/MainPage/index.tsx +++ b/src/frontend/src/pages/MainPage/index.tsx @@ -1,5 +1,5 @@ import { Group, ToyBrick } from "lucide-react"; -import { useContext, useEffect } from "react"; +import { useEffect } from "react"; import { Outlet, useLocation, useNavigate } from "react-router-dom"; import DropdownButton from "../../components/DropdownButtonComponent"; import IconComponent from "../../components/genericIconComponent"; @@ -16,9 +16,7 @@ export default function HomePage(): JSX.Element { const setCurrentFlowId = useFlowsManagerStore( (state) => state.setCurrentFlowId ); - const uploadFlows = useFlowsManagerStore( - (state) => state.uploadFlows - ); + const uploadFlows = useFlowsManagerStore((state) => state.uploadFlows); const setSuccessData = useAlertStore((state) => state.setSuccessData); const setErrorData = useAlertStore((state) => state.setErrorData); const location = useLocation(); diff --git a/src/frontend/src/pages/ProfileSettingsPage/index.tsx b/src/frontend/src/pages/ProfileSettingsPage/index.tsx index f1e9d2f5d..6a5bc7b70 100644 --- a/src/frontend/src/pages/ProfileSettingsPage/index.tsx +++ b/src/frontend/src/pages/ProfileSettingsPage/index.tsx @@ -10,14 +10,16 @@ import { CONTROL_PATCH_USER_STATE } from "../../constants/constants"; import { AuthContext } from "../../contexts/authContext"; import { resetPassword, updateUser } from "../../controllers/API"; import useAlertStore from "../../stores/alertStore"; +import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { inputHandlerEventType, patchUserInputStateType, } from "../../types/components"; import { gradients } from "../../utils/styleUtils"; -import useFlowsManagerStore from "../../stores/flowsManagerStore"; export default function ProfileSettingsPage(): JSX.Element { - const setCurrentFlowId = useFlowsManagerStore((state) => state.setCurrentFlowId); + const setCurrentFlowId = useFlowsManagerStore( + (state) => state.setCurrentFlowId + ); const [inputState, setInputState] = useState( CONTROL_PATCH_USER_STATE diff --git a/src/frontend/src/pages/StorePage/index.tsx b/src/frontend/src/pages/StorePage/index.tsx index c54893033..8321be91d 100644 --- a/src/frontend/src/pages/StorePage/index.tsx +++ b/src/frontend/src/pages/StorePage/index.tsx @@ -28,10 +28,10 @@ import { } from "../../controllers/API"; import StoreApiKeyModal from "../../modals/StoreApiKeyModal"; import useAlertStore from "../../stores/alertStore"; +import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { useStoreStore } from "../../stores/storeStore"; import { storeComponent } from "../../types/store"; import { cn } from "../../utils/utils"; -import useFlowsManagerStore from "../../stores/flowsManagerStore"; export default function StorePage(): JSX.Element { const hasApiKey = useStoreStore((state) => state.hasApiKey); @@ -45,7 +45,9 @@ export default function StorePage(): JSX.Element { const { apiKey } = useContext(AuthContext); const setErrorData = useAlertStore((state) => state.setErrorData); - const setCurrentFlowId = useFlowsManagerStore((state) => state.setCurrentFlowId); + const setCurrentFlowId = useFlowsManagerStore( + (state) => state.setCurrentFlowId + ); const [loading, setLoading] = useState(true); const [loadingTags, setLoadingTags] = useState(true); const { id } = useParams(); diff --git a/src/frontend/src/pages/ViewPage/index.tsx b/src/frontend/src/pages/ViewPage/index.tsx index 3f5f2db98..2e6ac9bac 100644 --- a/src/frontend/src/pages/ViewPage/index.tsx +++ b/src/frontend/src/pages/ViewPage/index.tsx @@ -1,11 +1,13 @@ import { useEffect } from "react"; import { useParams } from "react-router-dom"; -import Page from "../FlowPage/components/PageComponent"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; +import Page from "../FlowPage/components/PageComponent"; export default function ViewPage() { const currentFlow = useFlowsManagerStore((state) => state.currentFlow); - const setCurrentFlowId = useFlowsManagerStore((state) => state.setCurrentFlowId); + const setCurrentFlowId = useFlowsManagerStore( + (state) => state.setCurrentFlowId + ); const { id } = useParams(); // Set flow tab id @@ -15,9 +17,7 @@ export default function ViewPage() { return (
- {currentFlow && ( - - )} + {currentFlow && }
); } diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts index 18bd40bea..09cf6e541 100644 --- a/src/frontend/src/stores/flowStore.ts +++ b/src/frontend/src/stores/flowStore.ts @@ -1,6 +1,5 @@ import { cloneDeep } from "lodash"; import { - Connection, Edge, EdgeChange, Node, @@ -33,11 +32,10 @@ const useFlowStore = create((set, get) => ({ nodes: [], edges: [], isBuilding: false, - isBuilt: false, + isBuilt: false, reactFlowInstance: null, lastCopiedSelection: null, - resetFlow: ({ nodes, edges, viewport }) => { set({ nodes, @@ -45,7 +43,6 @@ const useFlowStore = create((set, get) => ({ flowState: undefined, sseData: {}, isBuilt: false, - }); get().reactFlowInstance!.setViewport(viewport); }, @@ -60,11 +57,9 @@ const useFlowStore = create((set, get) => ({ }, setFlowState: (flowState) => { const newFlowState = - typeof flowState === "function" - ? flowState(get().flowState) - : flowState; - - if(newFlowState !== get().flowState){ + typeof flowState === "function" ? flowState(get().flowState) : flowState; + + if (newFlowState !== get().flowState) { set(() => ({ flowState: newFlowState, })); @@ -87,30 +82,39 @@ const useFlowStore = create((set, get) => ({ let newChange = typeof change === "function" ? change(get().nodes) : change; let newEdges = cleanEdges(newChange, get().edges); - set({ edges: newEdges, nodes: newChange, flowState: undefined, isBuilt: false, sseData: {} }); + set({ + edges: newEdges, + nodes: newChange, + flowState: undefined, + isBuilt: false, + sseData: {}, + }); - const flowsManager = useFlowsManagerStore.getState() + const flowsManager = useFlowsManagerStore.getState(); flowsManager.autoSaveCurrentFlow( - newChange, - newEdges, - get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 } - ); - + newChange, + newEdges, + get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 } + ); }, setEdges: (change) => { let newChange = typeof change === "function" ? change(get().edges) : change; - set({ edges: newChange, flowState: undefined, isBuilt: false, sseData: {} }); + set({ + edges: newChange, + flowState: undefined, + isBuilt: false, + sseData: {}, + }); - const flowsManager = useFlowsManagerStore.getState() + const flowsManager = useFlowsManagerStore.getState(); flowsManager.autoSaveCurrentFlow( - get().nodes, - newChange, - get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 } - ); - + get().nodes, + newChange, + get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 } + ); }, setNode: (id: string, change: Node | ((oldState: Node) => Node)) => { let newChange = diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index e89b2a00f..87050d547 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -9,8 +9,10 @@ import { uploadFlowsToDatabase, } from "../controllers/API"; import { FlowType, NodeDataType } from "../types/flow"; -import { FlowState } from "../types/tabs"; -import { FlowsManagerStoreType, UseUndoRedoOptions } from "../types/zustand/flowsManager"; +import { + FlowsManagerStoreType, + UseUndoRedoOptions, +} from "../types/zustand/flowsManager"; import { addVersionToDuplicates, createFlowComponent, @@ -333,9 +335,11 @@ const useFlowsManagerStore = create((set, get) => ({ JSON.stringify(past[currentFlowId][pastLength - 1]) !== JSON.stringify(newState) ) { - past[currentFlowId] = past[currentFlowId] - .slice(pastLength - defaultOptions.maxHistorySize + 1, pastLength) - + past[currentFlowId] = past[currentFlowId].slice( + pastLength - defaultOptions.maxHistorySize + 1, + pastLength + ); + past[currentFlowId].push(newState); } else { past[currentFlowId] = [newState]; @@ -352,8 +356,11 @@ const useFlowsManagerStore = create((set, get) => ({ if (pastState) { past[currentFlowId] = past[currentFlowId].slice(0, pastLength - 1); - if(!future[currentFlowId]) future[currentFlowId] = []; - future[currentFlowId].push({ nodes: newState.nodes, edges: newState.edges }); + if (!future[currentFlowId]) future[currentFlowId] = []; + future[currentFlowId].push({ + nodes: newState.nodes, + edges: newState.edges, + }); newState.setNodes(pastState.nodes); newState.setEdges(pastState.edges); @@ -368,8 +375,11 @@ const useFlowsManagerStore = create((set, get) => ({ if (futureState) { future[currentFlowId] = future[currentFlowId].slice(0, futureLength - 1); - if(!past[currentFlowId]) past[currentFlowId] = []; - past[currentFlowId].push({ nodes: newState.nodes, edges: newState.edges }); + if (!past[currentFlowId]) past[currentFlowId] = []; + past[currentFlowId].push({ + nodes: newState.nodes, + edges: newState.edges, + }); newState.setNodes(futureState.nodes); newState.setEdges(futureState.edges); diff --git a/src/frontend/src/stores/locationStore.tsx b/src/frontend/src/stores/locationStore.tsx index 247304712..74445d3f3 100644 --- a/src/frontend/src/stores/locationStore.tsx +++ b/src/frontend/src/stores/locationStore.tsx @@ -2,27 +2,27 @@ import { create } from "zustand"; import { LocationStoreType } from "../types/zustand/location"; export const useLocationStore = create((set, get) => ({ - current: window.location.pathname.replace(/\/$/g, "").split("/"), - isStackedOpen: - window.innerWidth > 1024 && window.location.pathname.split("/")[1] - ? true - : false, - setCurrent: (newState) => { - set({ current: newState }); - }, - setIsStackedOpen: (newState) => { - set({ isStackedOpen: newState }); - }, - showSideBar: window.location.pathname.split("/")[1] ? true : false, - setShowSideBar: (newState) => { - set({ showSideBar: newState }); - }, - extraNavigation: { title: "" }, - setExtraNavigation: (newState) => { - set({ extraNavigation: newState }); - }, - extraComponent: <>, - setExtraComponent: (newState) => { - set({ extraComponent: newState }); - }, + current: window.location.pathname.replace(/\/$/g, "").split("/"), + isStackedOpen: + window.innerWidth > 1024 && window.location.pathname.split("/")[1] + ? true + : false, + setCurrent: (newState) => { + set({ current: newState }); + }, + setIsStackedOpen: (newState) => { + set({ isStackedOpen: newState }); + }, + showSideBar: window.location.pathname.split("/")[1] ? true : false, + setShowSideBar: (newState) => { + set({ showSideBar: newState }); + }, + extraNavigation: { title: "" }, + setExtraNavigation: (newState) => { + set({ extraNavigation: newState }); + }, + extraComponent: <>, + setExtraComponent: (newState) => { + set({ extraComponent: newState }); + }, })); diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index d604c8812..0548981ec 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -4,7 +4,6 @@ import { APIClassType, APITemplateType, TemplateVariableType } from "../api"; import { ChatMessageType } from "../chat"; import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index"; import { sourceHandleType, targetHandleType } from "./../flow/index"; -import { TypesStoreType } from "../zustand/types"; export type InputComponentType = { autoFocus?: boolean; onBlur?: (event: React.FocusEvent) => void; @@ -263,7 +262,6 @@ export type LoadingComponentProps = { export type ContentProps = { children: ReactNode; - }; export type HeaderProps = { children: ReactNode; description: string }; export type TriggerProps = { @@ -305,10 +303,9 @@ export type ConfirmationModalType = { modalContentTitle?: string; cancelText: string; confirmationText: string; - children: [ - React.ReactElement, - React.ReactElement - ] | React.ReactElement; + children: + | [React.ReactElement, React.ReactElement] + | React.ReactElement; icon: string; data?: any; index?: number; diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts index a4ded1642..ffaecf0d5 100644 --- a/src/frontend/src/types/zustand/flow/index.ts +++ b/src/frontend/src/types/zustand/flow/index.ts @@ -1,7 +1,6 @@ import { Edge, Node, - OnConnect, OnEdgesChange, OnNodesChange, ReactFlowInstance, @@ -14,11 +13,20 @@ export type FlowStoreType = { sseData: object; isBuilding: boolean; setIsBuilding: (isBuilding: boolean) => void; - resetFlow: (flow: {nodes: Node[], edges: Edge[], viewport: Viewport}) => void; + resetFlow: (flow: { + nodes: Node[]; + edges: Edge[]; + viewport: Viewport; + }) => void; reactFlowInstance: ReactFlowInstance | null; setReactFlowInstance: (newState: ReactFlowInstance) => void; flowState: FlowState | undefined; - setFlowState: (state: FlowState | undefined | ((oldState: FlowState | undefined) => FlowState)) => void; + setFlowState: ( + state: + | FlowState + | undefined + | ((oldState: FlowState | undefined) => FlowState) + ) => void; nodes: Node[]; edges: Edge[]; onNodesChange: OnNodesChange; @@ -39,5 +47,4 @@ export type FlowStoreType = { ) => void; isBuilt: boolean; setIsBuilt: (isBuilt: boolean) => void; - }; diff --git a/src/frontend/src/types/zustand/flowsManager/index.ts b/src/frontend/src/types/zustand/flowsManager/index.ts index a596ee724..2a20c20d8 100644 --- a/src/frontend/src/types/zustand/flowsManager/index.ts +++ b/src/frontend/src/types/zustand/flowsManager/index.ts @@ -1,6 +1,5 @@ -import { Node, Edge, Viewport, XYPosition } from "reactflow"; +import { Edge, Node, Viewport, XYPosition } from "reactflow"; import { FlowType } from "../../flow"; -import { FlowState, FlowsState } from "../../tabs"; export type FlowsManagerStoreType = { flows: Array; @@ -12,13 +11,35 @@ export type FlowsManagerStoreType = { setIsLoading: (isLoading: boolean) => void; refreshFlows: () => Promise; saveFlow: (flow: FlowType, silent?: boolean) => Promise; - autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => void; + autoSaveCurrentFlow: ( + nodes: Node[], + edges: Edge[], + viewport: Viewport + ) => void; uploadFlows: () => Promise; - uploadFlow: ({newProject, file, isComponent, position}: {newProject: boolean, file?: File, isComponent?: boolean, position?: XYPosition}) => Promise; - addFlow: (newProject: boolean, flow?: FlowType, override?: boolean, position?: XYPosition) => Promise; + uploadFlow: ({ + newProject, + file, + isComponent, + position, + }: { + newProject: boolean; + file?: File; + isComponent?: boolean; + position?: XYPosition; + }) => Promise; + addFlow: ( + newProject: boolean, + flow?: FlowType, + override?: boolean, + position?: XYPosition + ) => Promise; deleteComponent: (key: string) => Promise; removeFlow: (id: string) => Promise; - saveComponent: (component: any, override: boolean) => Promise; + saveComponent: ( + component: any, + override: boolean + ) => Promise; undo: () => void; redo: () => void; takeSnapshot: () => void; @@ -27,4 +48,4 @@ export type FlowsManagerStoreType = { export type UseUndoRedoOptions = { maxHistorySize: number; enableShortcuts: boolean; -}; \ No newline at end of file +}; diff --git a/src/frontend/src/types/zustand/types/index.ts b/src/frontend/src/types/zustand/types/index.ts index 904e3058e..f18d8b22e 100644 --- a/src/frontend/src/types/zustand/types/index.ts +++ b/src/frontend/src/types/zustand/types/index.ts @@ -1,13 +1,13 @@ import { APIClassType, APIDataType } from "../../api"; export type TypesStoreType = { - types: { [char: string]: string }; - setTypes: (newState: {}) => void; - templates: { [char: string]: APIClassType }; - setTemplates: (newState: {}) => void; - data: APIDataType; - setData: (newState: {}) => void; - getTypes: () => Promise; - setFilterEdge: (newState) => void; - getFilterEdge: any[]; -} \ No newline at end of file + types: { [char: string]: string }; + setTypes: (newState: {}) => void; + templates: { [char: string]: APIClassType }; + setTemplates: (newState: {}) => void; + data: APIDataType; + setData: (newState: {}) => void; + getTypes: () => Promise; + setFilterEdge: (newState) => void; + getFilterEdge: any[]; +}; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index e382267e9..7c8a0b7c0 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -415,7 +415,9 @@ export function handleKeyDown( } } -export function handleOnlyIntegerInput(event: React.KeyboardEvent) { +export function handleOnlyIntegerInput( + event: React.KeyboardEvent +) { if ( event.key === "." || event.key === "-" || diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index 86539634c..3d8c92eec 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -299,13 +299,8 @@ export function buildTweakObject(tweak: tweakType) { export function getChatInputField(flow: FlowType, flowState?: FlowState) { let chat_input_field = "text"; - if ( - flowState && - flowState.input_keys - ) { - chat_input_field = Object.keys( - flowState.input_keys! - )[0]; + if (flowState && flowState.input_keys) { + chat_input_field = Object.keys(flowState.input_keys!)[0]; } return chat_input_field; } From d042b28c52d3846af35cef4a64c0082b65e468e7 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 8 Jan 2024 15:54:50 -0300 Subject: [PATCH 83/89] Add access token as a cookie in auto_login endpoint --- src/backend/langflow/api/v1/login.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/backend/langflow/api/v1/login.py b/src/backend/langflow/api/v1/login.py index ed2fa2468..b8fa48577 100644 --- a/src/backend/langflow/api/v1/login.py +++ b/src/backend/langflow/api/v1/login.py @@ -45,9 +45,13 @@ async def login_to_get_access_token( @router.get("/auto_login") -async def auto_login(db: Session = Depends(get_session), settings_service=Depends(get_settings_service)): +async def auto_login( + response: Response, db: Session = Depends(get_session), settings_service=Depends(get_settings_service) +): if settings_service.auth_settings.AUTO_LOGIN: - return create_user_longterm_token(db) + tokens = create_user_longterm_token(db) + response.set_cookie("access_token_lf", tokens["access_token"], httponly=False, secure=True, samesite="strict") + return tokens raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, From d26b4b4c11755c87c3cebabc11372ee41c7849c2 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Mon, 8 Jan 2024 15:58:58 -0300 Subject: [PATCH 84/89] Fixed past and future state on undo redo, removed loading on flow page --- .../components/PageComponent/index.tsx | 27 ------------------- .../extraSidebarComponent/index.tsx | 4 --- src/frontend/src/stores/flowsManagerStore.ts | 4 +-- 3 files changed, 2 insertions(+), 33 deletions(-) diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 82c2c426a..7508f8da9 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -166,14 +166,9 @@ export default function Page({ const edgeUpdateSuccessful = useRef(true); - const [loading, setLoading] = useState(true); - const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); - const timeoutRef = useRef(); - useEffect(() => { - setLoading(true); if (reactFlowInstance) { resetFlow({ nodes: flow?.data?.nodes ?? [], @@ -182,20 +177,6 @@ export default function Page({ }); } - // Clear the previous timeout - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - // Create a new timeout - timeoutRef.current = setTimeout(() => { - setLoading(false); - }, 300); - - // Clear the timeout when the component is unmounted - return () => { - clearTimeout(timeoutRef.current); - }; }, [currentFlowId, reactFlowInstance]); const onConnectMod = useCallback( @@ -392,14 +373,6 @@ export default function Page({ {Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? (
-
- -
state.getFilterEdge); const setFilterEdge = useTypesStore((state) => state.setFilterEdge); const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow); - const saveFlow = useFlowsManagerStore((state) => state.saveFlow); - const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance); - const nodes = useFlowStore((state) => state.nodes); - const edges = useFlowStore((state) => state.edges); const currentFlow = useFlowsManagerStore((state) => state.currentFlow); const hasStore = useStoreStore((state) => state.hasStore); const hasApiKey = useStoreStore((state) => state.hasApiKey); diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts index 87050d547..a75b68015 100644 --- a/src/frontend/src/stores/flowsManagerStore.ts +++ b/src/frontend/src/stores/flowsManagerStore.ts @@ -351,7 +351,7 @@ const useFlowsManagerStore = create((set, get) => ({ const newState = useFlowStore.getState(); const currentFlowId = get().currentFlowId; const pastLength = past[currentFlowId]?.length ?? 0; - const pastState = past[currentFlowId][pastLength - 1] ?? null; + const pastState = past[currentFlowId]?.[pastLength - 1] ?? null; if (pastState) { past[currentFlowId] = past[currentFlowId].slice(0, pastLength - 1); @@ -370,7 +370,7 @@ const useFlowsManagerStore = create((set, get) => ({ const newState = useFlowStore.getState(); const currentFlowId = get().currentFlowId; const futureLength = future[currentFlowId]?.length ?? 0; - const futureState = future[currentFlowId][futureLength - 1] ?? null; + const futureState = future[currentFlowId]?.[futureLength - 1] ?? null; if (futureState) { future[currentFlowId] = future[currentFlowId].slice(0, futureLength - 1); From dbfa18bb950ceeb608a3da87aa232341929b594e Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 8 Jan 2024 16:07:31 -0300 Subject: [PATCH 85/89] Fix login delay and add access token to every request --- src/frontend/src/contexts/authContext.tsx | 4 +--- src/frontend/src/controllers/API/api.tsx | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index b220c1e49..9ebb34756 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -98,9 +98,7 @@ export function AuthProvider({ children }): React.ReactElement { function login(newAccessToken: string) { setAccessToken(newAccessToken); setIsAuthenticated(true); - setTimeout(() => { - getUser(); - }, 500); + getUser(); } async function logout() { diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index 27d98f43c..4e9151f15 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -104,6 +104,7 @@ function ApiInterceptor() { // Request interceptor to add access token to every request const requestInterceptor = api.interceptors.request.use( (config) => { + const accessToken = cookies.get("access_token_lf"); if (accessToken && !isAuthorizedURL(config?.url)) { config.headers["Authorization"] = `Bearer ${accessToken}`; } From d79915e7dc4f3b43f4a10f0b65076e64ef0d6785 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 8 Jan 2024 16:08:07 -0300 Subject: [PATCH 86/89] Remove unused Loading component and import statements --- .../src/pages/FlowPage/components/PageComponent/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 7508f8da9..82f6b9457 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -14,7 +14,6 @@ import ReactFlow, { } from "reactflow"; import GenericNode from "../../../../CustomNodes/GenericNode"; import Chat from "../../../../components/chatComponent"; -import Loading from "../../../../components/ui/loading"; import useAlertStore from "../../../../stores/alertStore"; import useFlowStore from "../../../../stores/flowStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; @@ -30,7 +29,7 @@ import { scapeJSONParse, validateSelection, } from "../../../../utils/reactflowUtils"; -import { cn, getRandomName, isWrappedWithClass } from "../../../../utils/utils"; +import { getRandomName, isWrappedWithClass } from "../../../../utils/utils"; import ConnectionLineComponent from "../ConnectionLineComponent"; import SelectionMenu from "../SelectionMenuComponent"; import ExtraSidebar from "../extraSidebarComponent"; @@ -176,7 +175,6 @@ export default function Page({ viewport: flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 }, }); } - }, [currentFlowId, reactFlowInstance]); const onConnectMod = useCallback( From c723924388b9b9d27ee2055c2ac8828d96d6b810 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Mon, 8 Jan 2024 16:22:18 -0300 Subject: [PATCH 87/89] removed unused location store context --- src/frontend/src/App.tsx | 6 +---- .../components/PageComponent/index.tsx | 11 --------- src/frontend/src/stores/locationStore.tsx | 15 ------------ .../src/types/zustand/location/index.ts | 24 ------------------- 4 files changed, 1 insertion(+), 55 deletions(-) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 5e54edae1..44f5261bd 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -26,15 +26,11 @@ import { useTypesStore } from "./stores/typesStore"; export default function App() { const setCurrent = useLocationStore((state) => state.setCurrent); - const setShowSideBar = useLocationStore((state) => state.setShowSideBar); - const setIsStackedOpen = useLocationStore((state) => state.setIsStackedOpen); let location = useLocation(); useEffect(() => { setCurrent(location.pathname.replace(/\/$/g, "").split("/")); - setShowSideBar(true); - setIsStackedOpen(true); - }, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]); + }, [location.pathname, setCurrent]); const errorData = useAlertStore((state) => state.errorData); const errorOpen = useAlertStore((state) => state.errorOpen); diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 82f6b9457..7adf33c09 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -155,12 +155,6 @@ export default function Page({ const [selectionMenuVisible, setSelectionMenuVisible] = useState(false); - const setExtraComponent = useLocationStore( - (state) => state.setExtraComponent - ); - const setExtraNavigation = useLocationStore( - (state) => state.setExtraNavigation - ); const setErrorData = useAlertStore((state) => state.setErrorData); const edgeUpdateSuccessful = useRef(true); @@ -304,11 +298,6 @@ export default function Page({ [getNodeId, setNodes, takeSnapshot, paste] ); - useEffect(() => { - setExtraComponent(); - setExtraNavigation({ title: "Components" }); - }, []); - const onEdgeUpdateStart = useCallback(() => { edgeUpdateSuccessful.current = false; }, []); diff --git a/src/frontend/src/stores/locationStore.tsx b/src/frontend/src/stores/locationStore.tsx index 74445d3f3..630bbeefa 100644 --- a/src/frontend/src/stores/locationStore.tsx +++ b/src/frontend/src/stores/locationStore.tsx @@ -10,19 +10,4 @@ export const useLocationStore = create((set, get) => ({ setCurrent: (newState) => { set({ current: newState }); }, - setIsStackedOpen: (newState) => { - set({ isStackedOpen: newState }); - }, - showSideBar: window.location.pathname.split("/")[1] ? true : false, - setShowSideBar: (newState) => { - set({ showSideBar: newState }); - }, - extraNavigation: { title: "" }, - setExtraNavigation: (newState) => { - set({ extraNavigation: newState }); - }, - extraComponent: <>, - setExtraComponent: (newState) => { - set({ extraComponent: newState }); - }, })); diff --git a/src/frontend/src/types/zustand/location/index.ts b/src/frontend/src/types/zustand/location/index.ts index 2d2d44766..5b6e690a7 100644 --- a/src/frontend/src/types/zustand/location/index.ts +++ b/src/frontend/src/types/zustand/location/index.ts @@ -1,28 +1,4 @@ export type LocationStoreType = { current: Array; setCurrent: (newState: Array) => void; - isStackedOpen: boolean; - setIsStackedOpen: (newState: boolean) => void; - showSideBar: boolean; - setShowSideBar: (newState: boolean) => void; - extraNavigation: { - title: string; - options?: Array<{ - name: string; - href: string; - icon: React.ElementType; - children?: Array; - }>; - }; - setExtraNavigation: (newState: { - title: string; - options?: Array<{ - name: string; - href: string; - icon: React.ElementType; - children?: Array; - }>; - }) => void; - extraComponent: any; - setExtraComponent: (newState: JSX.Element) => void; }; From ad2351e3e7770ac1d13de76deb4cd7cf850ba803 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Mon, 8 Jan 2024 16:27:42 -0300 Subject: [PATCH 88/89] removed location --- src/frontend/src/App.tsx | 8 -------- .../FlowPage/components/PageComponent/index.tsx | 1 - src/frontend/src/stores/locationStore.tsx | 13 ------------- src/frontend/src/types/zustand/location/index.ts | 4 ---- 4 files changed, 26 deletions(-) delete mode 100644 src/frontend/src/stores/locationStore.tsx delete mode 100644 src/frontend/src/types/zustand/location/index.ts diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 44f5261bd..3b88ee14c 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -1,6 +1,5 @@ import _ from "lodash"; import { useContext, useEffect, useState } from "react"; -import { useLocation } from "react-router-dom"; import "reactflow/dist/style.css"; import "./App.css"; @@ -21,16 +20,9 @@ import Router from "./routes"; import useAlertStore from "./stores/alertStore"; import { useDarkStore } from "./stores/darkStore"; import useFlowsManagerStore from "./stores/flowsManagerStore"; -import { useLocationStore } from "./stores/locationStore"; import { useTypesStore } from "./stores/typesStore"; export default function App() { - const setCurrent = useLocationStore((state) => state.setCurrent); - - let location = useLocation(); - useEffect(() => { - setCurrent(location.pathname.replace(/\/$/g, "").split("/")); - }, [location.pathname, setCurrent]); const errorData = useAlertStore((state) => state.errorData); const errorOpen = useAlertStore((state) => state.errorOpen); diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 7adf33c09..ae030e0b7 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 Chat from "../../../../components/chatComponent"; import useAlertStore from "../../../../stores/alertStore"; import useFlowStore from "../../../../stores/flowStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; -import { useLocationStore } from "../../../../stores/locationStore"; import { useTypesStore } from "../../../../stores/typesStore"; import { APIClassType } from "../../../../types/api"; import { FlowType, NodeType, targetHandleType } from "../../../../types/flow"; diff --git a/src/frontend/src/stores/locationStore.tsx b/src/frontend/src/stores/locationStore.tsx deleted file mode 100644 index 630bbeefa..000000000 --- a/src/frontend/src/stores/locationStore.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { create } from "zustand"; -import { LocationStoreType } from "../types/zustand/location"; - -export const useLocationStore = create((set, get) => ({ - current: window.location.pathname.replace(/\/$/g, "").split("/"), - isStackedOpen: - window.innerWidth > 1024 && window.location.pathname.split("/")[1] - ? true - : false, - setCurrent: (newState) => { - set({ current: newState }); - }, -})); diff --git a/src/frontend/src/types/zustand/location/index.ts b/src/frontend/src/types/zustand/location/index.ts deleted file mode 100644 index 5b6e690a7..000000000 --- a/src/frontend/src/types/zustand/location/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type LocationStoreType = { - current: Array; - setCurrent: (newState: Array) => void; -}; From b80cad7d9d4fa4423f5977f3960f43226bf5df0a Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 8 Jan 2024 16:51:56 -0300 Subject: [PATCH 89/89] fix(authAdminGuard): remove unnecessary promise return in logout function call fix(authGuard): remove unnecessary promise return in logout function call fix(headerComponent): remove unnecessary promise return in logout function call fix(authContext): add navigation to login page after logout fix(api): remove unnecessary promise return in logout function call and add navigation to login page after logout --- .../src/components/authAdminGuard/index.tsx | 12 ++++-------- src/frontend/src/components/authGuard/index.tsx | 9 +++------ .../src/components/headerComponent/index.tsx | 4 +--- src/frontend/src/contexts/authContext.tsx | 3 +++ src/frontend/src/controllers/API/api.tsx | 16 ++++------------ 5 files changed, 15 insertions(+), 29 deletions(-) diff --git a/src/frontend/src/components/authAdminGuard/index.tsx b/src/frontend/src/components/authAdminGuard/index.tsx index ece2248fa..96c404af3 100644 --- a/src/frontend/src/components/authAdminGuard/index.tsx +++ b/src/frontend/src/components/authAdminGuard/index.tsx @@ -7,14 +7,10 @@ export const ProtectedAdminRoute = ({ children }) => { useContext(AuthContext); if (!isAuthenticated) { - logout().then(() => { - return ; - }); - } - - if ((userData && !isAdmin) || autoLogin) { + logout(); + } else if ((userData && !isAdmin) || autoLogin) { return ; + } else { + return children; } - - return children; }; diff --git a/src/frontend/src/components/authGuard/index.tsx b/src/frontend/src/components/authGuard/index.tsx index 885f8fe93..8450248a9 100644 --- a/src/frontend/src/components/authGuard/index.tsx +++ b/src/frontend/src/components/authGuard/index.tsx @@ -1,14 +1,11 @@ import { useContext } from "react"; -import { Navigate } from "react-router-dom"; import { AuthContext } from "../../contexts/authContext"; export const ProtectedRoute = ({ children }) => { const { isAuthenticated, logout } = useContext(AuthContext); if (!isAuthenticated) { - logout().then(() => { - return ; - }); + logout(); + } else { + return children; } - - return children; }; diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index caae00b75..2b934942a 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -193,9 +193,7 @@ export default function Header(): JSX.Element { { - logout().then(() => { - navigate("/login"); - }); + logout(); }} > Sign Out diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index 9ebb34756..be2f279c9 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -1,4 +1,5 @@ import { createContext, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; import Cookies from "universal-cookie"; import { autoLogin as autoLoginApi, @@ -29,6 +30,7 @@ const initialValue: AuthContextType = { export const AuthContext = createContext(initialValue); export function AuthProvider({ children }): React.ReactElement { + const navigate = useNavigate(); const cookies = new Cookies(); const [accessToken, setAccessToken] = useState( cookies.get("access_token_lf") ?? null @@ -109,6 +111,7 @@ export function AuthProvider({ children }): React.ReactElement { setUserData(null); setAccessToken(null); setIsAuthenticated(false); + navigate("/login"); } catch (error) { console.error(error); throw error; diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index 4e9151f15..c6786cc1c 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -28,9 +28,7 @@ function ApiInterceptor() { authenticationErrorCount = authenticationErrorCount + 1; if (authenticationErrorCount > 3) { authenticationErrorCount = 0; - logout().then(() => { - navigate("/login"); - }); + logout(); } try { const res = await renewAccessToken(); @@ -47,14 +45,10 @@ function ApiInterceptor() { } } catch (error) { if (axios.isAxiosError(error) && error.response?.status === 401) { - logout().then(() => { - navigate("/login"); - }); + logout(); } else { console.error(error); - logout().then(() => { - navigate("/login"); - }); + logout(); } } } @@ -62,9 +56,7 @@ function ApiInterceptor() { if (!accessToken && error?.config?.url?.includes("login")) { return Promise.reject(error); } else { - logout().then(() => { - navigate("/login"); - }); + logout(); } } else { // if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {