Merge branch 'tabs' into dev

This commit is contained in:
Lucas Oliveira 2023-02-23 14:43:51 -03:00
commit 62d75a0446
16 changed files with 1025 additions and 101 deletions

View file

@ -23,11 +23,13 @@
"@types/react-dom": "^18.0.10",
"axios": "^1.3.2",
"react": "^18.2.0",
"react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
"react-icons": "^4.7.1",
"react-laag": "^2.0.5",
"react-router-dom": "^6.8.1",
"react-scripts": "5.0.1",
"react-tabs": "^6.0.0",
"reactflow": "^11.5.5",
"tailwindcss": "^3.2.6",
"typescript": "^4.9.5",
@ -4211,6 +4213,11 @@
"@types/node": "*"
}
},
"node_modules/@types/cookie": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
"integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
},
"node_modules/@types/d3": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz",
@ -4490,6 +4497,15 @@
"@types/node": "*"
}
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@ -14801,6 +14817,19 @@
"node": ">=14"
}
},
"node_modules/react-cookie": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz",
"integrity": "sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==",
"dependencies": {
"@types/hoist-non-react-statics": "^3.0.1",
"hoist-non-react-statics": "^3.0.0",
"universal-cookie": "^4.0.0"
},
"peerDependencies": {
"react": ">= 16.3.0"
}
},
"node_modules/react-dev-utils": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
@ -15070,6 +15099,18 @@
}
}
},
"node_modules/react-tabs": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.0.tgz",
"integrity": "sha512-8jKLKrlwxmn5/+xsa76yi27ZdB8E/WhlhQZw739O5UlOeUGtVoVeWnpqIewv/KbjTw7gQf/uA51zWUNt4IVygQ==",
"dependencies": {
"clsx": "^1.1.0",
"prop-types": "^15.5.0"
},
"peerDependencies": {
"react": "^18.0.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@ -16819,6 +16860,23 @@
"node": ">=8"
}
},
"node_modules/universal-cookie": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz",
"integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==",
"dependencies": {
"@types/cookie": "^0.3.3",
"cookie": "^0.4.0"
}
},
"node_modules/universal-cookie/node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",

View file

@ -18,11 +18,13 @@
"@types/react-dom": "^18.0.10",
"axios": "^1.3.2",
"react": "^18.2.0",
"react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
"react-icons": "^4.7.1",
"react-laag": "^2.0.5",
"react-router-dom": "^6.8.1",
"react-scripts": "5.0.1",
"react-tabs": "^6.0.0",
"reactflow": "^11.5.5",
"tailwindcss": "^3.2.6",
"typescript": "^4.9.5",

View file

@ -12,6 +12,8 @@ import { locationContext } from "./contexts/locationContext";
import FlowPage from "./pages/FlowPage";
import Sidebar from "./components/SidebarComponent";
import Header from "./components/HeaderComponent";
import { TabsProvider } from "./contexts/tabsContext";
import { TabsManager } from "./pages/FlowPage/flowManager";
export default function App() {
var _ = require("lodash");
@ -96,7 +98,7 @@ export default function App() {
return (
//need parent component with width and height
<div className="h-full flex flex-col">
<ReactFlowProvider>
<div className="flex grow-0 shrink basis-auto">
<Header userNavigation={userNavigation} user={user}></Header>
</div>
@ -108,7 +110,7 @@ export default function App() {
<main className="min-w-0 flex-1 border-t border-gray-200 flex">
{/* Primary column */}
<div className="w-full h-full">
<FlowPage />
<TabsManager></TabsManager>
</div>
</main>
</div>
@ -142,7 +144,6 @@ export default function App() {
</div>
))}
</div>
</ReactFlowProvider>
</div>
);
}

View file

@ -8,7 +8,7 @@ import { typesContext } from "../../contexts/typesContext";
export default function BooleanNode({ data }) {
const [enabled, setEnabled] = useState(false);
const {types} = useContext(typesContext);
const {types, deleteNode} = useContext(typesContext);
return (
<div className="prompt-node relative bg-white rounded-lg solid border flex flex-col justify-center">
<div className="w-full flex items-center justify-between gap-8 p-4 bg-gray-50 border-b ">
@ -21,7 +21,7 @@ export default function BooleanNode({ data }) {
</div>
<button
onClick={() => {
data.onDelete(data);
deleteNode(data.id);
}}
>
<TrashIcon className="text-gray-600 w-6 h-6 hover:text-red-500"></TrashIcon>

View file

@ -5,11 +5,12 @@ import {
nodeColors,
snakeToNormalCase,
} from "../../../../utils";
import { useEffect, useRef, useState } from "react";
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";
export default function ParameterComponent({
left,
@ -38,7 +39,8 @@ export default function ParameterComponent({
}, [data.id, position, updateNodeInternals]);
const [enabled, setEnabled] = useState(data.node.template[name]?.value ?? false);
let disabled = data.reactFlowInstance.getEdges().some((e) => (e.targetHandle === id));
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 mt-1 px-5 py-2">
@ -50,7 +52,7 @@ export default function ParameterComponent({
position={left ? Position.Left : Position.Right}
id={id}
isValidConnection={(connection) =>
isValidConnection(data, connection)
isValidConnection(connection,reactFlowInstance)
}
className={
(left ? "-ml-0.5 " : "-mr-0.5 ") +

View file

@ -11,9 +11,10 @@ import { typesContext } from "../../contexts/typesContext";
import { useContext } from "react";
export default function GenericNode({ data }) {
const {types} = useContext(typesContext);
const {types, deleteNode} = useContext(typesContext);
const Icon = nodeIcons[types[data.type]];
return (
<div className="prompt-node relative bg-white w-96 rounded-lg solid border flex flex-col justify-center">
<div className="w-full flex items-center justify-between p-4 gap-8 bg-gray-50 border-b ">
@ -24,7 +25,7 @@ export default function GenericNode({ data }) {
/>
<div className="truncate">{data.type}</div>
</div>
<button onClick={data.onDelete}>
<button onClick={() => {deleteNode(data.id)}}>
<TrashIcon className="w-6 h-6 hover:text-red-500"></TrashIcon>
</button>
</div>

View file

@ -13,7 +13,7 @@ import { typesContext } from "../../contexts/typesContext";
import TextAreaComponent from "../../components/textAreaComponent";
export default function InputNode({ data }) {
const { types } = useContext(typesContext);
const {types, deleteNode} = useContext(typesContext);
return (
<div className="prompt-node relative bg-white w-96 rounded-lg solid border flex flex-col justify-center">
<Tooltip title="Prefix: str">
@ -39,7 +39,7 @@ export default function InputNode({ data }) {
</div>
<button
onClick={() => {
data.onDelete(data);
deleteNode(data.id)
}}
>
<TrashIcon className="text-gray-600 w-6 h-6 hover:text-red-500"></TrashIcon>

View file

@ -1,6 +1,7 @@
import { AlertProvider } from "./alertContext";
import { LocationProvider } from "./locationContext";
import PopUpProvider from "./popUpContext";
import { TabsProvider } from "./tabsContext";
import { TypesProvider } from "./typesContext";
export default function ContextWrapper({ children }) {
@ -9,7 +10,9 @@ export default function ContextWrapper({ children }) {
<LocationProvider>
<PopUpProvider>
<TypesProvider>
<AlertProvider>{children}</AlertProvider>
<TabsProvider>
<AlertProvider>{children}</AlertProvider>
</TabsProvider>
</TypesProvider>
</PopUpProvider>
</LocationProvider>

View file

@ -0,0 +1,95 @@
import { createContext, useEffect, useState } from "react";
type flow={name:string,id:string,data:any}
type TabsContextType={
tabIndex:number;
setTabIndex:(index:number)=>void;
flows:Array<flow>
removeFlow:(id:string)=>void;
addFlow:(flowData?:any)=>void;
updateFlow:(newFlow:flow)=>void;
nodeId:number;
}
const TabsContextInitialValue = {
tabIndex : 0,
setTabIndex:(index:number)=>{},
flows:[],
removeFlow:(id:string)=>{},
addFlow:(flowData?:any)=>{},
updateFlow:(newFlow:flow)=>{},
nodeId:0,
}
export const TabsContext = createContext<TabsContextType>(TabsContextInitialValue)
export function TabsProvider({children}){
const [tabIndex,setTabIndex] = useState(0)
const [flows,setFlows] = useState<Array<flow>>([])
const [id, setId] = useState(0);
let nodeId = 0;
useEffect(() => {
if(flows.length !== 0)
window.localStorage.setItem('tabsData', JSON.stringify({tabIndex, flows, id, nodeId}));
}, [flows, id, nodeId, tabIndex]);
useEffect(() => {
let cookie = window.localStorage.getItem('tabsData');
if(cookie){
let cookieObject = JSON.parse(cookie);
setTabIndex(cookieObject.tabIndex);
setFlows(cookieObject.flows)
setId(cookieObject.id)
nodeId = cookieObject.nodeId
}
}, [])
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;
});
}
function addFlow(flowData?:flow) {
const data = flowData?flowData:null
let newFlow: flow = {name: "flow"+id, id: id.toString(), data}
setId((old) => old+1);
setFlows(prevState => {
const newFlows = [...prevState, newFlow];
return newFlows;
});
setTabIndex(flows.length);
}
function updateFlow(newFlow:flow){
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
}
return newFlows;
});
}
return(
<TabsContext.Provider value={{tabIndex,setTabIndex,flows, nodeId,removeFlow,addFlow,updateFlow}}>
{children}
</TabsContext.Provider>
)
}

View file

@ -1,12 +1,20 @@
import { createContext, useState } from "react";
import { ReactEventHandler, createContext, useState } from "react";
import { ReactFlowInstance } from "reactflow";
type typesContextType=
{
reactFlowInstance: ReactFlowInstance;
setReactFlowInstance: any;
deleteNode:(idx:number)=>void;
types: {};
setTypes:(newState:{})=>void;
}
const initialValue= {
reactFlowInstance: null,
setReactFlowInstance: ()=>{},
deleteNode: ()=>{},
types: {},
setTypes:()=>{},
}
@ -15,8 +23,14 @@ export const typesContext = createContext<typesContextType>(initialValue);
export function TypesProvider({children}){
const [types, setTypes] = useState({});
const [reactFlowInstance, setReactFlowInstance] = useState(null);
function deleteNode(idx){
reactFlowInstance.setNodes(
reactFlowInstance.getNodes().filter((n) => n.id !== idx)
);
}
return (
<typesContext.Provider value={{ types, setTypes}}>
<typesContext.Provider value={{ types, setTypes, reactFlowInstance, setReactFlowInstance, deleteNode}}>
{children}
</typesContext.Provider>
)

View file

@ -0,0 +1,606 @@
export const example = {
"nodes": [
{
"width": 384,
"height": 271,
"id": "dndnode_}5",
"type": "genericNode",
"position": {
"x": -640.9237482084102,
"y": 117.60473769101873
},
"data": {
"type": "ConversationBufferMemory",
"node": {
"template": {
"_type": "conversation_buffer",
"human_prefix": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": "Human"
},
"ai_prefix": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": "AI"
},
"buffer": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": ""
},
"output_key": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": null
},
"input_key": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": null
},
"memory_key": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": "history"
}
},
"description": "Buffer for storing conversation memory.",
"base_classes": [
"Memory"
]
},
"id": "dndnode_}5",
"value": null
},
"selected": false,
"positionAbsolute": {
"x": -640.9237482084102,
"y": 117.60473769101873
},
"dragging": false
},
{
"width": 384,
"height": 447,
"id": "dndnode_}7",
"type": "genericNode",
"position": {
"x": -86,
"y": 522
},
"data": {
"type": "LLMChain",
"node": {
"template": {
"_type": "llm_chain",
"memory": {
"type": "Memory",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multline": false,
"value": null
},
"verbose": {
"type": "bool",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multline": false,
"value": false
},
"prompt": {
"type": "BasePromptTemplate",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multline": false
},
"llm": {
"type": "BaseLLM",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multline": false
},
"output_key": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": "text"
}
},
"description": "Chain to run queries against LLMs.",
"base_classes": [
"Chain"
]
},
"id": "dndnode_}7",
"value": null
},
"selected": false,
"positionAbsolute": {
"x": -86,
"y": 522
},
"dragging": false
},
{
"width": 384,
"height": 357,
"id": "dndnode_}8",
"type": "genericNode",
"position": {
"x": -633.4,
"y": 230
},
"data": {
"type": "PromptTemplate",
"node": {
"template": {
"_type": "prompt",
"input_variables": {
"type": "str",
"required": true,
"placeholder": "",
"list": true,
"show": false,
"multline": false
},
"output_parser": {
"type": "BaseOutputParser",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": null
},
"template": {
"type": "str",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multline": true,
"value": "aaaaa"
},
"template_format": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": "f-string"
},
"validate_template": {
"type": "bool",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": true
}
},
"description": "Schema to represent a prompt for an LLM.",
"base_classes": [
"BasePromptTemplate"
]
},
"id": "dndnode_}8",
"value": null
},
"selected": false,
"positionAbsolute": {
"x": -633.4,
"y": 230
},
"dragging": false
},
{
"width": 384,
"height": 453,
"id": "dndnode_}9",
"type": "genericNode",
"position": {
"x": -655.1999999999999,
"y": 615
},
"data": {
"type": "AI21",
"node": {
"template": {
"_type": "ai21",
"cache": {
"type": "bool",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": null
},
"verbose": {
"type": "bool",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multline": false,
"value": false
},
"model": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": "j1-jumbo"
},
"temperature": {
"type": "float",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": 0.7
},
"maxTokens": {
"type": "int",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": 256
},
"minTokens": {
"type": "int",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": 0
},
"topP": {
"type": "float",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": 1
},
"presencePenalty": {
"type": "AI21PenaltyData",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": {
"scale": 0,
"applyToWhitespaces": true,
"applyToPunctuations": true,
"applyToNumbers": true,
"applyToStopwords": true,
"applyToEmojis": true
}
},
"countPenalty": {
"type": "AI21PenaltyData",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": {
"scale": 0,
"applyToWhitespaces": true,
"applyToPunctuations": true,
"applyToNumbers": true,
"applyToStopwords": true,
"applyToEmojis": true
}
},
"frequencyPenalty": {
"type": "AI21PenaltyData",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": {
"scale": 0,
"applyToWhitespaces": true,
"applyToPunctuations": true,
"applyToNumbers": true,
"applyToStopwords": true,
"applyToEmojis": true
}
},
"numResults": {
"type": "int",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": 1
},
"logitBias": {
"type": "dict[str, float]",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": null
},
"ai21_api_key": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multline": false,
"value": "aaaa"
},
"base_url": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": false,
"multline": false,
"value": null
}
},
"description": "Wrapper around AI21 large language models.To use, you should have the environment variable ``AI21_API_KEY``set with your API key.",
"base_classes": [
"LLM",
"BaseLLM"
]
},
"id": "dndnode_}9",
"value": null
},
"selected": false,
"positionAbsolute": {
"x": -655.1999999999999,
"y": 615
},
"dragging": false
},
{
"width": 384,
"height": 351,
"id": "dndnode_}11",
"type": "genericNode",
"position": {
"x": 638.4588569554073,
"y": 325.32407743706693
},
"data": {
"type": "ZeroShotAgent",
"node": {
"template": {
"_type": "zero-shot-react-description",
"llm_chain": {
"type": "LLMChain",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multline": false
},
"allowed_tools": {
"type": "Tool",
"required": false,
"placeholder": "",
"list": true,
"show": true,
"multline": false,
"value": null
},
"return_values": {
"type": "str",
"required": false,
"placeholder": "",
"list": true,
"show": false,
"multline": false,
"value": [
"output"
]
}
},
"description": "Agent for the MRKL chain.",
"base_classes": [
"Agent"
]
},
"id": "dndnode_}11",
"value": null
},
"positionAbsolute": {
"x": 638.4588569554073,
"y": 325.32407743706693
}
},
{
"width": 384,
"height": 283,
"id": "dndnode_}12",
"type": "genericNode",
"position": {
"x": -88.20259321315393,
"y": 992.0115499525607
},
"data": {
"type": "Requests",
"node": {
"template": {
"_type": "requests"
},
"name": "Requests",
"description": "A portal to the internet. Use this when you need to get specific content from a site. Input should be a specific url, and the output will be all the text on that page.",
"base_classes": [
"Tool"
]
},
"id": "dndnode_}12",
"value": null
},
"selected": false,
"positionAbsolute": {
"x": -88.20259321315393,
"y": 992.0115499525607
},
"dragging": false
},
{
"width": 155,
"height": 62,
"id": "dndnode_}13",
"type": "chatOutputNode",
"position": {
"x": 1187.9878614974666,
"y": 492.6933173991155
},
"data": {
"type": "chatOutput",
"id": "dndnode_}13",
"value": null
},
"selected": false,
"positionAbsolute": {
"x": 1187.9878614974666,
"y": 492.6933173991155
},
"dragging": false
},
{
"width": 139,
"height": 62,
"id": "dndnode_}14",
"type": "chatInputNode",
"position": {
"x": -1098.8338538506573,
"y": 562.4305007166358
},
"data": {
"type": "chatInput",
"id": "dndnode_}14",
"value": null
},
"selected": true,
"positionAbsolute": {
"x": -1098.8338538506573,
"y": 562.4305007166358
},
"dragging": false
}
],
"edges": [
{
"source": "dndnode_}2",
"sourceHandle": "PromptTemplate|example_prompt|dndnode_}2",
"target": "dndnode_}1",
"targetHandle": "PromptTemplate|dndnode_}1|BasePromptTemplate",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_}2PromptTemplate|example_prompt|dndnode_}2-dndnode_}1PromptTemplate|dndnode_}1|BasePromptTemplate"
},
{
"source": "dndnode_}7",
"sourceHandle": "BasePromptTemplate|prompt|dndnode_}7",
"target": "dndnode_}8",
"targetHandle": "PromptTemplate|dndnode_}8|BasePromptTemplate",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_}7BasePromptTemplate|prompt|dndnode_}7-dndnode_}8PromptTemplate|dndnode_}8|BasePromptTemplate"
},
{
"source": "dndnode_}7",
"sourceHandle": "BaseLLM|llm|dndnode_}7",
"target": "dndnode_}9",
"targetHandle": "AI21|dndnode_}9|LLM,|BaseLLM",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_}7BaseLLM|llm|dndnode_}7-dndnode_}9AI21|dndnode_}9|LLM,|BaseLLM"
},
{
"source": "dndnode_}7",
"sourceHandle": "Memory|memory|dndnode_}7",
"target": "dndnode_}5",
"targetHandle": "ConversationBufferMemory|dndnode_}5|Memory",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_}7Memory|memory|dndnode_}7-dndnode_}5ConversationBufferMemory|dndnode_}5|Memory"
},
{
"source": "dndnode_}11",
"sourceHandle": "LLMChain|llm_chain|dndnode_}11",
"target": "dndnode_}7",
"targetHandle": "LLMChain|dndnode_}7|Chain",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_}11LLMChain|llm_chain|dndnode_}11-dndnode_}7LLMChain|dndnode_}7|Chain"
},
{
"source": "dndnode_}11",
"sourceHandle": "Tool|allowed_tools|dndnode_}11",
"target": "dndnode_}12",
"targetHandle": "Requests|dndnode_}12|Tool",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_}11Tool|allowed_tools|dndnode_}11-dndnode_}12Requests|dndnode_}12|Tool"
},
{
"source": "dndnode_}13",
"sourceHandle": "str|output|dndnode_}13",
"target": "dndnode_}11",
"targetHandle": "ZeroShotAgent|dndnode_}11|Agent",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_}13str|output|dndnode_}13-dndnode_}11ZeroShotAgent|dndnode_}11|Agent",
"selected": false
}
],
"viewport": {
"x": 765.8392857133035,
"y": -83.25008407339476,
"zoom": 0.7169776240079136
}
}

View file

@ -12,58 +12,36 @@ import { typesContext } from "../../../../contexts/typesContext";
export default function ExtraSidebar() {
const [data, setData] = useState({});
const { setTypes } = useContext(typesContext);
const { setTypes} = useContext(typesContext);
async function getTypes(){
let d = await getAll();
setData(d.data);
setTypes(
Object.keys(d.data).reduce(
(acc, curr) => {
Object.keys(d.data[curr]).forEach((c) => {
acc[c] = curr;
d.data[curr][c].base_classes?.forEach((b) => {
acc[b] = curr;
});
});
return acc;
},
{
str: "advanced",
bool: "advanced",
chatOutput: "chat",
chatInput: "chat",
}
)
);
}
useEffect(() => {
getAll().then((d) => {
setData(d.data);
setTypes(
Object.keys(d.data).reduce(
(acc, curr) => {
Object.keys(d.data[curr]).forEach((c) => {
acc[c] = curr;
d.data[curr][c].base_classes?.forEach((b) => {
acc[b] = curr;
});
});
// console.log(acc);
return acc;
},
{
str: "advanced",
bool: "advanced",
chatOutput: "chat",
chatInput: "chat",
}
)
);
});
getTypes();
}, []);
useEffect(() => {
if(data){
setTypes(
Object.keys(data).reduce(
(acc, curr) => {
Object.keys(data[curr]).forEach((c) => {
acc[c] = curr;
data[curr][c].base_classes?.forEach((b) => {
acc[b] = curr;
});
});
// console.log(acc);
return acc;
},
{
str: "advanced",
bool: "advanced",
chatOutput: "chat",
chatInput: "chat",
}
)
);
}
}, [data, setTypes])
function onDragStart(event: React.DragEvent<any>, data) {
event.dataTransfer.effectAllowed = "move";

View file

@ -0,0 +1,49 @@
import { useContext, useEffect } from "react";
import { ReactFlowProvider } from "reactflow";
import FlowPage from "..";
import { TabsContext } from "../../../contexts/tabsContext";
import TabComponent from "./tabComponent";
import { example } from "../../../data_assets/example";
var _ = require("lodash");
export function TabsManager() {
const { flows, addFlow, tabIndex, setTabIndex } = useContext(TabsContext);
useEffect(() => {
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 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>
<div className="w-full h-full">
<ReactFlowProvider>
{flows[tabIndex] ? (
<FlowPage flow={flows[tabIndex]}></FlowPage>
) : (
<></>
)}
</ReactFlowProvider>
</div>
</div>
);
}

View file

@ -0,0 +1,80 @@
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/solid";
import { useContext, useRef, useState } from "react";
import { TabsContext } from "../../../../contexts/tabsContext";
var _ = require("lodash");
export default function TabComponent({ selected, flow, onClick }) {
const { removeFlow, updateFlow } = useContext(TabsContext);
const [isRename, setIsRename] = useState(false);
const [value, setValue] = useState("");
return (
<>
{flow ? (
!selected ? (
<div
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
onClick={(e) => {
e.stopPropagation();
removeFlow(flow.id);
}}
>
<XMarkIcon className="h-4 hover:bg-white rounded-full" />
</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 ? (
<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);
}}
/>
) : (
<span
className="text-left truncate"
onDoubleClick={() => {
setIsRename(true);
setValue(flow.name);
}}
>
{flow.name}
</span>
)}
<button
onClick={() => {
removeFlow(flow.id);
}}
>
<XMarkIcon className="h-4 hover:bg-gray-100 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-gray-300 -ml-px border-t border-t-transparent"
onClick={onClick}
>
<PlusIcon className="h-5 rounded-full hover:bg-white" />
</button>
</div>
)}
</>
);
}

View file

@ -5,6 +5,8 @@ import ReactFlow, {
addEdge,
useEdgesState,
useNodesState,
ReactFlowProvider,
useReactFlow,
} from "reactflow";
import { locationContext } from "../../contexts/locationContext";
import ExtraSidebar from "./components/extraSidebarComponent";
@ -16,6 +18,8 @@ import ChatOutputNode from "../../CustomNodes/ChatOutputNode";
import InputNode from "../../CustomNodes/InputNode";
import BooleanNode from "../../CustomNodes/BooleanNode";
import { alertContext } from "../../contexts/alertContext";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
const nodeTypes = {
genericNode: GenericNode,
@ -27,17 +31,44 @@ const nodeTypes = {
var _ = require("lodash");
export default function FlowPage() {
export default function FlowPage({ flow }) {
let { updateFlow, nodeId } = useContext(TabsContext);
const { types, reactFlowInstance, setReactFlowInstance } =
useContext(typesContext);
const reactFlowWrapper = useRef(null);
const getId = () => `dndnode_${_.uniqueId()}`;
function getId(){
console.log(nodeId);
nodeId = nodeId+1
return `dndnode_}`+nodeId;
};
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
const { setErrorData } = useContext(alertContext);
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [reactFlowInstance, setReactFlowInstance] = useState(null);
const [nodes, setNodes, onNodesChange] = useNodesState(
flow.data?.nodes ?? []
);
const [edges, setEdges, onEdgesChange] = useEdgesState(
flow.data?.edges ?? []
);
useEffect(() => {
if (reactFlowInstance && flow) {
flow.data = reactFlowInstance.toObject();
updateFlow(flow);
}
}, [nodes, edges]);
useEffect(() => {
setNodes(flow?.data?.nodes ?? []);
setEdges(flow?.data?.edges ?? []);
if (reactFlowInstance) {
reactFlowInstance.setViewport(
flow?.data?.viewport ?? { x: 1, y: 0, zoom: 1 }
);
}
}, [flow, reactFlowInstance, setEdges, setNodes]);
useEffect(() => {
setExtraComponent(<ExtraSidebar />);
@ -89,30 +120,24 @@ export default function FlowPage() {
y: event.clientY - reactflowBounds.top,
});
let newId = getId();
const newNode = {
id: newId,
type:
(data.type === "str"
data.type === "str"
? "inputNode"
: (data.type === "chatInput"
: data.type === "chatInput"
? "chatInputNode"
: (data.type === "chatOutput"
: data.type === "chatOutput"
? "chatOutputNode"
: (data.type === "bool"
: data.type === "bool"
? "booleanNode"
: "genericNode")))),
: "genericNode",
position,
data: {
...data,
id: newId,
value: null,
reactFlowInstance,
onDelete: () => {
setNodes(
reactFlowInstance.getNodes().filter((n) => n.id !== newId)
);
},
},
};
setNodes((nds) => nds.concat(newNode));
@ -128,22 +153,32 @@ export default function FlowPage() {
return (
<div className="w-full h-full" ref={reactFlowWrapper}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChangeMod}
onConnect={onConnect}
onInit={setReactFlowInstance}
nodeTypes={nodeTypes}
connectionLineComponent={connection}
onDragOver={onDragOver}
onDrop={onDrop}
>
<Background />
<Controls></Controls>
</ReactFlow>
<Chat reactFlowInstance={reactFlowInstance} />
{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={connection}
onDragOver={onDragOver}
onDrop={onDrop}
>
<Background />
<Controls></Controls>
</ReactFlow>
<Chat reactFlowInstance={reactFlowInstance} />
</>
) : (
<></>
)}
</div>
);
}

View file

@ -335,18 +335,18 @@ export function getConnectedNodes(edge: Edge, nodes: Array<Node>): Array<Node> {
}
export function isValidConnection(
data,
{ source, target, sourceHandle, targetHandle }
{ source, target, sourceHandle, targetHandle },
reactFlowInstance
) {
if (
sourceHandle.split('|')[0] === targetHandle.split("|")[0] ||
sourceHandle.split('|').slice(2).some((t) => t === targetHandle.split("|")[0]) ||
targetHandle.split("|")[0] === "str"
) {
let targetNode = data.reactFlowInstance.getNode(target).data.node;
let targetNode = reactFlowInstance.getNode(target).data.node;
if (!targetNode) {
if (
!data.reactFlowInstance
!reactFlowInstance
.getEdges()
.find((e) => e.targetHandle === targetHandle)
) {
@ -354,7 +354,7 @@ export function isValidConnection(
}
} else if (
(!targetNode.template[targetHandle.split("|")[1]].list &&
!data.reactFlowInstance
!reactFlowInstance
.getEdges()
.find((e) => e.targetHandle === targetHandle)) ||
targetNode.template[targetHandle.split("|")[1]].list