From 499d5238e31469ca74ea73d437c8ac1276c527a3 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Thu, 15 Jun 2023 14:52:26 -0300 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=94=A5=20refactor(chatModal):=20remov?= =?UTF-8?q?e=20unused=20function=20getWebSocketUrl=20The=20function=20getW?= =?UTF-8?q?ebSocketUrl=20was=20not=20being=20used=20in=20the=20code=20and?= =?UTF-8?q?=20was=20therefore=20removed=20to=20improve=20code=20readabilit?= =?UTF-8?q?y=20and=20maintainability.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/modals/chatModal/index.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index 670e66202..428bda5f3 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -1,9 +1,9 @@ import { Dialog, Transition } from "@headlessui/react"; import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline"; import { Fragment, useContext, useEffect, useRef, useState } from "react"; -import { FlowType, NodeType } from "../../types/flow"; +import { FlowType } from "../../types/flow"; import { alertContext } from "../../contexts/alertContext"; -import { toNormalCase, validateNodes } from "../../utils"; +import { validateNodes } from "../../utils"; import { typesContext } from "../../contexts/typesContext"; import ChatMessage from "./chatMessage"; import { FaEraser } from "react-icons/fa"; @@ -174,16 +174,6 @@ export default function ChatModal({ updateLastMessage({ str: data.message }); } } - function getWebSocketUrl(chatId, isDevelopment = false) { - const isSecureProtocol = window.location.protocol === "https:"; - const webSocketProtocol = isSecureProtocol ? "wss" : "ws"; - const host = isDevelopment ? "localhost:7860" : window.location.host; - const chatEndpoint = `/api/v1/chat/${chatId}`; - - return `${ - isDevelopment ? "ws" : webSocketProtocol - }://${host}${chatEndpoint}`; - } function connectWS() { try { 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 2/4] =?UTF-8?q?=F0=9F=94=A8=20refactor(flows.py):=20change?= =?UTF-8?q?=20response=20model=20of=20update=5Fflow=20endpoint=20to=20Flow?= =?UTF-8?q?Read=20=F0=9F=94=A8=20refactor(parameterComponent):=20remove=20?= =?UTF-8?q?unused=20imports=20and=20refactor=20onChange=20function=20to=20?= =?UTF-8?q?handleOnNewValue=20=E2=9C=A8=20feat(tabsContext):=20add=20tabsS?= =?UTF-8?q?tate=20and=20setTabsState=20to=20TabsContextType=20and=20TabsPr?= =?UTF-8?q?ovider=20=F0=9F=94=A8=20refactor(flowSettingsModal):=20refactor?= =?UTF-8?q?=20handleSaveFlow=20function=20to=20update=20flow=20and=20setTa?= =?UTF-8?q?bsState=20with=20isPending=20false=20The=20update=5Fflow=20endp?= =?UTF-8?q?oint=20now=20returns=20a=20FlowRead=20response=20model=20instea?= =?UTF-8?q?d=20of=20FlowReadWithStyle.=20The=20parameterComponent=20file?= =?UTF-8?q?=20has=20been=20refactored=20to=20remove=20unused=20imports=20a?= =?UTF-8?q?nd=20to=20use=20a=20handleOnNewValue=20function=20to=20handle?= =?UTF-8?q?=20onChange=20events.=20The=20TabsContextType=20and=20TabsProvi?= =?UTF-8?q?der=20have=20been=20updated=20to=20include=20tabsState=20and=20?= =?UTF-8?q?setTabsState.=20The=20flowSettingsModal=20has=20been=20refactor?= =?UTF-8?q?ed=20to=20update=20the=20flow=20and=20setTabsState=20with=20isP?= =?UTF-8?q?ending=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; + }; }; From 265646ded332de540073ea4e94ce45a5c00485da Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Thu, 15 Jun 2023 15:08:04 -0300 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=90=9B=20fix(chat.py):=20change=20the?= =?UTF-8?q?=20prefix=20of=20the=20error=20message=20to=20'data'=20to=20fix?= =?UTF-8?q?=20the=20server=20response=20The=20error=20message=20prefix=20w?= =?UTF-8?q?as=20changed=20from=20'data'=20to=20'error'=20to=20fix=20the=20?= =?UTF-8?q?server=20response.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index 18f547682..5943f6f78 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -109,7 +109,7 @@ async def stream_build(flow_id: str): chat_manager.set_cache(flow_id, graph.build()) except Exception as exc: logger.error("Error while building the flow: %s", exc) - yield f"data: {json.dumps({'error': str(exc)})}\n\n" + yield f"error: {json.dumps({'error': str(exc)})}\n\n" finally: yield f"data: {final_response}\n\n" From cb7c8b45cd40824baa27b463de57ffacb4a6cea0 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Thu, 15 Jun 2023 15:08:14 -0300 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=94=A8=20refactor(API):=20add=20type?= =?UTF-8?q?=20to=20updateFlowInDatabase=20function=20and=20format=20code?= =?UTF-8?q?=20The=20updateFlowInDatabase=20function=20now=20has=20a=20type?= =?UTF-8?q?=20added=20to=20it,=20which=20improves=20the=20readability=20of?= =?UTF-8?q?=20the=20code.=20The=20code=20has=20also=20been=20formatted=20t?= =?UTF-8?q?o=20improve=20consistency=20with=20the=20rest=20of=20the=20code?= =?UTF-8?q?base.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/controllers/API/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index b2d21bffd..f0c36a215 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -111,7 +111,9 @@ export async function saveFlowToDatabase(newFlow: { * @returns {Promise} The updated flow data. * @throws Will throw an error if the update fails. */ -export async function updateFlowInDatabase(updatedFlow: FlowType) { +export async function updateFlowInDatabase( + updatedFlow: FlowType +): Promise { try { const response = await axios.patch(`/api/v1/flows/${updatedFlow.id}`, { name: updatedFlow.name,