Merge branch 'dev' into uiImprovements

This commit is contained in:
anovazzi1 2023-05-02 22:52:57 -03:00
commit e2c8dfab2f
21 changed files with 598 additions and 564 deletions

View file

@ -1,4 +1,4 @@
from langflow.interface.loading import load_flow_from_json
from langflow.cache import cache_manager from langflow.cache import cache_manager
from langflow.interface.loading import load_flow_from_json
__all__ = ["load_flow_from_json", "cache_manager"] __all__ = ["load_flow_from_json", "cache_manager"]

View file

@ -1,4 +1,5 @@
from typing import Any from typing import Any
from langchain.callbacks.base import AsyncCallbackHandler from langchain.callbacks.base import AsyncCallbackHandler
from langflow.api.schemas import ChatResponse from langflow.api.schemas import ChatResponse

View file

@ -1,9 +1,12 @@
import asyncio import asyncio
from typing import Dict, List
from collections import defaultdict
from fastapi import WebSocket
import json import json
from collections import defaultdict
from typing import Dict, List
from fastapi import WebSocket
from langflow.api.schemas import ChatMessage, ChatResponse, FileResponse from langflow.api.schemas import ChatMessage, ChatResponse, FileResponse
from langflow.cache import cache_manager
from langflow.cache.manager import Subject from langflow.cache.manager import Subject
from langflow.interface.run import ( from langflow.interface.run import (
get_result_and_steps, get_result_and_steps,
@ -11,7 +14,6 @@ from langflow.interface.run import (
) )
from langflow.interface.utils import pil_to_base64, try_setting_streaming_options from langflow.interface.utils import pil_to_base64, try_setting_streaming_options
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.cache import cache_manager
class ChatHistory(Subject): class ChatHistory(Subject):

View file

@ -1,4 +1,5 @@
from typing import Any, Union from typing import Any, Union
from pydantic import BaseModel, validator from pydantic import BaseModel, validator

View file

@ -2,13 +2,13 @@ import base64
import contextlib import contextlib
import functools import functools
import hashlib import hashlib
import json import json
import os import os
import tempfile import tempfile
from collections import OrderedDict from collections import OrderedDict
from pathlib import Path from pathlib import Path
from typing import Any, Dict from typing import Any, Dict
import dill # type: ignore import dill # type: ignore
CACHE: Dict[str, Any] = {} CACHE: Dict[str, Any] = {}

View file

@ -1,7 +1,8 @@
from contextlib import contextmanager from contextlib import contextmanager
from typing import Any, Awaitable, Callable, List, Optional from typing import Any, Awaitable, Callable, List, Optional
from PIL import Image
import pandas as pd import pandas as pd
from PIL import Image
class Subject: class Subject:

View file

@ -26,9 +26,9 @@ from langchain.agents.agent_toolkits.vectorstore.prompt import (
) )
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS as SQL_FORMAT_INSTRUCTIONS from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS as SQL_FORMAT_INSTRUCTIONS
from langchain.base_language import BaseLanguageModel
from langchain.llms.base import BaseLLM from langchain.llms.base import BaseLLM
from langchain.memory.chat_memory import BaseChatMemory from langchain.memory.chat_memory import BaseChatMemory
from langchain.base_language import BaseLanguageModel
from langchain.sql_database import SQLDatabase from langchain.sql_database import SQLDatabase
from langchain.tools.python.tool import PythonAstREPLTool from langchain.tools.python.tool import PythonAstREPLTool
from langchain.tools.sql_database.prompt import QUERY_CHECKER from langchain.tools.sql_database.prompt import QUERY_CHECKER
@ -51,7 +51,7 @@ class JsonAgent(AgentExecutor):
@classmethod @classmethod
def from_toolkit_and_llm(cls, toolkit: JsonToolkit, llm: BaseLanguageModel): def from_toolkit_and_llm(cls, toolkit: JsonToolkit, llm: BaseLanguageModel):
tools = toolkit.get_tools() tools = toolkit.get_tools()
tool_names = [tool.name for tool in tools] tool_names = {tool.name for tool in tools}
prompt = ZeroShotAgent.create_prompt( prompt = ZeroShotAgent.create_prompt(
tools, tools,
prefix=JSON_PREFIX, prefix=JSON_PREFIX,
@ -109,7 +109,7 @@ class CSVAgent(AgentExecutor):
llm=llm, llm=llm,
prompt=partial_prompt, prompt=partial_prompt,
) )
tool_names = [tool.name for tool in tools] tool_names = {tool.name for tool in tools}
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs) agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs)
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True) return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
@ -146,7 +146,7 @@ class VectorStoreAgent(AgentExecutor):
llm=llm, llm=llm,
prompt=prompt, prompt=prompt,
) )
tool_names = [tool.name for tool in tools] tool_names = {tool.name for tool in tools}
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs) agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs)
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, verbose=True agent=agent, tools=tools, verbose=True
@ -212,7 +212,7 @@ class SQLAgent(AgentExecutor):
llm=llm, llm=llm,
prompt=prompt, prompt=prompt,
) )
tool_names = [tool.name for tool in tools] # type: ignore tool_names = {tool.name for tool in tools} # type: ignore
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs) agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs)
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(
agent=agent, agent=agent,
@ -255,7 +255,7 @@ class VectorStoreRouterAgent(AgentExecutor):
llm=llm, llm=llm,
prompt=prompt, prompt=prompt,
) )
tool_names = [tool.name for tool in tools] tool_names = {tool.name for tool in tools}
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs) agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs)
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, verbose=True agent=agent, tools=tools, verbose=True

View file

@ -21,7 +21,7 @@ class MalfoyAgent(AgentExecutor):
@classmethod @classmethod
def from_toolkit_and_llm(cls, toolkit: JsonToolkit, llm: BaseLanguageModel): def from_toolkit_and_llm(cls, toolkit: JsonToolkit, llm: BaseLanguageModel):
tools = toolkit.get_tools() tools = toolkit.get_tools()
tool_names = [tool.name for tool in tools] tool_names = {tool.name for tool in tools}
prompt = ZeroShotAgent.create_prompt( prompt = ZeroShotAgent.create_prompt(
tools, tools,
prefix=JSON_PREFIX, prefix=JSON_PREFIX,

View file

@ -17,6 +17,7 @@ from langchain.callbacks.base import BaseCallbackManager
from langchain.chains.loading import load_chain_from_config from langchain.chains.loading import load_chain_from_config
from langchain.llms.base import BaseLLM from langchain.llms.base import BaseLLM
from langchain.llms.loading import load_llm_from_config from langchain.llms.loading import load_llm_from_config
from pydantic import ValidationError
from langflow.interface.agents.custom import CUSTOM_AGENTS from langflow.interface.agents.custom import CUSTOM_AGENTS
from langflow.interface.importing.utils import import_by_type from langflow.interface.importing.utils import import_by_type
@ -25,7 +26,6 @@ from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.types import get_type_list from langflow.interface.types import get_type_list
from langflow.interface.utils import load_file_into_dict from langflow.interface.utils import load_file_into_dict
from langflow.utils import util, validate from langflow.utils import util, validate
from pydantic import ValidationError
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:

View file

@ -1,6 +1,7 @@
import contextlib import contextlib
import io import io
from typing import Any, Dict from typing import Any, Dict
from chromadb.errors import NotEnoughElementsException # type: ignore from chromadb.errors import NotEnoughElementsException # type: ignore
from langflow.cache.base import compute_dict_hash, load_cache, memoize_dict from langflow.cache.base import compute_dict_hash, load_cache, memoize_dict

View file

@ -1,14 +1,15 @@
import base64 import base64
from io import BytesIO
import json import json
import os import os
from PIL.Image import Image from io import BytesIO
import yaml
from langchain.callbacks.manager import AsyncCallbackManager from langchain.callbacks.manager import AsyncCallbackManager
from langchain.chat_models import AzureChatOpenAI, ChatOpenAI from langchain.chat_models import AzureChatOpenAI, ChatOpenAI
from langchain.llms import AzureOpenAI, OpenAI from langchain.llms import AzureOpenAI, OpenAI
from langflow.api.callback import StreamingLLMCallbackHandler from PIL.Image import Image
import yaml from langflow.api.callback import StreamingLLMCallbackHandler
def load_file_into_dict(file_path: str) -> dict: def load_file_into_dict(file_path: str) -> dict:

View file

@ -1,9 +1,9 @@
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from langflow.api.chat import router as chat_router
from langflow.api.endpoints import router as endpoints_router from langflow.api.endpoints import router as endpoints_router
from langflow.api.validate import router as validate_router from langflow.api.validate import router as validate_router
from langflow.api.chat import router as chat_router
def create_app(): def create_app():

View file

@ -162,14 +162,23 @@ class FrontendNode(BaseModel):
_type = _type.replace("Optional[", "")[:-1] _type = _type.replace("Optional[", "")[:-1]
# Check for list type # Check for list type
if "List" in _type: if "List" in _type or "Sequence" in _type:
_type = _type.replace("List[", "")[:-1] _type = _type.replace("List[", "")
_type = _type.replace("Sequence[", "")[:-1]
field.is_list = True field.is_list = True
# Replace 'Mapping' with 'dict' # Replace 'Mapping' with 'dict'
if "Mapping" in _type: if "Mapping" in _type:
_type = _type.replace("Mapping", "dict") _type = _type.replace("Mapping", "dict")
# {'type': 'Union[float, Tuple[float, float], NoneType]'} != {'type': 'float'}
if "Union" in _type:
_type = _type.replace("Union[", "")[:-1]
_type = _type.split(",")[0]
_type = _type.replace("]", "").replace("[", "")
field.field_type = _type
# Change type from str to Tool # Change type from str to Tool
field.field_type = "Tool" if key in {"allowed_tools"} else field.field_type field.field_type = "Tool" if key in {"allowed_tools"} else field.field_type

View file

@ -1,7 +1,7 @@
from functools import wraps
import importlib import importlib
import inspect import inspect
import re import re
from functools import wraps
from typing import Dict, Optional from typing import Dict, Optional
from docstring_parser import parse # type: ignore from docstring_parser import parse # type: ignore

View file

@ -1,235 +1,235 @@
import { import {
createContext, createContext,
useEffect, useEffect,
useState, useState,
useRef, useRef,
ReactNode, ReactNode,
useContext, useContext,
} from "react"; } from "react";
import { FlowType } from "../types/flow"; import { FlowType } from "../types/flow";
import { LangFlowState, TabsContextType } from "../types/tabs"; import { LangFlowState, TabsContextType } from "../types/tabs";
import { normalCaseToSnakeCase, updateObject } from "../utils"; import { normalCaseToSnakeCase, updateObject, updateTemplate } from "../utils";
import { alertContext } from "./alertContext"; import { alertContext } from "./alertContext";
import { typesContext } from "./typesContext"; import { typesContext } from "./typesContext";
import { TemplateVariableType } from "../types/api"; import { APITemplateType, TemplateVariableType } from "../types/api";
const { v4: uuidv4 } = require("uuid"); const { v4: uuidv4 } = require("uuid");
const TabsContextInitialValue: TabsContextType = { const TabsContextInitialValue: TabsContextType = {
save: () => {}, save: () => {},
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: FlowType) => {}, updateFlow: (newFlow: FlowType) => {},
incrementNodeId: () => 0, incrementNodeId: () => 0,
downloadFlow: (flow: FlowType) => {}, downloadFlow: (flow: FlowType) => {},
uploadFlow: () => {}, uploadFlow: () => {},
hardReset: () => {}, hardReset: () => {},
}; };
export const TabsContext = createContext<TabsContextType>( export const TabsContext = createContext<TabsContextType>(
TabsContextInitialValue TabsContextInitialValue
); );
export function TabsProvider({ children }: { children: ReactNode }) { export function TabsProvider({ children }: { children: ReactNode }) {
const { setNoticeData } = useContext(alertContext); const { setNoticeData } = useContext(alertContext);
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(""); const [id, setId] = useState("");
const { templates } = useContext(typesContext); const { templates } = useContext(typesContext);
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;
} }
function save() { function save() {
if (flows.length !== 0) if (flows.length !== 0)
window.localStorage.setItem( window.localStorage.setItem(
"tabsData", "tabsData",
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current }) JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current })
); );
} }
useEffect(() => { useEffect(() => {
//save tabs locally //save tabs locally
save(); save();
}, [flows, id, tabIndex, newNodeId]); }, [flows, id, tabIndex, newNodeId]);
useEffect(() => { useEffect(() => {
//get tabs locally saved //get tabs locally saved
let cookie = window.localStorage.getItem("tabsData"); let cookie = window.localStorage.getItem("tabsData");
if (cookie && Object.keys(templates).length > 0) { if (cookie && Object.keys(templates).length > 0) {
let cookieObject: LangFlowState = JSON.parse(cookie); let cookieObject: LangFlowState = JSON.parse(cookie);
cookieObject.flows.forEach((flow) => { cookieObject.flows.forEach((flow) => {
flow.data.nodes.forEach((node) => { flow.data.nodes.forEach((node) => {
if (Object.keys(templates[node.data.type]["template"]).length > 0) { if (Object.keys(templates[node.data.type]["template"]).length > 0) {
node.data.node.template = updateObject( node.data.node.template = updateTemplate(
templates[node.data.type][ templates[node.data.type][
"template" "template"
] as unknown as TemplateVariableType, ] as unknown as APITemplateType,
node.data.node.template as TemplateVariableType node.data.node.template as APITemplateType
); );
} }
}); });
}); });
setTabIndex(cookieObject.tabIndex); setTabIndex(cookieObject.tabIndex);
setFlows(cookieObject.flows); setFlows(cookieObject.flows);
setId(cookieObject.id); setId(cookieObject.id);
newNodeId.current = cookieObject.nodeId; newNodeId.current = cookieObject.nodeId;
} }
}, [templates]); }, [templates]);
function hardReset() { function hardReset() {
newNodeId.current = 0; newNodeId.current = 0;
setTabIndex(0); setTabIndex(0);
setFlows([]); setFlows([]);
setId(""); setId("");
} }
/** /**
* Downloads the current flow as a JSON file * Downloads the current flow as a JSON file
*/ */
function downloadFlow(flow: FlowType) { function downloadFlow(flow: FlowType) {
// create a data URI with the current flow data // 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(flow) JSON.stringify(flow)
)}`; )}`;
// create a link element and set its properties // 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 = `${normalCaseToSnakeCase(flows[tabIndex].name)}.json`; link.download = `${normalCaseToSnakeCase(flows[tabIndex].name)}.json`;
// simulate a click on the link element to trigger the download // simulate a click on the link element to trigger the download
link.click(); link.click();
setNoticeData({ setNoticeData({
title: "Warning: Critical data,JSON file may including API keys.", title: "Warning: Critical data,JSON file may including API keys.",
}); });
} }
/** /**
* Creates a file input and listens to a change event to upload a JSON flow file. * 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. * 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. * The resulting JSON object is passed to the addFlow function.
*/ */
function uploadFlow() { function uploadFlow() {
// create a file input // 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 // 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 // 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 // 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 // read the file as text
file.text().then((text) => { file.text().then((text) => {
// parse the text into a JSON object // parse the text into a JSON object
let flow: FlowType = JSON.parse(text); let flow: FlowType = JSON.parse(text);
flow.data.nodes.forEach((node) => {
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
node.data.node.template = updateObject(
templates[node.data.type][
"template"
] as unknown as TemplateVariableType,
node.data.node.template as TemplateVariableType
);
}
});
addFlow(flow); addFlow(flow);
}); });
} }
}; };
// trigger the file input click event to open the file dialog // trigger the file input click event to open the file dialog
input.click(); input.click();
} }
/** /**
* Removes a flow from an array of flows based on its id. * Removes a flow from an array of flows based on its id.
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks. * Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
* @param {string} id - The id of the flow to remove. * @param {string} id - The id of the flow to remove.
*/ */
function removeFlow(id: string) { function removeFlow(id: string) {
setFlows((prevState) => { setFlows((prevState) => {
const newFlows = [...prevState]; const newFlows = [...prevState];
const index = newFlows.findIndex((flow) => flow.id === id); const index = newFlows.findIndex((flow) => flow.id === id);
if (index >= 0) { if (index >= 0) {
if (index === tabIndex) { if (index === tabIndex) {
setTabIndex(flows.length - 2); setTabIndex(flows.length - 2);
newFlows.splice(index, 1); newFlows.splice(index, 1);
} else { } else {
let flowId = flows[tabIndex].id; let flowId = flows[tabIndex].id;
newFlows.splice(index, 1); newFlows.splice(index, 1);
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId)); setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
} }
} }
return newFlows; return newFlows;
}); });
} }
/** /**
* Add a new flow to the list of flows. * Add a new flow to the list of flows.
* @param flow Optional flow to add. * @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. // 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;
const description = flow?.description ? flow.description : ""; const description = flow?.description ? flow.description : "";
// Create a new flow with a default name if no flow is provided. if (data) {
let newFlow: FlowType = { data.nodes.forEach((node) => {
description, if (Object.keys(templates[node.data.type]["template"]).length > 0) {
name: flow?.name ?? "New Flow", node.data.node.template = updateTemplate(
id: id.toString(), templates[node.data.type]["template"] as unknown as APITemplateType,
data, node.data.node.template as APITemplateType
}; );
}
});
}
// Create a new flow with a default name if no flow is provided.
let newFlow: FlowType = {
description,
name: flow?.name ?? "New Flow",
id: id.toString(),
data,
};
// Increment the ID counter. // Increment the ID counter.
setId(uuidv4()); setId(uuidv4());
// Add the new flow to the list of flows. // 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. // Set the tab index to the new flow.
setTabIndex(flows.length); setTabIndex(flows.length);
} }
/** /**
* Updates an existing flow with new data * Updates an existing flow with new data
* @param newFlow - The new flow object containing the updated 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];
const index = newFlows.findIndex((flow) => flow.id === newFlow.id); const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
if (index !== -1) { if (index !== -1) {
newFlows[index].description = newFlow.description ?? ""; newFlows[index].description = newFlow.description ?? "";
newFlows[index].data = newFlow.data; newFlows[index].data = newFlow.data;
newFlows[index].name = newFlow.name; newFlows[index].name = newFlow.name;
} }
return newFlows; return newFlows;
}); });
} }
return ( return (
<TabsContext.Provider <TabsContext.Provider
value={{ value={{
save, save,
hardReset, hardReset,
tabIndex, tabIndex,
setTabIndex, setTabIndex,
flows, flows,
incrementNodeId, incrementNodeId,
removeFlow, removeFlow,
addFlow, addFlow,
updateFlow, updateFlow,
downloadFlow, downloadFlow,
uploadFlow, uploadFlow,
}} }}
> >
{children} {children}
</TabsContext.Provider> </TabsContext.Provider>
); );
} }

View file

@ -1,318 +1,319 @@
import { import {
RocketLaunchIcon, RocketLaunchIcon,
LinkIcon, LinkIcon,
CpuChipIcon, CpuChipIcon,
LightBulbIcon, LightBulbIcon,
CommandLineIcon, CommandLineIcon,
WrenchScrewdriverIcon, WrenchScrewdriverIcon,
WrenchIcon, WrenchIcon,
ComputerDesktopIcon, ComputerDesktopIcon,
Bars3CenterLeftIcon, Bars3CenterLeftIcon,
GiftIcon, GiftIcon,
PaperClipIcon, PaperClipIcon,
QuestionMarkCircleIcon, QuestionMarkCircleIcon,
FingerPrintIcon, FingerPrintIcon,
ScissorsIcon, ScissorsIcon,
CircleStackIcon, CircleStackIcon,
Squares2X2Icon, Squares2X2Icon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import { Connection, Edge, Node, ReactFlowInstance } from "reactflow"; import { Connection, Edge, Node, ReactFlowInstance } from "reactflow";
import { FlowType } from "./types/flow"; import { FlowType } from "./types/flow";
import { APITemplateType, TemplateVariableType } from "./types/api";
var _ = require("lodash"); var _ = require("lodash");
export function classNames(...classes: Array<string>) { export function classNames(...classes: Array<string>) {
return classes.filter(Boolean).join(" "); return classes.filter(Boolean).join(" ");
} }
export const textColors = { export const textColors = {
white: "text-white", white: "text-white",
red: "text-red-700", red: "text-red-700",
orange: "text-orange-700", orange: "text-orange-700",
amber: "text-amber-700", amber: "text-amber-700",
yellow: "text-yellow-700", yellow: "text-yellow-700",
lime: "text-lime-700", lime: "text-lime-700",
green: "text-green-700", green: "text-green-700",
emerald: "text-emerald-700", emerald: "text-emerald-700",
teal: "text-teal-700", teal: "text-teal-700",
cyan: "text-cyan-700", cyan: "text-cyan-700",
sky: "text-sky-700", sky: "text-sky-700",
blue: "text-blue-700", blue: "text-blue-700",
indigo: "text-indigo-700", indigo: "text-indigo-700",
violet: "text-violet-700", violet: "text-violet-700",
purple: "text-purple-700", purple: "text-purple-700",
fuchsia: "text-fuchsia-700", fuchsia: "text-fuchsia-700",
pink: "text-pink-700", pink: "text-pink-700",
rose: "text-rose-700", rose: "text-rose-700",
black: "text-black-700", black: "text-black-700",
gray: "text-gray-700", gray: "text-gray-700",
}; };
export const borderLColors = { export const borderLColors = {
white: "border-l-white", white: "border-l-white",
red: "border-l-red-500", red: "border-l-red-500",
orange: "border-l-orange-500", orange: "border-l-orange-500",
amber: "border-l-amber-500", amber: "border-l-amber-500",
yellow: "border-l-yellow-500", yellow: "border-l-yellow-500",
lime: "border-l-lime-500", lime: "border-l-lime-500",
green: "border-l-green-500", green: "border-l-green-500",
emerald: "border-l-emerald-500", emerald: "border-l-emerald-500",
teal: "border-l-teal-500", teal: "border-l-teal-500",
cyan: "border-l-cyan-500", cyan: "border-l-cyan-500",
sky: "border-l-sky-500", sky: "border-l-sky-500",
blue: "border-l-blue-500", blue: "border-l-blue-500",
indigo: "border-l-indigo-500", indigo: "border-l-indigo-500",
violet: "border-l-violet-500", violet: "border-l-violet-500",
purple: "border-l-purple-500", purple: "border-l-purple-500",
fuchsia: "border-l-fuchsia-500", fuchsia: "border-l-fuchsia-500",
pink: "border-l-pink-500", pink: "border-l-pink-500",
rose: "border-l-rose-500", rose: "border-l-rose-500",
black: "border-l-black-500", black: "border-l-black-500",
gray: "border-l-gray-500", gray: "border-l-gray-500",
}; };
export const nodeColors: { [char: string]: string } = { export const nodeColors: { [char: string]: string } = {
prompts: "#4367BF", prompts: "#4367BF",
llms: "#6344BE", llms: "#6344BE",
chains: "#FE7500", chains: "#FE7500",
agents: "#903BBE", agents: "#903BBE",
tools: "#FF3434", tools: "#FF3434",
memories: "#F5B85A", memories: "#F5B85A",
advanced: "#000000", advanced: "#000000",
chat: "#198BF6", chat: "#198BF6",
thought: "#272541", thought: "#272541",
embeddings: "#42BAA7", embeddings: "#42BAA7",
documentloaders: "#7AAE42", documentloaders: "#7AAE42",
vectorstores: "#AA8742", vectorstores: "#AA8742",
textsplitters: "#B47CB5", textsplitters: "#B47CB5",
toolkits: "#DB2C2C", toolkits: "#DB2C2C",
wrappers: "#E6277A", wrappers: "#E6277A",
utilities: "#31A3CC", utilities: "#31A3CC",
unknown: "#9CA3AF", unknown: "#9CA3AF",
}; };
export const nodeNames: { [char: string]: string } = { export const nodeNames: { [char: string]: string } = {
prompts: "Prompts", prompts: "Prompts",
llms: "LLMs", llms: "LLMs",
chains: "Chains", chains: "Chains",
agents: "Agents", agents: "Agents",
tools: "Tools", tools: "Tools",
memories: "Memories", memories: "Memories",
advanced: "Advanced", advanced: "Advanced",
chat: "Chat", chat: "Chat",
embeddings: "Embeddings", embeddings: "Embeddings",
documentloaders: "Document Loaders", documentloaders: "Document Loaders",
vectorstores: "Vector Stores", vectorstores: "Vector Stores",
toolkits: "Toolkits", toolkits: "Toolkits",
wrappers: "Wrappers", wrappers: "Wrappers",
textsplitters: "Text Splitters", textsplitters: "Text Splitters",
utilities: "Utilities", utilities: "Utilities",
unknown: "Unknown", unknown: "Unknown",
}; };
export const nodeIcons: { export const nodeIcons: {
[char: string]: React.ForwardRefExoticComponent< [char: string]: React.ForwardRefExoticComponent<
React.SVGProps<SVGSVGElement> React.SVGProps<SVGSVGElement>
>; >;
} = { } = {
agents: RocketLaunchIcon, agents: RocketLaunchIcon,
chains: LinkIcon, chains: LinkIcon,
memories: CpuChipIcon, memories: CpuChipIcon,
llms: LightBulbIcon, llms: LightBulbIcon,
prompts: CommandLineIcon, prompts: CommandLineIcon,
tools: WrenchIcon, tools: WrenchIcon,
advanced: ComputerDesktopIcon, advanced: ComputerDesktopIcon,
chat: Bars3CenterLeftIcon, chat: Bars3CenterLeftIcon,
embeddings: FingerPrintIcon, embeddings: FingerPrintIcon,
documentloaders: PaperClipIcon, documentloaders: PaperClipIcon,
vectorstores: CircleStackIcon, vectorstores: CircleStackIcon,
toolkits: WrenchScrewdriverIcon, toolkits: WrenchScrewdriverIcon,
textsplitters: ScissorsIcon, textsplitters: ScissorsIcon,
wrappers: GiftIcon, wrappers: GiftIcon,
utilities: Squares2X2Icon, utilities: Squares2X2Icon,
unknown: QuestionMarkCircleIcon, unknown: QuestionMarkCircleIcon,
}; };
export const bgColors = { export const bgColors = {
white: "bg-white", white: "bg-white",
red: "bg-red-100", red: "bg-red-100",
orange: "bg-orange-100", orange: "bg-orange-100",
amber: "bg-amber-100", amber: "bg-amber-100",
yellow: "bg-yellow-100", yellow: "bg-yellow-100",
lime: "bg-lime-100", lime: "bg-lime-100",
green: "bg-green-100", green: "bg-green-100",
emerald: "bg-emerald-100", emerald: "bg-emerald-100",
teal: "bg-teal-100", teal: "bg-teal-100",
cyan: "bg-cyan-100", cyan: "bg-cyan-100",
sky: "bg-sky-100", sky: "bg-sky-100",
blue: "bg-blue-100", blue: "bg-blue-100",
indigo: "bg-indigo-100", indigo: "bg-indigo-100",
violet: "bg-violet-100", violet: "bg-violet-100",
purple: "bg-purple-100", purple: "bg-purple-100",
fuchsia: "bg-fuchsia-100", fuchsia: "bg-fuchsia-100",
pink: "bg-pink-100", pink: "bg-pink-100",
rose: "bg-rose-100", rose: "bg-rose-100",
black: "bg-black-100", black: "bg-black-100",
gray: "bg-gray-100", gray: "bg-gray-100",
}; };
export const bgColorsHover = { export const bgColorsHover = {
white: "hover:bg-white", white: "hover:bg-white",
black: "hover:bg-black-50", black: "hover:bg-black-50",
gray: "hover:bg-gray-50", gray: "hover:bg-gray-50",
red: "hover:bg-red-50", red: "hover:bg-red-50",
orange: "hover:bg-orange-50", orange: "hover:bg-orange-50",
amber: "hover:bg-amber-50", amber: "hover:bg-amber-50",
yellow: "hover:bg-yellow-50", yellow: "hover:bg-yellow-50",
lime: "hover:bg-lime-50", lime: "hover:bg-lime-50",
green: "hover:bg-green-50", green: "hover:bg-green-50",
emerald: "hover:bg-emerald-50", emerald: "hover:bg-emerald-50",
teal: "hover:bg-teal-50", teal: "hover:bg-teal-50",
cyan: "hover:bg-cyan-50", cyan: "hover:bg-cyan-50",
sky: "hover:bg-sky-50", sky: "hover:bg-sky-50",
blue: "hover:bg-blue-50", blue: "hover:bg-blue-50",
indigo: "hover:bg-indigo-50", indigo: "hover:bg-indigo-50",
violet: "hover:bg-violet-50", violet: "hover:bg-violet-50",
purple: "hover:bg-purple-50", purple: "hover:bg-purple-50",
fuchsia: "hover:bg-fuchsia-50", fuchsia: "hover:bg-fuchsia-50",
pink: "hover:bg-pink-50", pink: "hover:bg-pink-50",
rose: "hover:bg-rose-50", rose: "hover:bg-rose-50",
}; };
export const textColorsHex = { export const textColorsHex = {
red: "rgb(185 28 28)", red: "rgb(185 28 28)",
orange: "rgb(194 65 12)", orange: "rgb(194 65 12)",
amber: "rgb(180 83 9)", amber: "rgb(180 83 9)",
yellow: "rgb(161 98 7)", yellow: "rgb(161 98 7)",
lime: "rgb(77 124 15)", lime: "rgb(77 124 15)",
green: "rgb(21 128 61)", green: "rgb(21 128 61)",
emerald: "rgb(4 120 87)", emerald: "rgb(4 120 87)",
teal: "rgb(15 118 110)", teal: "rgb(15 118 110)",
cyan: "rgb(14 116 144)", cyan: "rgb(14 116 144)",
sky: "rgb(3 105 161)", sky: "rgb(3 105 161)",
blue: "rgb(29 78 216)", blue: "rgb(29 78 216)",
indigo: "rgb(67 56 202)", indigo: "rgb(67 56 202)",
violet: "rgb(109 40 217)", violet: "rgb(109 40 217)",
purple: "rgb(126 34 206)", purple: "rgb(126 34 206)",
fuchsia: "rgb(162 28 175)", fuchsia: "rgb(162 28 175)",
pink: "rgb(190 24 93)", pink: "rgb(190 24 93)",
rose: "rgb(190 18 60)", rose: "rgb(190 18 60)",
}; };
export const bgColorsHex = { export const bgColorsHex = {
red: "rgb(254 226 226)", red: "rgb(254 226 226)",
orange: "rgb(255 237 213)", orange: "rgb(255 237 213)",
amber: "rgb(254 243 199)", amber: "rgb(254 243 199)",
yellow: "rgb(254 249 195)", yellow: "rgb(254 249 195)",
lime: "rgb(236 252 203)", lime: "rgb(236 252 203)",
green: "rgb(220 252 231)", green: "rgb(220 252 231)",
emerald: "rgb(209 250 229)", emerald: "rgb(209 250 229)",
teal: "rgb(204 251 241)", teal: "rgb(204 251 241)",
cyan: "rgb(207 250 254)", cyan: "rgb(207 250 254)",
sky: "rgb(224 242 254)", sky: "rgb(224 242 254)",
blue: "rgb(219 234 254)", blue: "rgb(219 234 254)",
indigo: "rgb(224 231 255)", indigo: "rgb(224 231 255)",
violet: "rgb(237 233 254)", violet: "rgb(237 233 254)",
purple: "rgb(243 232 255)", purple: "rgb(243 232 255)",
fuchsia: "rgb(250 232 255)", fuchsia: "rgb(250 232 255)",
pink: "rgb(252 231 243)", pink: "rgb(252 231 243)",
rose: "rgb(255 228 230)", rose: "rgb(255 228 230)",
}; };
export const taskTypeMap: { [key: string]: string } = { export const taskTypeMap: { [key: string]: string } = {
MULTICLASS_CLASSIFICATION: "Multiclass Classification", MULTICLASS_CLASSIFICATION: "Multiclass Classification",
}; };
const charWidths: { [char: string]: number } = { const charWidths: { [char: string]: number } = {
" ": 0.2, " ": 0.2,
"!": 0.2, "!": 0.2,
'"': 0.3, '"': 0.3,
"#": 0.5, "#": 0.5,
$: 0.5, $: 0.5,
"%": 0.5, "%": 0.5,
"&": 0.5, "&": 0.5,
"(": 0.2, "(": 0.2,
")": 0.2, ")": 0.2,
"*": 0.5, "*": 0.5,
"+": 0.5, "+": 0.5,
",": 0.2, ",": 0.2,
"-": 0.2, "-": 0.2,
".": 0.1, ".": 0.1,
"/": 0.5, "/": 0.5,
":": 0.2, ":": 0.2,
";": 0.2, ";": 0.2,
"<": 0.5, "<": 0.5,
"=": 0.5, "=": 0.5,
">": 0.5, ">": 0.5,
"?": 0.2, "?": 0.2,
"@": 0.5, "@": 0.5,
"[": 0.2, "[": 0.2,
"\\": 0.5, "\\": 0.5,
"]": 0.2, "]": 0.2,
"^": 0.5, "^": 0.5,
_: 0.2, _: 0.2,
"`": 0.5, "`": 0.5,
"{": 0.2, "{": 0.2,
"|": 0.2, "|": 0.2,
"}": 0.2, "}": 0.2,
"~": 0.5, "~": 0.5,
}; };
for (let i = 65; i <= 90; i++) { for (let i = 65; i <= 90; i++) {
charWidths[String.fromCharCode(i)] = 0.6; charWidths[String.fromCharCode(i)] = 0.6;
} }
for (let i = 97; i <= 122; i++) { for (let i = 97; i <= 122; i++) {
charWidths[String.fromCharCode(i)] = 0.5; charWidths[String.fromCharCode(i)] = 0.5;
} }
export function measureTextWidth(text: string, fontSize: number) { export function measureTextWidth(text: string, fontSize: number) {
let wordWidth = 0; let wordWidth = 0;
for (let j = 0; j < text.length; j++) { for (let j = 0; j < text.length; j++) {
let char = text[j]; let char = text[j];
let charWidth = charWidths[char] || 0.5; let charWidth = charWidths[char] || 0.5;
wordWidth += charWidth * fontSize; wordWidth += charWidth * fontSize;
} }
return wordWidth; return wordWidth;
} }
export function measureTextHeight( export function measureTextHeight(
text: string, text: string,
width: number, width: number,
fontSize: number fontSize: number
) { ) {
const charHeight = fontSize; const charHeight = fontSize;
const lineHeight = charHeight * 1.5; const lineHeight = charHeight * 1.5;
const words = text.split(" "); const words = text.split(" ");
let lineWidth = 0; let lineWidth = 0;
let totalHeight = 0; let totalHeight = 0;
for (let i = 0; i < words.length; i++) { for (let i = 0; i < words.length; i++) {
let word = words[i]; let word = words[i];
let wordWidth = measureTextWidth(word, fontSize); let wordWidth = measureTextWidth(word, fontSize);
if (lineWidth + wordWidth + charWidths[" "] * fontSize <= width) { if (lineWidth + wordWidth + charWidths[" "] * fontSize <= width) {
lineWidth += wordWidth + charWidths[" "] * fontSize; lineWidth += wordWidth + charWidths[" "] * fontSize;
} else { } else {
totalHeight += lineHeight; totalHeight += lineHeight;
lineWidth = wordWidth; lineWidth = wordWidth;
} }
} }
totalHeight += lineHeight; totalHeight += lineHeight;
return totalHeight; return totalHeight;
} }
export function toCamelCase(str: string) { export function toCamelCase(str: string) {
return str return str
.split(" ") .split(" ")
.map((word, index) => .map((word, index) =>
index === 0 index === 0
? word.toLowerCase() ? word.toLowerCase()
: word[0].toUpperCase() + word.slice(1).toLowerCase() : word[0].toUpperCase() + word.slice(1).toLowerCase()
) )
.join(""); .join("");
} }
export function toFirstUpperCase(str: string) { export function toFirstUpperCase(str: string) {
return str return str
.split(" ") .split(" ")
.map((word, index) => word[0].toUpperCase() + word.slice(1).toLowerCase()) .map((word, index) => word[0].toUpperCase() + word.slice(1).toLowerCase())
.join(""); .join("");
} }
export function toNormalCase(str: string) { export function toNormalCase(str: string) {
@ -337,62 +338,62 @@ export function toNormalCase(str: string) {
} }
export function normalCaseToSnakeCase(str: string) { export function normalCaseToSnakeCase(str: string) {
return str return str
.split(" ") .split(" ")
.map((word, index) => { .map((word, index) => {
if (index === 0) { if (index === 0) {
return word[0].toUpperCase() + word.slice(1).toLowerCase(); return word[0].toUpperCase() + word.slice(1).toLowerCase();
} }
return word.toLowerCase(); return word.toLowerCase();
}) })
.join("_"); .join("_");
} }
export function roundNumber(x: number, decimals: number) { export function roundNumber(x: number, decimals: number) {
return Math.round(x * Math.pow(10, decimals)) / Math.pow(10, decimals); return Math.round(x * Math.pow(10, decimals)) / Math.pow(10, decimals);
} }
export function getConnectedNodes(edge: Edge, nodes: Array<Node>): Array<Node> { export function getConnectedNodes(edge: Edge, nodes: Array<Node>): Array<Node> {
const sourceId = edge.source; const sourceId = edge.source;
const targetId = edge.target; const targetId = edge.target;
const connectedNodes = nodes.filter( const connectedNodes = nodes.filter(
(node) => node.id === targetId || node.id === sourceId (node) => node.id === targetId || node.id === sourceId
); );
return connectedNodes; return connectedNodes;
} }
export function isValidConnection( export function isValidConnection(
{ source, target, sourceHandle, targetHandle }: Connection, { source, target, sourceHandle, targetHandle }: Connection,
reactFlowInstance: ReactFlowInstance reactFlowInstance: ReactFlowInstance
) { ) {
if ( if (
sourceHandle.split("|")[0] === targetHandle.split("|")[0] || sourceHandle.split("|")[0] === targetHandle.split("|")[0] ||
sourceHandle sourceHandle
.split("|") .split("|")
.slice(2) .slice(2)
.some((t) => t === targetHandle.split("|")[0]) || .some((t) => t === targetHandle.split("|")[0]) ||
targetHandle.split("|")[0] === "str" targetHandle.split("|")[0] === "str"
) { ) {
let targetNode = reactFlowInstance.getNode(target).data.node; let targetNode = reactFlowInstance.getNode(target).data.node;
if (!targetNode) { if (!targetNode) {
if ( if (
!reactFlowInstance !reactFlowInstance
.getEdges() .getEdges()
.find((e) => e.targetHandle === targetHandle) .find((e) => e.targetHandle === targetHandle)
) { ) {
return true; return true;
} }
} else if ( } else if (
(!targetNode.template[targetHandle.split("|")[1]].list && (!targetNode.template[targetHandle.split("|")[1]].list &&
!reactFlowInstance !reactFlowInstance
.getEdges() .getEdges()
.find((e) => e.targetHandle === targetHandle)) || .find((e) => e.targetHandle === targetHandle)) ||
targetNode.template[targetHandle.split("|")[1]].list targetNode.template[targetHandle.split("|")[1]].list
) { ) {
return true; return true;
} }
} }
return false; return false;
} }
export function removeApiKeys(flow: FlowType): FlowType { export function removeApiKeys(flow: FlowType): FlowType {
@ -408,32 +409,48 @@ export function removeApiKeys(flow: FlowType): FlowType {
} }
export function updateObject<T extends Record<string, any>>( export function updateObject<T extends Record<string, any>>(
reference: T, reference: T,
objectToUpdate: T objectToUpdate: T
): T { ): T {
let clonedObject = _.cloneDeep(objectToUpdate); let clonedObject = _.cloneDeep(objectToUpdate);
// Loop through each key in the object to update // Loop through each key in the object to update
for (const key in clonedObject) { for (const key in clonedObject) {
// If the key is not in the reference object, delete it // If the key is not in the reference object, delete it
if (!(key in reference)) { if (!(key in reference)) {
delete clonedObject[key]; delete clonedObject[key];
} }
} }
// Loop through each key in the reference object // Loop through each key in the reference object
for (const key in reference) { for (const key in reference) {
// If the key is not in the object to update, add it // If the key is not in the object to update, add it
if (!(key in clonedObject)) { if (!(key in clonedObject)) {
clonedObject[key] = reference[key]; clonedObject[key] = reference[key];
} }
} }
return clonedObject; return clonedObject;
} }
export function debounce(func, wait) { export function debounce(func, wait) {
let timeout; let timeout;
return function (...args) { return function (...args) {
const context = this; const context = this;
clearTimeout(timeout); clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait); timeout = setTimeout(() => func.apply(context, args), wait);
}; };
}
export function updateTemplate(
reference: APITemplateType,
objectToUpdate: APITemplateType
): APITemplateType {
let clonedObject:APITemplateType = _.cloneDeep(reference);
// Loop through each key in the reference object
for (const key in clonedObject) {
// If the key is not in the object to update, add it
if (objectToUpdate[key] && objectToUpdate[key].value) {
clonedObject[key].value = objectToUpdate[key].value;
}
}
return clonedObject;
} }

View file

@ -1,11 +1,10 @@
import json import json
from pathlib import Path from pathlib import Path
from typing import AsyncGenerator from typing import AsyncGenerator
from httpx import AsyncClient
import pytest import pytest
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from httpx import AsyncClient
def pytest_configure(): def pytest_configure():

View file

@ -48,6 +48,7 @@ def test_zero_shot_agent(client: TestClient):
"type": "Tool", "type": "Tool",
"list": True, "list": True,
"advanced": False, "advanced": False,
"value": [],
} }

View file

@ -1,8 +1,9 @@
import pytest
from PIL import Image
import pandas as pd
from io import StringIO from io import StringIO
import pandas as pd
import pytest
from langflow.cache.manager import CacheManager from langflow.cache.manager import CacheManager
from PIL import Image
@pytest.fixture @pytest.fixture

View file

@ -291,7 +291,7 @@ def test_openai(client: TestClient):
"multiline": False, "multiline": False,
"password": False, "password": False,
"name": "request_timeout", "name": "request_timeout",
"type": "Union[float, Tuple[float, float], NoneType]", "type": "float",
"list": False, "list": False,
"advanced": False, "advanced": False,
} }
@ -418,10 +418,9 @@ def test_chat_open_ai(client: TestClient):
"placeholder": "", "placeholder": "",
"show": False, "show": False,
"multiline": False, "multiline": False,
"value": 60,
"password": False, "password": False,
"name": "request_timeout", "name": "request_timeout",
"type": "int", "type": "float",
"list": False, "list": False,
"advanced": False, "advanced": False,
} }

View file

@ -1,5 +1,6 @@
import json import json
from unittest.mock import patch from unittest.mock import patch
from fastapi.testclient import TestClient from fastapi.testclient import TestClient