diff --git a/src/backend/langflow/api/v1/flows.py b/src/backend/langflow/api/v1/flows.py index 0c407faa4..b707f5e05 100644 --- a/src/backend/langflow/api/v1/flows.py +++ b/src/backend/langflow/api/v1/flows.py @@ -49,7 +49,7 @@ def read_flow(*, session: Session = Depends(get_session), flow_id: UUID): raise HTTPException(status_code=404, detail="Flow not found") -@router.patch("/{flow_id}", response_model=FlowReadWithStyle, status_code=200) +@router.patch("/{flow_id}", response_model=FlowRead, status_code=200) def update_flow( *, session: Session = Depends(get_session), flow_id: UUID, flow: FlowUpdate ): diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index f9db94cfa..65f946ba2 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -1,14 +1,11 @@ import { Handle, Position, useUpdateNodeInternals } from "reactflow"; -import Tooltip from "../../../../components/TooltipComponent"; import { classNames, groupByFamily, isValidConnection, - toFirstUpperCase, } from "../../../../utils"; import { useContext, useEffect, useRef, useState } from "react"; import InputComponent from "../../../../components/inputComponent"; -import ToggleComponent from "../../../../components/toggleComponent"; import InputListComponent from "../../../../components/inputListComponent"; import TextAreaComponent from "../../../../components/textAreaComponent"; import { typesContext } from "../../../../contexts/typesContext"; @@ -43,6 +40,7 @@ export default function ParameterComponent({ const updateNodeInternals = useUpdateNodeInternals(); const [position, setPosition] = useState(0); const { closePopUp } = useContext(PopUpContext); + const { setTabsState, tabId } = useContext(TabsContext); useEffect(() => { if (ref.current && ref.current.offsetTop && ref.current.clientHeight) { @@ -66,6 +64,19 @@ export default function ParameterComponent({ reactFlowInstance?.getEdges().some((e) => e.targetHandle === id) ?? false; const [myData, setMyData] = useState(useContext(typesContext).data); + const handleOnNewValue = (newValue: any) => { + data.node.template[name].value = newValue; + // Set state to pending + setTabsState((prev) => { + return { + ...prev, + [tabId]: { + isPending: true, + }, + }; + }); + }; + useEffect(() => { const groupedObj = groupByFamily(myData, tooltipTitle); @@ -165,17 +176,13 @@ export default function ParameterComponent({ ? [""] : data.node.template[name].value } - onChange={(t: string[]) => { - data.node.template[name].value = t; - }} + onChange={handleOnNewValue} /> ) : data.node.template[name].multiline ? ( { - data.node.template[name].value = t; - }} + onChange={handleOnNewValue} /> ) : ( { - data.node.template[name].value = t; - }} + onChange={handleOnNewValue} /> )} @@ -195,7 +200,7 @@ export default function ParameterComponent({ disabled={disabled} enabled={enabled} setEnabled={(t) => { - data.node.template[name].value = t; + handleOnNewValue(t); setEnabled(t); }} size="large" @@ -207,9 +212,7 @@ export default function ParameterComponent({ disabled={disabled} disableCopyPaste={true} value={data.node.template[name].value ?? ""} - onChange={(t) => { - data.node.template[name].value = t; - }} + onChange={handleOnNewValue} /> ) : left === true && @@ -218,9 +221,7 @@ export default function ParameterComponent({
- (data.node.template[name].value = newValue) - } + onSelect={handleOnNewValue} value={data.node.template[name].value ?? "Choose an option"} >
@@ -228,17 +229,13 @@ export default function ParameterComponent({ { - data.node.template[name].value = t; - }} + onChange={handleOnNewValue} /> ) : left === true && type === "file" ? ( { - data.node.template[name].value = t; - }} + onChange={handleOnNewValue} fileTypes={data.node.template[name].fileTypes} suffixes={data.node.template[name].suffixes} onFileChange={(t: string) => { @@ -251,18 +248,14 @@ export default function ParameterComponent({ disabled={disabled} disableCopyPaste={true} value={data.node.template[name].value ?? ""} - onChange={(t) => { - data.node.template[name].value = t; - }} + onChange={handleOnNewValue} /> ) : left === true && type === "prompt" ? ( { - data.node.template[name].value = t; - }} + onChange={handleOnNewValue} /> ) : ( <> diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index ee5c4886c..e260dd42b 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -7,7 +7,7 @@ import { useContext, } from "react"; import { FlowType, NodeType } from "../types/flow"; -import { TabsContextType } from "../types/tabs"; +import { TabsContextType, TabsState } from "../types/tabs"; import { updateIds, updateTemplate } from "../utils"; import { alertContext } from "./alertContext"; import { typesContext } from "./typesContext"; @@ -43,7 +43,8 @@ const TabsContextInitialValue: TabsContextType = { setDisableCopyPaste: (state: boolean) => {}, lastCopiedSelection: null, setLastCopiedSelection: (selection: any) => {}, - + tabsState: {}, + setTabsState: (state: TabsState) => {}, getNodeId: (nodeType: string) => "", paste: ( selection: { nodes: any; edges: any }, @@ -64,6 +65,7 @@ export function TabsProvider({ children }: { children: ReactNode }) { const [id, setId] = useState(uid()); const { templates, reactFlowInstance } = useContext(typesContext); const [lastCopiedSelection, setLastCopiedSelection] = useState(null); + const [tabsState, setTabsState] = useState({}); const newNodeId = useRef(uid()); function incrementNodeId() { @@ -534,6 +536,7 @@ export function TabsProvider({ children }: { children: ReactNode }) { description: flowData.description, name: flow?.name ?? "New Flow", data: flowData.data, + id: "", }); const addFlowToLocalState = (newFlow) => { @@ -582,6 +585,8 @@ export function TabsProvider({ children }: { children: ReactNode }) { uploadFlows, uploadFlow, getNodeId, + tabsState, + setTabsState, paste, }} > diff --git a/src/frontend/src/modals/flowSettingsModal/index.tsx b/src/frontend/src/modals/flowSettingsModal/index.tsx index e052e7791..8d934e24d 100644 --- a/src/frontend/src/modals/flowSettingsModal/index.tsx +++ b/src/frontend/src/modals/flowSettingsModal/index.tsx @@ -1,4 +1,3 @@ -import { ArrowDownTrayIcon } from "@heroicons/react/24/outline"; import { useContext, useRef, useState } from "react"; import { alertContext } from "../../contexts/alertContext"; import { PopUpContext } from "../../contexts/popUpContext"; @@ -14,16 +13,16 @@ import { } from "../../components/ui/dialog"; import { Button } from "../../components/ui/button"; import { SETTINGS_DIALOG_SUBTITLE } from "../../constants"; -import { updateFlowInDatabase } from "../../controllers/API"; import EditFlowSettings from "../../components/EditFlowSettingsComponent"; import { Settings2 } from "lucide-react"; +import { updateFlowInDatabase } from "../../controllers/API"; export default function FlowSettingsModal() { const [open, setOpen] = useState(true); const { closePopUp } = useContext(PopUpContext); const { setErrorData, setSuccessData } = useContext(alertContext); const ref = useRef(); - const { flows, tabId, updateFlow } = useContext(TabsContext); + const { flows, tabId, updateFlow, setTabsState } = useContext(TabsContext); const maxLength = 50; function setModalOpen(x: boolean) { setOpen(x); @@ -34,22 +33,26 @@ export default function FlowSettingsModal() { } } - function handleSaveFlow(flow) { - if (name !== "") { - let newFlow = flows.find((f) => f.id === tabId); - if (newFlow) { - newFlow.name = name; - newFlow.description = description; - updateFlow(newFlow); - } - } + async function handleSaveFlow(flow) { try { - updateFlowInDatabase(flow); + const updatedFlow = await updateFlowInDatabase(flow); + if (updatedFlow) { + updateFlow(updatedFlow); + setTabsState((prev) => { + return { + ...prev, + [tabId]: { + isPending: false, + }, + }; + }); + } // updateFlowStyleInDataBase(flow); } catch (err) { setErrorData(err); } } + const [name, setName] = useState(flows.find((f) => f.id === tabId).name); const [description, setDescription] = useState( flows.find((f) => f.id === tabId).description diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 3daf2a67a..7b7ca5073 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -13,24 +13,23 @@ import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import ShadTooltip from "../../../../components/ShadTooltipComponent"; import { Code2, FileDown, FileUp, Save } from "lucide-react"; import { PopUpContext } from "../../../../contexts/popUpContext"; -import ImportModal from "../../../../modals/importModal"; import ExportModal from "../../../../modals/exportModal"; import ApiModal from "../../../../modals/ApiModal"; import { TabsContext } from "../../../../contexts/tabsContext"; import { alertContext } from "../../../../contexts/alertContext"; import { updateFlowInDatabase } from "../../../../controllers/API"; import { INPUT_STYLE } from "../../../../constants"; -import { Input } from "../../../../components/ui/input"; import { Separator } from "../../../../components/ui/separator"; export default function ExtraSidebar() { const { data } = useContext(typesContext); const { openPopUp } = useContext(PopUpContext); - const { flows, tabId, uploadFlow } = useContext(TabsContext); + const { flows, tabId, uploadFlow, updateFlow, tabsState, setTabsState } = + useContext(TabsContext); const { setSuccessData, 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 } @@ -62,9 +61,21 @@ export default function ExtraSidebar() { }); } - function handleSaveFlow(flow) { + async function handleSaveFlow(flow) { try { - updateFlowInDatabase(flow); + const updatedFlow = await updateFlowInDatabase(flow); + if (updatedFlow) { + updateFlow(updatedFlow); + setTabsState((prev) => { + console.log(prev); + return { + ...prev, + [tabId]: { + isPending: false, + }, + }; + }); + } // updateFlowStyleInDataBase(flow); } catch (err) { setErrorData(err); @@ -118,8 +129,13 @@ export default function ExtraSidebar() { handleSaveFlow(flows.find((f) => f.id === tabId)); setSuccessData({ title: "Changes saved successfully" }); }} + disabled={!isPending} > - + diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index a3232aaff..b4fca23b2 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -1,3 +1,4 @@ +import { Dispatch, SetStateAction } from "react"; import { FlowType } from "../flow"; export type TabsContextType = { @@ -18,6 +19,8 @@ export type TabsContextType = { disableCopyPaste: boolean; setDisableCopyPaste: (value: boolean) => void; getNodeId: (nodeType: string) => string; + tabsState: TabsState; + setTabsState: Dispatch>; paste: ( selection: { nodes: any; edges: any }, position: { x: number; y: number; paneX?: number; paneY?: number } @@ -26,9 +29,8 @@ export type TabsContextType = { setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void; }; -export type LangFlowState = { - tabIndex: number; - flows: FlowType[]; - id: string; - nodeId: number; +export type TabsState = { + [key: string]: { + isPending: boolean; + }; };