Merge branch 'dev' into merge_repo

This commit is contained in:
Ibis Prevedello 2023-02-28 20:27:09 -03:00
commit 13495cedc1
19 changed files with 862 additions and 690 deletions

View file

@ -1,149 +1,144 @@
import "reactflow/dist/style.css"; import "reactflow/dist/style.css";
import { useState, useRef, useEffect, useContext } from "react"; import { useState, useEffect, useContext } from "react";
import { ReactFlowProvider } from "reactflow";
import "./App.css"; import "./App.css";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import ErrorAlert from "./alerts/error"; import ErrorAlert from "./alerts/error";
import NoticeAlert from "./alerts/notice"; import NoticeAlert from "./alerts/notice";
import SuccessAlert from "./alerts/success"; import SuccessAlert from "./alerts/success";
import ExtraSidebar from "./components/ExtraSidebarComponent"; import ExtraSidebar from "./components/ExtraSidebarComponent";
import { alertContext } from "./contexts/alertContext"; import { alertContext } from "./contexts/alertContext";
import { locationContext } from "./contexts/locationContext"; import { locationContext } from "./contexts/locationContext";
import FlowPage from "./pages/FlowPage";
import Sidebar from "./components/SidebarComponent"; import Sidebar from "./components/SidebarComponent";
import Header from "./components/HeaderComponent"; import Header from "./components/HeaderComponent";
import { TabsProvider } from "./contexts/tabsContext";
import { TabsManager } from "./pages/FlowPage/flowManager"; import { TabsManager } from "./pages/FlowPage/flowManager";
export default function App() { export default function App() {
var _ = require("lodash"); var _ = require("lodash");
let { setAtual, setShowSideBar, setIsStackedOpen} = let { setCurrent, setShowSideBar, setIsStackedOpen } =
useContext(locationContext); useContext(locationContext);
let location = useLocation(); let location = useLocation();
useEffect(() => { useEffect(() => {
setAtual(location.pathname.replace(/\/$/g, "").split("/")); setCurrent(location.pathname.replace(/\/$/g, "").split("/"));
setShowSideBar(true); setShowSideBar(true);
setIsStackedOpen(true); setIsStackedOpen(true);
}, [location.pathname, setAtual, setIsStackedOpen, setShowSideBar]); }, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
const { const {
errorData, errorData,
errorOpen, errorOpen,
setErrorOpen, setErrorOpen,
noticeData, noticeData,
noticeOpen, noticeOpen,
setNoticeOpen, setNoticeOpen,
successData, successData,
successOpen, successOpen,
setSuccessOpen, setSuccessOpen,
} = useContext(alertContext); } = useContext(alertContext);
const [alertsList, setAlertsList] = useState([]); const [alertsList, setAlertsList] = useState([]);
useEffect(() => { useEffect(() => {
if (errorOpen && errorData) { if (errorOpen && errorData) {
setErrorOpen(false); setErrorOpen(false);
setAlertsList((old) => { setAlertsList((old) => {
let newAlertsList = [ let newAlertsList = [
...old, ...old,
{ type: "error", data: _.cloneDeep(errorData), id: _.uniqueId() }, { type: "error", data: _.cloneDeep(errorData), id: _.uniqueId() },
]; ];
return newAlertsList; return newAlertsList;
}); });
} else if (noticeOpen && noticeData) { } else if (noticeOpen && noticeData) {
setNoticeOpen(false); setNoticeOpen(false);
setAlertsList((old) => { setAlertsList((old) => {
let newAlertsList = [ let newAlertsList = [
...old, ...old,
{ type: "notice", data: _.cloneDeep(noticeData), id: _.uniqueId() }, { type: "notice", data: _.cloneDeep(noticeData), id: _.uniqueId() },
]; ];
return newAlertsList; return newAlertsList;
}); });
} else if (successOpen && successData) { } else if (successOpen && successData) {
setSuccessOpen(false); setSuccessOpen(false);
setAlertsList((old) => { setAlertsList((old) => {
let newAlertsList = [ let newAlertsList = [
...old, ...old,
{ type: "success", data: _.cloneDeep(successData), id: _.uniqueId() }, { type: "success", data: _.cloneDeep(successData), id: _.uniqueId() },
]; ];
return newAlertsList; return newAlertsList;
}); });
} }
}, [errorData, errorOpen, noticeData, noticeOpen, successData, successOpen]); }, [errorData, errorOpen, noticeData, noticeOpen, successData, successOpen]);
const removeAlert = (id: number) => { const removeAlert = (id: number) => {
setAlertsList((prevAlertsList) => setAlertsList((prevAlertsList) =>
prevAlertsList.filter((alert) => alert.id !== id) prevAlertsList.filter((alert) => alert.id !== id)
); );
}; };
const user = { const user = {
name: "Whitney Francis", name: "Whitney Francis",
email: "whitney.francis@example.com", email: "whitney.francis@example.com",
imageUrl: imageUrl:
"https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80", "https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
}; };
const userNavigation = [ const userNavigation = [
{ name: "Your Projects", href: "/" }, { name: "Your Projects", href: "/" },
// { // {
// name: "Account settings", // name: "Account settings",
// href: "http://localhost:4455/.ory/kratos/public/self-service/settings/browser", // href: "http://localhost:4455/.ory/kratos/public/self-service/settings/browser",
// }, // },
{ name: "Sign out", href: "/" }, { name: "Sign out", href: "/" },
]; ];
return ( return (
//need parent component with width and height //need parent component with width and height
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<div className="flex grow-0 shrink basis-auto">
<div className="flex grow-0 shrink basis-auto"> <Header userNavigation={userNavigation} user={user}></Header>
<Header userNavigation={userNavigation} user={user}></Header> </div>
</div> <div className="flex grow shrink basis-auto min-h-0 flex-1 overflow-hidden">
<div className="flex grow shrink basis-auto min-h-0 flex-1 overflow-hidden"> <Sidebar />
<Sidebar /> <ExtraSidebar />
<ExtraSidebar />
{/* Main area */} {/* Main area */}
<main className="min-w-0 flex-1 border-t border-gray-200 flex"> <main className="min-w-0 flex-1 border-t border-gray-200 flex">
{/* Primary column */} {/* Primary column */}
<div className="w-full h-full"> <div className="w-full h-full">
<TabsManager></TabsManager> <TabsManager></TabsManager>
</div> </div>
</main> </main>
</div> </div>
<div className="flex z-50 flex-col-reverse fixed bottom-5 left-5"> <div className="flex z-50 flex-col-reverse fixed bottom-5 left-5">
{alertsList.map((alert) => ( {alertsList.map((alert) => (
<div key={alert.id}> <div key={alert.id}>
{alert.type === "error" ? ( {alert.type === "error" ? (
<ErrorAlert <ErrorAlert
key={alert.id} key={alert.id}
title={alert.data.title} title={alert.data.title}
list={alert.data.list} list={alert.data.list}
id={alert.id} id={alert.id}
removeAlert={removeAlert} removeAlert={removeAlert}
/> />
) : alert.type === "notice" ? ( ) : alert.type === "notice" ? (
<NoticeAlert <NoticeAlert
key={alert.id} key={alert.id}
title={alert.data.title} title={alert.data.title}
link={alert.data.link} link={alert.data.link}
id={alert.id} id={alert.id}
removeAlert={removeAlert} removeAlert={removeAlert}
/> />
) : ( ) : (
<SuccessAlert <SuccessAlert
key={alert.id} key={alert.id}
title={alert.data.title} title={alert.data.title}
id={alert.id} id={alert.id}
removeAlert={removeAlert} removeAlert={removeAlert}
/> />
)} )}
</div> </div>
))} ))}
</div> </div>
</div> </div>
); );
} }

View file

@ -1,18 +0,0 @@
const defaultErrorMessages ={
deleteRLAS:"Could not remove label from record, please try again",
addRLAS:"Could not label this record, please try again",
deleteRecords:"Could not delete record, please try again later"
}
export class CustomError extends Error{
constructor(message:string){
super(message)
Object.setPrototypeOf(this,CustomError.prototype)
}
getErrorMessage(): string{
return defaultErrorMessages[this.message]?? "unknow error, please try again"
}
}

View file

@ -1,13 +1,15 @@
import { Disclosure } from "@headlessui/react"; import { Disclosure } from "@headlessui/react";
import { ChevronLeftIcon } from "@heroicons/react/24/outline"; import { ArrowUpTrayIcon, ChevronLeftIcon } from "@heroicons/react/24/outline";
import { useContext } from "react"; import { useContext } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { classNames } from "../../utils"; import { classNames } from "../../utils";
import { locationContext } from "../../contexts/locationContext"; import { locationContext } from "../../contexts/locationContext";
import { TabsContext } from "../../contexts/tabsContext";
export default function ExtraSidebar() { export default function ExtraSidebar() {
const {uploadFlow} = useContext(TabsContext)
const { const {
atual, current,
isStackedOpen, isStackedOpen,
setIsStackedOpen, setIsStackedOpen,
extraNavigation, extraNavigation,
@ -20,7 +22,7 @@ export default function ExtraSidebar() {
isStackedOpen ? "w-60" : "w-0 " isStackedOpen ? "w-60" : "w-0 "
} flex-shrink-0 flex overflow-hidden flex-col border-r transition-all duration-500`} } flex-shrink-0 flex overflow-hidden flex-col border-r transition-all duration-500`}
> >
<div className="w-60 overflow-y-auto scrollbar-hide h-full"> <div className="w-60 overflow-y-auto scrollbar-hide h-full flex flex-col items-start">
<div className="flex pt-4 px-4 justify-between align-middle w-full"> <div className="flex pt-4 px-4 justify-between align-middle w-full">
<span className="text-gray-900 text-lg ml-2 font-semibold"> <span className="text-gray-900 text-lg ml-2 font-semibold">
{extraNavigation.title} {extraNavigation.title}
@ -32,7 +34,7 @@ export default function ExtraSidebar() {
<ChevronLeftIcon className="h-6 w-6"></ChevronLeftIcon> <ChevronLeftIcon className="h-6 w-6"></ChevronLeftIcon>
</button> </button>
</div> </div>
<div className="flex flex-grow flex-col"> <div className="flex flex-grow flex-col w-full">
{extraNavigation.options ? ( {extraNavigation.options ? (
<div className="p-4"> <div className="p-4">
<nav className="flex-1 space-y-1"> <nav className="flex-1 space-y-1">
@ -42,7 +44,7 @@ export default function ExtraSidebar() {
<Link <Link
to={item.href} to={item.href}
className={classNames( className={classNames(
item.href.split("/")[2] === atual[4] item.href.split("/")[2] === current[4]
? "bg-gray-100 text-gray-900" ? "bg-gray-100 text-gray-900"
: "bg-white text-gray-600 hover:bg-gray-50 hover: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" "group w-full flex items-center pl-2 py-2 text-sm font-medium rounded-md"
@ -50,7 +52,7 @@ export default function ExtraSidebar() {
> >
<item.icon <item.icon
className={classNames( className={classNames(
item.href.split("/")[2] === atual[4] item.href.split("/")[2] === current[4]
? "text-gray-500" ? "text-gray-500"
: "text-gray-400 group-hover:text-gray-500", : "text-gray-400 group-hover:text-gray-500",
"mr-3 flex-shrink-0 h-6 w-6" "mr-3 flex-shrink-0 h-6 w-6"
@ -69,7 +71,7 @@ export default function ExtraSidebar() {
<> <>
<Disclosure.Button <Disclosure.Button
className={classNames( className={classNames(
item.href.split("/")[2] === atual[4] item.href.split("/")[2] === current[4]
? "bg-gray-100 text-gray-900" ? "bg-gray-100 text-gray-900"
: "bg-white text-gray-600 hover:bg-gray-50 hover: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" "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"
@ -102,7 +104,7 @@ export default function ExtraSidebar() {
key={subItem.name} key={subItem.name}
to={subItem.href} to={subItem.href}
className={classNames( className={classNames(
subItem.href.split("/")[3] === atual[5] subItem.href.split("/")[3] === current[5]
? "bg-gray-100 text-gray-900" ? "bg-gray-100 text-gray-900"
: "bg-white text-gray-600 hover:bg-gray-50 hover: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" "group flex w-full items-center rounded-md py-2 pl-11 pr-2 text-sm font-medium"
@ -122,7 +124,11 @@ export default function ExtraSidebar() {
) : ( ) : (
extraComponent extraComponent
)} )}
{/* need to convert to multi stackbar logic */}
</div> </div>
{/* <div className="w-full flex content-center justify-center mt-auto mb-8">
<button onClick={()=>uploadFlow()} className="flex content-center justify-center py-3 px-6 border rounded-lg border-blue-500 text-blue-500 hover:text-white hover:bg-blue-500"><span>import flow</span><ArrowUpTrayIcon className=" ml-2 w-5 h-5"/> </button>
</div> */}
</div> </div>
</aside> </aside>
</> </>

View file

@ -45,7 +45,7 @@ export default function Header({user, userNavigation}){
<div className="min-w-0 flex-1 ml-5"> <div className="min-w-0 flex-1 ml-5">
<Breadcrumb /> <Breadcrumb />
</div> </div>
{/* <div className="ml-10 flex shrink-0 items-center space-x-10 pr-4"> <div className="ml-10 flex shrink-0 items-center space-x-10 pr-4">
<div className="flex items-center space-x-8"> <div className="flex items-center space-x-8">
<span className="inline-flex"> <span className="inline-flex">
<button type="button" {...triggerProps} className="-mx-1 rounded-full bg-white p-1 text-gray-400 hover:text-gray-500 relative" onClick={()=>{setNotificationCenter(false);setIsOpen(true)}}> <button type="button" {...triggerProps} className="-mx-1 rounded-full bg-white p-1 text-gray-400 hover:text-gray-500 relative" onClick={()=>{setNotificationCenter(false);setIsOpen(true)}}>
@ -56,7 +56,7 @@ export default function Header({user, userNavigation}){
</button>{renderLayer(<div {...layerProps}><AlertDropdown closeFunction={()=>setIsOpen(false)} open={isOpen}></AlertDropdown></div>)} </button>{renderLayer(<div {...layerProps}><AlertDropdown closeFunction={()=>setIsOpen(false)} open={isOpen}></AlertDropdown></div>)}
</span> </span>
<Menu as="div" className="relative inline-block text-left"> {/* <Menu as="div" className="relative inline-block text-left">
<Menu.Button className="flex rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"> <Menu.Button className="flex rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2">
<span className="sr-only">Open user menu</span> <span className="sr-only">Open user menu</span>
<img className="h-8 w-8 rounded-full" src={user.imageUrl} alt="" /> <img className="h-8 w-8 rounded-full" src={user.imageUrl} alt="" />
@ -100,9 +100,9 @@ export default function Header({user, userNavigation}){
</div> </div>
</Menu.Items> </Menu.Items>
</Transition> </Transition>
</Menu> </Menu> */}
</div> </div>
</div> */} </div>
</div> </div>
</header> </header>
) )

View file

@ -4,14 +4,14 @@ import { useContext } from "react"
import { locationContext } from "../../../contexts/locationContext"; import { locationContext } from "../../../contexts/locationContext";
export default function SidebarButton({item}){ export default function SidebarButton({item}){
let {atual}= useContext(locationContext); let {current}= useContext(locationContext);
return ( return (
<> <>
<Link <Link
key={item.name} key={item.name}
to={item.href} to={item.href}
className={classNames( className={classNames(
item.href.split("/")[1]=== atual[3]? 'bg-gray-900 text-white' : 'text-gray-400 hover:bg-gray-700', item.href.split("/")[1]=== current[3]? 'bg-gray-900 text-white' : 'text-gray-400 hover:bg-gray-700',
'flex-shrink-0 inline-flex items-center justify-center h-14 w-14 rounded-lg' 'flex-shrink-0 inline-flex items-center justify-center h-14 w-14 rounded-lg'
)} )}
> >

View file

@ -22,7 +22,7 @@ function getPages(atual){
} }
export default function Breadcrumb(){ export default function Breadcrumb(){
let {atual} = useContext(locationContext); let {current} = useContext(locationContext);
return ( return (
<div> <div>
<nav className="flex ml-2" aria-label="Breadcrumb"> <nav className="flex ml-2" aria-label="Breadcrumb">
@ -35,7 +35,7 @@ export default function Breadcrumb(){
</Link> </Link>
</div> </div>
</li> </li>
{getPages(atual).map((page) => ( {getPages(current).map((page) => (
<li key={page.href}> <li key={page.href}>
<div className="flex items-center"> <div className="flex items-center">
<ChevronRightIcon className="h-5 w-5 flex-shrink-0 text-gray-400" aria-hidden="true" /> <ChevronRightIcon className="h-5 w-5 flex-shrink-0 text-gray-400" aria-hidden="true" />

View file

@ -9,13 +9,16 @@ import { useContext, useEffect, useRef, useState } from "react";
import { sendAll } from "../../controllers/NodesServices"; import { sendAll } from "../../controllers/NodesServices";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { nodeColors } from "../../utils"; import { nodeColors } from "../../utils";
import { TabsContext } from "../../contexts/tabsContext";
const _ = require("lodash"); const _ = require("lodash");
export default function Chat({ reactFlowInstance }) { export default function Chat({flow, reactFlowInstance }) {
const {updateFlow} = useContext(TabsContext)
const [saveChat,setSaveChat] = useState(false)
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
const [chatValue, setChatValue] = useState(""); const [chatValue, setChatValue] = useState("");
const [chatHistory, setChatHistory] = useState([]); const [chatHistory, setChatHistory] = useState(flow.chat);
const {setErrorData} = useContext(alertContext); const {setErrorData} = useContext(alertContext);
const addChatHistory = (message, isSend) => { const addChatHistory = (message, isSend) => {
setChatHistory((old) => { setChatHistory((old) => {
@ -23,9 +26,18 @@ export default function Chat({ reactFlowInstance }) {
newChat.push({ message, isSend }); newChat.push({ message, isSend });
return newChat; return newChat;
}); });
setSaveChat(chat=>!chat)
}; };
useEffect(()=>{ useEffect(()=>{
ref.current.scrollIntoView({behavior: 'smooth'}); console.log("flow")
updateFlow({..._.cloneDeep(flow),chat:chatHistory})
},[saveChat])
useEffect(()=>{
setChatHistory(flow.chat)
},[flow])
useEffect(()=>{
if(ref.current)
ref.current.scrollIntoView({behavior: 'smooth'});
}, [chatHistory]) }, [chatHistory])
function validateNodes(){ 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)))))))){ 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)))))))){
@ -40,6 +52,29 @@ export default function Chat({ reactFlowInstance }) {
return true; return true;
} }
const ref = useRef(null); const ref = useRef(null);
function sendMessage(){
console.log(reactFlowInstance.toObject())
if(chatValue !== ""){
if(validateNodes()){
if(validateChatNodes()){
let message = chatValue;
setChatValue("");
addChatHistory(message, true);
console.log({...reactFlowInstance.toObject(),message,chatHistory})
sendAll({...reactFlowInstance.toObject(),message,chatHistory}).then((r) => {addChatHistory(r.data.result, false);});
} else {
setErrorData({title: 'Error sending message', list:['Chat nodes are missing.']})
}
} else {
setErrorData({title: 'Error sending message', list:['There are required fields not filled yet.']})
}
} else {
setErrorData({title: 'Error sending message', list:['The message cannot be empty.']})
}
}
return ( return (
<> <>
<Transition <Transition
@ -71,14 +106,14 @@ export default function Chat({ reactFlowInstance }) {
{chatHistory.map((c, i) => ( {chatHistory.map((c, i) => (
<div key={i}> <div key={i}>
{c.isSend ? ( {c.isSend ? (
<div className="w-full text-start"> <div className="w-full text-end">
<div className="text-start inline-block bg-gray-200 rounded-xl p-3 overflow-hidden w-fit max-w-[280px] px-5 text-sm font-normal rounded-tl-none"> <div className="text-start inline-block bg-gray-200 rounded-xl p-3 overflow-hidden w-fit max-w-[280px] px-5 text-sm font-normal rounded-tr-none">
{c.message} {c.message}
</div> </div>
</div> </div>
) : ( ) : (
<div className="w-full text-end"> <div className="w-full text-start">
<div style={{backgroundColor: nodeColors['chat']}} className="text-start inline-block rounded-xl p-3 overflow-hidden w-fit max-w-[280px] px-5 text-sm text-white font-normal rounded-tr-none"> <div style={{backgroundColor: nodeColors['chat']}} className="text-start inline-block rounded-xl p-3 overflow-hidden w-fit max-w-[280px] px-5 text-sm text-white font-normal rounded-tl-none">
{c.message} {c.message}
</div> </div>
</div> </div>
@ -90,6 +125,11 @@ export default function Chat({ reactFlowInstance }) {
<div className="w-full bg-white border-t flex items-center justify-between p-3"> <div className="w-full bg-white border-t flex items-center justify-between p-3">
<div className="relative w-full mt-1 rounded-md shadow-sm"> <div className="relative w-full mt-1 rounded-md shadow-sm">
<input <input
onKeyDown={(event)=>{
if(event.key==='Enter'){
sendMessage()
}
}}
type="text" type="text"
value={chatValue} value={chatValue}
onChange={(e) => { onChange={(e) => {
@ -100,28 +140,7 @@ export default function Chat({ reactFlowInstance }) {
/> />
<div className="absolute inset-y-0 right-0 flex items-center pr-3"> <div className="absolute inset-y-0 right-0 flex items-center pr-3">
<button <button
onClick={() => { onClick={() => sendMessage()}
console.log(reactFlowInstance.toObject())
if(chatValue !== ""){
if(validateNodes()){
if(validateChatNodes()){
let message = chatValue;
setChatValue("");
addChatHistory(message, true);
sendAll({...reactFlowInstance.toObject(),message}).then((r) => {addChatHistory(r.data.result, false);});
} else {
setErrorData({title: 'Error sending message', list:['Chat nodes are missing.']})
}
} else {
setErrorData({title: 'Error sending message', list:['There are required fields not filled yet.']})
}
} else {
setErrorData({title: 'Error sending message', list:['The message cannot be empty.']})
}
}}
> >
<PaperAirplaneIcon <PaperAirplaneIcon
className="h-5 w-5 text-gray-400 hover:text-gray-600" className="h-5 w-5 text-gray-400 hover:text-gray-600"

View file

@ -3,101 +3,146 @@ import { alertDropdownItem } from "../alerts/alertDropDown";
var _ = require("lodash"); var _ = require("lodash");
type alertContextType= //types for alertContextType
{ type alertContextType = {
errorData: {title: string, list?: Array<string>}; errorData: { title: string; list?: Array<string> };
setErrorData:(newState:{title: string, list?: Array<string>})=>void; setErrorData: (newState: { title: string; list?: Array<string> }) => void;
errorOpen: boolean; errorOpen: boolean;
setErrorOpen:(newState:boolean)=>void; setErrorOpen: (newState: boolean) => void;
noticeData: {title: string, link?: string}; noticeData: { title: string; link?: string };
setNoticeData:(newState:{title: string, link?: string})=>void; setNoticeData: (newState: { title: string; link?: string }) => void;
noticeOpen: boolean; noticeOpen: boolean;
setNoticeOpen:(newState:boolean)=>void; setNoticeOpen: (newState: boolean) => void;
successData: {title: string}; successData: { title: string };
setSuccessData:(newState:{title: string})=>void; setSuccessData: (newState: { title: string }) => void;
successOpen: boolean; successOpen: boolean;
setSuccessOpen:(newState:boolean)=>void; setSuccessOpen: (newState: boolean) => void;
notificationCenter:boolean notificationCenter: boolean;
setNotificationCenter:(newState:boolean)=>void; setNotificationCenter: (newState: boolean) => void;
notificationList:Array<alertDropdownItem>; notificationList: Array<alertDropdownItem>;
pushNotificationList:(Object)=>void pushNotificationList: (Object) => void;
clearNotificationList:()=>void, clearNotificationList: () => void;
removeFromNotificationList:(index:number)=>void removeFromNotificationList: (index: number) => void;
} };
const initialValue= { //initial values to alertContextType
errorData: {title:"", list:[]}, const initialValue = {
setErrorData:()=>{}, errorData: { title: "", list: [] },
errorOpen: false, setErrorData: () => {},
setErrorOpen:()=>{}, errorOpen: false,
noticeData: {title:"", link:""}, setErrorOpen: () => {},
setNoticeData:()=>{}, noticeData: { title: "", link: "" },
noticeOpen: false, setNoticeData: () => {},
setNoticeOpen:()=>{}, noticeOpen: false,
successData: {title:""}, setNoticeOpen: () => {},
setSuccessData:()=>{}, successData: { title: "" },
successOpen: false, setSuccessData: () => {},
setSuccessOpen:()=>{}, successOpen: false,
notificationCenter:false, setSuccessOpen: () => {},
setNotificationCenter:()=>{}, notificationCenter: false,
notificationList:[], setNotificationCenter: () => {},
pushNotificationList:()=>{}, notificationList: [],
clearNotificationList:()=>{}, pushNotificationList: () => {},
removeFromNotificationList:()=>{} clearNotificationList: () => {},
} removeFromNotificationList: () => {},
};
export const alertContext = createContext<alertContextType>(initialValue); export const alertContext = createContext<alertContextType>(initialValue);
export function AlertProvider({children}){ export function AlertProvider({ children }) {
const [errorData, setErrorDataState] = useState<{title:string,list?:Array<string>}>({title:"",list:[]}); const [errorData, setErrorDataState] = useState<{
const [errorOpen, setErrorOpen] = useState(false); title: string;
const [noticeData, setNoticeDataState] = useState<{title:string,link?:string}>({title:"",link:""}); list?: Array<string>;
const [noticeOpen, setNoticeOpen] = useState(false); }>({ title: "", list: [] });
const [successData, setSuccessDataState] = useState<{title:string}>({title:""}); const [errorOpen, setErrorOpen] = useState(false);
const [successOpen, setSuccessOpen] = useState(false); const [noticeData, setNoticeDataState] = useState<{
const [notificationCenter,setNotificationCenter]=useState(false); title: string;
const [notificationList,setNotificationList] = useState([]) link?: string;
const pushNotificationList = (notification:alertDropdownItem)=>{ }>({ title: "", link: "" });
setNotificationList((old) => { const [noticeOpen, setNoticeOpen] = useState(false);
let newNotificationList = _.cloneDeep(old); const [successData, setSuccessDataState] = useState<{ title: string }>({
newNotificationList.unshift(notification); title: "",
return newNotificationList; });
}) const [successOpen, setSuccessOpen] = useState(false);
} const [notificationCenter, setNotificationCenter] = useState(false);
function setErrorData (newState:{title: string, list?: Array<string>}){ const [notificationList, setNotificationList] = useState([]);
setErrorDataState(newState) const pushNotificationList = (notification: alertDropdownItem) => {
setErrorOpen(true) setNotificationList((old) => {
if(newState.title){ let newNotificationList = _.cloneDeep(old);
setNotificationCenter(true) newNotificationList.unshift(notification);
pushNotificationList({type:"error",title:newState.title,list:newState.list, id:_.uniqueId()}) return newNotificationList;
} });
} };
function setNoticeData(newState:{title: string, link?: string}){ function setErrorData(newState: { title: string; list?: Array<string> }) {
setNoticeDataState(newState) setErrorDataState(newState);
setNoticeOpen(true) setErrorOpen(true);
if(newState.title){ if (newState.title) {
setNotificationCenter(true) setNotificationCenter(true);
pushNotificationList({type:"notice",title:newState.title,link:newState.link, id:_.uniqueId()}) pushNotificationList({
} type: "error",
} title: newState.title,
function setSuccessData(newState:{title: string}){ list: newState.list,
setSuccessDataState(newState) id: _.uniqueId(),
setSuccessOpen(true) });
if(newState.title){ }
setNotificationCenter(true) }
pushNotificationList({type:"success",title:newState.title, id:_.uniqueId()}) function setNoticeData(newState: { title: string; link?: string }) {
} setNoticeDataState(newState);
} setNoticeOpen(true);
function clearNotificationList(){ if (newState.title) {
setNotificationList([]) setNotificationCenter(true);
} pushNotificationList({
function removeFromNotificationList(index:number){ type: "notice",
setNotificationList((prevAlertsList) => prevAlertsList.filter((alert) => alert.id !== index)); title: newState.title,
} link: newState.link,
return ( id: _.uniqueId(),
<alertContext.Provider value={{ removeFromNotificationList,clearNotificationList,notificationList,pushNotificationList,setNotificationCenter,notificationCenter,errorData, setErrorData, });
errorOpen,setErrorOpen, noticeData, setNoticeData, noticeOpen, setNoticeOpen, successData, setSuccessData, successOpen, setSuccessOpen}}> }
{children} }
</alertContext.Provider> function setSuccessData(newState: { title: string }) {
) setSuccessDataState(newState);
setSuccessOpen(true);
if (newState.title) {
setNotificationCenter(true);
pushNotificationList({
type: "success",
title: newState.title,
id: _.uniqueId(),
});
}
}
function clearNotificationList() {
setNotificationList([]);
}
function removeFromNotificationList(index: number) {
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

@ -5,6 +5,7 @@ import { TabsProvider } from "./tabsContext";
import { TypesProvider } from "./typesContext"; import { TypesProvider } from "./typesContext";
export default function ContextWrapper({ children }) { export default function ContextWrapper({ children }) {
//element to wrap all context
return ( return (
<> <>
<LocationProvider> <LocationProvider>

View file

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

View file

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

View file

@ -1,102 +1,158 @@
import { createContext, useEffect, useState, useRef } from "react"; import { createContext, useEffect, useState, useRef } from "react";
import { example } from "../data_assets/example";
type flow={name:string,id:string,data:any} type flow = {
name: string;
id: string;
data: any;
chat: Array<{ message: string; isSend: boolean }>;
};
type TabsContextType={ type TabsContextType = {
tabIndex:number; tabIndex: number;
setTabIndex:(index:number)=>void; setTabIndex: (index: number) => void;
flows:Array<flow> flows: Array<flow>;
removeFlow:(id:string)=>void; removeFlow: (id: string) => void;
addFlow:(flowData?:any)=>void; addFlow: (flowData?: any) => void;
updateFlow:(newFlow:flow)=>void; updateFlow: (newFlow: flow) => void;
incrementNodeId:()=>number, incrementNodeId: () => number;
} downloadFlow: () => void;
uploadFlow: () => void;
};
const TabsContextInitialValue = { const TabsContextInitialValue = {
tabIndex : 0, tabIndex: 0,
setTabIndex:(index:number)=>{}, setTabIndex: (index: number) => {},
flows:[], flows: [],
removeFlow:(id:string)=>{}, removeFlow: (id: string) => {},
addFlow:(flowData?:any)=>{}, addFlow: (flowData?: any) => {},
updateFlow:(newFlow:flow)=>{}, updateFlow: (newFlow: flow) => {},
incrementNodeId:()=>0, incrementNodeId: () => 0,
downloadFlow: () => {},
uploadFlow: () => {},
};
} export const TabsContext = createContext<TabsContextType>(
TabsContextInitialValue
export const TabsContext = createContext<TabsContextType>(TabsContextInitialValue) );
let _ = require('lodash'); let _ = require("lodash");
export function TabsProvider({children}){ export function TabsProvider({ children }) {
const [tabIndex,setTabIndex] = useState(0) const [tabIndex, setTabIndex] = useState(0);
const [flows,setFlows] = useState<Array<flow>>([]) const [flows, setFlows] = useState<Array<flow>>([]);
const [id, setId] = useState(0); const [id, setId] = useState(0);
const newNodeId = useRef(0); const newNodeId = useRef(0);
function incrementNodeId(){ function incrementNodeId() {
newNodeId.current = newNodeId.current + 1; newNodeId.current = newNodeId.current + 1;
return newNodeId.current; return newNodeId.current;
} }
useEffect(() => { useEffect(() => {
if(flows.length !== 0) if (flows.length !== 0)
window.localStorage.setItem('tabsData', JSON.stringify({tabIndex, flows, id, nodeId: newNodeId.current})); window.localStorage.setItem(
}, [flows, id, tabIndex, newNodeId]); "tabsData",
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current })
useEffect(() => { );
let cookie = window.localStorage.getItem('tabsData'); }, [flows, id, tabIndex, newNodeId]);
if(cookie){
let cookieObject = JSON.parse(cookie); useEffect(() => {
setTabIndex(cookieObject.tabIndex); let cookie = window.localStorage.getItem("tabsData");
setFlows(cookieObject.flows); if (cookie) {
setId(cookieObject.id); let cookieObject = JSON.parse(cookie);
newNodeId.current= cookieObject.nodeId; setTabIndex(cookieObject.tabIndex);
} setFlows(cookieObject.flows);
}, []) setId(cookieObject.id);
newNodeId.current = cookieObject.nodeId;
function removeFlow(id:string){ }
setFlows(prevState=>{ }, []);
const newFlows = [...prevState];
const index = newFlows.findIndex(flow=>flow.id===id); function downloadFlow() {
if(index >= 0){ const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
if(index===tabIndex){ JSON.stringify(flows[tabIndex])
setTabIndex(flows.length-2); )}`;
newFlows.splice(index,1); const link = document.createElement("a");
} else { link.href = jsonString;
let flowId = flows[tabIndex].id; link.download = `${flows[tabIndex].name}.json`;
newFlows.splice(index,1); link.click();
setTabIndex(newFlows.findIndex(flow=>flow.id === flowId)); }
}
function uploadFlow() {
} const input = document.createElement("input");
return newFlows; input.type = "file";
}); input.onchange = (e: Event) => {
} if ((e.target as HTMLInputElement).files[0].type === "application/json") {
function addFlow(flowData?:flow) { const file = (e.target as HTMLInputElement).files[0];
const data = flowData?flowData:null file.text().then((text) => {
let newFlow: flow = {name: "flow"+id, id: id.toString(), data} console.log(JSON.parse(text),"json from upload")
setId((old) => old+1); addFlow(JSON.parse(text));
setFlows(prevState => { });
const newFlows = [...prevState, newFlow]; }
return newFlows; };
}); input.click();
setTabIndex(flows.length); }
}
function updateFlow(newFlow:flow){ function removeFlow(id: string) {
setFlows(prevState=>{ setFlows((prevState) => {
const newFlows = [...prevState]; const newFlows = [...prevState];
const index = newFlows.findIndex(flow=>flow.id===newFlow.id) const index = newFlows.findIndex((flow) => flow.id === id);
if(index!==-1){ if (index >= 0) {
newFlows[index].data = newFlow.data if (index === tabIndex) {
newFlows[index].name = newFlow.name setTabIndex(flows.length - 2);
} newFlows.splice(index, 1);
return newFlows; } else {
}); let flowId = flows[tabIndex].id;
} newFlows.splice(index, 1);
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
return( }
<TabsContext.Provider value={{tabIndex,setTabIndex,flows,incrementNodeId, removeFlow,addFlow,updateFlow}}> }
{children} return newFlows;
</TabsContext.Provider> });
) }
function addFlow(flow?: flow) {
const data = flow?.data ? flow.data : null;
let newFlow: flow = {
name: flow ? flow.name : "flow" + id,
id: id.toString(),
data,
chat: flow ? flow.chat : [],
};
setId((old) => old + 1);
setFlows((prevState) => {
const newFlows = [...prevState, newFlow];
return newFlows;
});
setTabIndex(flows.length);
}
function updateFlow(newFlow: flow) {
console.log(newFlow);
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={{
tabIndex,
setTabIndex,
flows,
incrementNodeId,
removeFlow,
addFlow,
updateFlow,
downloadFlow,
uploadFlow,
}}
>
{children}
</TabsContext.Provider>
);
} }

View file

@ -1,37 +1,44 @@
import { ReactEventHandler, createContext, useState } from "react"; import { createContext, useState } from "react";
import { ReactFlowInstance } from "reactflow"; import { ReactFlowInstance } from "reactflow";
type typesContextType= //context to share types adn functions from nodes to flow
{ type typesContextType = {
reactFlowInstance: ReactFlowInstance; reactFlowInstance: ReactFlowInstance;
setReactFlowInstance: any; setReactFlowInstance: any;
deleteNode:(idx:number)=>void; deleteNode: (idx: number) => void;
types: {}; types: {};
setTypes:(newState:{})=>void; setTypes: (newState: {}) => void;
};
} const initialValue = {
reactFlowInstance: null,
const initialValue= { setReactFlowInstance: () => {},
reactFlowInstance: null, deleteNode: () => {},
setReactFlowInstance: ()=>{}, types: {},
deleteNode: ()=>{}, setTypes: () => {},
types: {}, };
setTypes:()=>{},
}
export const typesContext = createContext<typesContextType>(initialValue); export const typesContext = createContext<typesContextType>(initialValue);
export function TypesProvider({children}){ export function TypesProvider({ children }) {
const [types, setTypes] = useState({}); const [types, setTypes] = useState({});
const [reactFlowInstance, setReactFlowInstance] = useState(null); const [reactFlowInstance, setReactFlowInstance] = useState(null);
function deleteNode(idx){ function deleteNode(idx) {
reactFlowInstance.setNodes( reactFlowInstance.setNodes(
reactFlowInstance.getNodes().filter((n) => n.id !== idx) reactFlowInstance.getNodes().filter((n) => n.id !== idx)
); );
} }
return ( return (
<typesContext.Provider value={{ types, setTypes, reactFlowInstance, setReactFlowInstance, deleteNode}}> <typesContext.Provider
{children} value={{
</typesContext.Provider> types,
) setTypes,
reactFlowInstance,
setReactFlowInstance,
deleteNode,
}}
>
{children}
</typesContext.Provider>
);
} }

View file

@ -10,9 +10,9 @@ import {
export const sidebarNavigation = [ export const sidebarNavigation = [
{ name: 'Home', href: '/', icon: HomeIcon, current: true }, { name: 'Home', href: '/', icon: HomeIcon, current: true },
{ name: 'Table', href: '/table/', icon: TableCellsIcon, current: false }, // { name: 'Table', href: '/table/', icon: TableCellsIcon, current: false },
//{ name: 'Train', href: '#', icon: BoltIcon, current: false }, // //{ name: 'Train', href: '#', icon: BoltIcon, current: false },
//{ name: 'Model Details', href: '#', icon: LightBulbIcon, current: false }, // //{ name: 'Model Details', href: '#', icon: LightBulbIcon, current: false },
//{ name: 'Deploy', href: '#', icon: RocketLaunchIcon, current: false }, // //{ name: 'Deploy', href: '#', icon: RocketLaunchIcon, current: false },
{ name: 'Settings', href: '/settings/', icon: Cog6ToothIcon, current: false }, // { name: 'Settings', href: '/settings/', icon: Cog6ToothIcon, current: false },
] ]

View file

@ -49,7 +49,7 @@ export default function ExtraSidebar() {
} }
return ( return (
<div className="mt-4"> <div className="mt-4 w-full">
{Object.keys(data).map((d, i) => ( {Object.keys(data).map((d, i) => (
<DisclosureComponent <DisclosureComponent
key={i} key={i}

View file

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

View file

@ -1,12 +1,13 @@
import { useCallback, useContext, useEffect, useRef, useState } from "react"; import { useCallback, useContext, useEffect, useRef, useState } from "react";
import ReactFlow, { import ReactFlow, {
Background, Background,
Controls, Controls,
addEdge, addEdge,
useEdgesState, useEdgesState,
useNodesState, useNodesState,
ReactFlowProvider, ReactFlowProvider,
useReactFlow, useReactFlow,
ControlButton,
} from "reactflow"; } from "reactflow";
import { locationContext } from "../../contexts/locationContext"; import { locationContext } from "../../contexts/locationContext";
import ExtraSidebar from "./components/extraSidebarComponent"; import ExtraSidebar from "./components/extraSidebarComponent";
@ -20,164 +21,181 @@ import BooleanNode from "../../CustomNodes/BooleanNode";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext"; import { typesContext } from "../../contexts/typesContext";
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
} from "@heroicons/react/24/outline";
const nodeTypes = { const nodeTypes = {
genericNode: GenericNode, genericNode: GenericNode,
inputNode: InputNode, inputNode: InputNode,
chatInputNode: ChatInputNode, chatInputNode: ChatInputNode,
chatOutputNode: ChatOutputNode, chatOutputNode: ChatOutputNode,
booleanNode: BooleanNode, booleanNode: BooleanNode,
}; };
var _ = require("lodash"); var _ = require("lodash");
export default function FlowPage({ flow }) { export default function FlowPage({ flow }) {
let { updateFlow, incrementNodeId } = useContext(TabsContext); let { updateFlow, incrementNodeId, downloadFlow, uploadFlow } =
const { types, reactFlowInstance, setReactFlowInstance } = useContext(TabsContext);
useContext(typesContext); const { types, reactFlowInstance, setReactFlowInstance } =
const reactFlowWrapper = useRef(null); useContext(typesContext);
const reactFlowWrapper = useRef(null);
function getId(){ function getId() {
return `dndnode_}`+incrementNodeId(); return `dndnode_` + incrementNodeId();
}; }
const { setExtraComponent, setExtraNavigation } = useContext(locationContext); const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
const [nodes, setNodes, onNodesChange] = useNodesState( const [nodes, setNodes, onNodesChange] = useNodesState(
flow.data?.nodes ?? [] flow.data?.nodes ?? []
); );
const [edges, setEdges, onEdgesChange] = useEdgesState( const [edges, setEdges, onEdgesChange] = useEdgesState(
flow.data?.edges ?? [] flow.data?.edges ?? []
); );
const {setViewport} = useReactFlow() const { setViewport } = useReactFlow();
useEffect(() => { useEffect(() => {
if (reactFlowInstance && flow) { if (reactFlowInstance && flow) {
flow.data = reactFlowInstance.toObject(); flow.data = reactFlowInstance.toObject();
updateFlow(flow); updateFlow(flow);
} }
}, [nodes, edges]); }, [nodes, edges]);
useEffect(() => { useEffect(() => {
setNodes(flow?.data?.nodes ?? []); setNodes(flow?.data?.nodes ?? []);
setEdges(flow?.data?.edges ?? []); setEdges(flow?.data?.edges ?? []);
if (reactFlowInstance) { if (reactFlowInstance) {
setViewport( setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 1 });
flow?.data?.viewport ?? { x: 1, y: 0, zoom: 1 } }
); }, [flow, reactFlowInstance, setEdges, setNodes]);
}
}, [flow, reactFlowInstance, setEdges, setNodes]);
useEffect(() => { useEffect(() => {
setExtraComponent(<ExtraSidebar />); setExtraComponent(<ExtraSidebar />);
setExtraNavigation({ title: "Nodes" }); setExtraNavigation({ title: "Nodes" });
}, [setExtraComponent, setExtraNavigation]); }, [setExtraComponent, setExtraNavigation]);
const onEdgesChangeMod = useCallback( const onEdgesChangeMod = useCallback(
(s) => { (s) => {
onEdgesChange(s); onEdgesChange(s);
setNodes((x) => { setNodes((x) => {
let newX = _.cloneDeep(x); let newX = _.cloneDeep(x);
return newX; return newX;
}); });
}, },
[onEdgesChange, setNodes] [onEdgesChange, setNodes]
); );
const onConnect = useCallback( const onConnect = useCallback(
(params) => { (params) => {
setEdges((eds) => setEdges((eds) =>
addEdge({ ...params, className: "animate-pulse" }, eds) addEdge({ ...params, className: "animate-pulse" }, eds)
); );
setNodes((x) => { setNodes((x) => {
let newX = _.cloneDeep(x); let newX = _.cloneDeep(x);
return newX; return newX;
}); });
}, },
[setEdges, setNodes] [setEdges, setNodes]
); );
const onDragOver = useCallback((event) => { const onDragOver = useCallback((event) => {
event.preventDefault(); event.preventDefault();
event.dataTransfer.dropEffect = "move"; event.dataTransfer.dropEffect = "move";
}, []); }, []);
const onDrop = useCallback( const onDrop = useCallback(
(event) => { (event) => {
event.preventDefault(); event.preventDefault();
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect(); const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
let data = JSON.parse(event.dataTransfer.getData("json")); let data = JSON.parse(event.dataTransfer.getData("json"));
if ( if (
data.type !== "chatInput" || data.type !== "chatInput" ||
(data.type === "chatInput" && (data.type === "chatInput" &&
!reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode")) !reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode"))
) { ) {
const position = reactFlowInstance.project({ const position = reactFlowInstance.project({
x: event.clientX - reactflowBounds.left, x: event.clientX - reactflowBounds.left,
y: event.clientY - reactflowBounds.top, y: event.clientY - reactflowBounds.top,
}); });
let newId = getId(); let newId = getId();
const newNode = { const newNode = {
id: newId, id: newId,
type: type:
data.type === "str" data.type === "str"
? "inputNode" ? "inputNode"
: data.type === "chatInput" : data.type === "chatInput"
? "chatInputNode" ? "chatInputNode"
: data.type === "chatOutput" : data.type === "chatOutput"
? "chatOutputNode" ? "chatOutputNode"
: data.type === "bool" : data.type === "bool"
? "booleanNode" ? "booleanNode"
: "genericNode", : "genericNode",
position, position,
data: { data: {
...data, ...data,
id: newId, id: newId,
value: null, value: null,
}, },
}; };
setNodes((nds) => nds.concat(newNode)); setNodes((nds) => nds.concat(newNode));
} else { } else {
setErrorData({ setErrorData({
title: "Error creating node", title: "Error creating node",
list: ["There can't be more than one chat input."], list: ["There can't be more than one chat input."],
}); });
} }
}, },
[reactFlowInstance, setErrorData, setNodes] [reactFlowInstance, setErrorData, setNodes]
); );
return ( return (
<div className="w-full h-full" ref={reactFlowWrapper}> <div className="w-full h-full" ref={reactFlowWrapper}>
{Object.keys(types).length > 0 ? ( {Object.keys(types).length > 0 ? (
<> <>
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
onMove={() => onMove={() =>
updateFlow({ ...flow, data: reactFlowInstance.toObject() }) updateFlow({ ...flow, data: reactFlowInstance.toObject() })
} }
edges={edges} edges={edges}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChangeMod} onEdgesChange={onEdgesChangeMod}
onConnect={onConnect} onConnect={onConnect}
onLoad={setReactFlowInstance} onLoad={setReactFlowInstance}
onInit={setReactFlowInstance} onInit={setReactFlowInstance}
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
connectionLineComponent={connection} connectionLineComponent={connection}
onDragOver={onDragOver} onDragOver={onDragOver}
onDrop={onDrop} onDrop={onDrop}
> >
<Background /> <Background />
<Controls></Controls> <Controls>
</ReactFlow> <ControlButton
<Chat reactFlowInstance={reactFlowInstance} /> className="text-black hover:text-blue-500"
</> onClick={() => uploadFlow()}
) : ( >
<></> <ArrowUpTrayIcon />
)} </ControlButton>
</div>
); <ControlButton
className="text-black hover:text-blue-500"
onClick={() => downloadFlow()}
>
<ArrowDownTrayIcon />
</ControlButton>
</Controls>
</ReactFlow>
<Chat flow={flow} reactFlowInstance={reactFlowInstance} />
</>
) : (
<></>
)}
</div>
);
} }

View file

@ -1 +0,0 @@
/// <reference types="react-scripts" />

View file

@ -1 +0,0 @@
import '@testing-library/jest-dom';