diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index d9a821ec8..0eb1bb900 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -1,4 +1,3 @@ -import { cloneDeep } from "lodash"; import { useContext, useEffect, useState } from "react"; import { NodeToolbar, useUpdateNodeInternals } from "reactflow"; import ShadTooltip from "../../components/ShadTooltipComponent"; @@ -14,11 +13,7 @@ import { undoRedoContext } from "../../contexts/undoRedoContext"; import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent"; import { validationStatusType } from "../../types/components"; import { NodeDataType } from "../../types/flow"; -import { - cleanEdges, - handleKeyDown, - scapedJSONStringfy, -} from "../../utils/reactflowUtils"; +import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils"; import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils"; import { classNames, getFieldTitle } from "../../utils/utils"; import ParameterComponent from "./components/parameterComponent"; @@ -34,7 +29,8 @@ export default function GenericNode({ xPos: number; yPos: number; }): JSX.Element { - const { updateFlow, flows, tabId } = useContext(FlowsContext); + const { updateFlow, flows, tabId, saveCurrentFlow } = + useContext(FlowsContext); const updateNodeInternals = useUpdateNodeInternals(); const { types, deleteNode, reactFlowInstance, setFilterEdge, getFilterEdge } = useContext(typesContext); @@ -87,18 +83,15 @@ export default function GenericNode({ // State for outline color const { sseData, isBuilding } = useSSE(); - useEffect(() => { - let myFlow = flows.find((flow) => flow.id === tabId); - if (reactFlowInstance && myFlow) { - let flow = cloneDeep(myFlow); - flow.data = reactFlowInstance.toObject(); + /* useEffect(() => { + let flow = flows.find((flow) => flow.id === tabId); + if (reactFlowInstance && flow && flow.data) { cleanEdges({ flow: { - edges: flow.data.edges, - nodes: flow.data.nodes, + edges: flow.data!.edges, + nodes: flow.data!.nodes, }, updateEdge: (edge) => { - flow.data!.edges = edge; reactFlowInstance.setEdges(edge); updateNodeInternals(data.id); }, @@ -106,7 +99,7 @@ export default function GenericNode({ updateFlow(flow); } countHandles(); - }, [data]); + }, [data]); */ useEffect(() => { setNodeDescription(data.node!.description); @@ -138,6 +131,7 @@ export default function GenericNode({ deleteNode={(id) => { takeSnapshot(); deleteNode(id); + saveCurrentFlow(); }} setShowNode={(show: boolean) => { data.showNode = show; @@ -150,10 +144,7 @@ export default function GenericNode({
diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index 21ea64e01..f1e374a03 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -76,11 +76,8 @@ export default function BuildTrigger({ } async function streamNodeData(flow: FlowType) { // Step 1: Make a POST request to send the flow data and receive a unique session ID - const id = saveFlow({ ...flow, data: reactFlowInstance!.toObject() }, true); - const response = await postBuildInit({ - ...flow, - data: reactFlowInstance!.toObject(), - }); + const id = saveFlow(flow, true); + const response = await postBuildInit(flow); const { flowId } = response.data; // Step 2: Use the session ID to establish an SSE connection using EventSource let validationResults: boolean[] = []; diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx index 429e84905..10cc0ca28 100644 --- a/src/frontend/src/contexts/flowsContext.tsx +++ b/src/frontend/src/contexts/flowsContext.tsx @@ -82,6 +82,7 @@ const FlowsContextInitialValue: FlowsContextType = { setLastCopiedSelection: (selection: any) => {}, tabsState: {}, setTabsState: (state: FlowsState) => {}, + saveCurrentFlow: () => {}, getNodeId: (nodeType: string) => "", setTweak: (tweak: any) => {}, getTweak: [], @@ -638,9 +639,14 @@ export function FlowsProvider({ children }: { children: ReactNode }) { }); } - async function saveFlow(newFlow: FlowType, silent?: boolean) { - if (newFlow?.data?.nodes?.length === 0) return; + function saveCurrentFlow() { + const currentFlow = flows.find((flow) => flow.id === tabId); + if (currentFlow && reactFlowInstance && currentFlow.data) { + updateFlow({ ...currentFlow, data: reactFlowInstance?.toObject()! }); + } + } + async function saveFlow(newFlow: FlowType, silent?: boolean) { try { // updates flow in db const updatedFlow = await updateFlowInDatabase(newFlow); @@ -712,6 +718,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) { setIsBuilt, lastCopiedSelection, setLastCopiedSelection, + saveCurrentFlow, hardReset, tabId, setTabId, diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx index f58a9cac2..5ea23b644 100644 --- a/src/frontend/src/modals/exportModal/index.tsx +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -68,7 +68,7 @@ const ExportModal = forwardRef( downloadFlow( { id: tabId, - data: reactFlowInstance?.toObject()!, + data: flow!.data!, description, name, last_tested_version: version, @@ -85,7 +85,7 @@ const ExportModal = forwardRef( downloadFlow( removeApiKeys({ id: tabId, - data: reactFlowInstance?.toObject()!, + data: flow!.data!, description, name, last_tested_version: version, diff --git a/src/frontend/src/modals/flowSettingsModal/index.tsx b/src/frontend/src/modals/flowSettingsModal/index.tsx index 929f6db4a..d4a4df9bf 100644 --- a/src/frontend/src/modals/flowSettingsModal/index.tsx +++ b/src/frontend/src/modals/flowSettingsModal/index.tsx @@ -34,7 +34,7 @@ export default function FlowSettingsModal({ useEffect(() => { const tempNameList: string[] = []; flows.forEach((flow: FlowType) => { - tempNameList.push(flow.name); + if ((flow.is_component ?? false) === false) tempNameList.push(flow.name); }); setNameList(tempNameList.filter((name) => name !== flow!.name)); }, [flows]); diff --git a/src/frontend/src/modals/formModal/index.tsx b/src/frontend/src/modals/formModal/index.tsx index f6f7c214f..09592e640 100644 --- a/src/frontend/src/modals/formModal/index.tsx +++ b/src/frontend/src/modals/formModal/index.tsx @@ -393,7 +393,7 @@ export default function FormModal({ const message = inputs; addChatHistory(message!, true, chatKey!, template.current); sendAll({ - ...reactFlowInstance?.toObject()!, + ...flow.data!, inputs: inputs!, chatHistory, name: flow.name, diff --git a/src/frontend/src/modals/shareModal/index.tsx b/src/frontend/src/modals/shareModal/index.tsx index c27a36c78..9f73137cb 100644 --- a/src/frontend/src/modals/shareModal/index.tsx +++ b/src/frontend/src/modals/shareModal/index.tsx @@ -8,6 +8,7 @@ import { Checkbox } from "../../components/ui/checkbox"; import { alertContext } from "../../contexts/alertContext"; import { FlowsContext } from "../../contexts/flowsContext"; import { StoreContext } from "../../contexts/storeContext"; +import { typesContext } from "../../contexts/typesContext"; import { getStoreComponents, getStoreTags, @@ -41,6 +42,7 @@ export default function ShareModal({ const { version, addFlow } = useContext(FlowsContext); const { hasApiKey, hasStore } = useContext(StoreContext); const { setSuccessData, setErrorData } = useContext(alertContext); + const { reactFlowInstance } = useContext(typesContext); const [checked, setChecked] = useState(false); const [name, setName] = useState(component?.name ?? ""); const [description, setDescription] = useState(component?.description ?? ""); @@ -84,7 +86,8 @@ export default function ShareModal({ filterByUser: true, }).then((res) => { res?.results?.forEach((element: any) => { - unavaliableNames.push({ name: element.name, id: element.id }); + if ((element.is_component ?? false) === is_component) + unavaliableNames.push({ name: element.name, id: element.id }); }); setUnavaliableNames(unavaliableNames); setLoadingNames(false); @@ -128,6 +131,8 @@ export default function ShareModal({ }); } + await saveFlow({ ...flows.find((flow) => flow.id === tabId)! }, true); + if (!update) saveFlowStore(flow!, getTagsIds(selectedTags, tags), sharePublic).then( successShare, diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 7b852a0c2..c0a821801 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -60,7 +60,6 @@ export default function Page({ let { updateFlow, uploadFlow, - addFlow, getNodeId, paste, lastCopiedSelection, @@ -69,7 +68,7 @@ export default function Page({ saveFlow, setTabsState, tabId, - flows, + saveCurrentFlow, } = useContext(FlowsContext); const { types, @@ -89,6 +88,12 @@ export default function Page({ 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 ( @@ -132,6 +137,7 @@ export default function Page({ takeSnapshot(); deleteNode(lastSelection.nodes.map((node) => node.id)); deleteEdge(lastSelection.edges.map((edge) => edge.id)); + saveCurrentFlowTimeout(); } } }; @@ -147,7 +153,12 @@ export default function Page({ document.removeEventListener("keydown", onKeyDown); document.removeEventListener("mousemove", handleMouseMove); }; - }, [lastCopiedSelection, lastSelection, takeSnapshot]); + }, [ + lastCopiedSelection, + lastSelection, + takeSnapshot, + saveCurrentFlowTimeout, + ]); const [selectionMenuVisible, setSelectionMenuVisible] = useState(false); @@ -190,16 +201,8 @@ export default function Page({ }, [flow, reactFlowInstance]); useEffect(() => { - const index = flows.findIndex((flowId) => flowId.id === flow.id); - const interval = setInterval(() => { - saveFlow( - { - ...flows[index]!, - data: reactFlowInstance ? reactFlowInstance!.toObject() : flow!.data, - }, - true - ); + saveFlow(flow, true); }, 30000); return () => { @@ -209,10 +212,6 @@ export default function Page({ const onEdgesChangeMod = useCallback( (change: EdgeChange[]) => { - updateFlow({ - ...flow!, - data: reactFlowInstance ? reactFlowInstance!.toObject() : flow!.data, - }); onEdgesChange(change); //@ts-ignore setTabsState((prev: FlowsState) => { @@ -220,18 +219,18 @@ export default function Page({ ...prev, [tabId]: { ...prev[tabId], - isPending: false, + isPending: true, }, }; }); + saveCurrentFlowTimeout(); }, - [onEdgesChange, setNodes, setTabsState, tabId] + [onEdgesChange, setNodes, setTabsState, saveCurrentFlowTimeout, tabId] ); const onNodesChangeMod = useCallback( (change: NodeChange[]) => { const changeString = JSON.stringify(change); - if (changeString !== nodesOnFlow) { onNodesChange(change); updateNodeFlow(changeString); @@ -245,9 +244,10 @@ export default function Page({ }, }; }); + saveCurrentFlowTimeout(); } }, - [onNodesChange, setTabsState, tabId, updateNodeFlow] + [onNodesChange, setTabsState, tabId, updateNodeFlow, saveCurrentFlowTimeout] ); function updateNodeFlow(changeString: string) { @@ -283,7 +283,7 @@ export default function Page({ return newX; }); }, - [setEdges, setNodes, takeSnapshot] + [setEdges, setNodes, takeSnapshot, addEdge] ); const onNodeDragStart: NodeDragHandler = useCallback(() => { @@ -394,10 +394,7 @@ export default function Page({ return () => { if (tabsState && tabsState[flow.id]?.isPending) { - saveFlow({ - ...flow!, - data: reactFlowInstance ? reactFlowInstance!.toObject() : flow!.data, - }); + saveFlow(flow); } }; }, []); @@ -475,11 +472,7 @@ export default function Page({ { - if (reactFlowInstance) - updateFlow({ - ...flow, - data: reactFlowInstance.toObject(), - }); + saveCurrentFlowTimeout(); }} edges={edges} onNodesChange={onNodesChangeMod} diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 274eac3c1..9db547c68 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -289,32 +289,34 @@ export default function ExtraSidebar(): JSX.Element {
- -
- -
-
+ onClick={(event) => { + saveFlow(flow!); + }} + > + 0 + ? " " + : " extra-side-bar-save-disable") + } + /> + +
+ + )}
{ModalMemo}
diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index 230081be7..dc01727ef 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -86,7 +86,7 @@ export default function NodeToolbarComponent({ break; case "show": takeSnapshot(); - setShowNode(data.showNode ? false : true); + setShowNode(data.showNode ?? true ? false : true); updateNodeInternals(data.id); break; case "Download": diff --git a/src/frontend/src/pages/MainPage/components/components/index.tsx b/src/frontend/src/pages/MainPage/components/components/index.tsx index 8cada7571..b4c19f181 100644 --- a/src/frontend/src/pages/MainPage/components/components/index.tsx +++ b/src/frontend/src/pages/MainPage/components/components/index.tsx @@ -26,7 +26,7 @@ export default function ComponentsComponent({ useEffect(() => { if (isLoading) return; const all = flows - .filter((f) => f.is_component === is_component) + .filter((f) => (f.is_component ?? false) === is_component) .sort((a, b) => { if (a?.updated_at && b?.updated_at) { return ( @@ -179,7 +179,8 @@ export default function ComponentsComponent({ pageSize={pageSize} rowsCount={[10, 20, 50, 100]} totalRowsCount={ - flows.filter((f) => f.is_component === is_component).length + flows.filter((f) => (f.is_component ?? false) === is_component) + .length } paginate={(pageSize, pageIndex) => { setPageIndex(pageIndex); diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index a3d764690..d4e120c1c 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -26,6 +26,7 @@ export type FlowsContextType = { uploadFlows: () => void; isBuilt: boolean; setIsBuilt: (state: boolean) => void; + saveCurrentFlow: () => void; uploadFlow: ({ newProject, file,