From 03076b3577789268c2c2de2bfe4ddce02d6ebb67 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Thu, 15 Jun 2023 15:06:52 -0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A8=20refactor(flows.py):=20change=20r?= =?UTF-8?q?esponse=20model=20of=20update=5Fflow=20endpoint=20to=20FlowRead?= =?UTF-8?q?=20=F0=9F=94=A8=20refactor(parameterComponent):=20remove=20unus?= =?UTF-8?q?ed=20imports=20and=20refactor=20onChange=20function=20to=20hand?= =?UTF-8?q?leOnNewValue=20=E2=9C=A8=20feat(tabsContext):=20add=20tabsState?= =?UTF-8?q?=20and=20setTabsState=20to=20TabsContextType=20and=20TabsProvid?= =?UTF-8?q?er=20=F0=9F=94=A8=20refactor(flowSettingsModal):=20refactor=20h?= =?UTF-8?q?andleSaveFlow=20function=20to=20update=20flow=20and=20setTabsSt?= =?UTF-8?q?ate=20with=20isPending=20false=20The=20update=5Fflow=20endpoint?= =?UTF-8?q?=20now=20returns=20a=20FlowRead=20response=20model=20instead=20?= =?UTF-8?q?of=20FlowReadWithStyle.=20The=20parameterComponent=20file=20has?= =?UTF-8?q?=20been=20refactored=20to=20remove=20unused=20imports=20and=20t?= =?UTF-8?q?o=20use=20a=20handleOnNewValue=20function=20to=20handle=20onCha?= =?UTF-8?q?nge=20events.=20The=20TabsContextType=20and=20TabsProvider=20ha?= =?UTF-8?q?ve=20been=20updated=20to=20include=20tabsState=20and=20setTabsS?= =?UTF-8?q?tate.=20The=20flowSettingsModal=20has=20been=20refactored=20to?= =?UTF-8?q?=20update=20the=20flow=20and=20setTabsState=20with=20isPending?= =?UTF-8?q?=20false.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔨 refactor(extraSidebarComponent): add tabsState and setTabsState to TabsContextType 🐛 fix(extraSidebarComponent): disable save button when flow is not pending 🐛 fix(extraSidebarComponent): update flow state after saving The TabsContextType now includes tabsState and setTabsState to allow for the management of the state of each tab. The save button is now disabled when the flow is not pending. The flow state is now updated after saving to reflect the changes made. --- src/backend/langflow/api/v1/flows.py | 2 +- .../components/parameterComponent/index.tsx | 55 ++++++++----------- src/frontend/src/contexts/tabsContext.tsx | 9 ++- .../src/modals/flowSettingsModal/index.tsx | 29 +++++----- .../extraSidebarComponent/index.tsx | 30 +++++++--- src/frontend/src/types/tabs/index.ts | 12 ++-- 6 files changed, 78 insertions(+), 59 deletions(-) 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; + }; };