diff --git a/langflow.db b/langflow.db index 377c77786..73aad8918 100644 Binary files a/langflow.db and b/langflow.db differ 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