🔧 chore(tabsContext.tsx): refactor code to improve readability and maintainability
✨ feat(tabsContext.tsx): add support for reading and writing flows to the database The code has been refactored to improve readability and maintainability. The `addFlow` function has been refactored to extract data from the flow and create a new flow with a default name if no flow is provided. The `processFlowEdges`, `processFlowNodes`, `updateNodeBaseClasses`, `updateNodeEdges`, `updateNodeDescription`, `updateNodeTemplate`, `updateStateWithTabsData`, and `updateStateWithDbData` functions have been added to improve code organization. Support for reading and writing flows to the database has been added to improve data persistence and allow for multiple users to access the same flows. 🔥 refactor(TabsProvider): remove unnecessary whitespace The commit removes an unnecessary whitespace in the TabsProvider function. 🐛 fix(API): add missing import statement for axios ✨ feat(API): add functions to interact with the backend API for CRUD operations on flows The missing import statement for axios has been added to the file. The new functions added to the API file allow the frontend to interact with the backend API for CRUD operations on flows. The functions added are `saveFlowToDatabase`, `updateFlowInDatabase`, `readFlowsFromDatabase`, `deleteFlowFromDatabase`, and `getFlowFromDatabase`. These functions allow the frontend to create, read, update, and delete flows from the backend API. 🎨 style(MainPage): refactor card component into a separate file The card component has been refactored into a separate file to improve code organization and readability. The `CardComponent` is now imported into the `MainPage` file and used to render the flow cards. The `handleSave` function has been updated to call the `updateFlowInDatabase` function with the current flow as an argument.
This commit is contained in:
parent
73014b63f2
commit
07d9d1163a
3 changed files with 350 additions and 166 deletions
|
|
@ -20,6 +20,10 @@ import { APITemplateType, TemplateVariableType } from "../types/api";
|
|||
import { v4 as uuidv4 } from "uuid";
|
||||
import { addEdge } from "reactflow";
|
||||
import _ from "lodash";
|
||||
import {
|
||||
readFlowsFromDatabase,
|
||||
deleteFlowFromDatabase,
|
||||
} from "../controllers/API";
|
||||
|
||||
const TabsContextInitialValue: TabsContextType = {
|
||||
save: () => {},
|
||||
|
|
@ -84,55 +88,158 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
//get tabs locally saved
|
||||
let cookie = window.localStorage.getItem("tabsData");
|
||||
if (cookie && Object.keys(templates).length > 0) {
|
||||
let cookieObject: LangFlowState = JSON.parse(cookie);
|
||||
try {
|
||||
cookieObject.flows.forEach((flow) => {
|
||||
if (!flow.data) {
|
||||
return;
|
||||
}
|
||||
flow.data.edges.forEach((edge) => {
|
||||
edge.className = "";
|
||||
edge.style = { stroke: "#555555" };
|
||||
});
|
||||
// function loadCookie(cookie: string) {
|
||||
// if (cookie && Object.keys(templates).length > 0) {
|
||||
// let cookieObject: LangFlowState = JSON.parse(cookie);
|
||||
// try {
|
||||
// cookieObject.flows.forEach((flow) => {
|
||||
// if (!flow.data) {
|
||||
// return;
|
||||
// }
|
||||
// flow.data.edges.forEach((edge) => {
|
||||
// edge.className = "";
|
||||
// edge.style = { stroke: "#555555" };
|
||||
// });
|
||||
|
||||
flow.data.nodes.forEach((node) => {
|
||||
const template = templates[node.data.type];
|
||||
if (!template) {
|
||||
setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
||||
return;
|
||||
}
|
||||
if (Object.keys(template["template"]).length > 0) {
|
||||
node.data.node.base_classes = template["base_classes"];
|
||||
flow.data.edges.forEach((edge) => {
|
||||
if (edge.source === node.id) {
|
||||
edge.sourceHandle = edge.sourceHandle
|
||||
.split("|")
|
||||
.slice(0, 2)
|
||||
.concat(template["base_classes"])
|
||||
.join("|");
|
||||
}
|
||||
});
|
||||
node.data.node.description = template["description"];
|
||||
node.data.node.template = updateTemplate(
|
||||
template["template"] as unknown as APITemplateType,
|
||||
node.data.node.template as APITemplateType
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
setTabIndex(cookieObject.tabIndex);
|
||||
setFlows(cookieObject.flows);
|
||||
setId(cookieObject.id);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
// flow.data.nodes.forEach((node) => {
|
||||
// const template = templates[node.data.type];
|
||||
// if (!template) {
|
||||
// setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
||||
// return;
|
||||
// }
|
||||
// if (Object.keys(template["template"]).length > 0) {
|
||||
// node.data.node.base_classes = template["base_classes"];
|
||||
// flow.data.edges.forEach((edge) => {
|
||||
// if (edge.source === node.id) {
|
||||
// edge.sourceHandle = edge.sourceHandle
|
||||
// .split("|")
|
||||
// .slice(0, 2)
|
||||
// .concat(template["base_classes"])
|
||||
// .join("|");
|
||||
// }
|
||||
// });
|
||||
// node.data.node.description = template["description"];
|
||||
// node.data.node.template = updateTemplate(
|
||||
// template["template"] as unknown as APITemplateType,
|
||||
// node.data.node.template as APITemplateType
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// setTabIndex(cookieObject.tabIndex);
|
||||
// setFlows(cookieObject.flows);
|
||||
// setId(cookieObject.id);
|
||||
// } catch (e) {
|
||||
// console.log(e);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
useEffect(() => {
|
||||
// get data from db
|
||||
//get tabs locally saved
|
||||
// let tabsData = getLocalStorageTabsData();
|
||||
getTabsDataFromDB().then((DbData) => {
|
||||
if (DbData && Object.keys(templates).length > 0) {
|
||||
try {
|
||||
processDBData(DbData);
|
||||
updateStateWithDbData(DbData);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [templates]);
|
||||
|
||||
function getLocalStorageTabsData() {
|
||||
let cookie = window.localStorage.getItem("tabsData");
|
||||
let cookieObject: LangFlowState = JSON.parse(cookie);
|
||||
return cookieObject;
|
||||
}
|
||||
|
||||
function getTabsDataFromDB() {
|
||||
//get tabs from db
|
||||
return readFlowsFromDatabase();
|
||||
}
|
||||
function processDBData(DbData) {
|
||||
DbData.forEach((flow) => {
|
||||
if (!flow.data) {
|
||||
return;
|
||||
}
|
||||
processFlowEdges(flow.data.edges);
|
||||
processFlowNodes(flow.data.nodes);
|
||||
});
|
||||
}
|
||||
function processTabsData(tabsData) {
|
||||
tabsData.flows.forEach((flow) => {
|
||||
if (!flow.data) {
|
||||
return;
|
||||
}
|
||||
processFlowEdges(flow.data.edges);
|
||||
processFlowNodes(flow.data.nodes);
|
||||
});
|
||||
}
|
||||
|
||||
function processFlowEdges(edges) {
|
||||
edges.forEach((edge) => {
|
||||
edge.className = "";
|
||||
edge.style = { stroke: "#555555" };
|
||||
});
|
||||
}
|
||||
|
||||
function processFlowNodes(nodes) {
|
||||
nodes.forEach((node) => {
|
||||
const template = templates[node.data.type];
|
||||
if (!template) {
|
||||
setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
||||
return;
|
||||
}
|
||||
if (Object.keys(template["template"]).length > 0) {
|
||||
updateNodeBaseClasses(node, template);
|
||||
updateNodeEdges(node, template);
|
||||
updateNodeDescription(node, template);
|
||||
updateNodeTemplate(node, template);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateNodeBaseClasses(node, template) {
|
||||
node.data.node.base_classes = template["base_classes"];
|
||||
}
|
||||
|
||||
function updateNodeEdges(node, template) {
|
||||
node.data.edges.forEach((edge) => {
|
||||
if (edge.source === node.id) {
|
||||
edge.sourceHandle = edge.sourceHandle
|
||||
.split("|")
|
||||
.slice(0, 2)
|
||||
.concat(template["base_classes"])
|
||||
.join("|");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateNodeDescription(node, template) {
|
||||
node.data.node.description = template["description"];
|
||||
}
|
||||
|
||||
function updateNodeTemplate(node, template) {
|
||||
node.data.node.template = updateTemplate(
|
||||
template["template"] as unknown as APITemplateType,
|
||||
node.data.node.template as APITemplateType
|
||||
);
|
||||
}
|
||||
|
||||
function updateStateWithTabsData(tabsData) {
|
||||
setTabIndex(tabsData.tabIndex);
|
||||
setFlows(tabsData.flows);
|
||||
setId(tabsData.id);
|
||||
}
|
||||
|
||||
function updateStateWithDbData(tabsData) {
|
||||
setFlows(tabsData);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
//save tabs locally
|
||||
console.log(id);
|
||||
|
|
@ -208,14 +315,16 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
const newFlows = [...prevState];
|
||||
const index = newFlows.findIndex((flow) => flow.id === id);
|
||||
if (index >= 0) {
|
||||
if (index === tabIndex) {
|
||||
setTabIndex(flows.length - 2);
|
||||
newFlows.splice(index, 1);
|
||||
} else {
|
||||
let flowId = flows[tabIndex].id;
|
||||
newFlows.splice(index, 1);
|
||||
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
|
||||
}
|
||||
deleteFlowFromDatabase(id).then(() => {
|
||||
if (index === tabIndex) {
|
||||
setTabIndex(flows.length - 2);
|
||||
newFlows.splice(index, 1);
|
||||
} else {
|
||||
let flowId = flows[tabIndex].id;
|
||||
newFlows.splice(index, 1);
|
||||
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
|
||||
}
|
||||
});
|
||||
}
|
||||
return newFlows;
|
||||
});
|
||||
|
|
@ -309,67 +418,90 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
reactFlowInstance.setEdges(edges);
|
||||
}
|
||||
|
||||
function addFlow(flow?: FlowType) {
|
||||
// Get data from the flow or set it to null if there's no flow provided.
|
||||
const addFlow = (flow?: FlowType) => {
|
||||
let flowData = extractDataFromFlow(flow);
|
||||
|
||||
let data = flow?.data ? flow.data : null;
|
||||
const description = flow?.description ? flow.description : "";
|
||||
if (data) {
|
||||
data.edges.forEach((edge) => {
|
||||
edge.style = { stroke: "inherit" };
|
||||
edge.className =
|
||||
edge.targetHandle.split("|")[0] === "Text"
|
||||
? "stroke-gray-800 dark:stroke-gray-300"
|
||||
: "stroke-gray-900 dark:stroke-gray-200";
|
||||
edge.animated = edge.targetHandle.split("|")[0] === "Text";
|
||||
});
|
||||
data.nodes.forEach((node) => {
|
||||
const template = templates[node.data.type];
|
||||
if (!template) {
|
||||
setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
||||
return;
|
||||
}
|
||||
if (Object.keys(template["template"]).length > 0) {
|
||||
node.data.node.base_classes = template["base_classes"];
|
||||
flow.data.edges.forEach((edge) => {
|
||||
if (edge.source === node.id) {
|
||||
edge.sourceHandle = edge.sourceHandle
|
||||
.split("|")
|
||||
.slice(0, 2)
|
||||
.concat(template["base_classes"])
|
||||
.join("|");
|
||||
}
|
||||
});
|
||||
node.data.node.description = template["description"];
|
||||
node.data.node.template = updateTemplate(
|
||||
template["template"] as unknown as APITemplateType,
|
||||
node.data.node.template as APITemplateType
|
||||
);
|
||||
}
|
||||
});
|
||||
updateIds(data, getNodeId);
|
||||
}
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
let newFlow: FlowType = {
|
||||
description,
|
||||
name: flow?.name ?? "New Flow",
|
||||
id: uuidv4(),
|
||||
data,
|
||||
};
|
||||
const newFlow = createNewFlow(flowData, flow);
|
||||
|
||||
// Increment the ID counter.
|
||||
setId(uuidv4());
|
||||
|
||||
// Add the new flow to the list of flows.
|
||||
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState, newFlow];
|
||||
return newFlows;
|
||||
});
|
||||
addFlowToLocalState(newFlow);
|
||||
|
||||
// Set the tab index to the new flow.
|
||||
setTabIndexToLocalState();
|
||||
};
|
||||
|
||||
const extractDataFromFlow = (flow) => {
|
||||
let data = flow?.data ? flow.data : null;
|
||||
const description = flow?.description ? flow.description : "";
|
||||
|
||||
if (data) {
|
||||
updateEdges(data.edges);
|
||||
updateNodes(data.nodes, data.edges);
|
||||
updateIds(data, getNodeId); // Assuming updateIds is defined elsewhere
|
||||
}
|
||||
|
||||
return { data, description };
|
||||
};
|
||||
|
||||
const updateEdges = (edges) => {
|
||||
edges.forEach((edge) => {
|
||||
edge.style = { stroke: "inherit" };
|
||||
edge.className =
|
||||
edge.targetHandle.split("|")[0] === "Text"
|
||||
? "stroke-gray-800 dark:stroke-gray-300"
|
||||
: "stroke-gray-900 dark:stroke-gray-200";
|
||||
edge.animated = edge.targetHandle.split("|")[0] === "Text";
|
||||
});
|
||||
};
|
||||
|
||||
const updateNodes = (nodes, edges) => {
|
||||
nodes.forEach((node) => {
|
||||
const template = templates[node.data.type];
|
||||
if (!template) {
|
||||
setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
||||
return;
|
||||
}
|
||||
if (Object.keys(template["template"]).length > 0) {
|
||||
node.data.node.base_classes = template["base_classes"];
|
||||
edges.forEach((edge) => {
|
||||
if (edge.source === node.id) {
|
||||
edge.sourceHandle = edge.sourceHandle
|
||||
.split("|")
|
||||
.slice(0, 2)
|
||||
.concat(template["base_classes"])
|
||||
.join("|");
|
||||
}
|
||||
});
|
||||
node.data.node.description = template["description"];
|
||||
node.data.node.template = updateTemplate(
|
||||
template["template"] as unknown as APITemplateType,
|
||||
node.data.node.template as APITemplateType
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const createNewFlow = (flowData, flow) => ({
|
||||
description: flowData.description,
|
||||
name: flow?.name ?? "New Flow",
|
||||
id: uuidv4(),
|
||||
data: flowData.data,
|
||||
});
|
||||
|
||||
const addFlowToLocalState = (newFlow) => {
|
||||
setFlows((prevState) => {
|
||||
return [...prevState, newFlow];
|
||||
});
|
||||
};
|
||||
|
||||
const setTabIndexToLocalState = () => {
|
||||
setTabIndex(flows.length);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates an existing flow with new data
|
||||
* @param newFlow - The new flow object containing the updated data
|
||||
|
|
@ -386,6 +518,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
return newFlows;
|
||||
});
|
||||
}
|
||||
|
||||
const [disableCopyPaste, setDisableCopyPaste] = useState(false);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -39,3 +39,96 @@ export async function getExamples(): Promise<FlowType[]> {
|
|||
|
||||
return await Promise.all(contentsPromises);
|
||||
}
|
||||
|
||||
export async function saveFlowToDatabase(newFlow: FlowType) {
|
||||
try {
|
||||
const response = await fetch("/flows/", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: newFlow.name,
|
||||
data: newFlow.data,
|
||||
description: newFlow.description,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateFlowInDatabase(updatedFlow: FlowType) {
|
||||
try {
|
||||
const response = await fetch(`/flows/${updatedFlow.id}`, {
|
||||
method: "PATCH", // Or "PATCH" depending on your backend API
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: updatedFlow.name,
|
||||
data: updatedFlow.data,
|
||||
description: updatedFlow.description,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function readFlowsFromDatabase() {
|
||||
try {
|
||||
const response = await fetch("/flows/");
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteFlowFromDatabase(flowId: string) {
|
||||
try {
|
||||
const response = await fetch(`/flows/${flowId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getFlowFromDatabase(flowId: number) {
|
||||
try {
|
||||
const response = await fetch(`/flows/${flowId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getHealth() {
|
||||
return await axios.get("/health");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
CodeBracketSquareIcon,
|
||||
GlobeAltIcon,
|
||||
PencilSquareIcon,
|
||||
CloudArrowUpIcon,
|
||||
PlusCircleIcon,
|
||||
PlusIcon,
|
||||
PlusSmallIcon,
|
||||
|
|
@ -66,6 +67,8 @@ import {
|
|||
MenubarRadioItem,
|
||||
MenubarTrigger,
|
||||
} from "../../components/ui/menubar";
|
||||
import { updateFlowInDatabase } from "../../controllers/API";
|
||||
import { CardComponent } from "../../components/cardComponent";
|
||||
|
||||
export default function HomePage() {
|
||||
const {
|
||||
|
|
@ -92,6 +95,12 @@ export default function HomePage() {
|
|||
addFlow();
|
||||
}
|
||||
}, [addFlow, flows.length, templates]);
|
||||
|
||||
function handleSave(flow) {
|
||||
// Put your save logic here.
|
||||
updateFlowInDatabase(flow);
|
||||
}
|
||||
|
||||
return (
|
||||
flows.length !== 0 && (
|
||||
<Tabs
|
||||
|
|
@ -155,6 +164,10 @@ export default function HomePage() {
|
|||
<MenubarMenu>
|
||||
<MenubarTrigger>Edit</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem onClick={handleSave(flows[tabIndex])}>
|
||||
<CloudArrowUpIcon className="w-4 h-4 mr-2" />
|
||||
Save
|
||||
</MenubarItem>
|
||||
<MenubarItem
|
||||
onClick={() => {
|
||||
setRename(true);
|
||||
|
|
@ -285,68 +298,13 @@ export default function HomePage() {
|
|||
<TabsContent value="myflows" className="w-full h-full bg-muted">
|
||||
<div className="w-full p-4 grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{flows.map((flow, idx) => (
|
||||
<Card className="group">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex justify-between items-start">
|
||||
<div className="flex gap-4 items-center">
|
||||
<span
|
||||
className={
|
||||
"rounded-md w-10 h-10 flex items-center justify-center text-2xl " +
|
||||
(idx === 0 ? "bg-blue-100" : " bg-orange-100")
|
||||
}
|
||||
>
|
||||
{idx === 0 ? "🤖" : "🛠️"}
|
||||
</span>
|
||||
{flow.name}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
removeFlow(flow.id);
|
||||
}}
|
||||
>
|
||||
<TrashIcon className="w-5 text-primary opacity-0 group-hover:opacity-100 transition-all" />
|
||||
</button>
|
||||
</CardTitle>
|
||||
<CardDescription className="pt-2 pb-2">
|
||||
<div className="truncate-doubleline">
|
||||
{idx === 0
|
||||
? "This flow creates an agent that accesses a department store database and APIs to monitor customer activity and overall storage."
|
||||
: "This is a new Flow"}
|
||||
{/* {flow.description} */}
|
||||
</div>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardFooter>
|
||||
<div className="flex gap-2 w-full justify-between items-end">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="secondary">
|
||||
{idx === 0 ? "Agent" : "Tool"}
|
||||
</Badge>
|
||||
{idx === 0 && (
|
||||
<Badge variant="secondary">
|
||||
<div className="w-3">
|
||||
<OpenAiIcon />
|
||||
</div>
|
||||
<span className="text-base"> </span>OpenAI+
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="whitespace-nowrap opacity-0 group-hover:opacity-100 transition-all"
|
||||
onClick={() => {
|
||||
setTabIndex(idx);
|
||||
setActiveTab("myflow");
|
||||
}}
|
||||
>
|
||||
<ArrowTopRightOnSquareIcon className="w-4 mr-2" />
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<CardComponent
|
||||
flow={flow}
|
||||
idx={idx}
|
||||
removeFlow={removeFlow}
|
||||
setTabIndex={setTabIndex}
|
||||
setActiveTab={setActiveTab}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue