diff --git a/src/frontend/src/contexts/index.tsx b/src/frontend/src/contexts/index.tsx index 783bd108c..bef4298e9 100644 --- a/src/frontend/src/contexts/index.tsx +++ b/src/frontend/src/contexts/index.tsx @@ -11,15 +11,15 @@ export default function ContextWrapper({ children }: { children: ReactNode }) { return ( <> - - + + {children} - - + + ); diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index 97e62e5e2..a1d6c4c2c 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -1,11 +1,20 @@ -import { createContext, useEffect, useState, useRef, ReactNode, useContext } from "react"; +import { + createContext, + useEffect, + useState, + useRef, + ReactNode, + useContext, +} from "react"; import { FlowType } from "../types/flow"; -import { TabsContextType } from "../types/tabs"; -import { normalCaseToSnakeCase } from "../utils"; +import { LangFlowState, TabsContextType } from "../types/tabs"; +import { normalCaseToSnakeCase, updateObject } from "../utils"; import { alertContext } from "./alertContext"; +import { typesContext } from "./typesContext"; +import { TemplateVariableType } from "../types/api"; const TabsContextInitialValue: TabsContextType = { - save:()=>{}, + save: () => {}, tabIndex: 0, setTabIndex: (index: number) => {}, flows: [], @@ -13,11 +22,11 @@ const TabsContextInitialValue: TabsContextType = { addFlow: (flowData?: any) => {}, updateFlow: (newFlow: FlowType) => {}, incrementNodeId: () => 0, - downloadFlow: (flow:FlowType) => {}, + downloadFlow: (flow: FlowType) => {}, uploadFlow: () => {}, lockChat: false, - setLockChat:(prevState:boolean)=>{}, - hardReset:()=>{}, + setLockChat: (prevState: boolean) => {}, + hardReset: () => {}, }; export const TabsContext = createContext( @@ -25,51 +34,65 @@ export const TabsContext = createContext( ); export function TabsProvider({ children }: { children: ReactNode }) { - const {setNoticeData} = useContext(alertContext) + const { setNoticeData } = useContext(alertContext); const [tabIndex, setTabIndex] = useState(0); const [flows, setFlows] = useState>([]); const [id, setId] = useState(0); const [lockChat, setLockChat] = useState(false); + const { templates } = useContext(typesContext); const newNodeId = useRef(0); function incrementNodeId() { newNodeId.current = newNodeId.current + 1; return newNodeId.current; } - function save(){ + function save() { + console.log("save"); if (flows.length !== 0) - window.localStorage.setItem( - "tabsData", - JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current }) - ); + window.localStorage.setItem( + "tabsData", + JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current }) + ); } useEffect(() => { //save tabs locally - save() + save(); }, [flows, id, tabIndex, newNodeId]); - - useEffect(() => { //get tabs locally saved let cookie = window.localStorage.getItem("tabsData"); - if (cookie) { - let cookieObject = JSON.parse(cookie); + if (cookie && Object.keys(templates).length > 0) { + let cookieObject: LangFlowState = JSON.parse(cookie); + cookieObject.flows.forEach((flow) => { + flow.data.nodes.forEach((node) => { + if (Object.keys(templates[node.data.type]["template"]).length>0) { + node.data.node.template = updateObject( + node.data.node.template as TemplateVariableType, + templates[node.data.type][ + "template" + ] as unknown as TemplateVariableType + ); + } + }); + }); setTabIndex(cookieObject.tabIndex); setFlows(cookieObject.flows); setId(cookieObject.id); newNodeId.current = cookieObject.nodeId; } - }, []); - function hardReset(){ - newNodeId.current=0; - setTabIndex(0);setFlows([]);setId(0); + }, [templates]); + function hardReset() { + newNodeId.current = 0; + setTabIndex(0); + setFlows([]); + setId(0); } /** * Downloads the current flow as a JSON file */ - function downloadFlow(flow:FlowType) { + function downloadFlow(flow: FlowType) { // create a data URI with the current flow data const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( JSON.stringify(flow) @@ -82,7 +105,9 @@ export function TabsProvider({ children }: { children: ReactNode }) { // simulate a click on the link element to trigger the download link.click(); - setNoticeData({title:"Warning: Critical data,JSON file may including API keys."}) + setNoticeData({ + title: "Warning: Critical data,JSON file may including API keys.", + }); } /** @@ -103,7 +128,19 @@ export function TabsProvider({ children }: { children: ReactNode }) { // read the file as text file.text().then((text) => { // parse the text into a JSON object - addFlow(JSON.parse(text)); + let flow: FlowType = JSON.parse(text); + flow.data.nodes.forEach((node) => { + if (Object.keys(templates[node.data.type]["template"]).length>0) { + node.data.node.template = updateObject( + node.data.node.template as TemplateVariableType, + templates[node.data.type][ + "template" + ] as unknown as TemplateVariableType + ); + } + }); + + addFlow(); }); } }; @@ -139,7 +176,7 @@ export function TabsProvider({ children }: { children: ReactNode }) { function addFlow(flow?: FlowType) { // Get data from the flow or set it to null if there's no flow provided. const data = flow?.data ? flow.data : null; - const description = flow?.description?flow.description:"" + const description = flow?.description ? flow.description : ""; // Create a new flow with a default name if no flow is provided. let newFlow: FlowType = { @@ -171,7 +208,7 @@ export function TabsProvider({ children }: { children: ReactNode }) { const newFlows = [...prevState]; const index = newFlows.findIndex((flow) => flow.id === newFlow.id); if (index !== -1) { - newFlows[index].description = newFlow.description??"" + newFlows[index].description = newFlow.description ?? ""; newFlows[index].data = newFlow.data; newFlows[index].name = newFlow.name; newFlows[index].chat = newFlow.chat; diff --git a/src/frontend/src/contexts/typesContext.tsx b/src/frontend/src/contexts/typesContext.tsx index d20012a84..ad86d4699 100644 --- a/src/frontend/src/contexts/typesContext.tsx +++ b/src/frontend/src/contexts/typesContext.tsx @@ -1,6 +1,8 @@ -import { createContext, ReactNode, useState } from "react"; +import { createContext, ReactNode, useEffect, useState } from "react"; import { Node} from "reactflow"; import { typesContextType } from "../types/typesContext"; +import { getAll } from "../controllers/API"; +import { APIKindType } from "../types/api"; //context to share types adn functions from nodes to flow @@ -10,6 +12,10 @@ const initialValue:typesContextType = { deleteNode: () => {}, types: {}, setTypes: () => {}, + templates: {}, + setTemplates: () => {}, + data:{}, + setData:()=>{} }; export const typesContext = createContext(initialValue); @@ -17,6 +23,42 @@ export const typesContext = createContext(initialValue); export function TypesProvider({ children }:{children:ReactNode}) { const [types, setTypes] = useState({}); const [reactFlowInstance, setReactFlowInstance] = useState(null); + const [templates, setTemplates] = useState({}); + const [data, setData] = useState({}); + + useEffect(() => { + async function getTypes(): Promise { + // Make an asynchronous API call to retrieve all data. + let result = await getAll(); + + // Update the state of the component with the retrieved data. + setData(result.data); + setTemplates( + Object.keys(result.data).reduce((acc, curr) => { + Object.keys(result.data[curr]).forEach((c: keyof APIKindType)=>{ + acc[c] = result.data[curr][c] + }) + return acc; + },{}) + ); + // Set the types by reducing over the keys of the result data and updating the accumulator. + setTypes( + Object.keys(result.data).reduce((acc, curr) => { + Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => { + acc[c] = curr; + // Add the base classes to the accumulator as well. + result.data[curr][c].base_classes?.forEach((b) => { + acc[b] = curr; + }); + }); + return acc; + }, {}) + ); + } + // Call the getTypes function. + getTypes(); + }, [setTypes]); + function deleteNode(idx:string) { reactFlowInstance.setNodes( reactFlowInstance.getNodes().filter((n:Node) => n.id !== idx) @@ -31,6 +73,10 @@ export function TypesProvider({ children }:{children:ReactNode}) { reactFlowInstance, setReactFlowInstance, deleteNode, + setTemplates, + templates, + data, + setData }} > {children} diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 0eb3876f1..9f08674b5 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -2,43 +2,14 @@ import { Bars2Icon } from "@heroicons/react/24/outline"; import DisclosureComponent from "../DisclosureComponent"; import { nodeColors, nodeIcons, nodeNames } from "../../../../utils"; import { useContext, useEffect, useState } from "react"; -import { getAll } from "../../../../controllers/API"; import { typesContext } from "../../../../contexts/typesContext"; import { APIClassType, - APIKindType, APIObjectType, } from "../../../../types/api"; export default function ExtraSidebar() { - const [data, setData] = useState({}); - const { setTypes } = useContext(typesContext); - - useEffect(() => { - async function getTypes(): Promise { - // Make an asynchronous API call to retrieve all data. - let result = await getAll(); - - // Update the state of the component with the retrieved data. - setData(result.data); - - // Set the types by reducing over the keys of the result data and updating the accumulator. - setTypes( - Object.keys(result.data).reduce((acc, curr) => { - Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => { - acc[c] = curr; - // Add the base classes to the accumulator as well. - result.data[curr][c].base_classes?.forEach((b) => { - acc[b] = curr; - }); - }); - return acc; - }, {}) - ); - } - // Call the getTypes function. - getTypes(); - }, [setTypes]); + const {data} = useContext(typesContext) function onDragStart( event: React.DragEvent, @@ -84,7 +55,9 @@ export default function ExtraSidebar() { ))} - {Object.keys(data[d]).length===0 &&
Coming soon
} + {Object.keys(data[d]).length === 0 && ( +
Coming soon
+ )} ))} diff --git a/src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx index 6d7a4d844..ff3f271a5 100644 --- a/src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx @@ -16,21 +16,23 @@ import AlertDropdown from "../../../../alerts/alertDropDown"; import { alertContext } from "../../../../contexts/alertContext"; import ImportModal from "../../../../modals/importModal"; import ExportModal from "../../../../modals/exportModal"; +import { typesContext } from "../../../../contexts/typesContext"; export default function TabsManagerComponent() { const { flows, addFlow, tabIndex, setTabIndex, uploadFlow, downloadFlow } = useContext(TabsContext); const { openPopUp } = useContext(PopUpContext); + const {templates} = useContext(typesContext) const AlertWidth = 256; const { dark, setDark } = useContext(darkContext); const { notificationCenter, setNotificationCenter } = useContext(alertContext); useEffect(() => { //create the first flow - if (flows.length === 0) { + if (flows.length === 0&& Object.keys(templates).length>0) { addFlow(); } - }, [addFlow, flows.length]); + }, [addFlow, flows.length,templates]); return (
diff --git a/src/frontend/src/pages/FlowPage/index.tsx b/src/frontend/src/pages/FlowPage/index.tsx index 4fc315926..eef70bf5b 100644 --- a/src/frontend/src/pages/FlowPage/index.tsx +++ b/src/frontend/src/pages/FlowPage/index.tsx @@ -32,7 +32,7 @@ var _ = require("lodash"); export default function FlowPage({ flow }:{flow:FlowType}) { let { updateFlow, incrementNodeId} = useContext(TabsContext); - const { types, reactFlowInstance, setReactFlowInstance } = + const { types, reactFlowInstance, setReactFlowInstance, templates } = useContext(typesContext); const reactFlowWrapper = useRef(null); @@ -180,7 +180,7 @@ export default function FlowPage({ flow }:{flow:FlowType}) { return (
- {Object.keys(types).length > 0 ? ( + {Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? ( <> void; hardReset:()=>void; -}; \ No newline at end of file +}; + +export type LangFlowState={ tabIndex:number, flows:FlowType[], id:number, nodeId:number } \ No newline at end of file diff --git a/src/frontend/src/types/templatesContext/index.ts b/src/frontend/src/types/templatesContext/index.ts new file mode 100644 index 000000000..88e2dd07c --- /dev/null +++ b/src/frontend/src/types/templatesContext/index.ts @@ -0,0 +1,7 @@ + +const template:{[char: string]: string}={} + +export type TemplateContextType = { + templates: typeof template; + setTemplates: (newState: {}) => void; +}; \ No newline at end of file diff --git a/src/frontend/src/types/typesContext/index.ts b/src/frontend/src/types/typesContext/index.ts index e943dafe3..59d22f365 100644 --- a/src/frontend/src/types/typesContext/index.ts +++ b/src/frontend/src/types/typesContext/index.ts @@ -1,6 +1,9 @@ import { ReactFlowInstance } from "reactflow"; -const types:{[char: string]: string}={} +const types:{[char: string]: string}={}; +const template:{[char: string]: string}={} +const data:{[char: string]: string}={} + export type typesContextType = { reactFlowInstance: ReactFlowInstance|null; @@ -8,4 +11,8 @@ export type typesContextType = { deleteNode: (idx: string) => void; types: typeof types; setTypes: (newState: {}) => void; + templates: typeof template; + setTemplates: (newState: {}) => void; + data: typeof data; + setData: (newState: {}) => void; }; \ No newline at end of file diff --git a/src/frontend/src/utils.ts b/src/frontend/src/utils.ts index 405d56297..874bbd18e 100644 --- a/src/frontend/src/utils.ts +++ b/src/frontend/src/utils.ts @@ -387,4 +387,23 @@ export function removeApiKeys(flow:FlowType):FlowType{ } }) return cleanFLow +} + +export function updateObject>(reference: T, objectToUpdate: T): T { + let clonedObject = _.cloneDeep(objectToUpdate) + // Loop through each key in the object to update + for (const key in clonedObject) { + // If the key is not in the reference object, delete it + if (!(key in reference)) { + delete clonedObject[key]; + } + } + // Loop through each key in the reference object + for (const key in reference) { + // If the key is not in the object to update, add it + if (!(key in clonedObject)) { + clonedObject[key] = reference[key]; + } + } + return clonedObject; } \ No newline at end of file