From d76d0b2d51a3e42e7ef6e9b316152f2bcd1ec89e Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Tue, 13 Jun 2023 10:51:10 -0300 Subject: [PATCH] Upload and Download Flows done, fixed deleting flow bug --- langflow.db | Bin 28672 -> 36864 bytes .../src/components/headerComponent/index.tsx | 6 -- src/frontend/src/contexts/tabsContext.tsx | 91 +++++++++++------- src/frontend/src/controllers/API/index.ts | 33 ++++++- src/frontend/src/modals/exportModal/index.tsx | 14 +-- .../extraSidebarComponent/index.tsx | 35 +++---- .../components/cardComponent/index.tsx | 11 +-- src/frontend/src/pages/MainPage/index.tsx | 32 ++++-- src/frontend/src/types/tabs/index.ts | 2 + 9 files changed, 142 insertions(+), 82 deletions(-) diff --git a/langflow.db b/langflow.db index 377c77786e3ba2532d4bb349454206142bfc81de..73aad891859b806590676b1b51ad865bca5dbe9f 100644 GIT binary patch literal 36864 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 literal 28672 zcmeI4PiP!f9LIOFn@x7JGcRcq(o5(-D<)86-n=(|9)g&*TN|5f?6#HmAmRP3Thc_+ zl(r|S9xVvo{L_mEy?7~#9=!D8MbK6dp(0*9=*5c&3jV?O)+SDw+EMV*@_X$3c78kG z-~01d`xiUG_Vr-4)s*Y2>l?w-^SuLy=X*hS z`FPMhvADP|Xf^fO^)vmuUU3zy^i|+OW=S;X)H(N#TAu1HEY0^$1xGrkg4wzWICokN{or+c7tY|z1N@rmxj(G#74+nU3T#&lRi7D1`WFYHl{J2LfNA(Pn1$}a&dDHuHCXQ6?uXOP zovJma$yD{y9knQZPHyy{+w8Bex%TRJtt=<{yUyH)bC-ka$n?}y^~F8gv*&6XD$JRi zF2y%_@Z_=Ou4C?ccGn5YRljxpzs}Je*dHEhOg}nxFYJF2ui4Es>Nb>TkI8nuf<8sB zqtl3@s{fh)hX1S|`$OIh?@h1oC0?z4vwgLFrkz?L6d(WufB+Bx0zd!=00AHX1nz4B zcyf4Z##>rDzq;Dh7lK3fo+f0Bnv`Ve3#FS7) zW1&n&6xNxcBvNsLcN9W2lOatEP9iC2mKi3cR#_ws7Dj|gf+q?gk|t@CWlY9dNJA24 z3~P=_#F%7hBrzT@gm6iyFpQ^B%xFqdPHjV>G}=T-YDkJvA%rku$De2(C7SBcWGRl~ zFvA&^QA${d{X&S65jRW=YF{)HB+JrBC0fRz36<0mdxa3Lq|zofVMHZWlCe~$nUq{6 zw#`&1+%AML#t9)tVqz#If(XU1wC!mNTcw2Ku|f#tA&F$Bk`xQdghfj;!i9{qOeAFn zw+bO?n6OZ@n9CT`m`9vrmXU;Fov>7;6gLYYl5o3T>>zMrXFrXYv=EF|Tu{s+De-6_ zgk&NzJWQ2f8I5AYQ)Xc>8pe@Ulq)<^2ni!W6|ptjwMd0Z#h_|x7Y$`9!wNSFA#r39 zY1d&EVnS)GxWO?o*iA6YQX1mnLP(;F*2a>G>`E4qf00e*l z5cnqoduvP8vP{UN6P4vYb~;g6wqu7AmE|`koTx0LF+S*1Ne%-!QCZf)A2e5zr|_Jp zEHlw|qOx4Xm=l#{A6iaSmTzb}QCWsz)QQS+3L{QbmPKedQCZ$#*on$A1@(cbBsWkS zG*^-h7}`@?YW}PHe{{8ienUT_AJGr!d-N^(2Hiqmpd08D^f9`QK0+U$Yv^6{4tmQ9 zp#T9O00e*l5C8%|00;m9AOHk_01yBI|1W`h&8tp2v(uR!&P+Hn?hJCqcgAz3?aY`n zEoYj}j5;&oOv9OBXX?(>oEfUsyyoEdfB*Li`U!o9zOqjNTu1Mrx6y0p3VH!;AdOC< zBj_=N(Oxu-T2=@J2mk>f00e*l5C8%|00;m9AOHk_!2L#`U7M+HJ@GP`cXsBT9eHOW z?~Lailz05R-p-Wkg~t-RCBJEM7LB=0ox&T!tT=bc*K85*n2G`EKBp8vOh`UeFF z00AHX1b_e#00KY&2mk>f00e*l5O{zIxZnTb{{I1F(@00KY&2mk>f00e*l5C8%| K00;m9OW;qNNgM(I diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index 23a9034cb..071841313 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -21,12 +21,6 @@ export default function Header() { const [rename, setRename] = useState(false); const { notificationCenter, setNotificationCenter, setErrorData } = useContext(alertContext); - useEffect(() => { - //create the first flow - if (flows.length === 0 && Object.keys(templates).length > 0) { - addFlow(); - } - }, [addFlow, flows.length, templates]); return (
diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index 0bc7754cd..749ca348c 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -19,11 +19,13 @@ import { typesContext } from "./typesContext"; import { APITemplateType, TemplateVariableType } from "../types/api"; import { v4 as uuidv4 } from "uuid"; import { addEdge } from "reactflow"; -import _ from "lodash"; +import _, { flow } from "lodash"; import { readFlowsFromDatabase, deleteFlowFromDatabase, saveFlowToDatabase, + downloadFlowsFromDatabase, + uploadFlowsToDatabase, } from "../controllers/API"; const TabsContextInitialValue: TabsContextType = { @@ -35,6 +37,8 @@ const TabsContextInitialValue: TabsContextType = { updateFlow: (newFlow: FlowType) => {}, incrementNodeId: () => uuidv4(), downloadFlow: (flow: FlowType) => {}, + downloadFlows: () => {}, + uploadFlows: () => {}, uploadFlow: () => {}, hardReset: () => {}, disableCopyPaste: false, @@ -56,7 +60,7 @@ export const TabsContext = createContext( export function TabsProvider({ children }: { children: ReactNode }) { const { setErrorData, setNoticeData } = useContext(alertContext); const [tabId, setTabId] = useState(""); - const [flows, setFlows] = useState>([]); + const [flows, setFlows] = useState([]); const [id, setId] = useState(uuidv4()); const { templates, reactFlowInstance } = useContext(typesContext); const [lastCopiedSelection, setLastCopiedSelection] = useState(null); @@ -114,10 +118,7 @@ export function TabsProvider({ children }: { children: ReactNode }) { // } // } - useEffect(() => { - // get data from db - //get tabs locally saved - // let tabsData = getLocalStorageTabsData(); + function refreshFlows() { getTabsDataFromDB().then((DbData) => { if (DbData && Object.keys(templates).length > 0) { try { @@ -128,6 +129,13 @@ export function TabsProvider({ children }: { children: ReactNode }) { } } }); + } + + useEffect(() => { + // get data from db + //get tabs locally saved + // let tabsData = getLocalStorageTabsData(); + refreshFlows(); }, [templates]); function getTabsDataFromDB() { @@ -148,20 +156,6 @@ export function TabsProvider({ children }: { children: ReactNode }) { }); } - function processTabsData(tabsData) { - tabsData.flows.forEach((flow) => { - try { - if (!flow.data) { - return; - } - processFlowEdges(flow); - processFlowNodes(flow); - } catch (e) { - console.error(e); - } - }); - } - function processFlowEdges(flow) { flow.data.edges.forEach((edge) => { edge.className = ""; @@ -244,6 +238,22 @@ export function TabsProvider({ children }: { children: ReactNode }) { }); } + 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(); + }); + } + function getNodeId() { return `dndnode_` + incrementNodeId(); } @@ -275,30 +285,41 @@ export function TabsProvider({ children }: { children: ReactNode }) { // 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 = (e: Event) => { + // check if the file type is application/json + if ((e.target as HTMLInputElement).files[0].type === "application/json") { + // get the file from the file input + const file = (e.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. */ function removeFlow(id: string) { - setFlows((prevState) => { - const newFlows = [...prevState]; - const index = newFlows.findIndex((flow) => flow.id === id); + const index = flows.findIndex((flow) => flow.id === id); + console.log(index); if (index >= 0) { deleteFlowFromDatabase(id).then(() => { - let tabIndex = flows.findIndex((flow) => flow.id === tabId); - if (index === tabIndex) { - setTabId(flows[flows.length - 2].id); - newFlows.splice(index, 1); - } else { - let flowId = flows[tabIndex].id; - newFlows.splice(index, 1); - setTabId(flowId); - } + setFlows(flows.filter((flow) => flow.id !== id)); }); } - return newFlows; - }); } /** * Add a new flow to the list of flows. @@ -510,6 +531,8 @@ export function TabsProvider({ children }: { children: ReactNode }) { addFlow, updateFlow, downloadFlow, + downloadFlows, + uploadFlows, uploadFlow, getNodeId, paste, diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 910aeacf6..007c7f9bb 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -148,6 +148,35 @@ export async function readFlowsFromDatabase() { } } +export async function downloadFlowsFromDatabase() { + try { + const response = await fetch("/flows/download/"); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + } catch (error) { + console.error(error); + throw error; + } +} + +export async function uploadFlowsToDatabase(flows) { + try { + const response = await fetch(`/flows/upload/`, { + method: "POST", // Or "PATCH" depending on your backend API + body: flows, + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + } catch (error) { + console.error(error); + throw error; + } +} + /** * Deletes a flow from the database. * @@ -198,7 +227,7 @@ export async function getFlowFromDatabase(flowId: number) { */ export async function getFlowStylesFromDatabase() { try { - const response = await fetch("/flows_styles/"); + const response = await fetch("/flow_styles/"); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -218,7 +247,7 @@ export async function getFlowStylesFromDatabase() { */ export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) { try { - const response = await fetch("/flows_styles/", { + const response = await fetch("/flow_styles/", { method: "POST", headers: { accept: "application/json", diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx index 4532afe33..a7abb7528 100644 --- a/src/frontend/src/modals/exportModal/index.tsx +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -30,7 +30,7 @@ export default function ExportModal() { const { closePopUp } = useContext(PopUpContext); const ref = useRef(); const { setErrorData } = useContext(alertContext); - const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext); + const { flows, tabId, updateFlow, downloadFlow } = useContext(TabsContext); function setModalOpen(x: boolean) { setOpen(x); if (x === false) { @@ -40,7 +40,7 @@ export default function ExportModal() { } } const [checked, setChecked] = useState(true); - const [name, setName] = useState(flows[tabIndex].name); + const [name, setName] = useState(flows.find((f) => f.id === tabId).name); return ( @@ -63,7 +63,7 @@ export default function ExportModal() { className="mt-2" onChange={(event) => { if (event.target.value != "") { - let newFlow = flows[tabIndex]; + let newFlow = flows.find((f) => f.id === tabId); newFlow.name = event.target.value; setName(event.target.value); updateFlow(newFlow); @@ -84,11 +84,11 @@ export default function ExportModal() { name="description" id="description" onChange={(event) => { - let newFlow = flows[tabIndex]; + let newFlow = flows.find((f) => f.id === tabId); newFlow.description = event.target.value; updateFlow(newFlow); }} - value={flows[tabIndex].description ?? null} + value={flows.find((f) => f.id === tabId).description ?? null} placeholder="Flow description" className="max-h-[100px] mt-2" rows={3} @@ -112,8 +112,8 @@ export default function ExportModal() {
+
+ { + handleSearchInput(e.target.value); + setSearch(e.target.value); + }} + /> +
+ +
+
+
diff --git a/src/frontend/src/pages/MainPage/components/cardComponent/index.tsx b/src/frontend/src/pages/MainPage/components/cardComponent/index.tsx index 8f40da563..0396959a0 100644 --- a/src/frontend/src/pages/MainPage/components/cardComponent/index.tsx +++ b/src/frontend/src/pages/MainPage/components/cardComponent/index.tsx @@ -24,16 +24,12 @@ import { Link } from "react-router-dom"; export const CardComponent = ({ flow, id, - removeFlow, - setTabId, }: { flow: FlowType; id: string; - removeFlow: (id: string) => void; - setTabId: (id: string) => void; }) => { const { setErrorData } = useContext(alertContext); - const { updateFlow } = useContext(TabsContext); + const { updateFlow, removeFlow, setTabId } = useContext(TabsContext); function handleSaveFlow(flow) { try { updateFlowInDatabase(flow); @@ -73,12 +69,11 @@ export const CardComponent = ({ }} /> +
diff --git a/src/frontend/src/pages/MainPage/index.tsx b/src/frontend/src/pages/MainPage/index.tsx index 4af118a44..d6c10e291 100644 --- a/src/frontend/src/pages/MainPage/index.tsx +++ b/src/frontend/src/pages/MainPage/index.tsx @@ -8,7 +8,7 @@ import ExtraSidebar from "../../components/ExtraSidebarComponent"; import { ReactFlowProvider } from "reactflow"; import FlowPage from "../FlowPage"; import { useContext, useEffect, useState } from "react"; -import { SunIcon, MoonIcon, BellIcon, GithubIcon } from "lucide-react"; +import { SunIcon, MoonIcon, BellIcon, GithubIcon, Download, Upload } from "lucide-react"; import { TabsContext } from "../../contexts/tabsContext"; import AlertDropdown from "../../alerts/alertDropDown"; import { alertContext } from "../../contexts/alertContext"; @@ -20,14 +20,15 @@ import { FaGithub } from "react-icons/fa"; import _ from "lodash"; -import { updateFlowInDatabase } from "../../controllers/API"; +import { updateFlowInDatabase, uploadFlowsToDatabase } from "../../controllers/API"; import { CardComponent } from "./components/cardComponent"; import { MenuBar } from "../../components/headerComponent/components/menuBar"; export default function HomePage() { const { flows, - removeFlow, setTabId, + downloadFlows, + uploadFlows, } = useContext(TabsContext); useEffect(() => { setTabId(""); @@ -36,13 +37,28 @@ export default function HomePage() {
+
+ + Flows + +
+ + +
+
- {Object.keys(flows).map((flow, idx) => ( + {flows.map((flow, idx) => ( ))}
diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 6023663d0..2b8dbc239 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -9,6 +9,8 @@ export type TabsContextType = { updateFlow: (newFlow: FlowType) => void; incrementNodeId: () => string; downloadFlow: (flow: FlowType) => void; + downloadFlows: () => void; + uploadFlows: () => void; uploadFlow: (newFlow?: boolean) => void; hardReset: () => void; //disable CopyPaste