Merge branch 'dev' of https://github.com/logspace-ai/langflow into dev
This commit is contained in:
commit
76b7d70d48
15 changed files with 170 additions and 280 deletions
|
|
@ -8,7 +8,6 @@ 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 Sidebar from "./components/SidebarComponent";
|
|
||||||
import Header from "./components/HeaderComponent";
|
import Header from "./components/HeaderComponent";
|
||||||
import TabsManagerComponent from "./pages/FlowPage/components/tabsManagerComponent";
|
import TabsManagerComponent from "./pages/FlowPage/components/tabsManagerComponent";
|
||||||
|
|
||||||
|
|
@ -36,38 +35,45 @@ export default function App() {
|
||||||
setSuccessOpen,
|
setSuccessOpen,
|
||||||
} = useContext(alertContext);
|
} = useContext(alertContext);
|
||||||
|
|
||||||
const [alertsList, setAlertsList] = useState<Array<{type:string,data:{title:string,list?:Array<string>,link?:string},id:string}>>([]);
|
// 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}>>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
// Use effect hook to update alertsList when a new alert is added
|
||||||
if (errorOpen && errorData) {
|
useEffect(() => {
|
||||||
setErrorOpen(false);
|
// If there is an error alert open with data, add it to the alertsList
|
||||||
setAlertsList((old) => {
|
if (errorOpen && errorData) {
|
||||||
let newAlertsList = [
|
setErrorOpen(false);
|
||||||
...old,
|
setAlertsList((old) => {
|
||||||
{ type: "error", data: _.cloneDeep(errorData), id: _.uniqueId() },
|
let newAlertsList = [
|
||||||
];
|
...old,
|
||||||
return newAlertsList;
|
{ type: "error", data: _.cloneDeep(errorData), id: _.uniqueId() },
|
||||||
});
|
];
|
||||||
} else if (noticeOpen && noticeData) {
|
return newAlertsList;
|
||||||
setNoticeOpen(false);
|
});
|
||||||
setAlertsList((old) => {
|
}
|
||||||
let newAlertsList = [
|
// If there is a notice alert open with data, add it to the alertsList
|
||||||
...old,
|
else if (noticeOpen && noticeData) {
|
||||||
{ type: "notice", data: _.cloneDeep(noticeData), id: _.uniqueId() },
|
setNoticeOpen(false);
|
||||||
];
|
setAlertsList((old) => {
|
||||||
return newAlertsList;
|
let newAlertsList = [
|
||||||
});
|
...old,
|
||||||
} else if (successOpen && successData) {
|
{ type: "notice", data: _.cloneDeep(noticeData), id: _.uniqueId() },
|
||||||
setSuccessOpen(false);
|
];
|
||||||
setAlertsList((old) => {
|
return newAlertsList;
|
||||||
let newAlertsList = [
|
});
|
||||||
...old,
|
}
|
||||||
{ type: "success", data: _.cloneDeep(successData), id: _.uniqueId() },
|
// If there is a success alert open with data, add it to the alertsList
|
||||||
];
|
else if (successOpen && successData) {
|
||||||
return newAlertsList;
|
setSuccessOpen(false);
|
||||||
});
|
setAlertsList((old) => {
|
||||||
}
|
let newAlertsList = [
|
||||||
}, [_, errorData, errorOpen, noticeData, noticeOpen, setErrorOpen, setNoticeOpen, setSuccessOpen, successData, successOpen]);
|
...old,
|
||||||
|
{ type: "success", data: _.cloneDeep(successData), id: _.uniqueId() },
|
||||||
|
];
|
||||||
|
return newAlertsList;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [_, errorData, errorOpen, noticeData, noticeOpen, setErrorOpen, setNoticeOpen, setSuccessOpen, successData, successOpen]);
|
||||||
|
|
||||||
const removeAlert = (id: string) => {
|
const removeAlert = (id: string) => {
|
||||||
setAlertsList((prevAlertsList) =>
|
setAlertsList((prevAlertsList) =>
|
||||||
|
|
@ -82,9 +88,7 @@ export default function App() {
|
||||||
<Header></Header>
|
<Header></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 />
|
|
||||||
<ExtraSidebar />
|
<ExtraSidebar />
|
||||||
|
|
||||||
{/* Main area */}
|
{/* Main area */}
|
||||||
<main className="min-w-0 flex-1 border-t border-gray-200 dark:border-gray-700 flex">
|
<main className="min-w-0 flex-1 border-t border-gray-200 dark:border-gray-700 flex">
|
||||||
{/* Primary column */}
|
{/* Primary column */}
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,6 @@ export default function ExtraSidebar() {
|
||||||
<span className="text-gray-900 dark:text-white text-lg ml-2 font-semibold">
|
<span className="text-gray-900 dark:text-white text-lg ml-2 font-semibold">
|
||||||
{extraNavigation.title}
|
{extraNavigation.title}
|
||||||
</span>
|
</span>
|
||||||
<button
|
|
||||||
className="text-gray-400 flex-shrink-0 inline-flex items-center justify-center rounded-lg"
|
|
||||||
onClick={() => setIsStackedOpen(false)}
|
|
||||||
>
|
|
||||||
<ChevronLeftIcon className="h-6 w-6"></ChevronLeftIcon>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow flex-col w-full">
|
<div className="flex flex-grow flex-col w-full">
|
||||||
{extraNavigation.options ? (
|
{extraNavigation.options ? (
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import {
|
||||||
MoonIcon,
|
MoonIcon,
|
||||||
SunIcon,
|
SunIcon,
|
||||||
} from '@heroicons/react/24/outline'
|
} from '@heroicons/react/24/outline'
|
||||||
import Breadcrumb from '../breadcrumbComponent'
|
|
||||||
import { alertContext } from '../../contexts/alertContext'
|
import { alertContext } from '../../contexts/alertContext'
|
||||||
import { useLayer } from 'react-laag'
|
import { useLayer } from 'react-laag'
|
||||||
import AlertDropdown from '../../alerts/alertDropDown'
|
import AlertDropdown from '../../alerts/alertDropDown'
|
||||||
|
|
@ -25,25 +24,8 @@ export default function Header(){
|
||||||
const {dark, setDark} = useContext(darkContext);
|
const {dark, setDark} = useContext(darkContext);
|
||||||
return (
|
return (
|
||||||
<header className="relative flex h-16 w-full shrink-0 items-center bg-white dark:bg-gray-800">
|
<header className="relative flex h-16 w-full shrink-0 items-center bg-white dark:bg-gray-800">
|
||||||
{/* Logo area */}
|
|
||||||
<div className="static shrink-0">
|
|
||||||
<a
|
|
||||||
href="/"
|
|
||||||
className="flex h-16 items-center justify-center bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-600 w-20"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className="h-8 w-auto"
|
|
||||||
src="https://tailwindui.com/img/logos/mark.svg?color=white"
|
|
||||||
alt="Your Company"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Desktop nav area */}
|
{/* Desktop nav area */}
|
||||||
<div className="flex min-w-0 flex-1 items-center justify-between">
|
<div className="flex min-w-0 flex-1 flex-row-reverse items-center justify-between">
|
||||||
<div className="min-w-0 flex-1 ml-5">
|
|
||||||
<Breadcrumb />
|
|
||||||
</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 gap-6">
|
<span className="inline-flex gap-6">
|
||||||
|
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
import SidebarButton from "./sidebarButton";
|
|
||||||
import { BsPlusSquare } from "react-icons/bs";
|
|
||||||
import { classNames } from "../../utils";
|
|
||||||
import { ChevronRightIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useContext } from "react";
|
|
||||||
import { sidebarNavigation } from "../../entities/sidebarNav";
|
|
||||||
import { locationContext } from "../../contexts/locationContext";
|
|
||||||
|
|
||||||
export default function Sidebar() {
|
|
||||||
let { showSideBar, isStackedOpen, setIsStackedOpen } =
|
|
||||||
useContext(locationContext);
|
|
||||||
let current = false;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
(showSideBar ? "w-20" : "w-0") +
|
|
||||||
" h-full overflow-hidden flex-col transition-all duration-500"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="w-20 h-full">
|
|
||||||
<nav
|
|
||||||
aria-label="Sidebar"
|
|
||||||
className="h-full overflow-y-auto bg-gray-800 dark:bg-gray-900"
|
|
||||||
>
|
|
||||||
<div className="flex flex-col h-full justify-between">
|
|
||||||
<div className="relative flex w-20 flex-col space-y-3 p-3">
|
|
||||||
{sidebarNavigation.map((item, index) => (
|
|
||||||
<SidebarButton item={item} key={index}></SidebarButton>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="relative flex w-20 flex-col items-center space-y-3 align-items: center;">
|
|
||||||
<button
|
|
||||||
key="New Project"
|
|
||||||
onClick={() => {
|
|
||||||
}}
|
|
||||||
className={classNames(
|
|
||||||
current
|
|
||||||
? "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"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="sr-only">"New Project"</span>
|
|
||||||
<BsPlusSquare className="h-8 w-8" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
className={` ${
|
|
||||||
isStackedOpen ? "h-0" : "h-12"
|
|
||||||
} overflow-hidden transition-all duration-500`}
|
|
||||||
>
|
|
||||||
<div className="h-10">
|
|
||||||
<button
|
|
||||||
className="text-gray-400 flex-shrink-0 inline-flex items-center justify-center rounded-lg"
|
|
||||||
onClick={() => setIsStackedOpen(true)}
|
|
||||||
>
|
|
||||||
<ChevronRightIcon className="h-6 w-6"></ChevronRightIcon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import { classNames } from "../../../utils"
|
|
||||||
import { Link } from "react-router-dom"
|
|
||||||
import { useContext } from "react"
|
|
||||||
import { locationContext } from "../../../contexts/locationContext";
|
|
||||||
import { sidebarNavigationItemType } from "../../../types/entities";
|
|
||||||
|
|
||||||
export default function SidebarButton({item}:{item:sidebarNavigationItemType}){
|
|
||||||
let {current}= useContext(locationContext);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Link
|
|
||||||
key={item.name}
|
|
||||||
to={item.href}
|
|
||||||
className={classNames(
|
|
||||||
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'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="sr-only">{item.name}</span>
|
|
||||||
<item.icon className="h-6 w-6" aria-hidden="true" />
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
import { ChevronRightIcon, HomeIcon } from '@heroicons/react/20/solid'
|
|
||||||
import { useContext } from 'react';
|
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import { locationContext } from '../../contexts/locationContext';
|
|
||||||
|
|
||||||
const breadcrumbNameMap: { [key: string]: string } = {
|
|
||||||
'/settings': 'Settings',
|
|
||||||
'/settings/general': 'General',
|
|
||||||
'/settings/general/main': 'Main',
|
|
||||||
'/table': 'Table',
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function getPages(atual:string[]){
|
|
||||||
let pages = [];
|
|
||||||
for(let i = 2; i <= atual.length; i++){
|
|
||||||
let to = '/' + atual.slice(1,i).join('/');
|
|
||||||
pages.push({ name: breadcrumbNameMap[to], href: to })
|
|
||||||
}
|
|
||||||
return (pages);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Breadcrumb(){
|
|
||||||
let {current} = useContext(locationContext);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<nav className="flex ml-2" aria-label="Breadcrumb">
|
|
||||||
<ol className="flex items-center space-x-4">
|
|
||||||
<li>
|
|
||||||
<div>
|
|
||||||
<Link to='/' className="text-gray-400 hover:text-gray-500">
|
|
||||||
<HomeIcon className="h-5 w-5 flex-shrink-0" aria-hidden="true" />
|
|
||||||
<span className="sr-only">Home</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{getPages(current).map((page) => (
|
|
||||||
<li key={page.href}>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<ChevronRightIcon className="h-5 w-5 flex-shrink-0 text-gray-400" aria-hidden="true" />
|
|
||||||
<a
|
|
||||||
href={page.href}
|
|
||||||
className="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700"
|
|
||||||
>
|
|
||||||
{page.name}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -105,14 +105,14 @@ export default function Chat({flow, reactFlowInstance }:ChatType) {
|
||||||
{chatHistory.map((c, i) => (
|
{chatHistory.map((c, i) => (
|
||||||
<div key={i}>
|
<div key={i}>
|
||||||
{!c.isSend ? (
|
{!c.isSend ? (
|
||||||
<div className="w-full text-end">
|
<div className="w-full text-start">
|
||||||
<div style={{backgroundColor: nodeColors['chat']}} className="text-start inline-block text-white rounded-xl p-3 overflow-hidden w-fit max-w-[280px] px-5 text-sm font-normal rounded-tr-none">
|
<div style={{backgroundColor: nodeColors['chat']}} className="text-start inline-block text-white rounded-xl p-3 overflow-hidden w-fit max-w-[280px] px-5 text-sm font-normal rounded-tl-none">
|
||||||
{c.message}
|
{c.message}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full text-start">
|
<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-tl-none">
|
<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">
|
||||||
{c.message}
|
{c.message}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -73,52 +73,68 @@ export function AlertProvider({ children }:{children:ReactNode}) {
|
||||||
return newNotificationList;
|
return newNotificationList;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
function setErrorData(newState: { title: string; list?: Array<string> }) {
|
/**
|
||||||
setErrorDataState(newState);
|
* Sets the error data state, opens the error dialog and pushes the new error notification to the notification list
|
||||||
setErrorOpen(true);
|
* @param newState An object containing the new error data, including title and optional list of error messages
|
||||||
if (newState.title) {
|
*/
|
||||||
setNotificationCenter(true);
|
function setErrorData(newState: { title: string; list?: Array<string> }) {
|
||||||
pushNotificationList({
|
setErrorDataState(newState);
|
||||||
type: "error",
|
setErrorOpen(true);
|
||||||
title: newState.title,
|
if (newState.title) {
|
||||||
list: newState.list,
|
setNotificationCenter(true);
|
||||||
id: _.uniqueId(),
|
pushNotificationList({
|
||||||
});
|
type: "error",
|
||||||
}
|
title: newState.title,
|
||||||
|
list: newState.list,
|
||||||
|
id: _.uniqueId(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function setNoticeData(newState: { title: string; link?: string }) {
|
}
|
||||||
setNoticeDataState(newState);
|
/**
|
||||||
setNoticeOpen(true);
|
* 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.
|
||||||
if (newState.title) {
|
* @param newState An object containing the title of the notice and optionally a link.
|
||||||
setNotificationCenter(true);
|
*/
|
||||||
pushNotificationList({
|
function setNoticeData(newState: { title: string; link?: string }) {
|
||||||
type: "notice",
|
setNoticeDataState(newState);
|
||||||
title: newState.title,
|
setNoticeOpen(true);
|
||||||
link: newState.link,
|
if (newState.title) {
|
||||||
id: _.uniqueId(),
|
// Add new notice to notification center
|
||||||
});
|
setNotificationCenter(true);
|
||||||
}
|
pushNotificationList({
|
||||||
|
type: "notice",
|
||||||
|
title: newState.title,
|
||||||
|
link: newState.link,
|
||||||
|
id: _.uniqueId(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function setSuccessData(newState: { title: string }) {
|
}
|
||||||
setSuccessDataState(newState);
|
/**
|
||||||
setSuccessOpen(true);
|
* Update the success data state and show a success alert notification.
|
||||||
if (newState.title) {
|
* @param newState - A state object with a "title" property to set in the success data state.
|
||||||
setNotificationCenter(true);
|
*/
|
||||||
pushNotificationList({
|
function setSuccessData(newState: { title: string }) {
|
||||||
type: "success",
|
setSuccessDataState(newState); // update the success data state with the provided new state
|
||||||
title: newState.title,
|
setSuccessOpen(true); // open the success alert
|
||||||
id: _.uniqueId(),
|
|
||||||
});
|
// 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() {
|
function clearNotificationList() {
|
||||||
setNotificationList([]);
|
setNotificationList([]);
|
||||||
}
|
}
|
||||||
function removeFromNotificationList(index: string) {
|
function removeFromNotificationList(index: string) {
|
||||||
|
// set the notification list to a new array that filters out the alert with the matching id
|
||||||
setNotificationList((prevAlertsList) =>
|
setNotificationList((prevAlertsList) =>
|
||||||
prevAlertsList.filter((alert) => alert.id !== index)
|
prevAlertsList.filter((alert) => alert.id !== index)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<alertContext.Provider
|
<alertContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import { createContext, useEffect, useState, useRef, ReactNode } from "react";
|
import { createContext, useEffect, useState, useRef, ReactNode } from "react";
|
||||||
import {FlowType} from "../types/flow"
|
import { FlowType } from "../types/flow";
|
||||||
import { TabsContextType } from "../types/tabs";
|
import { TabsContextType } from "../types/tabs";
|
||||||
|
|
||||||
|
const TabsContextInitialValue: TabsContextType = {
|
||||||
|
|
||||||
const TabsContextInitialValue:TabsContextType = {
|
|
||||||
tabIndex: 0,
|
tabIndex: 0,
|
||||||
setTabIndex: (index: number) => {},
|
setTabIndex: (index: number) => {},
|
||||||
flows: [],
|
flows: [],
|
||||||
|
|
@ -20,7 +18,7 @@ export const TabsContext = createContext<TabsContextType>(
|
||||||
TabsContextInitialValue
|
TabsContextInitialValue
|
||||||
);
|
);
|
||||||
|
|
||||||
export function TabsProvider({ children }:{children:ReactNode}) {
|
export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
const [tabIndex, setTabIndex] = useState(0);
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||||
const [id, setId] = useState(0);
|
const [id, setId] = useState(0);
|
||||||
|
|
@ -31,6 +29,7 @@ export function TabsProvider({ children }:{children:ReactNode}) {
|
||||||
return newNodeId.current;
|
return newNodeId.current;
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
//save tabs locally
|
||||||
if (flows.length !== 0)
|
if (flows.length !== 0)
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
"tabsData",
|
"tabsData",
|
||||||
|
|
@ -39,6 +38,7 @@ export function TabsProvider({ children }:{children:ReactNode}) {
|
||||||
}, [flows, id, tabIndex, newNodeId]);
|
}, [flows, id, tabIndex, newNodeId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
//get tabs locally saved
|
||||||
let cookie = window.localStorage.getItem("tabsData");
|
let cookie = window.localStorage.getItem("tabsData");
|
||||||
if (cookie) {
|
if (cookie) {
|
||||||
let cookieObject = JSON.parse(cookie);
|
let cookieObject = JSON.parse(cookie);
|
||||||
|
|
@ -49,27 +49,47 @@ export function TabsProvider({ children }:{children:ReactNode}) {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads the current flow as a JSON file
|
||||||
|
*/
|
||||||
function downloadFlow() {
|
function downloadFlow() {
|
||||||
|
// create a data URI with the current flow data
|
||||||
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
||||||
JSON.stringify(flows[tabIndex])
|
JSON.stringify(flows[tabIndex])
|
||||||
)}`;
|
)}`;
|
||||||
|
|
||||||
|
// create a link element and set its properties
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = jsonString;
|
link.href = jsonString;
|
||||||
link.download = `${flows[tabIndex].name}.json`;
|
link.download = `${flows[tabIndex].name}.json`;
|
||||||
|
|
||||||
|
// simulate a click on the link element to trigger the download
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
function uploadFlow() {
|
||||||
|
// create a file input
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
|
// add a change event listener to the file input
|
||||||
input.onchange = (e: Event) => {
|
input.onchange = (e: Event) => {
|
||||||
|
// check if the file type is application/json
|
||||||
if ((e.target as HTMLInputElement).files[0].type === "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];
|
const file = (e.target as HTMLInputElement).files[0];
|
||||||
|
// read the file as text
|
||||||
file.text().then((text) => {
|
file.text().then((text) => {
|
||||||
|
// parse the text into a JSON object
|
||||||
addFlow(JSON.parse(text));
|
addFlow(JSON.parse(text));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// trigger the file input click event to open the file dialog
|
||||||
input.click();
|
input.click();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
@ -94,21 +114,38 @@ export function TabsProvider({ children }:{children:ReactNode}) {
|
||||||
return newFlows;
|
return newFlows;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Add a new flow to the list of flows.
|
||||||
|
* @param flow Optional flow to add.
|
||||||
|
*/
|
||||||
function addFlow(flow?: FlowType) {
|
function addFlow(flow?: FlowType) {
|
||||||
|
// Get data from the flow or set it to null if there's no flow provided.
|
||||||
const data = flow?.data ? flow.data : null;
|
const data = flow?.data ? flow.data : null;
|
||||||
|
|
||||||
|
// Create a new flow with a default name if no flow is provided.
|
||||||
let newFlow: FlowType = {
|
let newFlow: FlowType = {
|
||||||
name: flow ? flow.name : "flow" + id,
|
name: flow ? flow.name : "flow" + id,
|
||||||
id: id.toString(),
|
id: id.toString(),
|
||||||
data,
|
data,
|
||||||
chat: flow ? flow.chat : [],
|
chat: flow ? flow.chat : [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Increment the ID counter.
|
||||||
setId((old) => old + 1);
|
setId((old) => old + 1);
|
||||||
|
|
||||||
|
// Add the new flow to the list of flows.
|
||||||
setFlows((prevState) => {
|
setFlows((prevState) => {
|
||||||
const newFlows = [...prevState, newFlow];
|
const newFlows = [...prevState, newFlow];
|
||||||
return newFlows;
|
return newFlows;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set the tab index to the new flow.
|
||||||
setTabIndex(flows.length);
|
setTabIndex(flows.length);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Updates an existing flow with new data
|
||||||
|
* @param newFlow - The new flow object containing the updated data
|
||||||
|
*/
|
||||||
function updateFlow(newFlow: FlowType) {
|
function updateFlow(newFlow: FlowType) {
|
||||||
setFlows((prevState) => {
|
setFlows((prevState) => {
|
||||||
const newFlows = [...prevState];
|
const newFlows = [...prevState];
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
export enum apiEnum
|
|
||||||
{
|
|
||||||
PromptTemplate="PromptTemplate"
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import {
|
|
||||||
HomeIcon,
|
|
||||||
} from '@heroicons/react/24/outline'
|
|
||||||
|
|
||||||
|
|
||||||
export const sidebarNavigation = [
|
|
||||||
{ name: 'Home', href: '/', icon: HomeIcon, current: true },
|
|
||||||
// { name: 'Table', href: '/table/', icon: TableCellsIcon, current: false },
|
|
||||||
// //{ name: 'Train', href: '#', icon: BoltIcon, current: false },
|
|
||||||
// //{ name: 'Model Details', href: '#', icon: LightBulbIcon, current: false },
|
|
||||||
// //{ name: 'Deploy', href: '#', icon: RocketLaunchIcon, current: false },
|
|
||||||
// { name: 'Settings', href: '/settings/', icon: Cog6ToothIcon, current: false },
|
|
||||||
]
|
|
||||||
|
|
@ -16,19 +16,26 @@ export default function ExtraSidebar() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getTypes():Promise<void>{
|
async function getTypes():Promise<void>{
|
||||||
|
// Define an object with initial values for the types.
|
||||||
const initialValue:{[char: string]: string} = {
|
const initialValue:{[char: string]: string} = {
|
||||||
str: "advanced",
|
str: "advanced",
|
||||||
bool: "advanced",
|
bool: "advanced",
|
||||||
chatOutput: "chat",
|
chatOutput: "chat",
|
||||||
chatInput: "chat",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make an asynchronous API call to retrieve all data.
|
||||||
let result = await getAll();
|
let result = await getAll();
|
||||||
|
|
||||||
|
// Update the state of the component with the retrieved data.
|
||||||
setData(result.data);
|
setData(result.data);
|
||||||
|
|
||||||
|
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
||||||
setTypes(
|
setTypes(
|
||||||
Object.keys(result.data).reduce(
|
Object.keys(result.data).reduce(
|
||||||
(acc, curr) => {
|
(acc, curr) => {
|
||||||
Object.keys(result.data[curr]).forEach((c:keyof APIKindType) => {
|
Object.keys(result.data[curr]).forEach((c:keyof APIKindType) => {
|
||||||
acc[c] = curr;
|
acc[c] = curr;
|
||||||
|
// Add the base classes to the accumulator as well.
|
||||||
result.data[curr][c].base_classes?.forEach((b) => {
|
result.data[curr][c].base_classes?.forEach((b) => {
|
||||||
acc[b] = curr;
|
acc[b] = curr;
|
||||||
});
|
});
|
||||||
|
|
@ -39,11 +46,13 @@ export default function ExtraSidebar() {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Call the getTypes function.
|
||||||
getTypes();
|
getTypes();
|
||||||
}, [setTypes]);
|
}, [setTypes]);
|
||||||
|
|
||||||
|
|
||||||
function onDragStart(event: React.DragEvent<any>, data:{type:string,node?:APIClassType}) {
|
function onDragStart(event: React.DragEvent<any>, data:{type:string,node?:APIClassType}) {
|
||||||
|
//start drag event
|
||||||
event.dataTransfer.effectAllowed = "move";
|
event.dataTransfer.effectAllowed = "move";
|
||||||
event.dataTransfer.setData("json", JSON.stringify(data));
|
event.dataTransfer.setData("json", JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,6 @@ export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,
|
||||||
>
|
>
|
||||||
{flow.name}
|
{flow.name}
|
||||||
</span>
|
</span>
|
||||||
{/* <ArrowDownTrayIcon
|
|
||||||
onClick={() => downloadFlow()}
|
|
||||||
className="w-4 h-4 hover:text-blue-500 cursor-pointer"
|
|
||||||
/> */}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import FlowPage from "../..";
|
||||||
export default function TabsManagerComponent() {
|
export default function TabsManagerComponent() {
|
||||||
const { flows, addFlow, tabIndex, setTabIndex } = useContext(TabsContext);
|
const { flows, addFlow, tabIndex, setTabIndex } = useContext(TabsContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
//create the first flow
|
||||||
if (flows.length === 0) {
|
if (flows.length === 0) {
|
||||||
addFlow();
|
addFlow();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
|
|
||||||
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 ?? []
|
||||||
);
|
);
|
||||||
|
|
@ -64,19 +63,18 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [nodes, edges]);
|
}, [nodes, edges]);
|
||||||
|
//update flow when tabs change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('oi')
|
|
||||||
setNodes(flow?.data?.nodes ?? []);
|
setNodes(flow?.data?.nodes ?? []);
|
||||||
setEdges(flow?.data?.edges ?? []);
|
setEdges(flow?.data?.edges ?? []);
|
||||||
if (reactFlowInstance) {
|
if (reactFlowInstance) {
|
||||||
setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 1 });
|
setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 1 });
|
||||||
}
|
}
|
||||||
}, [flow, reactFlowInstance, setEdges, setNodes, setViewport]);
|
}, [flow, reactFlowInstance, setEdges, setNodes, setViewport]);
|
||||||
|
//set extra sidebar
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setExtraComponent(<ExtraSidebar />);
|
setExtraComponent(<ExtraSidebar />);
|
||||||
setExtraNavigation({ title: "Nodes" });
|
setExtraNavigation({ title: "Componets" });
|
||||||
}, [setExtraComponent, setExtraNavigation]);
|
}, [setExtraComponent, setExtraNavigation]);
|
||||||
|
|
||||||
const onEdgesChangeMod = useCallback(
|
const onEdgesChangeMod = useCallback(
|
||||||
|
|
@ -111,24 +109,34 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
(event:React.DragEvent) => {
|
(event:React.DragEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Helper function to generate a unique node ID
|
||||||
function getId() {
|
function getId() {
|
||||||
return `dndnode_` + incrementNodeId();
|
return `dndnode_` + incrementNodeId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the current bounds of the ReactFlow wrapper element
|
||||||
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
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"));
|
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 (
|
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"))
|
||||||
) {
|
) {
|
||||||
|
// Calculate the position where the node should be created
|
||||||
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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Generate a unique node ID
|
||||||
let newId = getId();
|
let newId = getId();
|
||||||
|
|
||||||
|
// Create a new node object
|
||||||
const newNode:NodeType = {
|
const newNode:NodeType = {
|
||||||
id: newId,
|
id: newId,
|
||||||
type:
|
type:
|
||||||
|
|
@ -146,16 +154,21 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
value: null,
|
value: null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add the new node to the list of nodes in state
|
||||||
setNodes((nds) => nds.concat(newNode));
|
setNodes((nds) => nds.concat(newNode));
|
||||||
} else {
|
} else {
|
||||||
|
// If a chat input node already exists, set an error message
|
||||||
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."],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// Specify dependencies for useCallback
|
||||||
[incrementNodeId, reactFlowInstance, setErrorData, setNodes]
|
[incrementNodeId, reactFlowInstance, setErrorData, setNodes]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full" ref={reactFlowWrapper}>
|
<div className="w-full h-full" ref={reactFlowWrapper}>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue