refac: langflow_backend -> langflow

This commit is contained in:
Gabriel Almeida 2023-03-17 09:50:02 -03:00
commit 70dbc7eb1e
104 changed files with 174 additions and 50 deletions

42
src/frontend/src/App.css Normal file
View file

@ -0,0 +1,42 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

132
src/frontend/src/App.tsx Normal file
View file

@ -0,0 +1,132 @@
import "reactflow/dist/style.css";
import { useState, useEffect, useContext } from "react";
import "./App.css";
import { useLocation } from "react-router-dom";
import ErrorAlert from "./alerts/error";
import NoticeAlert from "./alerts/notice";
import SuccessAlert from "./alerts/success";
import ExtraSidebar from "./components/ExtraSidebarComponent";
import { alertContext } from "./contexts/alertContext";
import { locationContext } from "./contexts/locationContext";
import TabsManagerComponent from "./pages/FlowPage/components/tabsManagerComponent";
export default function App() {
var _ = require("lodash");
let { setCurrent, setShowSideBar, setIsStackedOpen } =
useContext(locationContext);
let location = useLocation();
useEffect(() => {
setCurrent(location.pathname.replace(/\/$/g, "").split("/"));
setShowSideBar(true);
setIsStackedOpen(true);
}, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
const {
errorData,
errorOpen,
setErrorOpen,
noticeData,
noticeOpen,
setNoticeOpen,
successData,
successOpen,
setSuccessOpen,
} = useContext(alertContext);
// Initialize state variable for the list of alerts
const [alertsList, setAlertsList] = useState<Array<{type:string,data:{title:string,list?:Array<string>,link?:string},id:string}>>([]);
// Use effect hook to update alertsList when a new alert is added
useEffect(() => {
// If there is an error alert open with data, add it to the alertsList
if (errorOpen && errorData) {
setErrorOpen(false);
setAlertsList((old) => {
let newAlertsList = [
...old,
{ type: "error", data: _.cloneDeep(errorData), id: _.uniqueId() },
];
return newAlertsList;
});
}
// If there is a notice alert open with data, add it to the alertsList
else if (noticeOpen && noticeData) {
setNoticeOpen(false);
setAlertsList((old) => {
let newAlertsList = [
...old,
{ type: "notice", data: _.cloneDeep(noticeData), id: _.uniqueId() },
];
return newAlertsList;
});
}
// If there is a success alert open with data, add it to the alertsList
else if (successOpen && successData) {
setSuccessOpen(false);
setAlertsList((old) => {
let newAlertsList = [
...old,
{ type: "success", data: _.cloneDeep(successData), id: _.uniqueId() },
];
return newAlertsList;
});
}
}, [_, errorData, errorOpen, noticeData, noticeOpen, setErrorOpen, setNoticeOpen, setSuccessOpen, successData, successOpen]);
const removeAlert = (id: string) => {
setAlertsList((prevAlertsList) =>
prevAlertsList.filter((alert) => alert.id !== id)
);
};
return (
//need parent component with width and height
<div className="h-full flex flex-col">
<div className="flex grow-0 shrink basis-auto">
</div>
<div className="flex grow shrink basis-auto min-h-0 flex-1 overflow-hidden">
<ExtraSidebar />
{/* Main area */}
<main className="min-w-0 flex-1 border-t border-gray-200 dark:border-gray-700 flex">
{/* Primary column */}
<div className="w-full h-full">
<TabsManagerComponent></TabsManagerComponent>
</div>
</main>
</div>
<div></div>
<div className="flex z-40 flex-col-reverse fixed bottom-5 left-5">
{alertsList.map((alert) => (
<div key={alert.id}>
{alert.type === "error" ? (
<ErrorAlert
key={alert.id}
title={alert.data.title}
list={alert.data.list}
id={alert.id}
removeAlert={removeAlert}
/>
) : alert.type === "notice" ? (
<NoticeAlert
key={alert.id}
title={alert.data.title}
link={alert.data.link}
id={alert.id}
removeAlert={removeAlert}
/>
) : (
<SuccessAlert
key={alert.id}
title={alert.data.title}
id={alert.id}
removeAlert={removeAlert}
/>
)}
</div>
))}
</div>
<a target={"_blank"} href="https://logspace.ai/" className="absolute bottom-1 left-1 text-gray-500 text-xs cursor-pointer font-sans tracking-wide">Created by Logspace</a>
</div>
);
}

View file

@ -0,0 +1,137 @@
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
import Tooltip from "../../../../components/TooltipComponent";
import { classNames, isValidConnection } 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";
import { ParameterComponentType } from "../../../../types/components";
import FloatComponent from "../../../../components/floatComponent";
export default function ParameterComponent({
left,
id,
data,
tooltipTitle,
title,
color,
type,
name = "",
required = false,
}: ParameterComponentType) {
const ref = useRef(null);
const updateNodeInternals = useUpdateNodeInternals();
const [position, setPosition] = useState(0);
useEffect(() => {
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
setPosition(ref.current.offsetTop + ref.current.clientHeight / 2);
updateNodeInternals(data.id);
}
}, [data.id, ref, updateNodeInternals]);
useEffect(() => {
updateNodeInternals(data.id);
}, [data.id, position, updateNodeInternals]);
const [enabled, setEnabled] = useState(
data.node.template[name]?.value ?? false
);
const { reactFlowInstance } = useContext(typesContext);
let disabled =
reactFlowInstance?.getEdges().some((e) => e.targetHandle === id) ?? false;
return (
<div
ref={ref}
className="w-full flex flex-wrap justify-between items-center bg-gray-50 dark:bg-gray-800 dark:text-white mt-1 px-5 py-2"
>
<>
<div className="text-sm truncate">
{title}
<span className="text-red-600">{required ? " *" : ""}</span>
</div>
{left && (type === "str" || type === "bool" || type === "float") ?
<></>
:
<Tooltip title={tooltipTitle + (required ? " (required)" : "")}>
<Handle
type={left ? "target" : "source"}
position={left ? Position.Left : Position.Right}
id={id}
isValidConnection={(connection) =>
isValidConnection(connection, reactFlowInstance)
}
className={classNames(
left ? "-ml-0.5 " : "-mr-0.5 ",
"w-3 h-3 rounded-full border-2 bg-white dark:bg-gray-800"
)}
style={{
borderColor: color,
top: position,
}}
></Handle>
</Tooltip>
}
{left === true && type === "str" ? (
<div className="mt-2 w-full">
{data.node.template[name].list ? (
<InputListComponent
disabled={disabled}
value={
!data.node.template[name].value ||
data.node.template[name].value === ""
? [""]
: data.node.template[name].value
}
onChange={(t: string[]) => {
data.node.template[name].value = t;
}}
/>
) : data.node.template[name].multiline ? (
<TextAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
) : (
<InputComponent
disabled={disabled}
password={data.node.template[name].password ?? true}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
)}
</div>
) : left === true && type === "bool" ? (
<div className="mt-2">
<ToggleComponent
disabled={disabled}
enabled={enabled}
setEnabled={(t) => {
data.node.template[name].value = t;
setEnabled(t);
}}
/>
</div>
) : left === true && type === "float" ? (
<FloatComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
) : (
<></>
)}
</>
</div>
);
}

View file

@ -0,0 +1,92 @@
import {
TrashIcon,
} from "@heroicons/react/24/outline";
import {
classNames,
nodeColors,
nodeIcons,
snakeToNormalCase,
} from "../../utils";
import ParameterComponent from "./components/parameterComponent";
import { typesContext } from "../../contexts/typesContext";
import { useContext } from "react";
import { NodeDataType} from "../../types/flow";
export default function GenericNode({ data, selected}:{data:NodeDataType,selected:boolean}) {
const {types, deleteNode} = useContext(typesContext);
const Icon = nodeIcons[types[data.type]];
return (
<div className={ classNames(selected?"border border-blue-500":"border dark:border-gray-700","prompt-node relative bg-white dark:bg-gray-900 w-96 rounded-lg flex flex-col justify-center")}>
<div className="w-full dark:text-white flex items-center justify-between p-4 gap-8 bg-gray-50 rounded-t-lg dark:bg-gray-800 border-b dark:border-b-gray-700 ">
<div className="w-full flex items-center truncate gap-4 text-lg">
<Icon
className="w-10 h-10 p-1 rounded"
style={{ color: nodeColors[types[data.type]] }}
/>
<div className="truncate">{data.type}</div>
</div>
<button onClick={() => {deleteNode(data.id)}}>
<TrashIcon className="w-6 h-6 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-500"></TrashIcon>
</button>
</div>
<div className="w-full h-full py-5">
<div className="w-full text-gray-500 px-5 text-sm">
{data.node.description}
</div>
<>
{Object.keys(data.node.template)
.filter((t) => t.charAt(0) !== "_")
.map((t:string, idx) => (
<div key={idx}>
{idx === 0 ? (
<div className="px-5 py-2 mt-2 dark:text-white text-center">Inputs:</div>
) : (
<></>
)}
{data.node.template[t].show ? (
<ParameterComponent
data={data}
color={
nodeColors[types[data.node.template[t].type]] ??
nodeColors[types[data.node.template[t].type]] ??
"black"
}
title={
snakeToNormalCase(t)
}
name={t}
tooltipTitle={
"Type: " +
data.node.template[t].type +
(data.node.template[t].list ? " list" : "")
}
required={data.node.template[t].required}
id={data.node.template[t].type + "|" + t + "|" + data.id}
left={true}
type={data.node.template[t].type}
/>
) : (
<></>
)}
</div>
))}
<div className="px-5 py-2 mt-2 dark:text-white text-center">Output:</div>
<ParameterComponent
data={data}
color={nodeColors[types[data.type]]}
title={data.type}
tooltipTitle={"Type: str"}
id={data.type + "|" + data.id + data.node.base_classes.map((b) => ("|" + b))}
type={'str'}
left={false}
/>
</>
</div>
</div>
);
}

View file

@ -0,0 +1,135 @@
import { XCircleIcon, XMarkIcon, InformationCircleIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
import { Link } from "react-router-dom";
import { Transition } from "@headlessui/react";
import { useState } from "react";
import { SingleAlertComponentType } from "../../../../types/alerts";
export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponentType) {
const [show, setShow] = useState(true);
const type = dropItem.type;
console.log(dropItem.id)
return (
<Transition
className="relative"
show={show}
appear={true}
enter="transition-transform duration-500 ease-out"
enterFrom={"transform translate-x-[-100%]"}
enterTo={"transform translate-x-0"}
leave="transition-transform duration-500 ease-in"
leaveFrom={"transform translate-x-0"}
leaveTo={"transform translate-x-[-100%]"}
>
{type === "error"?
<div className="flex bg-red-50 rounded-md p-3 mb-2 mx-2" key={dropItem.id}>
<div className="flex-shrink-0">
<XCircleIcon
className="h-5 w-5 text-red-400"
aria-hidden="true"
/>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">
{dropItem.title}
</h3>
{dropItem.list ? (
<div className="mt-2 text-sm text-red-700">
<ul className="list-disc space-y-1 pl-5">
{dropItem.list.map((item, idx) => (
<li key={idx}>{item}</li>
))}
</ul>
</div>
) : (
<></>
)}
</div>
<div className="ml-auto pl-3">
<div className="-mx-1.5 -my-1.5">
<button
type="button"
onClick={() => {
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
}}
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
>
<span className="sr-only">Dismiss</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
:(type === "notice" ?
<div className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2" key={dropItem.id}>
<div className="flex-shrink-0">
<InformationCircleIcon
className="h-5 w-5 text-blue-400"
aria-hidden="true"
/>
</div>
<div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-blue-700">{dropItem.title}</p>
<p className="mt-3 text-sm md:mt-0 md:ml-6">
{dropItem.link ? (
<Link
to={dropItem.link}
className="whitespace-nowrap font-medium text-blue-700 hover:text-blue-600"
>
Details
</Link>
) : (
<></>
)}
</p>
</div>
<div className="ml-auto pl-3">
<div className="-mx-1.5 -my-1.5">
<button
type="button"
onClick={() => {
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
}}
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500"
>
<span className="sr-only">Dismiss</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
:
<div className="flex bg-green-50 p-3 mb-2 mx-2 rounded-md" key={dropItem.id}>
<div className="flex-shrink-0">
<CheckCircleIcon
className="h-5 w-5 text-green-400"
aria-hidden="true"
/>
</div>
<div className="ml-3">
<p className="text-sm font-medium text-green-800">
{dropItem.title}
</p>
</div>
<div className="ml-auto pl-3">
<div className="-mx-1.5 -my-1.5">
<button
type="button"
onClick={() => {
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
}}
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500"
>
<span className="sr-only">Dismiss</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
)
}
</Transition>
)
}

View file

@ -0,0 +1,78 @@
import { useContext, useEffect, useRef } from "react";
import { alertContext } from "../../contexts/alertContext";
import { XMarkIcon } from "@heroicons/react/24/solid";
import { TrashIcon } from "@heroicons/react/24/outline";
import SingleAlert from "./components/singleAlertComponent";
import { AlertDropdownType } from "../../types/alerts";
import { PopUpContext } from "../../contexts/popUpContext";
export default function AlertDropdown({}: AlertDropdownType) {
const { closePopUp } = useContext(PopUpContext);
const componentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
componentRef.current &&
!componentRef.current.contains(event.target as Node)
) {
console.log(event)
closePopUp();
}
}
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
// Cleanup the event listener when the component is unmounted
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [componentRef]);
const {
notificationList,
clearNotificationList,
removeFromNotificationList,
} = useContext(alertContext);
return (
<div
ref={componentRef}
className="z-10 py-3 pb-4 rounded-md bg-white ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[16rem] h-[28rem] flex flex-col"
>
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800">
Notifications
<div className="flex gap-2 pr-3 ">
<button
className="hover:text-red-500"
onClick={() => {
closePopUp();
setTimeout(clearNotificationList, 100);
}}
>
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
</button>
<button className="hover:text-red-500" onClick={closePopUp}>
<XMarkIcon className="h-5 w-5" />
</button>
</div>
</div>
<div className="mt-2 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide">
{notificationList.length !== 0 ? (
notificationList.map((alertItem, index) => (
<SingleAlert
key={alertItem.id}
dropItem={alertItem}
removeAlert={removeFromNotificationList}
/>
))
) : (
<div className="h-full w-full pb-16 text-gray-500 flex justify-center items-center">
No new notifications
</div>
)}
</div>
</div>
);
}

View file

@ -0,0 +1,66 @@
import { Transition } from "@headlessui/react";
import { XCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { useEffect, useState } from "react";
import { ErrorAlertType } from "../../types/alerts";
export default function ErrorAlert({
title,
list = [],
id,
removeAlert,
}: ErrorAlertType) {
const [show, setShow] = useState(true);
useEffect(() => {
if (show) {
setTimeout(() => {
setShow(false);
setTimeout(() => {
removeAlert(id);
}, 500);
}, 5000);
}
}, [id, removeAlert, show]);
return (
<Transition
className="relative"
show={show}
appear={true}
enter="transition-transform duration-500 ease-out"
enterFrom={"transform translate-x-[-100%]"}
enterTo={"transform translate-x-0"}
leave="transition-transform duration-500 ease-in"
leaveFrom={"transform translate-x-0"}
leaveTo={"transform translate-x-[-100%]"}
>
<div
onClick={() => {
setShow(false);
setTimeout(() => {
removeAlert(id);
}, 500);
}}
className="rounded-md w-96 mt-6 shadow-xl bg-red-50 p-4 cursor-pointer"
>
<div className="flex">
<div className="flex-shrink-0">
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">{title}</h3>
{list.length !== 0 ? (
<div className="mt-2 text-sm text-red-700">
<ul className="list-disc space-y-1 pl-5">
{list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
) : (
<></>
)}
</div>
</div>
</div>
</Transition>
);
}

View file

@ -0,0 +1,67 @@
import { Transition } from "@headlessui/react";
import { InformationCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { NoticeAlertType } from "../../types/alerts";
export default function NoticeAlert({
title,
link = "",
id,
removeAlert,
}: NoticeAlertType) {
const [show, setShow] = useState(true);
useEffect(() => {
if (show) {
setTimeout(() => {
setShow(false);
setTimeout(() => {
removeAlert(id);
}, 500);
}, 5000);
}
}, [id, removeAlert, show]);
return (
<Transition
show={show}
enter="transition-transform duration-500 ease-out"
enterFrom={"transform translate-x-[-100%]"}
enterTo={"transform translate-x-0"}
leave="transition-transform duration-500 ease-in"
leaveFrom={"transform translate-x-0"}
leaveTo={"transform translate-x-[-100%]"}
>
<div
onClick={() => {
setShow(false);
removeAlert(id);
}}
className="rounded-md w-96 mt-6 shadow-xl bg-blue-50 p-4"
>
<div className="flex">
<div className="flex-shrink-0">
<InformationCircleIcon
className="h-5 w-5 text-blue-400"
aria-hidden="true"
/>
</div>
<div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-blue-700">{title}</p>
<p className="mt-3 text-sm md:mt-0 md:ml-6">
{link !== "" ? (
<Link
to={link}
className="whitespace-nowrap font-medium text-blue-700 hover:text-blue-600"
>
Details
</Link>
) : (
<></>
)}
</p>
</div>
</div>
</div>
</Transition>
);
}

View file

@ -0,0 +1,53 @@
import { Transition } from "@headlessui/react";
import { CheckCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { useEffect, useState } from "react";
import { SuccessAlertType } from "../../types/alerts";
export default function SuccessAlert({
title,
id,
removeAlert,
}: SuccessAlertType) {
const [show, setShow] = useState(true);
useEffect(() => {
if (show) {
setTimeout(() => {
setShow(false);
setTimeout(() => {
removeAlert(id);
}, 500);
}, 5000);
}
}, [id, removeAlert, show]);
return (
<Transition
show={show}
enter="transition-transform duration-500 ease-out"
enterFrom={"transform translate-x-[-100%]"}
enterTo={"transform translate-x-0"}
leave="transition-transform duration-500 ease-in"
leaveFrom={"transform translate-x-0"}
leaveTo={"transform translate-x-[-100%]"}
>
<div
onClick={() => {
setShow(false);
removeAlert(id);
}}
className="rounded-md w-96 mt-6 shadow-xl bg-green-50 p-4"
>
<div className="flex">
<div className="flex-shrink-0">
<CheckCircleIcon
className="h-5 w-5 text-green-400"
aria-hidden="true"
/>
</div>
<div className="ml-3">
<p className="text-sm font-medium text-green-800">{title}</p>
</div>
</div>
</div>
</Transition>
);
}

View file

@ -0,0 +1,124 @@
import { Disclosure } from "@headlessui/react";
import { ChevronLeftIcon } from "@heroicons/react/24/outline";
import { useContext } from "react";
import { Link } from "react-router-dom";
import { classNames } from "../../utils";
import { locationContext } from "../../contexts/locationContext";
export default function ExtraSidebar() {
const {
current,
isStackedOpen,
setIsStackedOpen,
extraNavigation,
extraComponent,
} = useContext(locationContext);
return (
<>
<aside
className={` ${
isStackedOpen ? "w-52" : "w-0 "
} flex-shrink-0 flex overflow-hidden flex-col border-r dark:border-r-gray-700 transition-all duration-500`}
>
<div className="w-52 dark:bg-gray-800 border dark:border-gray-700 overflow-y-auto scrollbar-hide h-full flex flex-col items-start">
<div className="flex pt-1 px-4 justify-between align-middle w-full">
<span className="text-gray-900 dark:text-white py-[2px] font-medium ">
{extraNavigation.title}
</span>
</div>
<div className="flex flex-grow flex-col w-full">
{extraNavigation.options ? (
<div className="p-4">
<nav className="flex-1 space-y-1">
{extraNavigation.options.map((item) =>
!item.children ? (
<div key={item.name}>
<Link
to={item.href}
className={classNames(
item.href.split("/")[2] === current[4]
? "bg-gray-100 text-gray-900"
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
"group w-full flex items-center pl-2 py-2 text-sm font-medium rounded-md"
)}
>
<item.icon
className={classNames(
item.href.split("/")[2] === current[4]
? "text-gray-500"
: "text-gray-400 group-hover:text-gray-500",
"mr-3 flex-shrink-0 h-6 w-6"
)}
/>
{item.name}
</Link>
</div>
) : (
<Disclosure
as="div"
key={item.name}
className="space-y-1"
>
{({ open }) => (
<>
<Disclosure.Button
className={classNames(
item.href.split("/")[2] === current[4]
? "bg-gray-100 text-gray-900"
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
"group w-full flex items-center pl-2 pr-1 py-2 text-left text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"
)}
>
<item.icon
className="mr-3 h-6 w-6 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
aria-hidden="true"
/>
<span className="flex-1">{item.name}</span>
<svg
className={classNames(
open
? "text-gray-400 rotate-90"
: "text-gray-300",
"ml-3 h-5 w-5 flex-shrink-0 transition-rotate duration-150 ease-in-out group-hover:text-gray-400"
)}
viewBox="0 0 20 20"
aria-hidden="true"
>
<path
d="M6 6L14 10L6 14V6Z"
fill="currentColor"
/>
</svg>
</Disclosure.Button>
<Disclosure.Panel className="space-y-1">
{item.children.map((subItem) => (
<Link
key={subItem.name}
to={subItem.href}
className={classNames(
subItem.href.split("/")[3] === current[5]
? "bg-gray-100 text-gray-900"
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
"group flex w-full items-center rounded-md py-2 pl-11 pr-2 text-sm font-medium"
)}
>
{subItem.name}
</Link>
))}
</Disclosure.Panel>
</>
)}
</Disclosure>
)
)}
</nav>
</div>
) : (
extraComponent
)}
</div>
</div>
</aside>
</>
);
}

View file

@ -0,0 +1,17 @@
import { styled } from '@mui/material/styles';
import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip';
export const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip {...props} classes={{ popper: className }} />
))(({ theme }) => ({
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.common.white,
color: 'rgba(0, 0, 0, 0.87)',
boxShadow: theme.shadows[2],
fontSize: 14,
},
[`& .${tooltipClasses.arrow}:before`]: {
color: theme.palette.common.white,
boxShadow: theme.shadows[1],
},
}));

View file

@ -0,0 +1,6 @@
import { ReactElement } from "react";
import { LightTooltip } from "../LightTooltipComponent";
export default function Tooltip({ children, title }:{children:ReactElement,title:string}) {
return <LightTooltip title={title} arrow>{children}</LightTooltip>;
}

View file

@ -0,0 +1,49 @@
import { ChatBubbleLeftEllipsisIcon, ChatBubbleOvalLeftEllipsisIcon, PlusSmallIcon } from "@heroicons/react/24/outline";
import { useState } from "react";
import { ChatMessageType } from "../../../types/chat";
import { nodeColors } from "../../../utils";
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
const [hidden, setHidden] = useState(true);
return (
<div>
{!chat.isSend ? (
<div className="w-full text-start">
<div
style={{ backgroundColor: nodeColors["chat"] }}
className=" relative text-start inline-block text-white rounded-xl overflow-hidden w-fit max-w-[280px] text-sm font-normal rounded-tl-none"
>
{hidden && chat.thought && chat.thought !== "" && (
<div
onClick={() => setHidden((prev) => !prev)}
className="absolute top-2 right-2 cursor-pointer"
>
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce" />
</div>
)}
{chat.thought && chat.thought !== "" && !hidden && (
<div
onClick={() => setHidden((prev) => !prev)}
style={{ backgroundColor: nodeColors["thought"] }}
className=" text-start inline-block w-full pb-3 pt-3 px-5 cursor-pointer"
dangerouslySetInnerHTML={{
__html: chat.thought.replace(/\n/g, "<br />"),
}}
></div>
)}
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="w-full rounded-b-md px-4 pb-3 pt-3 pr-8" style={{ backgroundColor: nodeColors["chat"] }}>
{chat.message}
</div>
</div>
</div>
) : (
<div className="w-full text-end">
<div className="text-start inline-block rounded-xl p-3 overflow-hidden w-fit max-w-[280px] px-5 text-sm text-black dark:text-white dark:bg-gray-700 bg-gray-200 font-normal rounded-tr-none">
{chat.message}
</div>
</div>
)}
</div>
);
}

View file

@ -0,0 +1,243 @@
import { Transition } from "@headlessui/react";
import {
Bars3CenterLeftIcon,
LockClosedIcon,
PaperAirplaneIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
import { MouseEventHandler, useContext, useEffect, useRef, useState } from "react";
import { sendAll } from "../../controllers/NodesServices";
import { alertContext } from "../../contexts/alertContext";
import { classNames, nodeColors } from "../../utils";
import { TabsContext } from "../../contexts/tabsContext";
import { ChatType } from "../../types/chat";
import ChatMessage from "./chatMessage";
const _ = require("lodash");
export default function Chat({ flow, reactFlowInstance }: ChatType) {
const { updateFlow,lockChat,setLockChat,flows,tabIndex } = useContext(TabsContext);
const [saveChat, setSaveChat] = useState(false);
const [open, setOpen] = useState(true);
const [chatValue, setChatValue] = useState("");
const [chatHistory, setChatHistory] = useState(flow.chat);
const { setErrorData } = useContext(alertContext);
const addChatHistory = (
message: string,
isSend: boolean,
thought?: string,
) => {
let tabsChange = false;
setChatHistory((old) => {
let newChat = _.cloneDeep(old);
if(JSON.stringify(flow.chat) !==JSON.stringify(old)){
console.log(old,flow.chat)
tabsChange = true
return old
}
if (thought) {
newChat.push({ message, isSend, thought });
} else {
newChat.push({ message, isSend });
}
return newChat;
});
if(tabsChange){
console.log(flow.chat)
if(thought){
updateFlow({..._.cloneDeep(flow),chat:[...flow.chat,{isSend,message,thought}]})
}
else{
updateFlow({..._.cloneDeep(flow),chat:[...flow.chat,{isSend,message}]})
}
}
setSaveChat((chat) => !chat);
};
useEffect(() => {
updateFlow({ ..._.cloneDeep(flow), chat: chatHistory });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [saveChat]);
useEffect(() => {
setChatHistory(flow.chat);
}, [flow]);
useEffect(() => {
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
}, [chatHistory]);
function validateNodes() {
if (
reactFlowInstance
.getNodes()
.some(
(n) =>
n.data.node &&
Object.keys(n.data.node.template).some(
(t: any) =>
n.data.node.template[t].required &&
n.data.node.template[t].value === "" &&
n.data.node.template[t].required &&
!reactFlowInstance
.getEdges()
.some(
(e) =>
e.sourceHandle.split("|")[1] === t &&
e.sourceHandle.split("|")[2] === n.id
)
)
)
) {
return false;
}
return true;
}
const ref = useRef(null);
function sendMessage() {
if (chatValue !== "") {
if (validateNodes()) {
setLockChat(true);
let message = chatValue;
setChatValue("");
addChatHistory(message, true);
console.log({ ...reactFlowInstance.toObject(), message, chatHistory });
sendAll({ ...reactFlowInstance.toObject(), message, chatHistory})
.then((r) => {
console.log(r.data);
addChatHistory(r.data.result, false, r.data.thought);
setLockChat(false);
})
.catch((error) => {
setErrorData({ title: error.message ?? "unknow error" });
setLockChat(false);
});
} else {
setErrorData({
title: "Error sending message",
list: [ "Oops! Looks like you missed some required information. Please fill in all the required fields before continuing."],
});
}
} else {
setErrorData({
title: "Error sending message",
list: ["The message cannot be empty."],
});
}
}
function clearChat() {
setChatHistory([])
updateFlow({ ..._.cloneDeep(flow), chat: []});
}
return (
<>
<Transition
show={open}
appear={true}
enter="transition ease-out duration-300"
enterFrom="translate-y-96"
enterTo="translate-y-0"
leave="transition ease-in duration-300"
leaveFrom="translate-y-0"
leaveTo="translate-y-96"
>
<div className="w-[340px] absolute bottom-0 right-1">
<div className="border dark:border-gray-700 h-full rounded-xl rounded-b-none bg-white dark:bg-gray-800 shadow">
<div
onClick={() => {
setOpen(false);
}}
className="flex justify-between cursor-pointer items-center px-5 py-2 border-b dark:border-b-gray-700"
>
<div className="flex gap-3 text-lg dark:text-white font-medium items-center">
<Bars3CenterLeftIcon
className="h-5 w-5 mt-1"
style={{ color: nodeColors["chat"] }}
/>
Chat
</div>
<button className="hover:text-blue-500"
onClick={(e) => {
e.stopPropagation()
clearChat();
}}
>
Clear
</button>
</div>
<div className="w-full h-[400px] flex gap-3 mb-auto overflow-y-auto scrollbar-hide flex-col bg-gray-50 dark:bg-gray-900 p-3 py-5">
{chatHistory.map((c, i) => (
<ChatMessage chat={c} key={i} />
))}
<div ref={ref}></div>
</div>
<div className="w-full bg-white dark:bg-gray-800 border-t dark:border-t-gray-600 flex items-center justify-between p-3">
<div className="relative w-full mt-1 rounded-md shadow-sm">
<input
onKeyDown={(event) => {
if (event.key === "Enter" && !lockChat) {
sendMessage();
}
}}
type="text"
disabled={lockChat}
value={lockChat ? "Thinking..." : chatValue}
onChange={(e) => {
setChatValue(e.target.value);
}}
className={classNames(
lockChat ? "bg-gray-500 text-white" : "dark:bg-gray-700",
"form-input block w-full rounded-md border-gray-300 dark:border-gray-600 dark:text-white pr-10 sm:text-sm"
)}
placeholder={"Send a message..."}
/>
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
<button disabled={lockChat} onClick={() => sendMessage()}>
{lockChat ? (
<LockClosedIcon
className="h-5 w-5 text-gray-400 dark:hover:text-gray-300 animate-pulse"
aria-hidden="true"
/>
) : (
<PaperAirplaneIcon
className="h-5 w-5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
aria-hidden="true"
/>
)}
</button>
</div>
</div>
</div>
</div>
</div>
</Transition>
<Transition
show={!open}
appear={true}
enter="transition ease-out duration-300"
enterFrom="translate-y-96"
enterTo="translate-y-0"
leave="transition ease-in duration-300"
leaveFrom="translate-y-0"
leaveTo="translate-y-96"
>
<div className="absolute bottom-0 right-1">
<div className="border flex justify-center align-center py-1 px-3 rounded-xl rounded-b-none bg-white dark:bg-gray-800 dark:border-gray-600 dark:text-white shadow">
<button
onClick={() => {
setOpen(true);
}}
>
<div className="flex gap-3 items-center">
<Bars3CenterLeftIcon
className="h-6 w-6 mt-1"
style={{ color: nodeColors["chat"] }}
/>
Chat
</div>
</button>
</div>
</div>
</Transition>
</>
);
}

View file

@ -0,0 +1,82 @@
import { Listbox, Transition } from "@headlessui/react";
import { ChevronUpDownIcon, CheckIcon } from "@heroicons/react/24/outline";
import { Fragment } from "react";
import { DropDownComponentType } from "../../types/components";
import { classNames } from "../../utils";
export default function Dropdown({title, value, options, onSelect}:DropDownComponentType) {
return (
<>
<Listbox value={value} onChange={onSelect}>
{({ open }) => (
<>
<Listbox.Label className="block text-sm font-medium text-gray-700">
{title}
</Listbox.Label>
<div className="relative mt-1">
<Listbox.Button className="relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm">
<span className="block truncate">{value}</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</span>
</Listbox.Button>
<Transition
show={open}
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{options.map((option, id) => (
<Listbox.Option
key={id}
className={({ active }) =>
classNames(
active ? "text-white bg-indigo-600" : "text-gray-900",
"relative cursor-default select-none py-2 pl-3 pr-9"
)
}
value={option}
>
{({ selected, active }) => (
<>
<span
className={classNames(
selected ? "font-semibold" : "font-normal",
"block truncate"
)}
>
{option}
</span>
{selected ? (
<span
className={classNames(
active ? "text-white" : "text-indigo-600",
"absolute inset-y-0 right-0 flex items-center pr-4"
)}
>
<CheckIcon
className="h-5 w-5"
aria-hidden="true"
/>
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</>
)}
</Listbox>
</>
);
}

View file

@ -0,0 +1,26 @@
import { useEffect, useState } from "react";
import { FloatComponentType } from "../../types/components";
export default function FloatComponent({value, onChange, disabled}: FloatComponentType){
const [myValue, setMyValue] = useState(value ?? "");
useEffect(()=> {
if(disabled){
setMyValue("");
onChange("");
}
}, [disabled, onChange])
return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
<input
type="number"
value={myValue}
className={"block w-full form-input dark:bg-gray-900 arrow-hide dark:border-gray-600 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + (disabled ? " bg-gray-200 dark:bg-gray-700" : "")}
placeholder="Type a number from zero to one"
onChange={(e) => {
setMyValue(e.target.value);
onChange(e.target.value);
}}
/>
</div>
);
}

View file

@ -0,0 +1,36 @@
import { useEffect, useState } from "react";
import { InputComponentType } from "../../types/components";
import { classNames } from "../../utils";
export default function InputComponent({
value,
onChange,
disabled,
password,
}: InputComponentType) {
const [myValue, setMyValue] = useState(value ?? "");
useEffect(() => {
if (disabled) {
setMyValue("");
onChange("");
}
}, [disabled, onChange]);
return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
<input
type="text"
value={myValue}
className={classNames(
"block w-full form-input dark:bg-gray-900 dark:border-gray-600 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm",
disabled ? " bg-gray-200 dark:bg-gray-700" : "",
password?"password":""
)}
placeholder="Type a text"
onChange={(e) => {
setMyValue(e.target.value);
onChange(e.target.value);
}}
/>
</div>
);
}

View file

@ -0,0 +1,54 @@
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { useEffect, useState } from "react";
import { InputListComponentType } from "../../types/components";
var _ = require("lodash");
export default function InputListComponent({ value, onChange, disabled}:InputListComponentType) {
const [inputList, setInputList] = useState(value ?? [""]);
useEffect(()=> {
if(disabled){
setInputList([""]);
onChange([""]);
}
}, [disabled, onChange])
return (
<div className={(disabled ? "pointer-events-none cursor-not-allowed" : "") + "flex flex-col gap-3"}>
{inputList.map((i, idx) => (
<div key={idx} className="w-full flex gap-3">
<input
type="text"
value={i}
className={"block w-full form-input rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + (disabled ? " bg-gray-200" : "")}
placeholder="Type a text"
onChange={(e) => {
setInputList((old) => {
let newInputList = _.cloneDeep(old);
newInputList[idx] = e.target.value;
return newInputList;
});
onChange(inputList);
}}
/>
{idx === inputList.length - 1 ?
<button onClick={() => {setInputList((old) => {
let newInputList = _.cloneDeep(old);
newInputList.push('');
return newInputList;
});
onChange(inputList);}}>
<PlusIcon className="w-4 h-4 hover:text-blue-600" />
</button>
: <button onClick={() => {setInputList((old) => {
let newInputList = _.cloneDeep(old);
newInputList.splice(idx, 1);
return newInputList;
});
onChange(inputList);}}>
<XMarkIcon className="w-4 h-4 hover:text-red-600" />
</button>}
</div>
))}
</div>
);
}

View file

@ -0,0 +1,16 @@
type LoadingComponentProps={
remSize:number
}
export default function LoadingComponent({remSize}:LoadingComponentProps){
return(
<div role="status" className="w-min m-auto">
<svg aria-hidden="true" className={`w-${remSize} h-${remSize} mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600`} viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
</svg>
<span className="animate-pulse text-blue-600 text-lg">Loading...</span>
</div>
)
}

View file

@ -0,0 +1,33 @@
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
import { useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import TextAreaModal from "../../modals/textAreaModal";
import { TextAreaComponentType } from "../../types/components";
export default function TextAreaComponent({ value, onChange, disabled }:TextAreaComponentType) {
const [myValue, setMyValue] = useState(value);
const { openPopUp } = useContext(PopUpContext);
useEffect(() => {
if (disabled) {
setMyValue([""]);
onChange([""]);
}
}, [disabled, onChange]);
return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
<div className="w-full flex items-center gap-3">
<span
className={
"truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
(disabled ? " bg-gray-200" : "")
}
>
{myValue !== "" ? myValue : 'Text empty'}
</span>
<button onClick={()=>{openPopUp(<TextAreaModal value={myValue} setValue={(t:string) => {setMyValue(t); onChange(t);}}/>)}}>
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-blue-600" />
</button>
</div>
</div>
);
}

View file

@ -0,0 +1,75 @@
import { Switch } from "@headlessui/react";
import { classNames } from "../../utils";
import { useEffect } from "react";
import { ToggleComponentType } from "../../types/components";
export default function ToggleComponent({ enabled, setEnabled, disabled }:ToggleComponentType) {
useEffect(()=> {
if(disabled){
setEnabled(false);
}
}, [disabled, setEnabled])
return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
<Switch
checked={enabled}
onChange={(x:boolean) => {
setEnabled(x);
}}
className={classNames(
enabled ? "bg-indigo-600" : "bg-gray-200 dark:bg-gray-600",
"relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out "
)}
>
<span className="sr-only">Use setting</span>
<span
className={classNames(
enabled ? "translate-x-5" : "translate-x-0",
"pointer-events-none relative inline-block h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out", disabled ? "bg-gray-200 dark:bg-gray-600" : "bg-white dark:bg-gray-800"
)}
>
<span
className={classNames(
enabled
? "opacity-0 ease-out duration-100"
: "opacity-100 ease-in duration-200",
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
)}
aria-hidden="true"
>
<svg
className="h-3 w-3 text-gray-400"
fill="none"
viewBox="0 0 12 12"
>
<path
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</span>
<span
className={classNames(
enabled
? "opacity-100 ease-in duration-200"
: "opacity-0 ease-out duration-100",
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
)}
aria-hidden="true"
>
<svg
className="h-3 w-3 text-indigo-600"
fill="currentColor"
viewBox="0 0 12 12"
>
<path d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" />
</svg>
</span>
</span>
</Switch>
</div>
);
}

View file

@ -0,0 +1,164 @@
import { createContext, ReactNode, useState } from "react";
import { AlertItemType } from "../types/alerts";
var _ = require("lodash");
//types for alertContextType
type alertContextType = {
errorData: { title: string; list?: Array<string> };
setErrorData: (newState: { title: string; list?: Array<string> }) => void;
errorOpen: boolean;
setErrorOpen: (newState: boolean) => void;
noticeData: { title: string; link?: string };
setNoticeData: (newState: { title: string; link?: string }) => void;
noticeOpen: boolean;
setNoticeOpen: (newState: boolean) => void;
successData: { title: string };
setSuccessData: (newState: { title: string }) => void;
successOpen: boolean;
setSuccessOpen: (newState: boolean) => void;
notificationCenter: boolean;
setNotificationCenter: (newState: boolean) => void;
notificationList: Array<AlertItemType>;
pushNotificationList: (Object:AlertItemType) => void;
clearNotificationList: () => void;
removeFromNotificationList: (index: string) => void;
};
//initial values to alertContextType
const initialValue:alertContextType = {
errorData: { title: "", list: [] },
setErrorData: () => {},
errorOpen: false,
setErrorOpen: () => {},
noticeData: { title: "", link: "" },
setNoticeData: () => {},
noticeOpen: false,
setNoticeOpen: () => {},
successData: { title: "" },
setSuccessData: () => {},
successOpen: false,
setSuccessOpen: () => {},
notificationCenter: false,
setNotificationCenter: () => {},
notificationList: [],
pushNotificationList: () => {},
clearNotificationList: () => {},
removeFromNotificationList: () => {},
};
export const alertContext = createContext<alertContextType>(initialValue);
export function AlertProvider({ children }:{children:ReactNode}) {
const [errorData, setErrorDataState] = useState<{
title: string;
list?: Array<string>;
}>({ title: "", list: [] });
const [errorOpen, setErrorOpen] = useState(false);
const [noticeData, setNoticeDataState] = useState<{
title: string;
link?: string;
}>({ title: "", link: "" });
const [noticeOpen, setNoticeOpen] = useState(false);
const [successData, setSuccessDataState] = useState<{ title: string }>({
title: "",
});
const [successOpen, setSuccessOpen] = useState(false);
const [notificationCenter, setNotificationCenter] = useState(false);
const [notificationList, setNotificationList] = useState([]);
const pushNotificationList = (notification: AlertItemType) => {
setNotificationList((old) => {
let newNotificationList = _.cloneDeep(old);
newNotificationList.unshift(notification);
return newNotificationList;
});
};
/**
* Sets the error data state, opens the error dialog and pushes the new error notification to the notification list
* @param newState An object containing the new error data, including title and optional list of error messages
*/
function setErrorData(newState: { title: string; list?: Array<string> }) {
setErrorDataState(newState);
setErrorOpen(true);
if (newState.title) {
setNotificationCenter(true);
pushNotificationList({
type: "error",
title: newState.title,
list: newState.list,
id: _.uniqueId(),
});
}
}
/**
* Sets the state of the notice data and opens the notice modal, also adds a new notice to the notification center if the title is defined.
* @param newState An object containing the title of the notice and optionally a link.
*/
function setNoticeData(newState: { title: string; link?: string }) {
setNoticeDataState(newState);
setNoticeOpen(true);
if (newState.title) {
// Add new notice to notification center
setNotificationCenter(true);
pushNotificationList({
type: "notice",
title: newState.title,
link: newState.link,
id: _.uniqueId(),
});
}
}
/**
* Update the success data state and show a success alert notification.
* @param newState - A state object with a "title" property to set in the success data state.
*/
function setSuccessData(newState: { title: string }) {
setSuccessDataState(newState); // update the success data state with the provided new state
setSuccessOpen(true); // open the success alert
// If the new state has a "title" property, add a new success notification to the list
if (newState.title) {
setNotificationCenter(true); // show the notification center
pushNotificationList({ // add the new notification to the list
type: "success",
title: newState.title,
id: _.uniqueId(),
});
}
}
function clearNotificationList() {
setNotificationList([]);
}
function removeFromNotificationList(index: string) {
// set the notification list to a new array that filters out the alert with the matching id
setNotificationList((prevAlertsList) =>
prevAlertsList.filter((alert) => alert.id !== index)
);
}
return (
<alertContext.Provider
value={{
removeFromNotificationList,
clearNotificationList,
notificationList,
pushNotificationList,
setNotificationCenter,
notificationCenter,
errorData,
setErrorData,
errorOpen,
setErrorOpen,
noticeData,
setNoticeData,
noticeOpen,
setNoticeOpen,
successData,
setSuccessData,
successOpen,
setSuccessOpen,
}}
>
{children}
</alertContext.Provider>
);
}

View file

@ -0,0 +1,34 @@
import { createContext, useEffect, useState } from "react";
type darkContextType = {
dark: {};
setDark: (newState: {}) => void;
};
const initialValue = {
dark: {},
setDark: () => {},
};
export const darkContext = createContext<darkContextType>(initialValue);
export function DarkProvider({ children }) {
const [dark, setDark] = useState(false);
useEffect(()=>{
if(dark){
document.getElementById("body").classList.add("dark");
} else {
document.getElementById("body").classList.remove("dark");
}
}, [dark])
return (
<darkContext.Provider
value={{
dark,
setDark,
}}
>
{children}
</darkContext.Provider>
);
}

View file

@ -0,0 +1,26 @@
import { ReactNode } from "react";
import { AlertProvider } from "./alertContext";
import { DarkProvider } from "./darkContext";
import { LocationProvider } from "./locationContext";
import PopUpProvider from "./popUpContext";
import { TabsProvider } from "./tabsContext";
import { TypesProvider } from "./typesContext";
export default function ContextWrapper({ children }: { children: ReactNode }) {
//element to wrap all context
return (
<>
<DarkProvider>
<LocationProvider>
<AlertProvider>
<PopUpProvider>
<TypesProvider>
<TabsProvider>{children}</TabsProvider>
</TypesProvider>
</PopUpProvider>
</AlertProvider>
</LocationProvider>
</DarkProvider>
</>
);
}

View file

@ -0,0 +1,79 @@
import { createContext, ReactNode, useState } from "react";
//types for location context
type locationContextType = {
current: Array<string>;
setCurrent: (newState: Array<string>) => void;
isStackedOpen: boolean;
setIsStackedOpen: (newState: boolean) => void;
showSideBar: boolean;
setShowSideBar: (newState: boolean) => void;
extraNavigation: {
title: string;
options?: Array<{
name: string;
href: string;
icon: any;
children?: Array<any>;
}>;
};
setExtraNavigation: (newState: {
title: string;
options?: Array<{
name: string;
href: string;
icon: any;
children?: Array<any>;
}>;
}) => void;
extraComponent: any;
setExtraComponent: (newState: any) => void;
};
//initial value for location context
const initialValue = {
//actual
current: window.location.pathname.replace(/\/$/g, "").split("/"),
isStackedOpen:
window.innerWidth > 1024 && window.location.pathname.split("/")[1]
? true
: false,
setCurrent: () => {},
setIsStackedOpen: () => {},
showSideBar: window.location.pathname.split("/")[1] ? true : false,
setShowSideBar: () => {},
extraNavigation: { title: "" },
setExtraNavigation: () => {},
extraComponent: <></>,
setExtraComponent: () => {},
};
export const locationContext = createContext<locationContextType>(initialValue);
export function LocationProvider({ children }:{children:ReactNode}) {
const [current, setCurrent] = useState(initialValue.current);
const [isStackedOpen, setIsStackedOpen] = useState(
initialValue.isStackedOpen
);
const [showSideBar, setShowSideBar] = useState(initialValue.showSideBar);
const [extraNavigation, setExtraNavigation] = useState({ title: "" });
const [extraComponent, setExtraComponent] = useState(<></>);
return (
<locationContext.Provider
value={{
isStackedOpen,
setIsStackedOpen,
current,
setCurrent,
showSideBar,
setShowSideBar,
extraNavigation,
setExtraNavigation,
extraComponent,
setExtraComponent,
}}
>
{children}
</locationContext.Provider>
);
}

View file

@ -0,0 +1,33 @@
import { createContext } from "react";
import React, { useState } from "react";
//context to set JSX element on the DOM
export const PopUpContext = createContext({
openPopUp: (popUpElement: JSX.Element) => {},
closePopUp: () => {},
});
interface PopUpProviderProps {
children: React.ReactNode;
}
const PopUpProvider = ({ children }: PopUpProviderProps) => {
const [popUpElement, setPopUpElement] = useState<JSX.Element | null>(null);
const openPopUp = (element: JSX.Element) => {
setPopUpElement(element);
};
const closePopUp = () => {
setPopUpElement(null);
};
return (
<PopUpContext.Provider value={{ openPopUp, closePopUp }}>
{children}
{popUpElement}
</PopUpContext.Provider>
);
};
export default PopUpProvider;

View file

@ -0,0 +1,188 @@
import { createContext, useEffect, useState, useRef, ReactNode, useContext } from "react";
import { FlowType } from "../types/flow";
import { TabsContextType } from "../types/tabs";
import { normalCaseToSnakeCase } from "../utils";
import { alertContext } from "./alertContext";
const TabsContextInitialValue: TabsContextType = {
tabIndex: 0,
setTabIndex: (index: number) => {},
flows: [],
removeFlow: (id: string) => {},
addFlow: (flowData?: any) => {},
updateFlow: (newFlow: FlowType) => {},
incrementNodeId: () => 0,
downloadFlow: () => {},
uploadFlow: () => {},
lockChat: false,
setLockChat:(prevState:boolean)=>{}
};
export const TabsContext = createContext<TabsContextType>(
TabsContextInitialValue
);
export function TabsProvider({ children }: { children: ReactNode }) {
const {setNoticeData} = useContext(alertContext)
const [tabIndex, setTabIndex] = useState(0);
const [flows, setFlows] = useState<Array<FlowType>>([]);
const [id, setId] = useState(0);
const [lockChat, setLockChat] = useState(false);
const newNodeId = useRef(0);
function incrementNodeId() {
newNodeId.current = newNodeId.current + 1;
return newNodeId.current;
}
useEffect(() => {
//save tabs locally
if (flows.length !== 0)
window.localStorage.setItem(
"tabsData",
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current })
);
}, [flows, id, tabIndex, newNodeId]);
useEffect(() => {
//get tabs locally saved
let cookie = window.localStorage.getItem("tabsData");
if (cookie) {
let cookieObject = JSON.parse(cookie);
setTabIndex(cookieObject.tabIndex);
setFlows(cookieObject.flows);
setId(cookieObject.id);
newNodeId.current = cookieObject.nodeId;
}
}, []);
/**
* Downloads the current flow as a JSON file
*/
function downloadFlow() {
// create a data URI with the current flow data
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify(flows[tabIndex])
)}`;
// create a link element and set its properties
const link = document.createElement("a");
link.href = jsonString;
link.download = `${normalCaseToSnakeCase(flows[tabIndex].name)}.json`;
// simulate a click on the link element to trigger the download
link.click();
setNoticeData({title:"Warning: Critical data, including API keys, in JSON file. Keep secure and do not share."})
}
/**
* Creates a file input and listens to a change event to upload a JSON flow file.
* If the file type is application/json, the file is read and parsed into a JSON object.
* The resulting JSON object is passed to the addFlow function.
*/
function uploadFlow() {
// 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
file.text().then((text) => {
// parse the text into a JSON object
addFlow(JSON.parse(text));
});
}
};
// 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);
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));
}
}
return newFlows;
});
}
/**
* Add a new flow to the list of flows.
* @param flow Optional flow to add.
*/
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;
// Create a new flow with a default name if no flow is provided.
let newFlow: FlowType = {
name: flow ? flow.name : "New Flow " + (flows.length===0?"":flows.length),
id: id.toString(),
data,
chat: flow ? flow.chat : [],
};
// Increment the ID counter.
setId((old) => old + 1);
// Add the new flow to the list of flows.
setFlows((prevState) => {
const newFlows = [...prevState, newFlow];
return newFlows;
});
// Set the tab index to the new flow.
setTabIndex(flows.length);
}
/**
* Updates an existing flow with new data
* @param newFlow - The new flow object containing the updated data
*/
function updateFlow(newFlow: FlowType) {
setFlows((prevState) => {
const newFlows = [...prevState];
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
if (index !== -1) {
newFlows[index].data = newFlow.data;
newFlows[index].name = newFlow.name;
newFlows[index].chat = newFlow.chat;
}
return newFlows;
});
}
return (
<TabsContext.Provider
value={{
lockChat,
setLockChat,
tabIndex,
setTabIndex,
flows,
incrementNodeId,
removeFlow,
addFlow,
updateFlow,
downloadFlow,
uploadFlow,
}}
>
{children}
</TabsContext.Provider>
);
}

View file

@ -0,0 +1,39 @@
import { createContext, ReactNode, useState } from "react";
import { Node} from "reactflow";
import { typesContextType } from "../types/typesContext";
//context to share types adn functions from nodes to flow
const initialValue:typesContextType = {
reactFlowInstance: null,
setReactFlowInstance: () => {},
deleteNode: () => {},
types: {},
setTypes: () => {},
};
export const typesContext = createContext<typesContextType>(initialValue);
export function TypesProvider({ children }:{children:ReactNode}) {
const [types, setTypes] = useState({});
const [reactFlowInstance, setReactFlowInstance] = useState(null);
function deleteNode(idx:string) {
reactFlowInstance.setNodes(
reactFlowInstance.getNodes().filter((n:Node) => n.id !== idx)
);
reactFlowInstance.setEdges(reactFlowInstance.getEdges().filter((ns) => ns.source !== idx && ns.target !== idx));
}
return (
<typesContext.Provider
value={{
types,
setTypes,
reactFlowInstance,
setReactFlowInstance,
deleteNode,
}}
>
{children}
</typesContext.Provider>
);
}

View file

@ -0,0 +1,13 @@
import { APIObjectType, sendAllProps } from '../../types/api/index';
import axios, { AxiosResponse } from "axios";
const backendUrl = process.env.BACKEND || "http://localhost:5003";
export async function getAll():Promise<AxiosResponse<APIObjectType>> {
return await axios.get(`${backendUrl}/all`);
}
export async function sendAll(data:sendAllProps) {
console.log(data);
return await axios.post(`${backendUrl}/predict`, data);
}

View file

@ -0,0 +1,15 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View file

@ -0,0 +1,18 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
import ContextWrapper from './contexts';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<ContextWrapper>
<BrowserRouter>
<App />
</BrowserRouter>
</ContextWrapper>
);
reportWebVitals();

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,104 @@
import { Dialog, Transition } from "@headlessui/react";
import { XMarkIcon, ClipboardDocumentListIcon } from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
export default function TextAreaModal({value, setValue}:{setValue:(value:string)=>void,value:string|string[]}){
const [open, setOpen] = useState(true);
const [myValue, setMyValue] = useState(value);
const { closePopUp } = useContext(PopUpContext);
const ref = useRef();
function setModalOpen(x:boolean){
setOpen(x);
if(x === false){
setTimeout(() => {closePopUp()}, 300);
}
}
return (
<Transition.Root show={open} appear={true} as={Fragment}>
<Dialog
as="div"
className="relative z-10"
onClose={setModalOpen}
initialFocus={ref}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button
type="button"
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
onClick={() => {
setModalOpen(false);
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="h-full w-full flex flex-col justify-center items-center">
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
<div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-gray-900 sm:mx-0 sm:h-10 sm:w-10">
<ClipboardDocumentListIcon
className="h-6 w-6 text-blue-600"
aria-hidden="true"
/>
</div>
<div className="mt-4 text-center sm:ml-4 sm:text-left">
<Dialog.Title
as="h3"
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
>
Edit text
</Dialog.Title>
</div>
</div>
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
<div className="flex h-full w-full">
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
<textarea ref={ref} className="form-input h-full w-full rounded-lg border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white" value={myValue} onChange={(e) => {setMyValue(e.target.value); setValue(e.target.value)}}/>
</div>
</div>
</div>
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
<button
type="button"
className="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => {
setModalOpen(false);
}}
>
Finish editing
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
)
}

View file

@ -0,0 +1,35 @@
import { ConnectionLineComponentProps } from 'reactflow';
const ConnectionLineComponent = ({
fromX,
fromY,
toX,
toY,
connectionLineStyle = {} // provide a default value for connectionLineStyle
}:ConnectionLineComponentProps) => {
return (
<g>
<path
fill="none"
stroke="#222"
strokeWidth={1.5}
className="animated dark:stroke-gray-400"
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
style={connectionLineStyle}
/>
<circle
cx={toX}
cy={toY}
fill="#fff"
r={3}
stroke="#222"
className="dark:stroke-gray-400 dark:fill-gray-800"
strokeWidth={1.5}
/>
</g>
);
};
export default ConnectionLineComponent;

View file

@ -0,0 +1,47 @@
import {
ChevronRightIcon,
} from "@heroicons/react/24/solid";
import { Disclosure } from "@headlessui/react";
import { DisclosureComponentType } from "../../../../types/components";
export default function DisclosureComponent({
button: { title, Icon, buttons = [] },
children,
}: DisclosureComponentType
) {
return (
<Disclosure as="div" key={title}>
{({ open }) => (
<>
<div>
<div className="select-none bg-gray-50 dark:bg-gray-700 dark:border-y-gray-600 w-full flex justify-between items-center -mt-px px-3 py-2 border-y border-y-gray-200">
<div className="flex gap-4">
<Icon className="w-6 text-gray-800 dark:text-white" />
<span className="flex items-center text-sm text-gray-800 dark:text-white font-medium">
{title}
</span>
</div>
<div className="flex gap-2">
{buttons.map((x, index) => (
<button key={index} onClick={x.onClick}>
{x.Icon}
</button>
))}
<Disclosure.Button as="button">
<ChevronRightIcon
className={`${
open ? "rotate-90 transform" : ""
} h-4 w-4 text-gray-800 dark:text-white`}
/>
</Disclosure.Button>
</div>
</div>
</div>
<Disclosure.Panel as="div" className="-mt-px">
{children}
</Disclosure.Panel>
</>
)}
</Disclosure>
);
}

View file

@ -0,0 +1,86 @@
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/NodesServices";
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<void>{
// 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]);
function onDragStart(event: React.DragEvent<any>, data:{type:string,node?:APIClassType}) {
//start drag event
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("json", JSON.stringify(data));
}
return (
<div className="mt-1 w-full">
{Object.keys(data).map((d:keyof APIObjectType, i) => (
<DisclosureComponent
key={i}
button={{ title: nodeNames[d], Icon: nodeIcons[d] }}
>
<div className="p-2 flex flex-col gap-2">
{Object.keys(data[d]).map((t: string, k) => (
<div key={k}>
<div
draggable
className={" cursor-grab border-l-8 rounded-l-md"}
style={{ borderLeftColor: nodeColors[d] }}
onDragStart={(event) =>
onDragStart(event, {
type: t,
node: data[d][t],
})
}
>
<div className="flex w-full justify-between text-sm px-3 py-1 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
<span className="text-black dark:text-white w-36 truncate text-xs">{t}</span>
<Bars2Icon className="w-4 h-6 text-gray-400 dark:text-gray-600" />
</div>
</div>
</div>
))}
</div>
</DisclosureComponent>
))}
</div>
);
}

View file

@ -0,0 +1,87 @@
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/solid";
import { useContext, useState } from "react";
import { TabsContext } from "../../../../contexts/tabsContext";
import { FlowType } from "../../../../types/flow";
var _ = require("lodash");
export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,selected:boolean,onClick:()=>void}) {
const { removeFlow, updateFlow, flows } =
useContext(TabsContext);
const [isRename, setIsRename] = useState(false);
const [value, setValue] = useState("");
return (
<>
{flow ? (
!selected ? (
<div
className="dark:text-white flex justify-between select-none truncate w-44 items-center px-4 my-1.5 border-x border-x-gray-300 dark:border-x-gray-600 -ml-px"
onClick={onClick}
>
{flow.name}
<button
onClick={(e) => {
e.stopPropagation();
removeFlow(flow.id);
}}
>
<XMarkIcon className="h-4 hover:bg-white dark:hover:bg-gray-600 rounded-full" />
</button>
</div>
) : (
<div className="bg-white dark:text-white dark:bg-gray-700 flex select-none justify-between w-44 items-center border border-b-0 border-gray-300 dark:border-gray-600 px-4 py-1 rounded-t-xl -ml-px">
{isRename ? (
<input
autoFocus
className="bg-transparent focus:border-none active:outline hover:outline focus:outline outline-gray-300 rounded-md w-28"
onBlur={() => {
setIsRename(false);
if (value !== "") {
let newFlow = _.cloneDeep(flow);
newFlow.name = value;
updateFlow(newFlow);
}
}}
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
) : (
<div className="flex items-center gap-2">
<span
className="text-left truncate"
onDoubleClick={() => {
setIsRename(true);
setValue(flow.name);
}}
>
{flow.name}
</span>
</div>
)}
<button
onClick={() => {
removeFlow(flow.id);
}}
>
{flows.length > 1 && (
<XMarkIcon className="h-4 hover:bg-gray-100 dark:hover:bg-gray-600 rounded-full" />
)}
</button>
</div>
)
) : (
<div className="h-full py-1.5 flex justify-center items-center">
<button
className="px-3 flex items-center h-full pb-0.5 pt-0.5 border-x-gray-300 dark:border-x-gray-600 dark:text-white -ml-px"
onClick={onClick}
>
<PlusIcon className="h-5 rounded-full hover:bg-white dark:hover:bg-gray-600" />
</button>
</div>
)}
</>
);
}

View file

@ -0,0 +1,83 @@
import { useContext, useEffect, useState } from "react";
import { ReactFlowProvider } from "reactflow";
import TabComponent from "../tabComponent";
import { TabsContext } from "../../../../contexts/tabsContext";
import FlowPage from "../..";
import { darkContext } from "../../../../contexts/darkContext";
import { BellIcon, MoonIcon, SunIcon } from "@heroicons/react/24/outline";
import { PopUpContext } from "../../../../contexts/popUpContext";
import AlertDropdown from "../../../../alerts/alertDropDown";
import { alertContext } from "../../../../contexts/alertContext";
export default function TabsManagerComponent() {
const { flows, addFlow, tabIndex, setTabIndex } = useContext(TabsContext);
const { openPopUp } = useContext(PopUpContext);
const AlertWidth = 256
const { dark, setDark } = useContext(darkContext);
const {notificationCenter, setNotificationCenter} = useContext(alertContext)
useEffect(() => {
//create the first flow
if (flows.length === 0) {
addFlow();
}
}, [addFlow, flows.length]);
return (
<div className="h-full w-full flex flex-col">
<div className="w-full flex pr-2 flex-row text-center items-center bg-gray-100 dark:bg-gray-800 px-2">
{flows.map((flow, index) => {
return (
<TabComponent
onClick={() => setTabIndex(index)}
selected={index === tabIndex}
key={index}
flow={flow}
/>
);
})}
<TabComponent
onClick={() => {
addFlow();
}}
selected={false}
flow={null}
/>
<div className="ml-auto mr-2 flex gap-3">
<button
className="text-gray-400 hover:text-gray-500 "
onClick={() => {
setDark(!dark);
}}
>
{dark ? (
<SunIcon className="h-5 w-5" />
) : (
<MoonIcon className="h-5 w-5" />
)}
</button>
<button
className="text-gray-400 hover:text-gray-500 relative"
onClick={(event: React.MouseEvent<HTMLElement>) => {
setNotificationCenter(false)
const top = (event.target as Element).getBoundingClientRect().top
const left = (event.target as Element).getBoundingClientRect().left
openPopUp(<div className="z-10 absolute" style={{top:top+20, left:left-AlertWidth}}><AlertDropdown/></div>)
}}
>
{notificationCenter&&<div className='absolute w-1.5 h-1.5 rounded-full bg-red-600 right-[3px]'></div>}
<BellIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
<div className="w-full h-full">
<ReactFlowProvider>
{flows[tabIndex] ? (
<FlowPage flow={flows[tabIndex]}></FlowPage>
) : (
<></>
)}
</ReactFlowProvider>
</div>
</div>
);
}

View file

@ -0,0 +1,205 @@
import { useCallback, useContext, useEffect, useRef } from "react";
import ReactFlow, {
Background,
Controls,
addEdge,
useEdgesState,
useNodesState,
useReactFlow,
ControlButton,
EdgeChange,
Connection,
} from "reactflow";
import { locationContext } from "../../contexts/locationContext";
import ExtraSidebar from "./components/extraSidebarComponent";
import Chat from "../../components/chatComponent";
import GenericNode from "../../CustomNodes/GenericNode";
import { alertContext } from "../../contexts/alertContext";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
} from "@heroicons/react/24/outline";
import ConnectionLineComponent from "./components/ConnectionLineComponent";
import { FlowType, NodeType } from "../../types/flow";
import { APIClassType } from "../../types/api";
const nodeTypes = {
genericNode: GenericNode,
};
var _ = require("lodash");
export default function FlowPage({ flow }:{flow:FlowType}) {
let { updateFlow, incrementNodeId, downloadFlow, uploadFlow } =
useContext(TabsContext);
const { types, reactFlowInstance, setReactFlowInstance } =
useContext(typesContext);
const reactFlowWrapper = useRef(null);
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
const { setErrorData } = useContext(alertContext);
const [nodes, setNodes, onNodesChange] = useNodesState(
flow.data?.nodes ?? []
);
const [edges, setEdges, onEdgesChange] = useEdgesState(
flow.data?.edges ?? []
);
const { setViewport } = useReactFlow();
useEffect(() => {
if (reactFlowInstance && flow) {
flow.data = reactFlowInstance.toObject();
updateFlow(flow);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes, edges]);
//update flow when tabs change
useEffect(() => {
setNodes(flow?.data?.nodes ?? []);
setEdges(flow?.data?.edges ?? []);
if (reactFlowInstance) {
setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 });
}
}, [flow, reactFlowInstance, setEdges, setNodes, setViewport]);
//set extra sidebar
useEffect(() => {
setExtraComponent(<ExtraSidebar />);
setExtraNavigation({ title: "Components" });
}, [setExtraComponent, setExtraNavigation]);
const onEdgesChangeMod = useCallback(
(s:EdgeChange[]) => {
onEdgesChange(s);
setNodes((x) => {
let newX = _.cloneDeep(x);
return newX;
});
},
[onEdgesChange, setNodes]
);
const onConnect = useCallback(
(params:Connection) => {
setEdges((eds) =>
addEdge({ ...params, className: "animate-pulse" }, eds)
);
setNodes((x) => {
let newX = _.cloneDeep(x);
return newX;
});
},
[setEdges, setNodes]
);
const onDragOver = useCallback((event:React.DragEvent) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
}, []);
const onDrop = useCallback(
(event:React.DragEvent) => {
event.preventDefault();
// Helper function to generate a unique node ID
function getId() {
return `dndnode_` + incrementNodeId();
}
// Get the current bounds of the ReactFlow wrapper element
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
// Extract the data from the drag event and parse it as a JSON object
let data:{type:string,node?:APIClassType} = JSON.parse(event.dataTransfer.getData("json"));
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
if (
data.type !== "chatInput" ||
(data.type === "chatInput" &&
!reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode"))
) {
// Calculate the position where the node should be created
const position = reactFlowInstance.project({
x: event.clientX - reactflowBounds.left,
y: event.clientY - reactflowBounds.top,
});
// Generate a unique node ID
let newId = getId();
// Create a new node object
const newNode:NodeType = {
id: newId,
type: "genericNode",
position,
data: {
...data,
id: newId,
value: null,
},
};
// Add the new node to the list of nodes in state
setNodes((nds) => nds.concat(newNode));
} else {
// If a chat input node already exists, set an error message
setErrorData({
title: "Error creating node",
list: ["There can't be more than one chat input."],
});
}
},
// Specify dependencies for useCallback
[incrementNodeId, reactFlowInstance, setErrorData, setNodes]
);
const onDelete = (mynodes) => {
setEdges(edges.filter((ns) => !nodes.some((n) => ns.source === n.id || ns.target === n.id)));
}
return (
<div className="w-full h-full" ref={reactFlowWrapper}>
{Object.keys(types).length > 0 ? (
<>
<ReactFlow
nodes={nodes}
onMove={() =>
updateFlow({ ...flow, data: reactFlowInstance.toObject() })
}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChangeMod}
onConnect={onConnect}
onLoad={setReactFlowInstance}
onInit={setReactFlowInstance}
nodeTypes={nodeTypes}
connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver}
onDrop={onDrop}
onNodesDelete={onDelete}
>
<Background className="dark:bg-gray-900"/>
<Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600">
<ControlButton
onClick={() => uploadFlow()}
>
<ArrowUpTrayIcon />
</ControlButton>
<ControlButton
onClick={() => downloadFlow()}
>
<ArrowDownTrayIcon />
</ControlButton>
</Controls>
</ReactFlow>
<Chat flow={flow} reactFlowInstance={reactFlowInstance} />
</>
) : (
<></>
)}
</div>
);
}

View file

@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View file

@ -0,0 +1,12 @@
export type ErrorAlertType = {title:string,list:Array<string>,id:string,removeAlert:(id:string)=>void}
export type NoticeAlertType = {title:string,link:string,id:string,removeAlert:(id:string)=>void}
export type SuccessAlertType = {title:string,id:string, removeAlert:(id:string)=>void}
export type SingleAlertComponentType = {dropItem:AlertItemType,removeAlert:(index:string)=>void}
export type AlertDropdownType = {};
export type AlertItemType = {
type: "notice" | "error" | "success";
title: string;
link?: string;
list?: Array<string>;
id: string;
};

View file

@ -0,0 +1,15 @@
import { Node,Edge,Viewport } from "reactflow"
//kind and class are just representative names to represent the actual structure of the object received by the API
export type APIObjectType = {kind:APIKindType,[key:string]:APIKindType}
export type APIKindType= {class:APIClassType,[key:string]:APIClassType}
export type APITemplateType = {variable:TemplateVariableType,[key:string]:TemplateVariableType}
export type APIClassType ={base_classes:Array<string>,description:string,template:APITemplateType,[key:string]:Array<string>|string|APITemplateType}
export type TemplateVariableType = {type:string,required:boolean,placeholder?:string,list:boolean,show:boolean,multiline?:boolean,value?:any,[key:string]:any}
export type sendAllProps={
nodes: Node[];
edges: Edge[];
viewport: Viewport;
message:string;
chatHistory:{message:string,isSend:boolean}[],
};

View file

@ -0,0 +1,5 @@
import { ReactFlowInstance } from 'reactflow';
import { FlowType } from "../flow";
export type ChatType = {flow:FlowType,reactFlowInstance:ReactFlowInstance}
export type ChatMessageType = { message: string; isSend: boolean, thought?:string }

View file

@ -0,0 +1,59 @@
import { ForwardRefExoticComponent, ReactElement, ReactNode } from "react";
import { NodeDataType } from "../flow/index";
export type InputComponentType = {
value: string;
disabled?: boolean;
onChange: (value: string) => void;
password: boolean;
};
export type ToggleComponentType = {
enabled: boolean;
setEnabled: (state: boolean) => void;
disabled: boolean;
};
export type DropDownComponentType = {
title: string;
value: string;
options: string[];
onSelect: (value: string) => void;
};
export type ParameterComponentType = {
data: NodeDataType;
title: string;
id: string;
color: string;
left: boolean;
type: string;
required?: boolean;
name?: string;
tooltipTitle: string;
};
export type InputListComponentType = {
value: string[];
onChange: (value: string[]) => void;
disabled: boolean;
};
export type TextAreaComponentType = {
disabled: boolean;
onChange: (value: string[] | string) => void;
value: string[] | string;
};
export type DisclosureComponentType = {
children: ReactNode;
button: {
title: string;
Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
buttons?: {
Icon: ReactElement;
title: string;
onClick: (event?: React.MouseEvent) => void;
}[];
};
};
export type FloatComponentType = {
value: string;
disabled?: boolean;
onChange: (value: string) => void;
};

View file

@ -0,0 +1,3 @@
import { HomeIcon } from "@heroicons/react/24/outline";
export type sidebarNavigationItemType = { name: string, href: string, icon: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>, current: boolean }

View file

@ -0,0 +1,12 @@
import { ChatMessageType } from './../chat/index';
import { APIClassType } from '../api/index';
import { ReactFlowJsonObject, XYPosition } from "reactflow";
export type FlowType = {
name: string;
id: string;
data: ReactFlowJsonObject;
chat: Array<ChatMessageType>;
};
export type NodeType = {id:string,type:string,position:XYPosition,data:NodeDataType}
export type NodeDataType = {type:string,node?:APIClassType,id:string,value:any}

View file

@ -0,0 +1,15 @@
import { FlowType } from "../flow";
export type TabsContextType = {
tabIndex: number;
setTabIndex: (index: number) => void;
flows: Array<FlowType>;
removeFlow: (id: string) => void;
addFlow: (flowData?: any) => void;
updateFlow: (newFlow: FlowType) => void;
incrementNodeId: () => number;
downloadFlow: () => void;
uploadFlow: () => void;
lockChat:boolean,
setLockChat:(prevState:boolean)=>void
};

View file

@ -0,0 +1,11 @@
import { ReactFlowInstance } from "reactflow";
const types:{[char: string]: string}={}
export type typesContextType = {
reactFlowInstance: ReactFlowInstance|null;
setReactFlowInstance: any;
deleteNode: (idx: string) => void;
types: typeof types;
setTypes: (newState: {}) => void;
};

343
src/frontend/src/utils.ts Normal file
View file

@ -0,0 +1,343 @@
import {
RocketLaunchIcon,
LinkIcon,
CpuChipIcon,
LightBulbIcon,
CommandLineIcon,
WrenchScrewdriverIcon,
ComputerDesktopIcon,
Bars3CenterLeftIcon,
} from "@heroicons/react/24/outline";
import { Connection, Edge, Node, ReactFlowInstance } from "reactflow";
export function classNames(...classes:Array<string>) {
return classes.filter(Boolean).join(" ");
}
export const textColors = {
white: "text-white",
red: "text-red-700",
orange: "text-orange-700",
amber: "text-amber-700",
yellow: "text-yellow-700",
lime: "text-lime-700",
green: "text-green-700",
emerald: "text-emerald-700",
teal: "text-teal-700",
cyan: "text-cyan-700",
sky: "text-sky-700",
blue: "text-blue-700",
indigo: "text-indigo-700",
violet: "text-violet-700",
purple: "text-purple-700",
fuchsia: "text-fuchsia-700",
pink: "text-pink-700",
rose: "text-rose-700",
black: "text-black-700",
gray: "text-gray-700",
};
export const borderLColors = {
white: "border-l-white",
red: "border-l-red-500",
orange: "border-l-orange-500",
amber: "border-l-amber-500",
yellow: "border-l-yellow-500",
lime: "border-l-lime-500",
green: "border-l-green-500",
emerald: "border-l-emerald-500",
teal: "border-l-teal-500",
cyan: "border-l-cyan-500",
sky: "border-l-sky-500",
blue: "border-l-blue-500",
indigo: "border-l-indigo-500",
violet: "border-l-violet-500",
purple: "border-l-purple-500",
fuchsia: "border-l-fuchsia-500",
pink: "border-l-pink-500",
rose: "border-l-rose-500",
black: "border-l-black-500",
gray: "border-l-gray-500",
};
export const nodeColors: {[char: string]: string} = {
prompts: "#4367BF",
llms: "#6344BE",
chains: "#FE7500",
agents: "#903BBE",
tools: "#FF3434",
memories: "#FF9135",
advanced: "#000000",
chat: "#454173",
thought:"#272541"
};
export const nodeNames:{[char: string]: string} = {
prompts: "Prompts",
llms: "LLMs",
chains: "Chains",
agents: "Agents",
tools: "Tools",
memories: "Memories",
advanced: "Advanced",
chat: "Chat",
};
export const nodeIcons:{[char: string]: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>} = {
agents: RocketLaunchIcon,
chains: LinkIcon,
memories: CpuChipIcon,
llms: LightBulbIcon,
prompts: CommandLineIcon,
tools: WrenchScrewdriverIcon,
advanced: ComputerDesktopIcon,
chat: Bars3CenterLeftIcon,
};
export const bgColors = {
white: "bg-white",
red: "bg-red-100",
orange: "bg-orange-100",
amber: "bg-amber-100",
yellow: "bg-yellow-100",
lime: "bg-lime-100",
green: "bg-green-100",
emerald: "bg-emerald-100",
teal: "bg-teal-100",
cyan: "bg-cyan-100",
sky: "bg-sky-100",
blue: "bg-blue-100",
indigo: "bg-indigo-100",
violet: "bg-violet-100",
purple: "bg-purple-100",
fuchsia: "bg-fuchsia-100",
pink: "bg-pink-100",
rose: "bg-rose-100",
black: "bg-black-100",
gray: "bg-gray-100",
};
export const bgColorsHover = {
white: "hover:bg-white",
black: "hover:bg-black-50",
gray: "hover:bg-gray-50",
red: "hover:bg-red-50",
orange: "hover:bg-orange-50",
amber: "hover:bg-amber-50",
yellow: "hover:bg-yellow-50",
lime: "hover:bg-lime-50",
green: "hover:bg-green-50",
emerald: "hover:bg-emerald-50",
teal: "hover:bg-teal-50",
cyan: "hover:bg-cyan-50",
sky: "hover:bg-sky-50",
blue: "hover:bg-blue-50",
indigo: "hover:bg-indigo-50",
violet: "hover:bg-violet-50",
purple: "hover:bg-purple-50",
fuchsia: "hover:bg-fuchsia-50",
pink: "hover:bg-pink-50",
rose: "hover:bg-rose-50",
};
export const textColorsHex = {
red: "rgb(185 28 28)",
orange: "rgb(194 65 12)",
amber: "rgb(180 83 9)",
yellow: "rgb(161 98 7)",
lime: "rgb(77 124 15)",
green: "rgb(21 128 61)",
emerald: "rgb(4 120 87)",
teal: "rgb(15 118 110)",
cyan: "rgb(14 116 144)",
sky: "rgb(3 105 161)",
blue: "rgb(29 78 216)",
indigo: "rgb(67 56 202)",
violet: "rgb(109 40 217)",
purple: "rgb(126 34 206)",
fuchsia: "rgb(162 28 175)",
pink: "rgb(190 24 93)",
rose: "rgb(190 18 60)",
};
export const bgColorsHex = {
red: "rgb(254 226 226)",
orange: "rgb(255 237 213)",
amber: "rgb(254 243 199)",
yellow: "rgb(254 249 195)",
lime: "rgb(236 252 203)",
green: "rgb(220 252 231)",
emerald: "rgb(209 250 229)",
teal: "rgb(204 251 241)",
cyan: "rgb(207 250 254)",
sky: "rgb(224 242 254)",
blue: "rgb(219 234 254)",
indigo: "rgb(224 231 255)",
violet: "rgb(237 233 254)",
purple: "rgb(243 232 255)",
fuchsia: "rgb(250 232 255)",
pink: "rgb(252 231 243)",
rose: "rgb(255 228 230)",
};
export const taskTypeMap: { [key: string]: string } = {
MULTICLASS_CLASSIFICATION: "Multiclass Classification",
};
const charWidths:{[char: string]: number} = {
" ": 0.2,
"!": 0.2,
'"': 0.3,
"#": 0.5,
$: 0.5,
"%": 0.5,
"&": 0.5,
"(": 0.2,
")": 0.2,
"*": 0.5,
"+": 0.5,
",": 0.2,
"-": 0.2,
".": 0.1,
"/": 0.5,
":": 0.2,
";": 0.2,
"<": 0.5,
"=": 0.5,
">": 0.5,
"?": 0.2,
"@": 0.5,
"[": 0.2,
"\\": 0.5,
"]": 0.2,
"^": 0.5,
_: 0.2,
"`": 0.5,
"{": 0.2,
"|": 0.2,
"}": 0.2,
"~": 0.5,
};
for (let i = 65; i <= 90; i++) {
charWidths[String.fromCharCode(i)] = 0.6;
}
for (let i = 97; i <= 122; i++) {
charWidths[String.fromCharCode(i)] = 0.5;
}
export function measureTextWidth(text: string, fontSize:number) {
let wordWidth = 0;
for (let j = 0; j < text.length; j++) {
let char = text[j];
let charWidth = charWidths[char] || 0.5;
wordWidth += charWidth * fontSize;
}
return wordWidth;
}
export function measureTextHeight(text: string, width:number, fontSize:number) {
const charHeight = fontSize;
const lineHeight = charHeight * 1.5;
const words = text.split(" ");
let lineWidth = 0;
let totalHeight = 0;
for (let i = 0; i < words.length; i++) {
let word = words[i];
let wordWidth = measureTextWidth(word, fontSize);
if (lineWidth + wordWidth + charWidths[" "] * fontSize <= width) {
lineWidth += wordWidth + charWidths[" "] * fontSize;
} else {
totalHeight += lineHeight;
lineWidth = wordWidth;
}
}
totalHeight += lineHeight;
return totalHeight;
}
export function toCamelCase(str: string) {
return str
.split(" ")
.map((word, index) =>
index === 0
? word.toLowerCase()
: word[0].toUpperCase() + word.slice(1).toLowerCase()
)
.join("");
}
export function toFirstUpperCase(str: string) {
return str
.split(" ")
.map((word, index) => word[0].toUpperCase() + word.slice(1).toLowerCase())
.join("");
}
export function snakeToNormalCase(str: string) {
return str
.split("_")
.map((word, index) => {
if (index === 0) {
return word[0].toUpperCase() + word.slice(1).toLowerCase();
}
return word.toLowerCase();
})
.join(" ");
}
export function normalCaseToSnakeCase(str:string){
return str
.split(" ")
.map((word, index) => {
if (index === 0) {
return word[0].toUpperCase() + word.slice(1).toLowerCase();
}
return word.toLowerCase();
})
.join("_");
}
export function roundNumber(x:number, decimals:number) {
return Math.round(x * Math.pow(10, decimals)) / Math.pow(10, decimals);
}
export function getConnectedNodes(edge: Edge, nodes: Array<Node>): Array<Node> {
const sourceId = edge.source;
const targetId = edge.target;
const connectedNodes = nodes.filter(
(node) => node.id === targetId || node.id === sourceId
);
return connectedNodes;
}
export function isValidConnection(
{ source, target, sourceHandle, targetHandle }:Connection,
reactFlowInstance:ReactFlowInstance
) {
if (
sourceHandle.split('|')[0] === targetHandle.split("|")[0] ||
sourceHandle.split('|').slice(2).some((t) => t === targetHandle.split("|")[0]) ||
targetHandle.split("|")[0] === "str"
) {
let targetNode = reactFlowInstance.getNode(target).data.node;
if (!targetNode) {
if (
!reactFlowInstance
.getEdges()
.find((e) => e.targetHandle === targetHandle)
) {
return true;
}
} else if (
(!targetNode.template[targetHandle.split("|")[1]].list &&
!reactFlowInstance
.getEdges()
.find((e) => e.targetHandle === targetHandle)) ||
targetNode.template[targetHandle.split("|")[1]].list
) {
return true;
}
}
return false;
}