Revert "Release 0.5.6"

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-11-06 23:18:49 -03:00 • committed by GitHub
commit 79b12b8135
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
134 changed files with 2228 additions and 11169 deletions

View file

@ -61,8 +61,6 @@ def set_var_for_macos_issue():
import os
os.environ["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"] = "YES"
# https://stackoverflow.com/questions/75747888/uwsgi-segmentation-fault-with-flask-python-app-behind-nginx-after-running-for-2 # noqa
os.environ["no_proxy"] = "*" # to avoid error with gunicorn
logger.debug("Set OBJC_DISABLE_INITIALIZE_FORK_SAFETY to YES to avoid error")

View file

@ -187,18 +187,14 @@ async def stream_build(
valid = False
update_build_status(cache_service, flow_id, BuildStatus.FAILURE)
vertex_id = (
vertex.parent_node_id if vertex.parent_is_top_level else vertex.id
)
if vertex_id in graph.top_level_nodes:
response = {
"valid": valid,
"params": params,
"id": vertex_id,
"progress": round(i / number_of_nodes, 2),
}
response = {
"valid": valid,
"params": params,
"id": vertex.id,
"progress": round(i / number_of_nodes, 2),
}
yield str(StreamData(event="message", data=response))
yield str(StreamData(event="message", data=response))
langchain_object = graph.build()
# Now we need to check the input_keys to send them to the client

View file

@ -54,9 +54,10 @@ def get_all(
logger.debug("Building langchain types dict")
try:
return get_all_types_dict(settings_service)
types_dict = get_all_types_dict(settings_service)
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc
return types_dict
# For backwards compatibility we will keep the old endpoint
@ -69,7 +70,7 @@ def get_all(
"/process/{flow_id}",
response_model=ProcessResponse,
)
async def process(
async def process_flow(
session: Annotated[Session, Depends(get_session)],
flow_id: str,
inputs: Optional[dict] = None,

View file

@ -79,5 +79,4 @@ class ConversationalAgent(CustomComponent):
memory=memory,
verbose=True,
return_intermediate_steps=True,
handle_parsing_errors=True,
)

View file

@ -1,232 +0,0 @@
from langflow import CustomComponent
from langchain.schema import Document
from typing import Any, Dict, List
loaders_info: List[Dict[str, Any]] = [
{
"loader": "AirbyteJSONLoader",
"name": "Airbyte JSON (.jsonl)",
"import": "langchain.document_loaders.AirbyteJSONLoader",
"defaultFor": ["jsonl"],
"allowdTypes": ["jsonl"],
},
{
"loader": "JSONLoader",
"name": "JSON (.json)",
"import": "langchain.document_loaders.JSONLoader",
"defaultFor": ["json"],
"allowdTypes": ["json"],
},
{
"loader": "BSHTMLLoader",
"name": "BeautifulSoup4 HTML (.html, .htm)",
"import": "langchain.document_loaders.BSHTMLLoader",
"allowdTypes": ["html", "htm"],
},
{
"loader": "CSVLoader",
"name": "CSV (.csv)",
"import": "langchain.document_loaders.CSVLoader",
"defaultFor": ["csv"],
"allowdTypes": ["csv"],
},
{
"loader": "CoNLLULoader",
"name": "CoNLL-U (.conllu)",
"import": "langchain.document_loaders.CoNLLULoader",
"defaultFor": ["conllu"],
"allowdTypes": ["conllu"],
},
{
"loader": "EverNoteLoader",
"name": "EverNote (.enex)",
"import": "langchain.document_loaders.EverNoteLoader",
"defaultFor": ["enex"],
"allowdTypes": ["enex"],
},
{
"loader": "FacebookChatLoader",
"name": "Facebook Chat (.json)",
"import": "langchain.document_loaders.FacebookChatLoader",
"allowdTypes": ["json"],
},
{
"loader": "OutlookMessageLoader",
"name": "Outlook Message (.msg)",
"import": "langchain.document_loaders.OutlookMessageLoader",
"defaultFor": ["msg"],
"allowdTypes": ["msg"],
},
{
"loader": "PyPDFLoader",
"name": "PyPDF (.pdf)",
"import": "langchain.document_loaders.PyPDFLoader",
"defaultFor": ["pdf"],
"allowdTypes": ["pdf"],
},
{
"loader": "STRLoader",
"name": "Subtitle (.str)",
"import": "langchain.document_loaders.STRLoader",
"defaultFor": ["str"],
"allowdTypes": ["str"],
},
{
"loader": "TextLoader",
"name": "Text (.txt)",
"import": "langchain.document_loaders.TextLoader",
"defaultFor": ["txt"],
"allowdTypes": ["txt"],
},
{
"loader": "UnstructuredEmailLoader",
"name": "Unstructured Email (.eml)",
"import": "langchain.document_loaders.UnstructuredEmailLoader",
"defaultFor": ["eml"],
"allowdTypes": ["eml"],
},
{
"loader": "UnstructuredHTMLLoader",
"name": "Unstructured HTML (.html, .htm)",
"import": "langchain.document_loaders.UnstructuredHTMLLoader",
"defaultFor": ["html", "htm"],
"allowdTypes": ["html", "htm"],
},
{
"loader": "UnstructuredMarkdownLoader",
"name": "Unstructured Markdown (.md)",
"import": "langchain.document_loaders.UnstructuredMarkdownLoader",
"defaultFor": ["md"],
"allowdTypes": ["md"],
},
{
"loader": "UnstructuredPowerPointLoader",
"name": "Unstructured PowerPoint (.pptx)",
"import": "langchain.document_loaders.UnstructuredPowerPointLoader",
"defaultFor": ["pptx"],
"allowdTypes": ["pptx"],
},
{
"loader": "UnstructuredWordLoader",
"name": "Unstructured Word (.docx)",
"import": "langchain.document_loaders.UnstructuredWordLoader",
"defaultFor": ["docx"],
"allowdTypes": ["docx"],
},
]
class FileLoaderComponent(CustomComponent):
display_name: str = "File Loader"
description: str = "Generic File Loader"
beta = True
def build_config(self):
loader_options = ["Automatic"] + [
loader_info["name"] for loader_info in loaders_info
]
file_types = []
suffixes = []
for loader_info in loaders_info:
if "allowedTypes" in loader_info:
file_types.extend(loader_info["allowedTypes"])
suffixes.extend([f".{ext}" for ext in loader_info["allowedTypes"]])
return {
"file_path": {
"display_name": "File Path",
"required": True,
"field_type": "file",
"file_types": [
"json",
"txt",
"csv",
"jsonl",
"html",
"htm",
"conllu",
"enex",
"msg",
"pdf",
"srt",
"eml",
"md",
"pptx",
"docx",
],
"suffixes": [
".json",
".txt",
".csv",
".jsonl",
".html",
".htm",
".conllu",
".enex",
".msg",
".pdf",
".srt",
".eml",
".md",
".pptx",
".docx",
],
# "file_types" : file_types,
# "suffixes": suffixes,
},
"loader": {
"display_name": "Loader",
"is_list": True,
"required": True,
"options": loader_options,
"value": "Automatic",
},
"code": {"show": False},
}
def build(self, file_path: str, loader: str) -> Document:
file_type = file_path.split(".")[-1]
# Mapeie o nome do loader selecionado para suas informações
selected_loader_info = None
for loader_info in loaders_info:
if loader_info["name"] == loader:
selected_loader_info = loader_info
break
if selected_loader_info is None and loader != "Automatic":
raise ValueError(f"Loader {loader} not found in the loader info list")
if loader == "Automatic":
# Determine o loader automaticamente com base na extensão do arquivo
default_loader_info = None
for info in loaders_info:
if "defaultFor" in info and file_type in info["defaultFor"]:
default_loader_info = info
break
if default_loader_info is None:
raise ValueError(f"No default loader found for file type: {file_type}")
selected_loader_info = default_loader_info
if isinstance(selected_loader_info, dict):
loader_import: str = selected_loader_info["import"]
else:
raise ValueError(
f"Loader info for {loader} is not a dict\nLoader info:\n{selected_loader_info}"
)
module_name, class_name = loader_import.rsplit(".", 1)
try:
# Importe o loader dinamicamente
loader_module = __import__(module_name, fromlist=[class_name])
loader_instance = getattr(loader_module, class_name)
except ImportError as e:
raise ValueError(
f"Loader {loader} could not be imported\nLoader info:\n{selected_loader_info}"
) from e
result = loader_instance(file_path=file_path)
return result.load()

View file

@ -1,62 +0,0 @@
from typing import List
from langflow import CustomComponent
from langchain.document_loaders import AZLyricsLoader
from langchain.document_loaders import CollegeConfidentialLoader
from langchain.document_loaders import GitbookLoader
from langchain.document_loaders import HNLoader
from langchain.document_loaders import IFixitLoader
from langchain.document_loaders import IMSDbLoader
from langchain.document_loaders import WebBaseLoader
from langchain.schema import Document
class UrlLoaderComponent(CustomComponent):
display_name: str = "Url Loader"
description: str = "Generic Url Loader Component"
def build_config(self):
return {
"web_path": {
"display_name": "Url",
"required": True,
},
"loader": {
"display_name": "Loader",
"is_list": True,
"required": True,
"options": [
"AZLyricsLoader",
"CollegeConfidentialLoader",
"GitbookLoader",
"HNLoader",
"IFixitLoader",
"IMSDbLoader",
"WebBaseLoader",
],
"value": "WebBaseLoader",
},
"code": {"show": False},
}
def build(self, web_path: str, loader: str) -> List[Document]:
if loader == "AZLyricsLoader":
loader_instance = AZLyricsLoader(web_path=web_path) # type: ignore
elif loader == "CollegeConfidentialLoader":
loader_instance = CollegeConfidentialLoader(web_path=web_path) # type: ignore
elif loader == "GitbookLoader":
loader_instance = GitbookLoader(web_page=web_path) # type: ignore
elif loader == "HNLoader":
loader_instance = HNLoader(web_path=web_path) # type: ignore
elif loader == "IFixitLoader":
loader_instance = IFixitLoader(web_path=web_path) # type: ignore
elif loader == "IMSDbLoader":
loader_instance = IMSDbLoader(web_path=web_path) # type: ignore
elif loader == "WebBaseLoader":
loader_instance = WebBaseLoader(web_path=web_path) # type: ignore
if loader_instance is None:
raise ValueError(f"No loader found for: {web_path}")
return loader_instance.load()

View file

@ -1,45 +0,0 @@
from typing import Optional
from langflow import CustomComponent
from langchain.llms.bedrock import Bedrock
from langchain.llms.base import BaseLLM
class AmazonBedrockComponent(CustomComponent):
display_name: str = "Amazon Bedrock"
description: str = "LLM model from Amazon Bedrock."
def build_config(self):
return {
"model_id": {
"display_name": "Model Id",
"options": [
"ai21.j2-grande-instruct",
"ai21.j2-jumbo-instruct",
"ai21.j2-mid",
"ai21.j2-mid-v1",
"ai21.j2-ultra",
"ai21.j2-ultra-v1",
"anthropic.claude-instant-v1",
"anthropic.claude-v1",
"anthropic.claude-v2",
"cohere.command-text-v14",
],
},
"credentials_profile_name": {"display_name": "Credentials Profile Name"},
"streaming": {"display_name": "Streaming", "field_type": "bool"},
"code": {"show": False},
}
def build(
self,
model_id: str = "anthropic.claude-instant-v1",
credentials_profile_name: Optional[str] = None,
) -> BaseLLM:
try:
output = Bedrock(
credentials_profile_name=credentials_profile_name,
model_id=model_id,
) # type: ignore
except Exception as e:
raise ValueError("Could not connect to AmazonBedrock API.") from e
return output

View file

@ -1,92 +0,0 @@
from typing import Optional
from langflow import CustomComponent
from langchain.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint
from langchain.llms.base import BaseLLM
class QianfanChatEndpointComponent(CustomComponent):
display_name: str = "QianfanChatEndpoint"
description: str = (
"Baidu Qianfan chat models. Get more detail from "
"https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint."
)
def build_config(self):
return {
"model": {
"display_name": "Model Name",
"options": [
"ERNIE-Bot",
"ERNIE-Bot-turbo",
"BLOOMZ-7B",
"Llama-2-7b-chat",
"Llama-2-13b-chat",
"Llama-2-70b-chat",
"Qianfan-BLOOMZ-7B-compressed",
"Qianfan-Chinese-Llama-2-7B",
"ChatGLM2-6B-32K",
"AquilaChat-7B",
],
"info": "https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint",
"required": True,
},
"qianfan_ak": {
"display_name": "Qianfan Ak",
"required": True,
"password": True,
"info": "which you could get from https://cloud.baidu.com/product/wenxinworkshop",
},
"qianfan_sk": {
"display_name": "Qianfan Sk",
"required": True,
"password": True,
"info": "which you could get from https://cloud.baidu.com/product/wenxinworkshop",
},
"top_p": {
"display_name": "Top p",
"field_type": "float",
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
"value": 0.8,
},
"temperature": {
"display_name": "Temperature",
"field_type": "float",
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
"value": 0.95,
},
"penalty_score": {
"display_name": "Penalty Score",
"field_type": "float",
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
"value": 1.0,
},
"endpoint": {
"display_name": "Endpoint",
"info": "Endpoint of the Qianfan LLM, required if custom model used.",
},
"code": {"show": False},
}
def build(
self,
model: str = "ERNIE-Bot-turbo",
qianfan_ak: Optional[str] = None,
qianfan_sk: Optional[str] = None,
top_p: Optional[float] = None,
temperature: Optional[float] = None,
penalty_score: Optional[float] = None,
endpoint: Optional[str] = None,
) -> BaseLLM:
try:
output = QianfanChatEndpoint( # type: ignore
model=model,
qianfan_ak=qianfan_ak,
qianfan_sk=qianfan_sk,
top_p=top_p,
temperature=temperature,
penalty_score=penalty_score,
endpoint=endpoint,
)
except Exception as e:
raise ValueError("Could not connect to Baidu Qianfan API.") from e
return output # type: ignore

View file

@ -1,92 +0,0 @@
from typing import Optional
from langflow import CustomComponent
from langchain.llms.baidu_qianfan_endpoint import QianfanLLMEndpoint
from langchain.llms.base import BaseLLM
class QianfanLLMEndpointComponent(CustomComponent):
display_name: str = "QianfanLLMEndpoint"
description: str = (
"Baidu Qianfan hosted open source or customized models. "
"Get more detail from https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint"
)
def build_config(self):
return {
"model": {
"display_name": "Model Name",
"options": [
"ERNIE-Bot",
"ERNIE-Bot-turbo",
"BLOOMZ-7B",
"Llama-2-7b-chat",
"Llama-2-13b-chat",
"Llama-2-70b-chat",
"Qianfan-BLOOMZ-7B-compressed",
"Qianfan-Chinese-Llama-2-7B",
"ChatGLM2-6B-32K",
"AquilaChat-7B",
],
"info": "https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint",
"required": True,
},
"qianfan_ak": {
"display_name": "Qianfan Ak",
"required": True,
"password": True,
"info": "which you could get from https://cloud.baidu.com/product/wenxinworkshop",
},
"qianfan_sk": {
"display_name": "Qianfan Sk",
"required": True,
"password": True,
"info": "which you could get from https://cloud.baidu.com/product/wenxinworkshop",
},
"top_p": {
"display_name": "Top p",
"field_type": "float",
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
"value": 0.8,
},
"temperature": {
"display_name": "Temperature",
"field_type": "float",
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
"value": 0.95,
},
"penalty_score": {
"display_name": "Penalty Score",
"field_type": "float",
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
"value": 1.0,
},
"endpoint": {
"display_name": "Endpoint",
"info": "Endpoint of the Qianfan LLM, required if custom model used.",
},
"code": {"show": False},
}
def build(
self,
model: str = "ERNIE-Bot-turbo",
qianfan_ak: Optional[str] = None,
qianfan_sk: Optional[str] = None,
top_p: Optional[float] = None,
temperature: Optional[float] = None,
penalty_score: Optional[float] = None,
endpoint: Optional[str] = None,
) -> BaseLLM:
try:
output = QianfanLLMEndpoint( # type: ignore
model=model,
qianfan_ak=qianfan_ak,
qianfan_sk=qianfan_sk,
top_p=top_p,
temperature=temperature,
penalty_score=penalty_score,
endpoint=endpoint,
)
except Exception as e:
raise ValueError("Could not connect to Baidu Qianfan API.") from e
return output # type: ignore

View file

@ -1,6 +1,6 @@
from typing import Optional
from langflow import CustomComponent
from langchain.llms.huggingface_endpoint import HuggingFaceEndpoint
from langchain.llms import HuggingFaceEndpoint
from langchain.llms.base import BaseLLM
@ -13,6 +13,7 @@ class HuggingFaceEndpointsComponent(CustomComponent):
"endpoint_url": {"display_name": "Endpoint URL", "password": True},
"task": {
"display_name": "Task",
"type": "select",
"options": ["text2text-generation", "text-generation", "summarization"],
},
"huggingfacehub_api_token": {"display_name": "API token", "password": True},
@ -26,7 +27,7 @@ class HuggingFaceEndpointsComponent(CustomComponent):
def build(
self,
endpoint_url: str,
task: str = "text2text-generation",
task="text2text-generation",
huggingfacehub_api_token: Optional[str] = None,
model_kwargs: Optional[dict] = None,
) -> BaseLLM:
@ -35,7 +36,6 @@ class HuggingFaceEndpointsComponent(CustomComponent):
endpoint_url=endpoint_url,
task=task,
huggingfacehub_api_token=huggingfacehub_api_token,
model_kwargs=model_kwargs,
)
except Exception as e:
raise ValueError("Could not connect to HuggingFace Endpoints API.") from e

View file

@ -1,48 +0,0 @@
from typing import Optional
from langflow import CustomComponent
from langchain.retrievers import AmazonKendraRetriever
from langchain.schema import BaseRetriever
class AmazonKendraRetrieverComponent(CustomComponent):
display_name: str = "Amazon Kendra Retriever"
description: str = "Retriever that uses the Amazon Kendra API."
def build_config(self):
return {
"index_id": {"display_name": "Index ID"},
"region_name": {"display_name": "Region Name"},
"credentials_profile_name": {"display_name": "Credentials Profile Name"},
"attribute_filter": {
"display_name": "Attribute Filter",
"field_type": "code",
},
"top_k": {"display_name": "Top K", "field_type": "int"},
"user_context": {
"display_name": "User Context",
"field_type": "code",
},
"code": {"show": False},
}
def build(
self,
index_id: str,
top_k: int = 3,
region_name: Optional[str] = None,
credentials_profile_name: Optional[str] = None,
attribute_filter: Optional[dict] = None,
user_context: Optional[dict] = None,
) -> BaseRetriever:
try:
output = AmazonKendraRetriever(
index_id=index_id,
top_k=top_k,
region_name=region_name,
credentials_profile_name=credentials_profile_name,
attribute_filter=attribute_filter,
user_context=user_context,
) # type: ignore
except Exception as e:
raise ValueError("Could not connect to AmazonKendra API.") from e
return output

View file

@ -14,7 +14,7 @@ class ChromaComponent(CustomComponent):
A custom component for implementing a Vector Store using Chroma.
"""
display_name: str = "Chroma"
display_name: str = "Chroma (Custom Component)"
description: str = "Implementation of Vector Store using Chroma"
documentation = "https://python.langchain.com/docs/integrations/vectorstores/chroma"
beta = True

View file

@ -5,6 +5,7 @@ from langchain.vectorstores import Vectara
from langchain.schema import Document
from langchain.vectorstores.base import VectorStore
from langchain.schema import BaseRetriever
from langchain.embeddings.base import Embeddings
class VectaraComponent(CustomComponent):
@ -21,6 +22,7 @@ class VectaraComponent(CustomComponent):
"vectara_api_key": {"display_name": "Vectara API Key", "password": True},
"code": {"show": False},
"documents": {"display_name": "Documents"},
"embedding": {"display_name": "Embedding"},
}
def build(
@ -28,21 +30,21 @@ class VectaraComponent(CustomComponent):
vectara_customer_id: str,
vectara_corpus_id: str,
vectara_api_key: str,
embedding: Optional[Embeddings] = None,
documents: Optional[Document] = None,
) -> Union[VectorStore, BaseRetriever]:
# If documents, then we need to create a Vectara instance using .from_documents
if documents is not None:
if documents is not None and embedding is not None:
return Vectara.from_documents(
documents=documents, # type: ignore
vectara_customer_id=vectara_customer_id,
vectara_corpus_id=vectara_corpus_id,
vectara_api_key=vectara_api_key,
source="langflow",
embedding=embedding,
)
return Vectara(
vectara_customer_id=vectara_customer_id,
vectara_corpus_id=vectara_corpus_id,
vectara_api_key=vectara_api_key,
source="langflow",
)

View file

@ -265,8 +265,8 @@ retrievers:
# ZepRetriever:
# documentation: "https://python.langchain.com/docs/modules/data_connection/retrievers/integrations/zep_memorystore"
vectorstores:
# Chroma:
# documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/chroma"
Chroma:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/chroma"
Qdrant:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/qdrant"
Weaviate:

View file

@ -1,53 +1,3 @@
# LANGCHAIN_BASE_TYPES = {
# "Chain": Chain,
# "AgentExecutor": AgentExecutor,
# "Tool": Tool,
# "BaseLLM": BaseLLM,
# "PromptTemplate": PromptTemplate,
# "BaseLoader": BaseLoader,
# "Document": Document,
# "TextSplitter": TextSplitter,
# "VectorStore": VectorStore,
# "Embeddings": Embeddings,
# "BaseRetriever": BaseRetriever,
# "BaseOutputParser": BaseOutputParser,
# "BaseMemory": BaseMemory,
# "BaseChatMemory": BaseChatMemory,
# }
from .constants import (
Tool,
PromptTemplate,
Chain,
BaseChatMemory,
BaseLLM,
BaseLoader,
BaseMemory,
BaseOutputParser,
BaseRetriever,
VectorStore,
Embeddings,
TextSplitter,
Document,
AgentExecutor,
NestedDict,
Data,
)
from .base import NestedDict
__all__ = [
"NestedDict",
"Data",
"Tool",
"PromptTemplate",
"Chain",
"BaseChatMemory",
"BaseLLM",
"BaseLoader",
"BaseMemory",
"BaseOutputParser",
"BaseRetriever",
"VectorStore",
"Embeddings",
"TextSplitter",
"Document",
"AgentExecutor",
]
__all__ = ["NestedDict"]

View file

@ -0,0 +1,4 @@
from typing import Union, Dict
# Type alias for more complex dicts
NestedDict = Dict[str, Union[str, Dict]]

View file

@ -1,50 +0,0 @@
from langchain.agents.agent import AgentExecutor
from langchain.chains.base import Chain
from langchain.document_loaders.base import BaseLoader
from langchain.llms.base import BaseLLM
from langchain.memory.chat_memory import BaseChatMemory
from langchain.prompts import PromptTemplate
from langchain.schema import BaseOutputParser, BaseRetriever, Document
from langchain.schema.embeddings import Embeddings
from langchain.schema.memory import BaseMemory
from langchain.text_splitter import TextSplitter
from langchain.tools import Tool
from langchain.vectorstores.base import VectorStore
from typing import Union, Dict
# Type alias for more complex dicts
NestedDict = Dict[str, Union[str, Dict]]
class Data:
pass
LANGCHAIN_BASE_TYPES = {
"Chain": Chain,
"AgentExecutor": AgentExecutor,
"Tool": Tool,
"BaseLLM": BaseLLM,
"PromptTemplate": PromptTemplate,
"BaseLoader": BaseLoader,
"Document": Document,
"TextSplitter": TextSplitter,
"VectorStore": VectorStore,
"Embeddings": Embeddings,
"BaseRetriever": BaseRetriever,
"BaseOutputParser": BaseOutputParser,
"BaseMemory": BaseMemory,
"BaseChatMemory": BaseChatMemory,
}
# Langchain base types plus Python base types
CUSTOM_COMPONENT_SUPPORTED_TYPES = {
**LANGCHAIN_BASE_TYPES,
"str": str,
"int": int,
"float": float,
"bool": bool,
"list": list,
"dict": dict,
"NestedDict": NestedDict,
"Data": Data,
}

View file

@ -1,79 +1,28 @@
from loguru import logger
from typing import TYPE_CHECKING
from pydantic import BaseModel, Field
from typing import List, Optional
if TYPE_CHECKING:
from langflow.graph.vertex.base import Vertex
class SourceHandle(BaseModel):
baseClasses: List[str] = Field(
..., description="List of base classes for the source handle."
)
dataType: str = Field(..., description="Data type for the source handle.")
id: str = Field(..., description="Unique identifier for the source handle.")
class TargetHandle(BaseModel):
fieldName: str = Field(..., description="Field name for the target handle.")
id: str = Field(..., description="Unique identifier for the target handle.")
inputTypes: Optional[List[str]] = Field(
None, description="List of input types for the target handle."
)
type: str = Field(..., description="Type of the target handle.")
class Edge:
def __init__(self, source: "Vertex", target: "Vertex", edge: dict):
self.source: "Vertex" = source
self.target: "Vertex" = target
if data := edge.get("data", {}):
self._source_handle = data.get("sourceHandle", {})
self._target_handle = data.get("targetHandle", {})
self.source_handle: SourceHandle = SourceHandle(**self._source_handle)
self.target_handle: TargetHandle = TargetHandle(**self._target_handle)
self.target_param = self.target_handle.fieldName
# validate handles
self.validate_handles()
else:
# Logging here because this is a breaking change
logger.error("Edge data is empty")
self._source_handle = edge.get("sourceHandle", "")
self._target_handle = edge.get("targetHandle", "")
# 'BaseLoader;BaseOutputParser|documents|PromptTemplate-zmTlD'
# target_param is documents
self.target_param = self._target_handle.split("|")[1]
# Validate in __init__ to fail fast
self.validate_edge()
self.source_handle = edge.get("sourceHandle", "")
self.target_handle = edge.get("targetHandle", "")
# 'BaseLoader;BaseOutputParser|documents|PromptTemplate-zmTlD'
# target_param is documents
self.target_param = self.target_handle.split("|")[1]
def validate_handles(self) -> None:
if self.target_handle.inputTypes is None:
self.valid_handles = (
self.target_handle.type in self.source_handle.baseClasses
)
else:
self.valid_handles = (
any(
baseClass in self.target_handle.inputTypes
for baseClass in self.source_handle.baseClasses
)
or self.target_handle.type in self.source_handle.baseClasses
)
if not self.valid_handles:
logger.debug(self.source_handle)
logger.debug(self.target_handle)
raise ValueError(
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} "
f"has invalid handles"
)
self.validate_edge()
def __setstate__(self, state):
self.source = state["source"]
self.target = state["target"]
self.target_param = state["target_param"]
self.source_handle = state.get("source_handle")
self.target_handle = state.get("target_handle")
self.source_handle = state["source_handle"]
self.target_handle = state["target_handle"]
def reset(self) -> None:
self.source._build_params()

View file

@ -2,7 +2,6 @@ from typing import Dict, Generator, List, Type, Union
from langflow.graph.edge.base import Edge
from langflow.graph.graph.constants import lazy_load_vertex_dict
from langflow.graph.graph.utils import process_flow
from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.types import (
FileToolVertex,
@ -20,21 +19,11 @@ class Graph:
def __init__(
self,
nodes: List[Dict],
nodes: List[Dict[str, Union[str, Dict[str, Union[str, List[str]]]]]],
edges: List[Dict[str, str]],
) -> None:
self._nodes = nodes
self._edges = edges
self.raw_graph_data = {"nodes": nodes, "edges": edges}
self.top_level_nodes = []
for node in self._nodes:
if node_id := node.get("id"):
self.top_level_nodes.append(node_id)
self._graph_data = process_flow(self.raw_graph_data)
self._nodes = self._graph_data["nodes"]
self._edges = self._graph_data["edges"]
self._build_graph()
def __setstate__(self, state):
@ -61,7 +50,6 @@ class Graph:
edges = payload["edges"]
return cls(nodes, edges)
except KeyError as exc:
logger.exception(exc)
raise ValueError(
f"Invalid payload. Expected keys 'nodes' and 'edges'. Found {list(payload.keys())}"
) from exc
@ -227,9 +215,7 @@ class Graph:
node_lc_type: str = node_data["node"]["template"]["_type"] # type: ignore
VertexClass = self._get_vertex_class(node_type, node_lc_type)
vertex = VertexClass(node)
vertex.set_top_level(self.top_level_nodes)
nodes.append(vertex)
nodes.append(VertexClass(node))
return nodes

View file

@ -1,230 +0,0 @@
from collections import deque
import copy
def find_last_node(nodes, edges):
"""
This function receives a flow and returns the last node.
"""
return next((n for n in nodes if all(e["source"] != n["id"] for e in edges)), None)
def add_parent_node_id(nodes, parent_node_id):
"""
This function receives a list of nodes and adds a parent_node_id to each node.
"""
for node in nodes:
node["parent_node_id"] = parent_node_id
def ungroup_node(group_node_data, base_flow):
template, flow = (
group_node_data["node"]["template"],
group_node_data["node"]["flow"],
)
parent_node_id = group_node_data["id"]
g_nodes = flow["data"]["nodes"]
add_parent_node_id(g_nodes, parent_node_id)
g_edges = flow["data"]["edges"]
# Redirect edges to the correct proxy node
updated_edges = get_updated_edges(
base_flow, g_nodes, g_edges, group_node_data["id"]
)
# Update template values
update_template(template, g_nodes)
nodes = [
n for n in base_flow["nodes"] if n["id"] != group_node_data["id"]
] + g_nodes
edges = (
[
e
for e in base_flow["edges"]
if e["target"] != group_node_data["id"]
and e["source"] != group_node_data["id"]
]
+ g_edges
+ updated_edges
)
base_flow["nodes"] = nodes
base_flow["edges"] = edges
return nodes
def process_flow(flow_object):
cloned_flow = copy.deepcopy(flow_object)
processed_nodes = set() # To keep track of processed nodes
def process_node(node):
node_id = node.get("id")
# If node already processed, skip
if node_id in processed_nodes:
return
if (
node.get("data")
and node["data"].get("node")
and node["data"]["node"].get("flow")
):
process_flow(node["data"]["node"]["flow"]["data"])
new_nodes = ungroup_node(node["data"], cloned_flow)
# Add new nodes to the queue for future processing
nodes_to_process.extend(new_nodes)
# Mark node as processed
processed_nodes.add(node_id)
nodes_to_process = deque(cloned_flow["nodes"])
while nodes_to_process:
node = nodes_to_process.popleft()
process_node(node)
return cloned_flow
def update_template(template, g_nodes):
"""
Updates the template of a node in a graph with the given template.
Args:
template (dict): The new template to update the node with.
g_nodes (list): The list of nodes in the graph.
Returns:
None
"""
for _, value in template.items():
if not value.get("proxy"):
continue
proxy_dict = value["proxy"]
field, id_ = proxy_dict["field"], proxy_dict["id"]
node_index = next((i for i, n in enumerate(g_nodes) if n["id"] == id_), -1)
if node_index != -1:
display_name = None
show = g_nodes[node_index]["data"]["node"]["template"][field]["show"]
advanced = g_nodes[node_index]["data"]["node"]["template"][field][
"advanced"
]
if "display_name" in g_nodes[node_index]["data"]["node"]["template"][field]:
display_name = g_nodes[node_index]["data"]["node"]["template"][field][
"display_name"
]
else:
display_name = g_nodes[node_index]["data"]["node"]["template"][field][
"name"
]
g_nodes[node_index]["data"]["node"]["template"][field] = value
g_nodes[node_index]["data"]["node"]["template"][field]["show"] = show
g_nodes[node_index]["data"]["node"]["template"][field][
"advanced"
] = advanced
g_nodes[node_index]["data"]["node"]["template"][field][
"display_name"
] = display_name
def update_target_handle(new_edge, g_nodes, group_node_id):
"""
Updates the target handle of a given edge if it is a proxy node.
Args:
new_edge (dict): The edge to update.
g_nodes (list): The list of nodes in the graph.
group_node_id (str): The ID of the group node.
Returns:
dict: The updated edge.
"""
target_handle = new_edge["data"]["targetHandle"]
if target_handle.get("proxy"):
proxy_id = target_handle["proxy"]["id"]
if node := next((n for n in g_nodes if n["id"] == proxy_id), None):
set_new_target_handle(proxy_id, new_edge, target_handle, node)
return new_edge
def set_new_target_handle(proxy_id, new_edge, target_handle, node):
"""
Sets a new target handle for a given edge.
Args:
proxy_id (str): The ID of the proxy.
new_edge (dict): The new edge to be created.
target_handle (dict): The target handle of the edge.
node (dict): The node containing the edge.
Returns:
None
"""
new_edge["target"] = proxy_id
_type = target_handle.get("type")
if _type is None:
raise KeyError("The 'type' key must be present in target_handle.")
field = target_handle["proxy"]["field"]
new_target_handle = {
"fieldName": field,
"type": _type,
"id": proxy_id,
}
if node["data"]["node"].get("flow"):
new_target_handle["proxy"] = {
"field": node["data"]["node"]["template"][field]["proxy"]["field"],
"id": node["data"]["node"]["template"][field]["proxy"]["id"],
}
if input_types := target_handle.get("inputTypes"):
new_target_handle["inputTypes"] = input_types
new_edge["data"]["targetHandle"] = new_target_handle
def update_source_handle(new_edge, g_nodes, g_edges):
"""
Updates the source handle of a given edge to the last node in the flow data.
Args:
new_edge (dict): The edge to update.
flow_data (dict): The flow data containing the nodes and edges.
Returns:
dict: The updated edge with the new source handle.
"""
last_node = copy.deepcopy(find_last_node(g_nodes, g_edges))
new_edge["source"] = last_node["id"]
new_source_handle = new_edge["data"]["sourceHandle"]
new_source_handle["id"] = last_node["id"]
new_edge["data"]["sourceHandle"] = new_source_handle
return new_edge
def get_updated_edges(base_flow, g_nodes, g_edges, group_node_id):
"""
Given a base flow, a list of graph nodes and a group node id, returns a list of updated edges.
An updated edge is an edge that has its target or source handle updated based on the group node id.
Args:
base_flow (dict): The base flow containing a list of edges.
g_nodes (list): A list of graph nodes.
group_node_id (str): The id of the group node.
Returns:
list: A list of updated edges.
"""
updated_edges = []
for edge in base_flow["edges"]:
new_edge = copy.deepcopy(edge)
if new_edge["target"] == group_node_id:
new_edge = update_target_handle(new_edge, g_nodes, group_node_id)
if new_edge["source"] == group_node_id:
new_edge = update_source_handle(new_edge, g_nodes, g_edges)
if edge["target"] == group_node_id or edge["source"] == group_node_id:
updated_edges.append(new_edge)
return updated_edges

View file

@ -38,8 +38,6 @@ class Vertex:
self.task_id: Optional[str] = None
self.is_task = is_task
self.params = params or {}
self.parent_node_id: Optional[str] = self._data.get("parent_node_id")
self.parent_is_top_level = False
def reset_params(self):
for edge in self.edges:
@ -90,11 +88,6 @@ class Vertex:
self._built = False
self.artifacts: Dict[str, Any] = {}
self.task_id: Optional[str] = None
self.parent_node_id = state["parent_node_id"]
self.parent_is_top_level = state["parent_is_top_level"]
def set_top_level(self, top_level_nodes: List[str]) -> None:
self.parent_is_top_level = self.parent_node_id in top_level_nodes
def _parse_data(self) -> None:
self.data = self._data["data"]
@ -216,16 +209,6 @@ class Vertex:
}
elif isinstance(_value, dict):
params[key] = _value
elif value.get("type") == "int" and value.get("value") is not None:
try:
params[key] = int(value.get("value"))
except ValueError:
params[key] = value.get("value")
elif value.get("type") == "float" and value.get("value") is not None:
try:
params[key] = float(value.get("value"))
except ValueError:
params[key] = value.get("value")
else:
params[key] = value.get("value")
@ -359,7 +342,7 @@ class Vertex:
except Exception as exc:
logger.exception(exc)
raise ValueError(
f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}"
f"Error building node {self.vertex_type}: {str(exc)}"
) from exc
def _update_built_object_and_artifacts(self, result):

View file

@ -206,10 +206,6 @@ class ChainVertex(Vertex):
) -> Any:
if not self._built or force:
# Check if the chain requires a PromptVertex
# Temporarily remove "code" from the params
self.params.pop("code", None)
for key, value in self.params.items():
if isinstance(value, PromptVertex):
# Build the PromptVertex, passing the tools if available

View file

@ -16,10 +16,8 @@ from langchain.agents.agent_toolkits import (
)
from langchain.agents.agent_toolkits.json.prompt import JSON_PREFIX, JSON_SUFFIX
from langchain.agents.agent_toolkits.json.toolkit import JsonToolkit
from langchain_experimental.agents.agent_toolkits.pandas.prompt import (
PREFIX as PANDAS_PREFIX,
)
from langchain_experimental.agents.agent_toolkits.pandas.prompt import (
from langchain.agents.agent_toolkits.pandas.prompt import PREFIX as PANDAS_PREFIX
from langchain.agents.agent_toolkits.pandas.prompt import (
SUFFIX_WITH_DF as PANDAS_SUFFIX,
)
from langchain.agents.agent_toolkits.sql.prompt import SQL_PREFIX, SQL_SUFFIX
@ -33,7 +31,7 @@ from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
from langchain.base_language import BaseLanguageModel
from langchain.memory.chat_memory import BaseChatMemory
from langchain.sql_database import SQLDatabase
from langchain_experimental.tools.python.tool import PythonAstREPLTool
from langchain.tools.python.tool import PythonAstREPLTool
from langchain.tools.sql_database.prompt import QUERY_CHECKER
from langflow.interface.base import CustomAgentExecutor

View file

@ -1,33 +1,65 @@
from langchain.prompts import PromptTemplate
from langchain.chains.base import Chain
from langchain.document_loaders.base import BaseLoader
from langchain.schema.embeddings import Embeddings
from langchain.llms.base import BaseLLM
from langchain.schema import BaseRetriever, Document
from langchain.text_splitter import TextSplitter
from langchain.tools import Tool
from langchain.vectorstores.base import VectorStore
from langchain.schema import BaseOutputParser
from langchain.schema.memory import BaseMemory
from langchain.memory.chat_memory import BaseChatMemory
from langchain.agents.agent import AgentExecutor
LANGCHAIN_BASE_TYPES = {
"Chain": Chain,
"AgentExecutor": AgentExecutor,
"Tool": Tool,
"BaseLLM": BaseLLM,
"PromptTemplate": PromptTemplate,
"BaseLoader": BaseLoader,
"Document": Document,
"TextSplitter": TextSplitter,
"VectorStore": VectorStore,
"Embeddings": Embeddings,
"BaseRetriever": BaseRetriever,
"BaseOutputParser": BaseOutputParser,
"BaseMemory": BaseMemory,
"BaseChatMemory": BaseChatMemory,
}
# Langchain base types plus Python base types
CUSTOM_COMPONENT_SUPPORTED_TYPES = {
**LANGCHAIN_BASE_TYPES,
"str": str,
"int": int,
"float": float,
"bool": bool,
"list": list,
"dict": dict,
}
DEFAULT_CUSTOM_COMPONENT_CODE = """from langflow import CustomComponent
from langflow.field_typing import (
Tool,
PromptTemplate,
Chain,
BaseChatMemory,
BaseLLM,
BaseLoader,
BaseMemory,
BaseOutputParser,
BaseRetriever,
VectorStore,
Embeddings,
TextSplitter,
Document,
AgentExecutor,
NestedDict,
Data,
)
from langchain.llms.base import BaseLLM
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.schema import Document
import requests
class Component(CustomComponent):
class YourComponent(CustomComponent):
display_name: str = "Custom Component"
description: str = "Create any custom component you want!"
def build_config(self):
return {"param": {"display_name": "Parameter"}}
def build(self, param: Data) -> Data:
return param
return { "url": { "multiline": True, "required": True } }
def build(self, url: str, llm: BaseLLM, prompt: PromptTemplate) -> Document:
response = requests.get(url)
chain = LLMChain(llm=llm, prompt=prompt)
result = chain.run(response.text[:300])
return Document(page_content=str(result))
"""

View file

@ -1,7 +1,7 @@
from typing import Any, Callable, List, Optional, Union
from uuid import UUID
from fastapi import HTTPException
from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
from langflow.interface.custom.component import Component
from langflow.interface.custom.directory_reader import DirectoryReader
from langflow.services.getters import get_db_service
@ -108,9 +108,6 @@ class CustomComponent(Component, extra=Extra.allow):
),
},
)
elif not arg.get("type"):
# Set the type to Data
arg["type"] = "Data"
return args
@property

View file

@ -5,6 +5,7 @@ from langchain.agents.load_tools import (
_EXTRA_OPTIONAL_TOOLS,
_LLM_TOOLS,
)
from langchain.tools.python.tool import PythonInputs
from langflow.custom import customs
from langflow.interface.base import LangChainTypeCreator
@ -161,8 +162,14 @@ class ToolCreator(LangChainTypeCreator):
template = Template(fields=fields, type_name=tool_type)
tool_params = {**tool_params, **self.type_to_loader_dict[name]["params"]}
template_dict = template.to_dict()
if (
"args_schema" in template_dict
and template_dict.get("args_schema").get("value") == PythonInputs
):
template_dict["args_schema"]["value"] = ""
return {
"template": util.format_dict(template.to_dict()),
"template": util.format_dict(template_dict),
**tool_params,
"base_classes": base_classes,
}

View file

@ -4,7 +4,7 @@ from typing import Any, List
from langflow.api.utils import get_new_key
from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator
from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
from langflow.interface.custom.utils import extract_inner_type
from langflow.interface.document_loaders.base import documentloader_creator
from langflow.interface.embeddings.base import embedding_creator
@ -288,24 +288,6 @@ def add_base_classes(frontend_node, return_types: List[str]):
frontend_node.get("base_classes").append(base_class)
def add_output_types(frontend_node, return_types: List[str]):
"""Add output types to the frontend node"""
for return_type in return_types:
if return_type not in CUSTOM_COMPONENT_SUPPORTED_TYPES or return_type is None:
raise HTTPException(
status_code=400,
detail={
"error": (
"Invalid return type should be one of: "
f"{list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())}"
),
"traceback": traceback.format_exc(),
},
)
frontend_node.get("output_types").append(return_type)
def build_langchain_template_custom_component(custom_component: CustomComponent):
"""Build a custom component template for the langchain"""
try:
@ -332,9 +314,6 @@ def build_langchain_template_custom_component(custom_component: CustomComponent)
add_base_classes(
frontend_node, custom_component.get_function_entrypoint_return_type
)
add_output_types(
frontend_node, custom_component.get_function_entrypoint_return_type
)
logger.debug("Added base classes")
return frontend_node
except Exception as exc:

View file

@ -28,10 +28,14 @@ class UtilityCreator(LangChainTypeCreator):
"""
if self.type_dict is None:
settings_service = get_settings_service()
self.type_dict = {
utility_name: import_class(f"langchain.utilities.{utility_name}")
for utility_name in utilities.__all__
}
self.type_dict = {}
for utility_name in utilities.__all__:
try:
imported = import_class(f"langchain.utilities.{utility_name}")
self.type_dict[utility_name] = imported
except Exception:
pass
self.type_dict["SQLDatabase"] = utilities.SQLDatabase
# Filter according to settings.utilities
self.type_dict = {

View file

@ -34,9 +34,7 @@ def get_langfuse_callback(trace_id):
if langfuse := LangfuseInstance.get():
logger.debug("Langfuse credentials found")
try:
trace = langfuse.trace(
CreateTrace(name="langflow-" + trace_id, id=trace_id)
)
trace = langfuse.trace(CreateTrace(id=trace_id))
return trace.getNewHandler()
except Exception as exc:
logger.error(f"Error initializing langfuse callback: {exc}")

View file

@ -32,11 +32,7 @@ def initialize_database():
try:
database_service.run_migrations()
except CommandError as exc:
# if "overlaps with other requested revisions" or "Can't locate revision identified by"
# are not in the exception, we can't handle it
if "overlaps with other requested revisions" not in str(
exc
) and "Can't locate revision identified by" not in str(exc):
if "Can't locate revision identified by" not in str(exc):
raise exc
# This means there's wrong revision in the DB
# We need to delete the alembic_version table

View file

@ -44,7 +44,7 @@ class FieldFormatters(BaseModel):
class FrontendNode(BaseModel):
template: Template
description: Optional[str] = None
description: str
base_classes: List[str]
name: str = ""
display_name: str = ""

View file

@ -87,8 +87,6 @@ class ChainFrontendNode(FrontendNode):
field.required = True
field.show = True
field.advanced = False
field.field_type = "BaseLanguageModel" # temporary fix
field.is_list = False
if field.name == "return_source_documents":
field.required = False

View file

@ -2,7 +2,6 @@ from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode
from langflow.template.template.base import Template
from langflow.interface.custom.constants import DEFAULT_CUSTOM_COMPONENT_CODE
from typing import Optional
class CustomComponentFrontendNode(FrontendNode):
@ -25,7 +24,7 @@ class CustomComponentFrontendNode(FrontendNode):
)
],
)
description: Optional[str] = None
description: str = "Create any custom component you want!"
base_classes: list[str] = []
def to_dict(self):

View file

@ -44,7 +44,6 @@ class PromptFrontendNode(FrontendNode):
# All prompt fields should be password=False
field.password = False
field.dynamic = True
class PromptTemplateNode(FrontendNode):

View file

@ -6,11 +6,14 @@ OPENAI_MODELS = [
"text-ada-001",
]
CHAT_OPENAI_MODELS = [
"gpt-4-1106-preview",
"gpt-3.5-turbo-0613",
"gpt-3.5-turbo",
"gpt-3.5-turbo-16k-0613",
"gpt-3.5-turbo-16k",
"gpt-4-0613",
"gpt-4-32k-0613",
"gpt-4",
"gpt-4-32k",
"gpt-3.5-turbo",
"gpt-3.5-turbo-16k",
]
ANTHROPIC_MODELS = [

View file

@ -191,9 +191,7 @@ def get_base_classes(cls):
"""Get the base classes of a class.
These are used to determine the output of the nodes.
"""
if hasattr(cls, "__bases__") and cls.__bases__:
bases = cls.__bases__
if bases := cls.__bases__:
result = []
for base in bases:
if any(type in base.__module__ for type in ["pydantic", "abc"]):
@ -276,7 +274,6 @@ def format_dict(
_type = remove_optional_wrapper(_type)
_type = check_list_type(_type, value)
_type = replace_mapping_with_dict(_type)
_type = get_type_from_union_literal(_type)
value["type"] = get_formatted_type(key, _type)
value["show"] = should_show_field(value, key)
@ -463,11 +460,3 @@ def build_loader_repr_from_documents(documents: List[Document]) -> str:
\nAvg. Document Length (characters): {int(avg_length)}
Documents: {documents[:3]}..."""
return "0 documents"
def get_type_from_union_literal(union_literal: str) -> str:
# if types are literal strings
# the type is a string
if "Literal" in union_literal:
return "str"
return union_literal

View file

@ -22,5 +22,5 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
/test-results/
/playwright-report/*/
/playwright-report/
/playwright/.cache/

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li>
<a href="./e2e/index.html">e2e report</a>
</li>
<li>
<a href="./onlyFront/index.html">frontEnd Only report</a>
</li>
</ul>
</body>
</html>

View file

@ -20,13 +20,11 @@ export default defineConfig({
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
["html", { open: "never", outputFolder: "playwright-report/test-results" }],
],
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: "http://127.0.0.1:3000",
// baseURL: 'http://127.0.0.1:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
@ -71,16 +69,9 @@ export default defineConfig({
],
/* Run your local dev server before starting the tests */
// webServer: [
// {
// command: "npm run backend",
// reuseExistingServer: !process.env.CI,
// timeout: 120 * 1000,
// },
// {
// command: "npm run start",
// url: "http://127.0.0.1:3000",
// reuseExistingServer: !process.env.CI,
// },
// ],
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
});

View file

@ -1,78 +0,0 @@
#!/bin/bash
# Default value for the --ui flag
ui=false
# Parse command-line arguments
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
--ui)
ui=true
shift
;;
*)
echo "Unknown option: $key"
exit 1
;;
esac
shift
done
# Function to forcibly terminate a process by port
terminate_process_by_port() {
port="$1"
echo "Terminating process on port: $port"
fuser -k -n tcp "$port" # Forcefully terminate processes using the specified port
echo "Process terminated."
}
# Trap signals to ensure cleanup on script termination
trap 'terminate_process_by_port 7860; terminate_process_by_port 3000' EXIT
# install playwright if there is not installed yet
npx playwright install
# Navigate to the project root directory (where the Makefile is located)
cd ../../
# Start the frontend using 'make frontend' in the background
make frontend &
# Give some time for the frontend to start (adjust sleep duration as needed)
sleep 10
# Navigate to the test directory
cd src/frontend
# Run frontend only Playwright tests with or without UI based on the --ui flag
if [ "$ui" = true ]; then
PLAYWRIGHT_HTML_REPORT=playwright-report/onlyFront npx playwright test tests/onlyFront --ui --project=chromium
else
PLAYWRIGHT_HTML_REPORT=playwright-report/onlyFront npx playwright test tests/onlyFront --project=chromium
fi
# Navigate back to the project root directory
cd ../../
# Start the backend using 'make backend' in the background
make backend &
# Give some time for the backend to start (adjust sleep duration as needed)
sleep 25
# Navigate back to the test directory
cd src/frontend
# Run Playwright tests with or without UI based on the --ui flag
if [ "$ui" = true ]; then
PLAYWRIGHT_HTML_REPORT=playwright-report/e2e npx playwright test tests/end-to-end --ui --project=chromium
else
PLAYWRIGHT_HTML_REPORT=playwright-report/e2e npx playwright test tests/end-to-end --project=chromium
fi
npx playwright show-report
# After the tests are finished, you can add cleanup or teardown logic here if needed
# The trap will automatically terminate processes by port on script exit

View file

@ -16,8 +16,8 @@ import {
FETCH_ERROR_MESSAGE,
} from "./constants/constants";
import { alertContext } from "./contexts/alertContext";
import { FlowsContext } from "./contexts/flowsContext";
import { locationContext } from "./contexts/locationContext";
import { TabsContext } from "./contexts/tabsContext";
import { typesContext } from "./contexts/typesContext";
import Router from "./routes";
@ -30,7 +30,7 @@ export default function App() {
setShowSideBar(true);
setIsStackedOpen(true);
}, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
const { hardReset } = useContext(FlowsContext);
const { hardReset } = useContext(TabsContext);
const {
errorData,

View file

@ -23,15 +23,15 @@ import TextAreaComponent from "../../../../components/textAreaComponent";
import ToggleShadComponent from "../../../../components/toggleShadComponent";
import { Button } from "../../../../components/ui/button";
import { TOOLTIP_EMPTY } from "../../../../constants/constants";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext";
import { ParameterComponentType } from "../../../../types/components";
import { TabsState } from "../../../../types/tabs";
import {
convertObjToArray,
convertValuesToNumbers,
hasDuplicateKeys,
isValidConnection,
scapedJSONStringfy,
} from "../../../../utils/reactflowUtils";
import {
nodeColors,
@ -53,16 +53,14 @@ export default function ParameterComponent({
required = false,
optionalHandle = null,
info = "",
proxy,
showNode,
index = "",
}: ParameterComponentType): JSX.Element {
const ref = useRef<HTMLDivElement>(null);
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
const updateNodeInternals = useUpdateNodeInternals();
const [position, setPosition] = useState(0);
const { setTabsState, tabId, flows } = useContext(FlowsContext);
const { setTabsState, tabId, flows } = useContext(TabsContext);
const flow = flows.find((flow) => flow.id === tabId)?.data?.nodes ?? null;
@ -82,9 +80,8 @@ export default function ParameterComponent({
const { reactFlowInstance, setFilterEdge } = useContext(typesContext);
let disabled =
reactFlowInstance
?.getEdges()
.some((edge) => edge.targetHandle === scapedJSONStringfy(id)) ?? false;
reactFlowInstance?.getEdges().some((edge) => edge.targetHandle === id) ??
false;
const { data: myData } = useContext(typesContext);
@ -115,6 +112,7 @@ export default function ParameterComponent({
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
useEffect(() => {
if (name === "openai_api_base") console.log(info);
// @ts-ignore
infoHtml.current = (
<div className="h-full w-full break-words">
@ -212,40 +210,32 @@ export default function ParameterComponent({
!optionalHandle ? (
<></>
) : (
<Button className="h-7 truncate bg-muted p-0 text-sm font-normal text-black hover:bg-muted">
<div className="flex">
<ShadTooltip
styleClasses={"tooltip-fixed-width custom-scroll nowheel"}
delayDuration={0}
content={refHtml.current}
side={left ? "left" : "right"}
>
<Handle
type={left ? "target" : "source"}
position={left ? Position.Left : Position.Right}
id={
proxy
? scapedJSONStringfy({ ...id, proxy })
: scapedJSONStringfy(id)
}
isValidConnection={(connection) =>
isValidConnection(connection, reactFlowInstance!)
}
className={classNames(
left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
"h-3 w-3 rounded-full border-2 bg-background"
)}
style={{
borderColor: color,
top: position,
}}
onClick={() => {
setFilterEdge(groupedEdge.current);
}}
></Handle>
</ShadTooltip>
</div>
</Button>
<ShadTooltip
styleClasses={"tooltip-fixed-width custom-scroll nowheel"}
delayDuration={0}
content={refHtml.current}
side={left ? "left" : "right"}
>
<Handle
type={left ? "target" : "source"}
position={left ? Position.Left : Position.Right}
id={id}
isValidConnection={(connection) =>
isValidConnection(connection, reactFlowInstance!)
}
className={classNames(
left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
"h-3 w-3 rounded-full border-2 bg-background"
)}
style={{
borderColor: color,
top: position,
}}
onClick={() => {
setFilterEdge(groupedEdge.current);
}}
></Handle>
</ShadTooltip>
)
) : (
<div
@ -260,13 +250,7 @@ export default function ParameterComponent({
(info !== "" ? " flex items-center" : "")
}
>
{proxy ? (
<ShadTooltip content={<span>{proxy.id}</span>}>
<span>{title}</span>
</ShadTooltip>
) : (
title
)}
{title}
<span className="text-status-red">{required ? " *" : ""}</span>
<div className="">
{info !== "" && (
@ -306,11 +290,7 @@ export default function ParameterComponent({
<Handle
type={left ? "target" : "source"}
position={left ? Position.Left : Position.Right}
id={
proxy
? scapedJSONStringfy({ ...id, proxy })
: scapedJSONStringfy(id)
}
id={id}
isValidConnection={(connection) =>
isValidConnection(connection, reactFlowInstance!)
}
@ -351,11 +331,9 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"textarea-" + index}
/>
) : (
<InputComponent
id={"input-" + index}
disabled={disabled}
password={data.node?.template[name].password ?? false}
value={data.node?.template[name].value ?? ""}
@ -366,7 +344,6 @@ export default function ParameterComponent({
) : left === true && type === "bool" ? (
<div className="mt-2 w-full">
<ToggleShadComponent
id={"toggle-" + index}
disabled={disabled}
enabled={data.node?.template[name].value ?? false}
setEnabled={(isEnabled) => {
@ -396,11 +373,6 @@ export default function ParameterComponent({
) : left === true && type === "code" ? (
<div className="mt-2 w-full">
<CodeAreaComponent
readonly={
data.node?.flow && data.node.template[name].dynamic
? true
: false
}
dynamic={data.node?.template[name].dynamic ?? false}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
@ -409,7 +381,6 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"code-input-" + index}
/>
</div>
) : left === true && type === "file" ? (
@ -431,13 +402,11 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"int-input-" + index}
/>
</div>
) : left === true && type === "prompt" ? (
<div className="mt-2 w-full">
<PromptAreaComponent
readonly={data.node?.flow ? true : false}
field_name={name}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
@ -451,7 +420,6 @@ export default function ParameterComponent({
onChange={(e) => {
handleOnNewValue(e);
}}
id={"prompt-input-" + index}
/>
</div>
) : left === true && type === "NestedDict" ? (

View file

@ -4,46 +4,30 @@ import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../components/ShadTooltipComponent";
import Tooltip from "../../components/TooltipComponent";
import IconComponent from "../../components/genericIconComponent";
import InputComponent from "../../components/inputComponent";
import { Textarea } from "../../components/ui/textarea";
import { useSSE } from "../../contexts/SSEContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import { validationStatusType } from "../../types/components";
import { NodeDataType } from "../../types/flow";
import {
cleanEdges,
handleKeyDown,
scapedJSONStringfy,
} from "../../utils/reactflowUtils";
import { cleanEdges } from "../../utils/reactflowUtils";
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
import { classNames, getFieldTitle } from "../../utils/utils";
import { classNames, toTitleCase } from "../../utils/utils";
import ParameterComponent from "./components/parameterComponent";
export default function GenericNode({
data: olddata,
xPos,
yPos,
selected,
}: {
data: NodeDataType;
selected: boolean;
xPos: number;
yPos: number;
}): JSX.Element {
const [data, setData] = useState(olddata);
const { updateFlow, flows, tabId } = useContext(FlowsContext);
const { updateFlow, flows, tabId } = useContext(TabsContext);
const updateNodeInternals = useUpdateNodeInternals();
const { types, deleteNode, reactFlowInstance, setFilterEdge, getFilterEdge } =
useContext(typesContext);
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
const [inputName, setInputName] = useState(true);
const [nodeName, setNodeName] = useState(data.node!.display_name);
const [inputDescription, setInputDescription] = useState(false);
const [nodeDescription, setNodeDescription] = useState(
data.node?.description!
);
const [validationStatus, setValidationStatus] =
useState<validationStatusType | null>(null);
const [showNode, setShowNode] = useState<boolean>(true);
@ -127,7 +111,6 @@ export default function GenericNode({
<>
<NodeToolbar>
<NodeToolbarComponent
position={{ x: xPos, y: yPos }}
data={data}
setData={setData}
deleteNode={deleteNode}
@ -168,7 +151,7 @@ export default function GenericNode({
}
>
<IconComponent
name={data.node?.flow ? "Ungroup" : name}
name={name}
className={
"generic-node-icon " +
(!showNode && "absolute inset-x-6 h-12 w-12")
@ -177,35 +160,11 @@ export default function GenericNode({
/>
{showNode && (
<div className="generic-node-tooltip-div">
{data.node?.flow && inputName ? (
<div>
<InputComponent
autoFocus
onBlur={() => {
setInputName(false);
if (nodeName.trim() !== "") {
setNodeName(nodeName);
data.node!.display_name = nodeName;
} else {
setNodeName(data.node!.display_name);
}
}}
value={nodeName}
onChange={setNodeName}
password={false}
blurOnEnter={true}
/>
<ShadTooltip content={data.node?.display_name}>
<div className="generic-node-tooltip-div text-primary">
{data.node?.display_name}
</div>
) : (
<ShadTooltip content={data.node?.display_name}>
<div
className="generic-node-tooltip-div text-primary"
onDoubleClick={() => setInputName(true)}
>
{data.node?.display_name}
</div>
</ShadTooltip>
)}
</ShadTooltip>
</div>
)}
</div>
@ -219,15 +178,16 @@ export default function GenericNode({
data.node!.template[templateField].show &&
!data.node!.template[templateField].advanced && (
<ParameterComponent
index={idx.toString()}
key={scapedJSONStringfy({
inputTypes:
data.node!.template[templateField].input_types,
type: data.node!.template[templateField].type,
id: data.id,
fieldName: templateField,
proxy: data.node!.template[templateField].proxy,
})}
key={
(data.node!.template[
templateField
].input_types?.join(";") ??
data.node!.template[templateField].type) +
"|" +
templateField +
"|" +
data.id
}
data={data}
setData={setData}
color={
@ -239,10 +199,15 @@ export default function GenericNode({
] ??
nodeColors.unknown
}
title={getFieldTitle(
data.node?.template!,
templateField
)}
title={
data.node?.template[templateField].display_name
? data.node.template[templateField].display_name
: data.node?.template[templateField].name
? toTitleCase(
data.node.template[templateField].name
)
: toTitleCase(templateField)
}
info={data.node?.template[templateField].info}
name={templateField}
tooltipTitle={
@ -252,31 +217,31 @@ export default function GenericNode({
data.node?.template[templateField].type
}
required={
data.node!.template[templateField].required
data.node?.template[templateField].required
}
id={
(data.node?.template[
templateField
].input_types?.join(";") ??
data.node?.template[templateField].type) +
"|" +
templateField +
"|" +
data.id
}
id={{
inputTypes:
data.node!.template[templateField].input_types,
type: data.node!.template[templateField].type,
id: data.id,
fieldName: templateField,
}}
left={true}
type={data.node?.template[templateField].type}
optionalHandle={
data.node?.template[templateField].input_types
}
proxy={data.node?.template[templateField].proxy}
showNode={showNode}
/>
)
)}
<ParameterComponent
key={scapedJSONStringfy({
baseClasses: data.node!.base_classes,
id: data.id,
dataType: data.type,
})}
key={[data.type, data.id, ...data.node!.base_classes].join(
"|"
)}
data={data}
setData={setData}
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
@ -287,11 +252,9 @@ export default function GenericNode({
: data.type
}
tooltipTitle={data.node?.base_classes.join("\n")}
id={{
baseClasses: data.node!.base_classes,
id: data.id,
dataType: data.type,
}}
id={[data.type, data.id, ...data.node!.base_classes].join(
"|"
)}
type={data.node?.base_classes.join("|")}
left={false}
showNode={showNode}
@ -367,71 +330,33 @@ export default function GenericNode({
className={
showNode
? "generic-node-desc " +
(data.node?.description !== "" ? "py-5" : "pb-5")
(data.node?.description !== "" && showNode ? "py-5" : "pb-5")
: ""
}
>
{data.node?.description !== "" &&
showNode &&
data.node?.flow &&
inputDescription ? (
<Textarea
autoFocus
onBlur={() => {
setInputDescription(false);
if (nodeDescription.trim() !== "") {
setNodeDescription(nodeDescription);
data.node!.description = nodeDescription;
} else {
setNodeDescription(data.node!.description);
}
}}
value={nodeDescription}
onChange={(e) => setNodeDescription(e.target.value)}
onKeyDown={(e) => {
handleKeyDown(e, nodeDescription, "");
if (
e.key === "Enter" &&
e.shiftKey === false &&
e.ctrlKey === false &&
e.altKey === false
) {
setInputDescription(false);
if (nodeDescription.trim() !== "") {
setNodeDescription(nodeDescription);
data.node!.description = nodeDescription;
} else {
setNodeDescription(data.node!.description);
}
}
}}
/>
) : (
<div
className="generic-node-desc-text"
onDoubleClick={() => setInputDescription(true)}
>
{data.node?.description !== "" && showNode && (
<div className="generic-node-desc-text">
{data.node?.description}
</div>
)}
<>
{Object.keys(data.node!.template)
.filter((templateField) => templateField.charAt(0) !== "_")
.sort()
.map((templateField: string, idx) => (
<div key={idx}>
{data.node!.template[templateField].show &&
!data.node!.template[templateField].advanced ? (
<ParameterComponent
index={idx.toString()}
key={scapedJSONStringfy({
inputTypes:
data.node!.template[templateField].input_types,
type: data.node!.template[templateField].type,
id: data.id,
fieldName: templateField,
proxy: data.node!.template[templateField].proxy,
})}
key={
(data.node!.template[templateField].input_types?.join(
";"
) ?? data.node!.template[templateField].type) +
"|" +
templateField +
"|" +
data.id
}
data={data}
setData={setData}
color={
@ -443,10 +368,15 @@ export default function GenericNode({
] ??
nodeColors.unknown
}
title={getFieldTitle(
data.node?.template!,
templateField
)}
title={
data.node?.template[templateField].display_name
? data.node.template[templateField].display_name
: data.node?.template[templateField].name
? toTitleCase(
data.node.template[templateField].name
)
: toTitleCase(templateField)
}
info={data.node?.template[templateField].info}
name={templateField}
tooltipTitle={
@ -454,20 +384,21 @@ export default function GenericNode({
"\n"
) ?? data.node?.template[templateField].type
}
required={data.node!.template[templateField].required}
id={{
inputTypes:
data.node!.template[templateField].input_types,
type: data.node!.template[templateField].type,
id: data.id,
fieldName: templateField,
}}
required={data.node?.template[templateField].required}
id={
(data.node?.template[templateField].input_types?.join(
";"
) ?? data.node?.template[templateField].type) +
"|" +
templateField +
"|" +
data.id
}
left={true}
type={data.node?.template[templateField].type}
optionalHandle={
data.node?.template[templateField].input_types
}
proxy={data.node?.template[templateField].proxy}
showNode={showNode}
/>
) : (
@ -484,11 +415,7 @@ export default function GenericNode({
{" "}
</div>
<ParameterComponent
key={scapedJSONStringfy({
baseClasses: data.node!.base_classes,
id: data.id,
dataType: data.type,
})}
key={[data.type, data.id, ...data.node!.base_classes].join("|")}
data={data}
setData={setData}
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
@ -498,11 +425,7 @@ export default function GenericNode({
: data.type
}
tooltipTitle={data.node?.base_classes.join("\n")}
id={{
baseClasses: data.node!.base_classes,
id: data.id,
dataType: data.type,
}}
id={[data.type, data.id, ...data.node!.base_classes].join("|")}
type={data.node?.base_classes.join("|")}
left={false}
showNode={showNode}

View file

@ -21,7 +21,6 @@ export default function DropdownButton({
<DropdownMenu open={showOptions}>
<DropdownMenuTrigger asChild>
<Button
id="new-project-btn"
variant="primary"
className="relative pr-10"
onClick={(event) => {

View file

@ -72,7 +72,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
type="text"
name="name"
value={name ?? ""}
placeholder="Flow name"
placeholder="File name"
id="name"
maxLength={maxLength}
/>

View file

@ -1,5 +1,5 @@
import { useContext } from "react";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import { cardComponentPropsType } from "../../types/components";
import { gradients } from "../../utils/styleUtils";
import IconComponent from "../genericIconComponent";
@ -17,7 +17,7 @@ export const CardComponent = ({
onDelete,
button,
}: cardComponentPropsType): JSX.Element => {
const { removeFlow } = useContext(FlowsContext);
const { removeFlow } = useContext(TabsContext);
return (
<Card className="group">

View file

@ -7,7 +7,7 @@ import { typesContext } from "../../../contexts/typesContext";
import { postBuildInit } from "../../../controllers/API";
import { FlowType } from "../../../types/flow";
import { FlowsContext } from "../../../contexts/flowsContext";
import { TabsContext } from "../../../contexts/tabsContext";
import { parsedDataType } from "../../../types/components";
import { TabsState } from "../../../types/tabs";
import { validateNodes } from "../../../utils/reactflowUtils";
@ -26,7 +26,7 @@ export default function BuildTrigger({
}): JSX.Element {
const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE();
const { reactFlowInstance } = useContext(typesContext);
const { setTabsState } = useContext(FlowsContext);
const { setTabsState } = useContext(TabsContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
const [isIconTouched, setIsIconTouched] = useState(false);
const eventClick = isBuilding ? "pointer-events-none" : "";
@ -80,54 +80,61 @@ export default function BuildTrigger({
const { flowId } = response.data;
// Step 2: Use the session ID to establish an SSE connection using EventSource
let validationResults: boolean[] = [];
let finished = false;
const apiUrl = `/api/v1/build/stream/${flowId}`;
return new Promise<boolean>((resolve, reject) => {
const eventSource = new EventSource(apiUrl);
const eventSource = new EventSource(apiUrl);
eventSource.onmessage = (event) => {
// If the event is parseable, return
if (!event.data) {
return;
}
const parsedData = JSON.parse(event.data);
// if the event is the end of the stream, close the connection
if (parsedData.end_of_stream) {
eventSource.close();
resolve(validationResults.every((result) => result));
} else if (parsedData.log) {
// If the event is a log, log it
setSuccessData({ title: parsedData.log });
} else if (parsedData.input_keys !== undefined) {
//@ts-ignore
setTabsState((old: TabsState) => {
return {
...old,
[flowId]: {
...old[flowId],
formKeysData: parsedData,
},
};
});
} else {
// Otherwise, process the data
const isValid = processStreamResult(parsedData);
setProgress(parsedData.progress);
validationResults.push(isValid);
}
};
eventSource.onerror = (error: any) => {
console.error("EventSource failed:", error);
if (error.data) {
const parsedData = JSON.parse(error.data);
setErrorData({ title: parsedData.error });
setIsBuilding(false);
}
eventSource.onmessage = (event) => {
// If the event is parseable, return
if (!event.data) {
return;
}
const parsedData = JSON.parse(event.data);
// if the event is the end of the stream, close the connection
if (parsedData.end_of_stream) {
// Close the connection and finish
finished = true;
eventSource.close();
reject(new Error("Streaming failed"));
};
});
return;
} else if (parsedData.log) {
// If the event is a log, log it
setSuccessData({ title: parsedData.log });
} else if (parsedData.input_keys !== undefined) {
//@ts-ignore
setTabsState((old: TabsState) => {
return {
...old,
[flowId]: {
...old[flowId],
formKeysData: parsedData,
},
};
});
} else {
// Otherwise, process the data
const isValid = processStreamResult(parsedData);
setProgress(parsedData.progress);
validationResults.push(isValid);
}
};
eventSource.onerror = (error: any) => {
console.error("EventSource failed:", error);
eventSource.close();
if (error.data) {
const parsedData = JSON.parse(error.data);
setErrorData({ title: parsedData.error });
setIsBuilding(false);
}
};
// Step 3: Wait for the stream to finish
while (!finished) {
await new Promise((resolve) => setTimeout(resolve, 100));
finished = validationResults.length === flow.data!.nodes.length;
}
// Step 4: Return true if all nodes are valid, false otherwise
return validationResults.every((result) => result);
}
function processStreamResult(parsedData: parsedDataType) {

View file

@ -5,7 +5,7 @@ import BuildTrigger from "./buildTrigger";
import ChatTrigger from "./chatTrigger";
import * as _ from "lodash";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import { getBuildStatus } from "../../controllers/API";
import FormModal from "../../modals/formModal";
import { NodeType } from "../../types/flow";
@ -13,7 +13,7 @@ import { NodeType } from "../../types/flow";
export default function Chat({ flow }: ChatType): JSX.Element {
const [open, setOpen] = useState(false);
const [canOpen, setCanOpen] = useState(false);
const { tabsState, isBuilt, setIsBuilt } = useContext(FlowsContext);
const { tabsState, isBuilt, setIsBuilt } = useContext(TabsContext);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {

View file

@ -12,8 +12,6 @@ export default function CodeAreaComponent({
nodeClass,
dynamic,
setNodeClass,
id = "",
readonly = false,
}: CodeAreaComponentType) {
const [myValue, setMyValue] = useState(
typeof value == "string" ? value : JSON.stringify(value)
@ -32,7 +30,6 @@ export default function CodeAreaComponent({
return (
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<CodeAreaModal
readonly={readonly}
dynamic={dynamic}
value={myValue}
nodeClass={nodeClass}
@ -44,7 +41,6 @@ export default function CodeAreaComponent({
>
<div className="flex w-full items-center">
<span
id={id}
className={
editNode
? "input-edit-node input-dialog"

View file

@ -63,7 +63,7 @@ export default function CodeTabsComponent({
}, [flow]);
useEffect(() => {
if (tweaks && data) {
if (tweaks) {
unselectAllNodes({
data,
updateNodes: (nodes) => {
@ -604,14 +604,6 @@ export default function CodeTabsComponent({
].type === "prompt" ? (
<div className="mx-auto">
<PromptAreaComponent
readonly={
node.data.node?.flow &&
node.data.node.template[
templateField
].dynamic
? true
: false
}
editNode={true}
disabled={false}
value={
@ -654,14 +646,6 @@ export default function CodeTabsComponent({
<CodeAreaComponent
disabled={false}
editNode={true}
readonly={
node.data.node?.flow &&
node.data.node.template[
templateField
].dynamic
? true
: false
}
value={
!node.data.node.template[
templateField

View file

@ -22,6 +22,7 @@ export default function DictComponent({
}, [value]);
const ref = useRef(value);
debugger;
return (
<div
className={classNames(

View file

@ -23,7 +23,6 @@ export default function FloatComponent({
return (
<div className="w-full">
<Input
id="float-input"
type="number"
step={step}
min={min}

View file

@ -1,5 +1,5 @@
import { useContext, useState } from "react";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { TabsContext } from "../../../../contexts/tabsContext";
import {
DropdownMenu,
DropdownMenuContent,
@ -17,7 +17,7 @@ import IconComponent from "../../../genericIconComponent";
import { Button } from "../../../ui/button";
export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
const { addFlow } = useContext(FlowsContext);
const { addFlow } = useContext(TabsContext);
const { setErrorData } = useContext(alertContext);
const { undo, redo } = useContext(undoRedoContext);
const [openSettings, setOpenSettings] = useState(false);
@ -26,7 +26,7 @@ export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
function handleAddFlow() {
try {
addFlow(true).then((id) => {
addFlow(undefined, true).then((id) => {
navigate("/flow/" + id);
});
// saveFlowStyleInDataBase();

View file

@ -6,7 +6,7 @@ import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { darkContext } from "../../contexts/darkContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import { gradients } from "../../utils/styleUtils";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
@ -22,7 +22,7 @@ import { Separator } from "../ui/separator";
import MenuBar from "./components/menuBar";
export default function Header(): JSX.Element {
const { flows, tabId } = useContext(FlowsContext);
const { flows, tabId } = useContext(TabsContext);
const { dark, setDark } = useContext(darkContext);
const { notificationCenter } = useContext(alertContext);
const location = useLocation();

View file

@ -1,13 +1,11 @@
import * as Form from "@radix-ui/react-form";
import { useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";
import { InputComponentType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils";
import { classNames } from "../../utils/utils";
import { Input } from "../ui/input";
export default function InputComponent({
autoFocus = false,
onBlur,
value,
onChange,
disabled,
@ -17,11 +15,9 @@ export default function InputComponent({
editNode = false,
placeholder = "Type something...",
className,
id = "",
blurOnEnter = false,
}: InputComponentType): JSX.Element {
const [pwdVisible, setPwdVisible] = useState(false);
const refInput = useRef<HTMLInputElement>(null);
// Clear component state
useEffect(() => {
if (disabled) {
@ -34,10 +30,6 @@ export default function InputComponent({
{isForm ? (
<Form.Control asChild>
<Input
id={"form-" + id}
ref={refInput}
onBlur={onBlur}
autoFocus={autoFocus}
type={password && !pwdVisible ? "password" : "text"}
value={value}
disabled={disabled}
@ -57,18 +49,13 @@ export default function InputComponent({
}}
onKeyDown={(e) => {
handleKeyDown(e, value, "");
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
}}
/>
</Form.Control>
) : (
<Input
id={id}
ref={refInput}
type="text"
onBlur={onBlur}
value={value}
autoFocus={autoFocus}
disabled={disabled}
required={required}
className={classNames(
@ -86,7 +73,6 @@ export default function InputComponent({
}}
onKeyDown={(e) => {
handleKeyDown(e, value, "");
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
}}
/>
)}

View file

@ -1,6 +1,6 @@
import { useContext, useEffect, useState } from "react";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import { uploadFile } from "../../controllers/API";
import { FileComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
@ -17,7 +17,7 @@ export default function InputFileComponent({
const [myValue, setMyValue] = useState(value);
const [loading, setLoading] = useState(false);
const { setErrorData } = useContext(alertContext);
const { tabId } = useContext(FlowsContext);
const { tabId } = useContext(TabsContext);
// Clear component state
useEffect(() => {

View file

@ -8,7 +8,6 @@ export default function IntComponent({
onChange,
disabled,
editNode = false,
id = "",
}: FloatComponentType): JSX.Element {
const min = 0;
@ -22,7 +21,6 @@ export default function IntComponent({
return (
<div className="w-full">
<Input
id={id}
onKeyDown={(event) => {
if (
event.key !== "Backspace" &&

View file

@ -55,7 +55,6 @@ export default function KeypairListComponent({
return (
<div key={idx} className="flex w-full gap-2">
<Input
id={"keypair" + index}
type="text"
value={key.trim()}
className={classNames(
@ -73,7 +72,6 @@ export default function KeypairListComponent({
/>
<Input
id={"keypair" + (index + 100).toString()}
type="text"
value={obj[key]}
className={editNode ? "input-edit-node" : ""}
@ -90,7 +88,6 @@ export default function KeypairListComponent({
newInputList.push({ "": "" });
onChange(newInputList);
}}
id={"plusbtn" + index.toString()}
>
<IconComponent
name="Plus"
@ -104,7 +101,6 @@ export default function KeypairListComponent({
newInputList.splice(index, 1);
onChange(newInputList);
}}
id={"minusbtn" + index.toString()}
>
<IconComponent
name="X"

View file

@ -14,9 +14,7 @@ export default function PromptAreaComponent({
onChange,
disabled,
editNode = false,
id = "",
readonly = false,
}: PromptAreaComponentType): JSX.Element {
}: PromptAreaComponentType) {
useEffect(() => {
if (disabled) {
onChange("");
@ -24,8 +22,7 @@ export default function PromptAreaComponent({
}, [disabled]);
useEffect(() => {
//prevent update from prompt template after group node if prompt is wrongly marked as not dynamic
if (value !== "" && !editNode && !readonly && !nodeClass?.flow) {
if (value !== "" && !editNode) {
postValidatePrompt(field_name!, value, nodeClass!).then((apiReturn) => {
if (apiReturn.data) {
setNodeClass!(apiReturn.data.frontend_node);
@ -38,8 +35,6 @@ export default function PromptAreaComponent({
return (
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<GenericModal
id={id}
readonly={readonly}
type={TypeModal.PROMPT}
value={value}
buttonText="Check & Save"
@ -52,7 +47,6 @@ export default function PromptAreaComponent({
>
<div className="flex w-full items-center">
<span
id={id}
className={
editNode
? "input-edit-node input-dialog"

View file

@ -10,7 +10,6 @@ export default function TextAreaComponent({
onChange,
disabled,
editNode = false,
id = "",
}: TextAreaComponentType): JSX.Element {
// Clear text area
useEffect(() => {
@ -22,7 +21,6 @@ export default function TextAreaComponent({
return (
<div className="flex w-full items-center">
<Input
id={id}
value={value}
disabled={disabled}
className={editNode ? "input-edit-node" : ""}

View file

@ -6,7 +6,6 @@ export default function ToggleShadComponent({
setEnabled,
disabled,
size,
id = "",
}: ToggleComponentType): JSX.Element {
let scaleX, scaleY;
switch (size) {
@ -30,7 +29,6 @@ export default function ToggleShadComponent({
return (
<div className={disabled ? "pointer-events-none cursor-not-allowed " : ""}>
<Switch
id={id}
style={{
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
}}

View file

@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
"overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
className
)}
{...props}

View file

@ -7,8 +7,8 @@ import { SSEProvider } from "./SSEContext";
import { AlertProvider } from "./alertContext";
import { AuthProvider } from "./authContext";
import { DarkProvider } from "./darkContext";
import { TabsProvider } from "./flowsContext";
import { LocationProvider } from "./locationContext";
import { TabsProvider } from "./tabsContext";
import { TypesProvider } from "./typesContext";
import { UndoRedoProvider } from "./undoRedoContext";

View file

@ -1,5 +1,5 @@
import { AxiosError } from "axios";
import _, { cloneDeep } from "lodash";
import _ from "lodash";
import {
ReactNode,
createContext,
@ -21,20 +21,10 @@ import {
} from "../controllers/API";
import { APIClassType, APITemplateType } from "../types/api";
import { tweakType } from "../types/components";
import {
FlowType,
NodeDataType,
NodeType,
sourceHandleType,
targetHandleType,
} from "../types/flow";
import { FlowsContextType, TabsState } from "../types/tabs";
import { FlowType, NodeDataType, NodeType } from "../types/flow";
import { TabsContextType, TabsState } from "../types/tabs";
import {
addVersionToDuplicates,
checkOldEdgesHandles,
scapeJSONParse,
scapedJSONStringfy,
updateEdgesHandleIds,
updateIds,
updateTemplate,
} from "../utils/reactflowUtils";
@ -45,13 +35,13 @@ import { typesContext } from "./typesContext";
const uid = new ShortUniqueId({ length: 5 });
const FlowsContextInitialValue: FlowsContextType = {
const TabsContextInitialValue: TabsContextType = {
tabId: "",
setTabId: (index: string) => {},
isLoading: true,
flows: [],
removeFlow: (id: string) => {},
addFlow: async (newProject: boolean, flowData?: FlowType) => "",
addFlow: async (flowData?: any) => "",
updateFlow: (newFlow: FlowType) => {},
incrementNodeId: () => uid(),
downloadFlow: (flow: FlowType) => {},
@ -75,8 +65,8 @@ const FlowsContextInitialValue: FlowsContextType = {
) => {},
};
export const FlowsContext = createContext<FlowsContextType>(
FlowsContextInitialValue
export const TabsContext = createContext<TabsContextType>(
TabsContextInitialValue
);
export function TabsProvider({ children }: { children: ReactNode }) {
@ -144,18 +134,14 @@ export function TabsProvider({ children }: { children: ReactNode }) {
if (!flow.data) {
return;
}
processDataFromFlow(flow, false);
processFlowEdges(flow);
processFlowNodes(flow);
} catch (e) {}
});
}
function processFlowEdges(flow: FlowType) {
if (!flow.data || !flow.data.edges) return;
if (checkOldEdgesHandles(flow.data.edges)) {
const newEdges = updateEdgesHandleIds(flow.data);
flow.data.edges = newEdges;
}
//update edges colors
flow.data.edges.forEach((edge) => {
edge.className = "";
edge.style = { stroke: "#555" };
@ -173,7 +159,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
function processFlowNodes(flow: FlowType) {
if (!flow.data || !flow.data.nodes) return;
flow.data.nodes.forEach((node: NodeType) => {
if (node.data.node?.flow) return;
if (skipNodeUpdate.includes(node.data.type)) return;
const template = templates[node.data.type];
if (!template) {
@ -183,7 +168,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
if (Object.keys(template["template"]).length > 0) {
updateDisplay_name(node, template);
updateNodeBaseClasses(node, template);
//update baseclasses in edges
updateNodeEdges(flow, node, template);
updateNodeDescription(node, template);
updateNodeTemplate(node, template);
@ -203,12 +187,11 @@ export function TabsProvider({ children }: { children: ReactNode }) {
) {
flow.data!.edges.forEach((edge) => {
if (edge.source === node.id) {
let sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle!
);
sourceHandleObject.baseClasses = template["base_classes"];
edge.data.sourceHandle = sourceHandleObject;
edge.sourceHandle = scapedJSONStringfy(sourceHandleObject);
edge.sourceHandle = edge.sourceHandle
?.split("|")
.slice(0, 2)
.concat(template["base_classes"])
.join("|");
}
});
}
@ -260,6 +243,9 @@ export function TabsProvider({ children }: { children: ReactNode }) {
// simulate a click on the link element to trigger the download
link.click();
setNoticeData({
title: "Warning: Critical data, JSON file may include API keys.",
});
}
function downloadFlows() {
@ -288,7 +274,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
* The resulting JSON object is passed to the addFlow function.
*/
async function uploadFlow(
newProject: boolean,
newProject?: boolean,
file?: File
): Promise<String | undefined> {
let id;
@ -297,13 +283,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
let fileData = JSON.parse(text);
if (fileData.flows) {
fileData.flows.forEach((flow: FlowType) => {
id = addFlow(newProject, flow);
id = addFlow(flow, newProject);
});
}
// parse the text into a JSON object
let flow: FlowType = JSON.parse(text);
id = await addFlow(newProject, flow);
id = await addFlow(flow, newProject);
} else {
// create a file input
const input = document.createElement("input");
@ -318,7 +304,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const currentfile = (e.target as HTMLInputElement).files![0];
let text = await currentfile.text();
let flow: FlowType = JSON.parse(text);
const flowId = await addFlow(newProject, flow);
const flowId = await addFlow(flow, newProject);
resolve(flowId);
}
};
@ -369,6 +355,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
* Add a new flow to the list of flows.
* @param flow Optional flow to add.
*/
function paste(
selectionInstance: { nodes: Node[]; edges: Edge[] },
position: { x: number; y: number; paneX?: number; paneY?: number }
@ -417,28 +404,19 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
reactFlowInstance!.setNodes(nodes);
selectionInstance.edges.forEach((edge: Edge) => {
selectionInstance.edges.forEach((edge) => {
let source = idsMap[edge.source];
let target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle!
);
let sourceHandle = scapedJSONStringfy({
...sourceHandleObject,
id: source,
});
sourceHandleObject.id = source;
edge.data.sourceHandle = sourceHandleObject;
const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!
);
let targetHandle = scapedJSONStringfy({
...targetHandleObject,
id: target,
});
targetHandleObject.id = target;
edge.data.targetHandle = targetHandleObject;
let sourceHandleSplitted = edge.sourceHandle!.split("|");
let sourceHandle =
sourceHandleSplitted[0] +
"|" +
source +
"|" +
sourceHandleSplitted.slice(2).join("|");
let targetHandleSplitted = edge.targetHandle!.split("|");
let targetHandle =
targetHandleSplitted.slice(0, -1).join("|") + "|" + target;
let id =
"reactflow__edge-" +
source +
@ -453,13 +431,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
sourceHandle,
targetHandle,
id,
data: cloneDeep(edge.data),
style: { stroke: "#555" },
className:
targetHandleObject.type === "Text"
targetHandle.split("|")[0] === "Text"
? "stroke-gray-800 "
: "stroke-gray-900 ",
animated: targetHandleObject.type === "Text",
animated: targetHandle.split("|")[0] === "Text",
selected: false,
},
edges.map((edge) => ({ ...edge, selected: false }))
@ -469,16 +446,19 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
const addFlow = async (
newProject: Boolean,
flow?: FlowType
flow?: FlowType,
newProject?: Boolean
): Promise<String | undefined> => {
if (newProject) {
let flowData = flow
? processDataFromFlow(flow)
: { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
let flowData = extractDataFromFlow(flow!);
if (flowData.description == "") {
flowData.description = getRandomDescription();
}
// Create a new flow with a default name if no flow is provided.
const newFlow = createNewFlow(flowData, flow!);
processFlowEdges(newFlow);
processFlowNodes(newFlow);
const flowName = addVersionToDuplicates(newFlow, flows);
@ -506,37 +486,31 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
};
const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
const extractDataFromFlow = (flow: FlowType) => {
let data = flow?.data ? flow.data : null;
const description = flow?.description ? flow.description : "";
if (data) {
processFlowEdges(flow);
//prevent node update for now
// processFlowNodes(flow);
//add animation to text type edges
updateEdges(data.edges);
// updateNodes(data.nodes, data.edges);
if (refreshIds) updateIds(data, getNodeId); // Assuming updateIds is defined elsewhere
updateNodes(data.nodes, data.edges);
updateIds(data, getNodeId); // Assuming updateIds is defined elsewhere
}
return data;
return { data, description };
};
const updateEdges = (edges: Edge[]) => {
edges.forEach((edge) => {
const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!
);
edge.className =
(targetHandleObject.type === "Text"
(edge.targetHandle!.split("|")[0] === "Text"
? "stroke-gray-800 "
: "stroke-gray-900 ") + " stroke-connection";
edge.animated = targetHandleObject.type === "Text";
edge.animated = edge.targetHandle!.split("|")[0] === "Text";
});
};
const updateNodes = (nodes: Node[], edges: Edge[]) => {
nodes.forEach((node) => {
if (node.data.node?.flow) return;
if (skipNodeUpdate.includes(node.data.type)) return;
const template = templates[node.data.type];
if (!template) {
@ -546,13 +520,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
if (Object.keys(template["template"]).length > 0) {
node.data.node.base_classes = template["base_classes"];
edges.forEach((edge) => {
let sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle!
);
if (edge.source === node.id) {
let newSourceHandle = sourceHandleObject;
newSourceHandle.baseClasses.concat(template["base_classes"]);
edge.sourceHandle = scapedJSONStringfy(newSourceHandle);
edge.sourceHandle = edge
.sourceHandle!.split("|")
.slice(0, 2)
.concat(template["base_classes"])
.join("|");
}
});
node.data.node.description = template["description"];
@ -565,12 +538,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
};
const createNewFlow = (
flowData: ReactFlowJsonObject | null,
flowData: { data: ReactFlowJsonObject | null; description: string },
flow: FlowType
) => ({
description: flow?.description ?? getRandomDescription(),
description: flowData.description,
name: flow?.name ?? getRandomName(),
data: flowData,
data: flowData.data,
id: "",
});
@ -638,7 +611,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const [isBuilt, setIsBuilt] = useState(false);
return (
<FlowsContext.Provider
<TabsContext.Provider
value={{
saveFlow,
isBuilt,
@ -667,6 +640,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}}
>
{children}
</FlowsContext.Provider>
</TabsContext.Provider>
);
}

View file

@ -13,7 +13,7 @@ import {
undoRedoContextType,
} from "../types/typesContext";
import { isWrappedWithClass } from "../utils/utils";
import { FlowsContext } from "./flowsContext";
import { TabsContext } from "./tabsContext";
const initialValue = {
undo: () => {},
@ -29,7 +29,7 @@ const defaultOptions: UseUndoRedoOptions = {
export const undoRedoContext = createContext<undoRedoContextType>(initialValue);
export function UndoRedoProvider({ children }) {
const { tabId, flows } = useContext(FlowsContext);
const { tabId, flows } = useContext(TabsContext);
const [past, setPast] = useState<HistoryItem[][]>(flows.map(() => []));
const [future, setFuture] = useState<HistoryItem[][]>(flows.map(() => []));

View file

@ -15,7 +15,7 @@ import CodeTabsComponent from "../../components/codeTabsComponent";
import IconComponent from "../../components/genericIconComponent";
import { EXPORT_CODE_DIALOG } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import { TemplateVariableType } from "../../types/api";
import { tweakType, uniqueTweakType } from "../../types/components";
import { FlowType, NodeType } from "../../types/flow/index";
@ -45,7 +45,7 @@ const ApiModal = forwardRef(
const [activeTab, setActiveTab] = useState("0");
const tweak = useRef<tweakType>([]);
const tweaksList = useRef<string[]>([]);
const { setTweak, getTweak, tabsState } = useContext(FlowsContext);
const { setTweak, getTweak, tabsState } = useContext(TabsContext);
const pythonApiCode = getPythonApiCode(
flow,
autoLogin,

View file

@ -7,8 +7,6 @@ import {
useRef,
useState,
} from "react";
import { useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../components/ShadTooltipComponent";
import CodeAreaComponent from "../../components/codeAreaComponent";
import DictComponent from "../../components/dictComponent";
import Dropdown from "../../components/dropdownComponent";
@ -33,7 +31,7 @@ import {
TableRow,
} from "../../components/ui/table";
import { limitScrollFieldsModal } from "../../constants/constants";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import { NodeDataType } from "../../types/flow";
import { TabsState } from "../../types/tabs";
@ -65,11 +63,10 @@ const EditNodeModal = forwardRef(
ref
) => {
const [modalOpen, setModalOpen] = useState(open ?? false);
const updateNodeInternals = useUpdateNodeInternals();
const myData = useRef(data);
const { setTabsState, tabId } = useContext(FlowsContext);
const { setTabsState, tabId } = useContext(TabsContext);
const { reactFlowInstance } = useContext(typesContext);
let disabled =
reactFlowInstance
@ -85,14 +82,11 @@ const EditNodeModal = forwardRef(
const handleOnNewValue = (newValue: any, name) => {
myData.current.node!.template[name].value = newValue;
setDataValue(newValue);
updateNodeInternals(data.id);
};
useEffect(() => {
if (modalOpen) {
myData.current = data; // reset data to what it is on node when opening modal
onClose!(modalOpen);
}
myData.current = data; // reset data to what it is on node when opening modal
onClose!(modalOpen);
}, [modalOpen]);
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
@ -172,27 +166,11 @@ const EditNodeModal = forwardRef(
.map((templateParam, index) => (
<TableRow key={index} className="h-10">
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
<ShadTooltip
content={
myData.current.node?.template[templateParam]
.proxy
? myData.current.node?.template[
templateParam
].proxy?.id
: null
}
>
<span>
{myData.current.node?.template[templateParam]
.display_name
? myData.current.node.template[
templateParam
].display_name
: myData.current.node?.template[
templateParam
].name}
</span>
</ShadTooltip>
{myData.current.node?.template[templateParam].name
? myData.current.node.template[templateParam]
.name
: myData.current.node?.template[templateParam]
.display_name}
</TableCell>
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
{myData.current.node?.template[templateParam]
@ -225,7 +203,6 @@ const EditNodeModal = forwardRef(
templateParam
].multiline ? (
<TextAreaComponent
id={"textarea-edit-" + index}
disabled={disabled}
editNode={true}
value={
@ -239,7 +216,6 @@ const EditNodeModal = forwardRef(
/>
) : (
<InputComponent
id={"input-" + index}
editNode={true}
disabled={disabled}
password={
@ -335,7 +311,6 @@ const EditNodeModal = forwardRef(
<div className="ml-auto">
{" "}
<ToggleShadComponent
id={"toggle-edit-" + index}
disabled={disabled}
enabled={
myData.current.node.template[
@ -394,7 +369,6 @@ const EditNodeModal = forwardRef(
.type === "int" ? (
<div className="mx-auto">
<IntComponent
id={"int-input-" + index}
disabled={disabled}
editNode={true}
value={
@ -442,9 +416,6 @@ const EditNodeModal = forwardRef(
.type === "prompt" ? (
<div className="mx-auto">
<PromptAreaComponent
readonly={
myData.current.node?.flow ? true : false
}
field_name={templateParam}
editNode={true}
disabled={disabled}
@ -460,21 +431,12 @@ const EditNodeModal = forwardRef(
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
id={"prompt-area-edit" + index}
/>
</div>
) : myData.current.node?.template[templateParam]
.type === "code" ? (
<div className="mx-auto">
<CodeAreaComponent
readonly={
myData.current.node?.flow &&
myData.current.node.template[
templateParam
].dynamic
? true
: false
}
dynamic={
data.node!.template[templateParam]
.dynamic ?? false
@ -493,7 +455,6 @@ const EditNodeModal = forwardRef(
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
id={"code-area-edit" + index}
/>
</div>
) : myData.current.node?.template[templateParam]
@ -506,11 +467,6 @@ const EditNodeModal = forwardRef(
<TableCell className="p-0 text-right">
<div className="items-center text-center">
<ToggleShadComponent
id={
"show" +
myData.current.node?.template[templateParam]
.name
}
enabled={
!myData.current.node?.template[
templateParam
@ -536,7 +492,6 @@ const EditNodeModal = forwardRef(
<BaseModal.Footer>
<Button
id={"saveChangesBtn"}
className="mt-3"
onClick={() => {
const newData = cloneDeep(myData.current);

View file

@ -8,7 +8,6 @@ import { useContext, useEffect, useState } from "react";
import AceEditor from "react-ace";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext";
@ -24,7 +23,6 @@ export default function CodeAreaModal({
setNodeClass,
children,
dynamic,
readonly = false,
}: codeAreaModalPropsType): JSX.Element {
const [code, setCode] = useState(value);
const { dark } = useContext(darkContext);
@ -145,15 +143,9 @@ export default function CodeAreaModal({
/>
</BaseModal.Header>
<BaseModal.Content>
<Input
value={code}
className="absolute left-[500%] top-[500%]"
id="codeValue"
/>
<div className="flex h-full w-full flex-col transition-all">
<div className="h-full w-full">
<AceEditor
readOnly={readonly}
value={code}
mode="python"
height={height ?? "100%"}
@ -188,13 +180,7 @@ export default function CodeAreaModal({
</div>
</div>
<div className="flex h-fit w-full justify-end">
<Button
className="mt-3"
onClick={handleClick}
type="submit"
id="checkAndSaveBtn"
disabled={readonly}
>
<Button className="mt-3" onClick={handleClick} type="submit">
Check & Save
</Button>
</div>

View file

@ -4,18 +4,14 @@ import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox";
import { EXPORT_DIALOG_SUBTITLE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { typesContext } from "../../contexts/typesContext";
import { TabsContext } from "../../contexts/tabsContext";
import { removeApiKeys } from "../../utils/reactflowUtils";
import BaseModal from "../baseModal";
const ExportModal = forwardRef(
(props: { children: ReactNode }, ref): JSX.Element => {
const { flows, tabId, downloadFlow } = useContext(FlowsContext);
const { reactFlowInstance } = useContext(typesContext);
const { setNoticeData } = useContext(alertContext);
const [checked, setChecked] = useState(true);
const { flows, tabId, downloadFlow } = useContext(TabsContext);
const [checked, setChecked] = useState(false);
const flow = flows.find((f) => f.id === tabId);
useEffect(() => {
setName(flow!.name);
@ -48,7 +44,6 @@ const ExportModal = forwardRef(
<div className="mt-3 flex items-center space-x-2">
<Checkbox
id="terms"
checked={checked}
onCheckedChange={(event: boolean) => {
setChecked(event);
}}
@ -57,38 +52,20 @@ const ExportModal = forwardRef(
Save with my API keys
</label>
</div>
<span className="text-xs text-destructive">
Caution: Uncheck this box only removes API keys from fields
specifically designated for API keys.
</span>
</BaseModal.Content>
<BaseModal.Footer>
<Button
onClick={() => {
if (checked) {
if (checked)
downloadFlow(
{
id: tabId,
data: reactFlowInstance?.toObject()!,
description,
name,
},
flows.find((flow) => flow.id === tabId)!,
name!,
description
);
setNoticeData({
title:
"Warning: Critical data, JSON file may include API keys.",
});
} else
else
downloadFlow(
removeApiKeys({
id: tabId,
data: reactFlowInstance?.toObject()!,
description,
name,
}),
removeApiKeys(flows.find((flow) => flow.id === tabId)!),
name!,
description
);

View file

@ -3,7 +3,7 @@ import EditFlowSettings from "../../components/EditFlowSettingsComponent";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import { FlowSettingsPropsType } from "../../types/components";
import BaseModal from "../baseModal";
@ -11,7 +11,7 @@ export default function FlowSettingsModal({
open,
setOpen,
}: FlowSettingsPropsType): JSX.Element {
const { flows, tabId, updateFlow, saveFlow } = useContext(FlowsContext);
const { flows, tabId, updateFlow, saveFlow } = useContext(TabsContext);
const flow = flows.find((f) => f.id === tabId);
useEffect(() => {
setName(flow!.name);

View file

@ -24,7 +24,7 @@ import {
import { Textarea } from "../../components/ui/textarea";
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import { getBuildStatus } from "../../controllers/API";
import { TabsState } from "../../types/tabs";
import { validateNodes } from "../../utils/reactflowUtils";
@ -38,7 +38,7 @@ export default function FormModal({
setOpen: (open: boolean) => void;
flow: FlowType;
}): JSX.Element {
const { tabsState, setTabsState } = useContext(FlowsContext);
const { tabsState, setTabsState } = useContext(TabsContext);
const [chatValue, setChatValue] = useState(() => {
try {
const { formKeysData } = tabsState[flow.id];

View file

@ -30,8 +30,6 @@ export default function GenericModal({
nodeClass,
setNodeClass,
children,
id = "",
readonly = false,
}: genericModalPropsType): JSX.Element {
const [myButtonText] = useState(buttonText);
const [myModalTitle] = useState(modalTitle);
@ -210,9 +208,8 @@ export default function GenericModal({
"flex h-full w-full"
)}
>
{type === TypeModal.PROMPT && isEdit && !readonly ? (
{type === TypeModal.PROMPT && isEdit ? (
<Textarea
id={"modal-" + id}
ref={divRefPrompt}
className="form-input h-full w-full rounded-lg custom-scroll focus-visible:ring-1"
value={inputValue}
@ -229,7 +226,7 @@ export default function GenericModal({
handleKeyDown(e, inputValue, "");
}}
/>
) : type === TypeModal.PROMPT && (!isEdit || readonly) ? (
) : type === TypeModal.PROMPT && !isEdit ? (
<SanitizedHTMLWrapper
className={getClassByNumberLength()}
content={coloredContent}
@ -251,7 +248,6 @@ export default function GenericModal({
onKeyDown={(e) => {
handleKeyDown(e, value, "");
}}
readOnly={readonly}
/>
) : (
<></>
@ -288,7 +284,7 @@ export default function GenericModal({
className="m-1 max-w-[40vw] cursor-default truncate p-2.5 text-sm"
>
<div className="relative bottom-[1px]">
<span id={"badge" + index.toString()}>
<span>
{word.replace(/[{}]/g, "").length > 59
? word.replace(/[{}]/g, "").slice(0, 56) +
"..."
@ -308,8 +304,6 @@ export default function GenericModal({
)}
</div>
<Button
id="genericModalBtnSave"
disabled={readonly}
onClick={() => {
switch (myModalType) {
case TypeModal.TEXT:

View file

@ -0,0 +1,101 @@
import { buttonBoxPropsType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
export default function ButtonBox({
onClick,
title,
description,
icon,
bgColor,
textColor,
deactivate,
size,
}: buttonBoxPropsType): JSX.Element {
let bigCircle: string;
let smallCircle: string;
let titleFontSize: string;
let descriptionFontSize: string;
let padding: string;
let marginTop: string;
let height: string;
let width: string;
let textHeight: number;
let textWidth: number;
switch (size) {
case "small":
bigCircle = "h-12 w-12";
smallCircle = "h-8 w-8";
titleFontSize = "text-sm";
descriptionFontSize = "text-xs";
padding = "p-2 py-3";
marginTop = "mt-2";
height = "h-36";
width = "w-32";
break;
case "medium":
bigCircle = "h-16 w-16";
smallCircle = "h-12 w-12";
titleFontSize = "text-base";
descriptionFontSize = "text-sm";
padding = "p-4 py-5";
marginTop = "mt-3";
height = "h-44";
width = "w-36";
break;
case "big":
bigCircle = "h-20 w-20";
smallCircle = "h-16 w-16";
titleFontSize = "text-lg";
descriptionFontSize = "text-sm";
padding = "p-8 py-10";
marginTop = "mt-6";
height = "h-56";
width = "w-44";
break;
default:
bigCircle = "h-20 w-20";
smallCircle = "h-16 w-16";
titleFontSize = "text-lg";
descriptionFontSize = "text-sm";
padding = "p-8 py-10";
marginTop = "mt-6";
height = "h-56";
width = "w-44";
break;
}
return (
<button disabled={deactivate} onClick={onClick}>
<div
className={classNames(
"button-box-modal-div",
bgColor,
height,
width,
padding
)}
>
<div
className={`flex items-center justify-center ${bigCircle} mb-1 rounded-full bg-background/30`}
>
<div
className={`flex items-center justify-center ${smallCircle} rounded-full bg-background`}
>
<div className={textColor}>{icon}</div>
</div>
</div>
<div className="mb-auto mt-auto w-full">
<h3
className={classNames(
"w-full font-semibold text-background truncate-multiline word-break-break-word",
titleFontSize,
marginTop
)}
>
{title}
</h3>
</div>
</div>
</button>
);
}

View file

@ -0,0 +1,190 @@
import {
ArrowLeftIcon,
ArrowUpTrayIcon,
ComputerDesktopIcon,
DocumentDuplicateIcon,
} from "@heroicons/react/24/outline";
import { useContext, useRef, useState } from "react";
import LoadingComponent from "../../components/loadingComponent";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import { IMPORT_DIALOG_SUBTITLE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { TabsContext } from "../../contexts/tabsContext";
import { getExamples } from "../../controllers/API";
import { FlowType } from "../../types/flow";
import { classNames } from "../../utils/utils";
import ButtonBox from "./buttonBox";
export default function ImportModal(): JSX.Element {
const [open, setOpen] = useState(true);
const { setErrorData } = useContext(alertContext);
const ref = useRef();
const [showExamples, setShowExamples] = useState(false);
const [loadingExamples, setLoadingExamples] = useState(false);
const [examples, setExamples] = useState<FlowType[]>([]);
const { uploadFlow, addFlow } = useContext(TabsContext);
function handleExamples(): void {
setLoadingExamples(true);
getExamples()
.then((result) => {
setLoadingExamples(false);
setExamples(result);
})
.catch((error) =>
setErrorData({
title: "there was an error loading examples, please try again",
list: [error.message],
})
);
}
const [modalOpen, setModalOpen] = useState(false);
return (
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger>
<DialogContent
className={classNames(
showExamples
? "h-[600px] lg:max-w-[650px]"
: "h-[450px] lg:max-w-[650px]"
)}
>
<DialogHeader>
<DialogTitle className="flex items-center">
{showExamples && (
<>
<div className="dialog-header-modal-div">
<button
type="button"
className="dialog-header-modal-button disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
onClick={() => {
setShowExamples(false);
}}
>
<ArrowLeftIcon
className="ml-1 h-5 w-5 text-foreground"
aria-hidden="true"
/>
</button>
</div>
</>
)}
<span className={classNames(showExamples ? "pl-8 pr-2" : "pr-2")}>
{showExamples ? "Select an example" : "Import"}
</span>
<ArrowUpTrayIcon
className="ml-1 h-5 w-5 text-foreground"
aria-hidden="true"
/>
</DialogTitle>
<DialogDescription>{IMPORT_DIALOG_SUBTITLE}</DialogDescription>
</DialogHeader>
<div
className={classNames(
"dialog-modal-examples-div",
showExamples && !loadingExamples
? "dialog-modal-example-true"
: "dialog-modal-example-false"
)}
>
{!showExamples && (
<div className="dialog-modal-button-box-div">
<ButtonBox
size="big"
bgColor="bg-medium-emerald "
description="Prebuilt Examples"
icon={<DocumentDuplicateIcon className="document-icon" />}
onClick={() => {
setShowExamples(true);
handleExamples();
}}
textColor="text-medium-emerald "
title="Examples"
></ButtonBox>
<ButtonBox
size="big"
bgColor="bg-almost-dark-blue "
description="Import from Local"
icon={<ComputerDesktopIcon className="document-icon" />}
onClick={() => {
uploadFlow();
setModalOpen(false);
}}
textColor="text-almost-dark-blue "
title="Local File"
></ButtonBox>
</div>
)}
{showExamples && loadingExamples && (
<div className="loading-component-div">
<LoadingComponent remSize={30} />
</div>
)}
{showExamples &&
!loadingExamples &&
examples.map((example, index) => {
return (
<div key={example.name} className="m-2">
{" "}
<ButtonBox
size="small"
bgColor="bg-medium-emerald "
description={example.description ?? "Prebuilt Examples"}
icon={
<DocumentDuplicateIcon
strokeWidth={1.5}
className="h-6 w-6 flex-shrink-0"
/>
}
onClick={() => {
addFlow(example, false);
setModalOpen(false);
}}
textColor="text-medium-emerald "
title={example.name}
></ButtonBox>
</div>
);
})}
</div>
<DialogFooter>
<div className="dialog-modal-footer">
<a
href="https://github.com/logspace-ai/langflow_examples"
target="_blank"
className="dialog-modal-footer-link "
rel="noreferrer"
>
<svg
width="24"
viewBox="0 0 98 96"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
fill="currentColor"
/>
</svg>
<span className="ml-2 ">Langflow Examples</span>
</a>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View file

@ -22,7 +22,7 @@ import {
} from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import {
addUser,
deleteUser,
@ -44,7 +44,7 @@ export default function AdminPage() {
const { userData } = useContext(AuthContext);
const [totalRowsCount, setTotalRowsCount] = useState(0);
const { setTabId } = useContext(FlowsContext);
const { setTabId } = useContext(TabsContext);
// set null id
useEffect(() => {

View file

@ -1,7 +1,7 @@
import { useContext, useEffect, useState } from "react";
import { Button } from "../../components/ui/button";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import { useNavigate } from "react-router-dom";
import { CardComponent } from "../../components/cardComponent";
@ -12,7 +12,7 @@ import { getExamples } from "../../controllers/API";
import { FlowType } from "../../types/flow";
export default function CommunityPage(): JSX.Element {
const { flows, setTabId, downloadFlows, uploadFlows, addFlow } =
useContext(FlowsContext);
useContext(TabsContext);
// set null id
useEffect(() => {
@ -94,7 +94,7 @@ export default function CommunityPage(): JSX.Element {
size="sm"
className="whitespace-nowrap "
onClick={() => {
addFlow(true, flow).then((id) => {
addFlow(flow, true).then((id) => {
navigate("/flow/" + id);
});
}}

View file

@ -8,13 +8,12 @@ export default function DisclosureComponent({
openDisc,
}: DisclosureComponentType): JSX.Element {
return (
<Disclosure as="div" defaultOpen={openDisc} key={title}>
<Disclosure as="div" key={title}>
{({ open }) => (
<>
<div>
<Disclosure.Button className="components-disclosure-arrangement">
<div className="flex gap-4">
{/* BUG ON THIS ICON */}
<Icon strokeWidth={1.5} size={22} className="text-primary" />
<span className="components-disclosure-title">{title}</span>
</div>
@ -35,7 +34,9 @@ export default function DisclosureComponent({
</div>
</Disclosure.Button>
</div>
<Disclosure.Panel as="div">{children}</Disclosure.Panel>
<Disclosure.Panel as="div" static={openDisc}>
{children}
</Disclosure.Panel>
</>
)}
</Disclosure>

View file

@ -28,23 +28,16 @@ import ReactFlow, {
import GenericNode from "../../../../CustomNodes/GenericNode";
import Chat from "../../../../components/chatComponent";
import { alertContext } from "../../../../contexts/alertContext";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { locationContext } from "../../../../contexts/locationContext";
import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import { APIClassType } from "../../../../types/api";
import { FlowType, NodeType, targetHandleType } from "../../../../types/flow";
import { FlowType, NodeType } from "../../../../types/flow";
import { TabsState } from "../../../../types/tabs";
import {
generateFlow,
generateNodeFromFlow,
isValidConnection,
scapeJSONParse,
validateSelection,
} from "../../../../utils/reactflowUtils";
import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
import { isValidConnection } from "../../../../utils/reactflowUtils";
import { isWrappedWithClass } from "../../../../utils/utils";
import ConnectionLineComponent from "../ConnectionLineComponent";
import SelectionMenu from "../SelectionMenuComponent";
import ExtraSidebar from "../extraSidebarComponent";
const nodeTypes = {
@ -70,7 +63,7 @@ export default function Page({
saveFlow,
setTabsState,
tabId,
} = useContext(FlowsContext);
} = useContext(TabsContext);
const {
types,
reactFlowInstance,
@ -245,19 +238,12 @@ export default function Page({
addEdge(
{
...params,
data: {
targetHandle: scapeJSONParse(params.targetHandle!),
sourceHandle: scapeJSONParse(params.sourceHandle!),
},
style: { stroke: "#555" },
className:
((scapeJSONParse(params.targetHandle!) as targetHandleType)
.type === "Text"
(params.targetHandle?.split("|")[0] === "Text"
? "stroke-foreground "
: "stroke-foreground ") + " stroke-connection",
animated:
(scapeJSONParse(params.targetHandle!) as targetHandleType)
.type === "Text",
animated: params.targetHandle?.split("|")[0] === "Text",
},
eds
)
@ -310,6 +296,7 @@ export default function Page({
event.dataTransfer.getData("nodedata")
);
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
// Calculate the position where the node should be created
const position = reactFlowInstance!.project({
x: event.clientX - reactflowBounds!.left,
@ -407,7 +394,7 @@ export default function Page({
edgeUpdateSuccessful.current = true;
}, []);
const [selectionEnded, setSelectionEnded] = useState(true);
const [selectionEnded, setSelectionEnded] = useState(false);
const onSelectionEnd = useCallback(() => {
setSelectionEnded(true);
@ -447,7 +434,7 @@ export default function Page({
<div className="h-full w-full" ref={reactFlowWrapper}>
{Object.keys(templates).length > 0 &&
Object.keys(types).length > 0 ? (
<div id="react-flow-id" className="h-full w-full">
<div className="h-full w-full">
<ReactFlow
nodes={nodes}
onMove={() => {
@ -494,50 +481,6 @@ export default function Page({
[&>button]:border-b-border hover:[&>button]:bg-border"
></Controls>
)}
<SelectionMenu
isVisible={selectionMenuVisible}
nodes={lastSelection?.nodes}
onClick={() => {
if (
validateSelection(lastSelection!, edges).length === 0
) {
const { newFlow } = generateFlow(
lastSelection!,
reactFlowInstance!,
getRandomName()
);
const newGroupNode = generateNodeFromFlow(
newFlow,
getNodeId
);
setNodes((oldNodes) => [
...oldNodes.filter(
(oldNodes) =>
!lastSelection?.nodes.some(
(selectionNode) =>
selectionNode.id === oldNodes.id
)
),
newGroupNode,
]);
setEdges((oldEdges) =>
oldEdges.filter(
(oldEdge) =>
!lastSelection!.nodes.some(
(selectionNode) =>
selectionNode.id === oldEdge.target ||
selectionNode.id === oldEdge.source
)
)
);
} else {
setErrorData({
title: "Invalid selection",
list: validateSelection(lastSelection!, edges),
});
}
}}
/>
</ReactFlow>
{!view && (
<Chat flow={flow} reactFlowInstance={reactFlowInstance!} />

View file

@ -1,55 +0,0 @@
import { useEffect, useState } from "react";
import { NodeToolbar } from "reactflow";
import IconComponent from "../../../../components/genericIconComponent";
export default function SelectionMenu({ onClick, nodes, isVisible }) {
const [isOpen, setIsOpen] = useState(false);
const [isTransitioning, setIsTransitioning] = useState(false);
const [lastNodes, setLastNodes] = useState(nodes);
// nodes get saved to not be gone after the toolbar closes
useEffect(() => {
setLastNodes(nodes);
}, [isOpen]);
// transition starts after and ends before the toolbar closes
useEffect(() => {
if (isVisible) {
setIsOpen(true);
setTimeout(() => {
setIsTransitioning(true);
}, 50);
} else {
setIsTransitioning(false);
setTimeout(() => {
setIsOpen(false);
}, 500);
}
}, [isVisible]);
return (
<NodeToolbar
isVisible={isOpen}
offset={5}
nodeId={
lastNodes && lastNodes.length > 0 ? lastNodes.map((n) => n.id) : []
}
>
<div className="h-10 w-28 overflow-hidden">
<div
className={
"h-10 w-24 rounded-md border border-indigo-300 bg-white px-2.5 text-gray-700 shadow-inner transition-all duration-500 ease-in-out dark:bg-gray-800 dark:text-gray-300" +
(isTransitioning ? " translate-y-0" : " translate-y-10")
}
>
<button
className="flex h-full w-full items-center justify-between text-sm hover:text-indigo-500"
onClick={onClick}
>
<IconComponent name="Group" className="w-6" />
Group
</button>
</div>
</div>
</NodeToolbar>
);
}

View file

@ -5,7 +5,7 @@ import IconComponent from "../../../../components/genericIconComponent";
import { Input } from "../../../../components/ui/input";
import { Separator } from "../../../../components/ui/separator";
import { alertContext } from "../../../../contexts/alertContext";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext";
import ApiModal from "../../../../modals/ApiModal";
import ExportModal from "../../../../modals/exportModal";
@ -22,7 +22,7 @@ export default function ExtraSidebar(): JSX.Element {
const { data, templates, getFilterEdge, setFilterEdge } =
useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } =
useContext(FlowsContext);
useContext(TabsContext);
const { setSuccessData, setErrorData } = useContext(alertContext);
const [dataFilter, setFilterData] = useState(data);
const [search, setSearch] = useState("");
@ -122,7 +122,7 @@ export default function ExtraSidebar(): JSX.Element {
}
}
});
setSearch("");
setSearch("search");
return ret;
});
}
@ -136,7 +136,7 @@ export default function ExtraSidebar(): JSX.Element {
<button
className="extra-side-bar-buttons"
onClick={() => {
uploadFlow(false);
uploadFlow();
}}
>
<IconComponent name="FileUp" className="side-bar-button-size " />
@ -227,12 +227,8 @@ export default function ExtraSidebar(): JSX.Element {
.map((SBSectionName: keyof APIObjectType, index) =>
Object.keys(dataFilter[SBSectionName]).length > 0 ? (
<DisclosureComponent
openDisc={
getFilterEdge.length !== 0 || search.length !== 0
? true
: false
}
key={index + search + JSON.stringify(getFilterEdge)}
openDisc={search.length == 0 ? false : true}
key={index}
button={{
title: nodeNames[SBSectionName] ?? nodeNames.unknown,
Icon:
@ -275,13 +271,7 @@ export default function ExtraSidebar(): JSX.Element {
);
}}
>
<div
className="side-bar-components-div-form"
id={
"side" +
data[SBSectionName][SBItemName].display_name
}
>
<div className="side-bar-components-div-form">
<span className="side-bar-components-text">
{data[SBSectionName][SBItemName].display_name}
</span>

View file

@ -8,20 +8,15 @@ import {
SelectItem,
SelectTrigger,
} from "../../../../components/ui/select-custom";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { TabsContext } from "../../../../contexts/tabsContext";
import EditNodeModal from "../../../../modals/EditNodeModal";
import { nodeToolbarPropsType } from "../../../../types/components";
import {
expandGroupNode,
updateFlowPosition,
} from "../../../../utils/reactflowUtils";
import { classNames, getRandomKeyByssmm } from "../../../../utils/utils";
export default function NodeToolbarComponent({
data,
setData,
deleteNode,
position,
setShowNode,
numberOfHandles,
showNode,
@ -52,9 +47,7 @@ export default function NodeToolbarComponent({
return true;
}
const isMinimal = canMinimize();
const isGroup = data.node?.flow ? true : false;
const { paste } = useContext(FlowsContext);
const { paste } = useContext(TabsContext);
const reactFlowInstance = useReactFlow();
const [showModalAdvanced, setShowModalAdvanced] = useState(false);
const [selectedValue, setSelectedValue] = useState("");
@ -72,10 +65,6 @@ export default function NodeToolbarComponent({
if (event.includes("disabled")) {
return;
}
if (event.includes("ungroup")) {
updateFlowPosition(position, data.node?.flow!);
expandGroupNode(data, reactFlowInstance);
}
};
return (
@ -145,11 +134,11 @@ export default function NodeToolbarComponent({
</a>
</ShadTooltip>
{isMinimal || isGroup ? (
{isMinimal ? (
<Select onValueChange={handleSelectChange} value={selectedValue}>
<ShadTooltip content="More" side="top">
<SelectTrigger>
<div id="advancedIcon">
<div>
<div
className={classNames(
"relative -ml-px inline-flex h-8 w-[31px] items-center rounded-r-md bg-background text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10" +
@ -174,7 +163,6 @@ export default function NodeToolbarComponent({
}
>
<div
id="editAdvancedBtn"
className={
"flex " +
(nodeLength == 0
@ -191,7 +179,7 @@ export default function NodeToolbarComponent({
</SelectItem>
{isMinimal && (
<SelectItem value={getRandomKeyByssmm() + "show"}>
<div className="flex" id="editAdvanced">
<div className="flex">
<IconComponent
name={showNode ? "Minimize2" : "Maximize2"}
className="relative top-0.5 mr-2 h-4 w-4"
@ -200,22 +188,11 @@ export default function NodeToolbarComponent({
</div>
</SelectItem>
)}
{isGroup && (
<SelectItem value={getRandomKeyByssmm() + "ungroup"}>
<div className="flex">
<IconComponent
name="Ungroup"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
Ungroup{" "}
</div>
</SelectItem>
)}
</SelectContent>
</Select>
) : (
<ShadTooltip content="Edit" side="top">
<div id="editAdvancedIcon">
<div>
<button
disabled={nodeLength === 0}
onClick={() => setShowModalAdvanced(true)}
@ -245,6 +222,29 @@ export default function NodeToolbarComponent({
<></>
</EditNodeModal>
)}
{/*
<ShadTooltip content="Edit" side="top">
<div>
<EditNodeModal
data={data}
setData={setData}
nodeLength={nodeLength}
>
<div
className={classNames(
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10" +
(!canMinimize() && " rounded-r-md ") +
(nodeLength == 0
? " text-muted-foreground"
: " text-foreground")
)}
>
<IconComponent name="Settings2" className="h-4 w-4 " />
</div>
</EditNodeModal>
</div>
</ShadTooltip> */}
</span>
</div>
</>

View file

@ -1,12 +1,12 @@
import { useContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import Header from "../../components/headerComponent";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import { getVersion } from "../../controllers/API";
import Page from "./components/PageComponent";
export default function FlowPage(): JSX.Element {
const { flows, tabId, setTabId } = useContext(FlowsContext);
const { flows, tabId, setTabId } = useContext(TabsContext);
const { id } = useParams();
// Set flow tab id

View file

@ -8,7 +8,7 @@ import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
import { Button } from "../../components/ui/button";
import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
export default function HomePage(): JSX.Element {
const {
flows,
@ -19,7 +19,7 @@ export default function HomePage(): JSX.Element {
removeFlow,
uploadFlow,
isLoading,
} = useContext(FlowsContext);
} = useContext(TabsContext);
const { setErrorData } = useContext(alertContext);
const dropdownOptions = [
{
@ -104,7 +104,7 @@ export default function HomePage(): JSX.Element {
<DropdownButton
firstButtonName="New Project"
onFirstBtnClick={() => {
addFlow(true).then((id) => {
addFlow(null!, true).then((id) => {
navigate("/flow/" + id);
});
}}

View file

@ -9,7 +9,7 @@ import { Button } from "../../components/ui/button";
import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { TabsContext } from "../../contexts/tabsContext";
import { resetPassword, updateUser } from "../../controllers/API";
import {
inputHandlerEventType,
@ -17,7 +17,7 @@ import {
} from "../../types/components";
import { gradients } from "../../utils/styleUtils";
export default function ProfileSettingsPage(): JSX.Element {
const { setTabId } = useContext(FlowsContext);
const { setTabId } = useContext(TabsContext);
const [inputState, setInputState] = useState<patchUserInputStateType>(
CONTROL_PATCH_USER_STATE

View file

@ -1,18 +1,28 @@
import { useContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { FlowsContext } from "../../contexts/flowsContext";
import { darkContext } from "../../contexts/darkContext";
import { TabsContext } from "../../contexts/tabsContext";
import { getVersion } from "../../controllers/API";
import Page from "../FlowPage/components/PageComponent";
export default function ViewPage() {
const { flows, tabId, setTabId } = useContext(FlowsContext);
const { id } = useParams();
const { flows, tabId, setTabId } = useContext(TabsContext);
const { setDark } = useContext(darkContext);
const { id, theme } = useParams();
// Set flow tab id
useEffect(() => {
setTabId(id!);
}, [id]);
useEffect(() => {
if (theme) {
setDark(theme === "dark");
} else {
setDark(false);
}
}, [theme]);
// Initialize state variable for the version
const [version, setVersion] = useState("");
useEffect(() => {

View file

@ -44,7 +44,7 @@ const Router = () => {
}
/>
<Route
path="view"
path="view/:theme?"
element={
<ProtectedRoute>
<ViewPage />

View file

@ -505,7 +505,7 @@
}
.header-arrangement {
@apply flex-max-width h-12 items-center justify-between border-border bg-background;
@apply flex-max-width h-12 items-center justify-between border-border bg-muted;
}
.header-start-display {
@apply flex w-[30%] items-center justify-start gap-2;
@ -853,7 +853,7 @@
}
.api-modal-tabs {
@apply flex h-full max-w-full flex-col overflow-hidden rounded-md border bg-muted text-center sm:w-[75vw] md:w-[75vw] lg:w-[75vw] xl:w-[76vw] 2xl:w-full;
@apply flex h-full flex-col max-w-full overflow-hidden rounded-md border bg-muted text-center sm:w-[75vw] md:w-[75vw] lg:w-[75vw] xl:w-[76vw] 2xl:w-full;
}
.api-modal-tablist-div {
@apply flex items-center justify-between px-2;

View file

@ -1,10 +1,10 @@
import { Edge, Node, Viewport } from "reactflow";
import { FlowType } from "../flow";
//kind and class are just representative names to represent the actual structure of the object received by the API
export type APIDataType = { [key: string]: APIKindType };
export type APIObjectType = { kind: APIKindType; [key: string]: APIKindType };
export type APIKindType = { class: APIClassType; [key: string]: APIClassType };
export type APITemplateType = {
variable: TemplateVariableType;
[key: string]: TemplateVariableType;
};
@ -23,13 +23,10 @@ export type APIClassType = {
beta?: boolean;
documentation: string;
error?: string;
flow?: FlowType;
[key: string]:
| Array<string>
| string
| APITemplateType
| boolean
| FlowType
| CustomFieldsType
| boolean
| undefined;
@ -41,14 +38,9 @@ export type TemplateVariableType = {
placeholder?: string;
list: boolean;
show: boolean;
readonly: boolean;
multiline?: boolean;
value?: any;
dynamic?: boolean;
proxy?: { id: string; field: string };
input_types?: Array<string>;
display_name?: string;
name?: string;
[key: string]: any;
};
export type sendAllProps = {

View file

@ -1,13 +1,11 @@
import { ReactElement, ReactNode } from "react";
import { ReactFlowJsonObject, XYPosition } from "reactflow";
import { ReactFlowJsonObject } from "reactflow";
import { APIClassType, APITemplateType, TemplateVariableType } from "../api";
import { ChatMessageType } from "../chat";
import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index";
import { typesContextType } from "../typesContext";
import { sourceHandleType, targetHandleType } from "./../flow/index";
export type InputComponentType = {
autoFocus?: boolean;
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
value: string;
disabled?: boolean;
onChange: (value: string) => void;
@ -19,15 +17,12 @@ export type InputComponentType = {
showPass?: boolean;
placeholder?: string;
className?: string;
id?: string;
blurOnEnter?: boolean;
};
export type ToggleComponentType = {
enabled: boolean;
setEnabled: (state: boolean) => void;
disabled: boolean | undefined;
size: "small" | "medium" | "large";
id?: string;
};
export type DropDownComponentType = {
value: string;
@ -41,7 +36,7 @@ export type ParameterComponentType = {
data: NodeDataType;
setData: (value: NodeDataType) => void;
title: string;
id: sourceHandleType | targetHandleType;
id: string;
color: string;
left: boolean;
type: string | undefined;
@ -51,9 +46,7 @@ export type ParameterComponentType = {
dataContext?: typesContextType;
optionalHandle?: Array<String> | null;
info?: string;
proxy?: { field: string; id: string };
showNode?: boolean;
index?: string;
};
export type InputListComponentType = {
value: string[];
@ -87,8 +80,6 @@ export type TextAreaComponentType = {
onChange: (value: string[] | string) => void;
value: string;
editNode?: boolean;
id?: string;
readonly?: boolean;
};
export type PromptAreaComponentType = {
@ -98,9 +89,7 @@ export type PromptAreaComponentType = {
disabled: boolean;
onChange: (value: string[] | string) => void;
value: string;
readonly?: boolean;
editNode?: boolean;
id?: string;
};
export type CodeAreaComponentType = {
@ -111,8 +100,6 @@ export type CodeAreaComponentType = {
nodeClass?: APIClassType;
setNodeClass?: (value: APIClassType) => void;
dynamic?: boolean;
id?: string;
readonly?: boolean;
};
export type FileComponentType = {
@ -143,7 +130,6 @@ export type FloatComponentType = {
disabled?: boolean;
onChange: (value: string) => void;
editNode?: boolean;
id?: string;
};
export type TooltipComponentType = {
@ -440,7 +426,6 @@ export type nodeToolbarPropsType = {
data: NodeDataType;
deleteNode: (idx: string) => void;
setData: (newState: NodeDataType) => void;
position: XYPosition;
setShowNode: (boolean: any) => void;
numberOfHandles: boolean[] | [];
showNode: boolean;
@ -476,7 +461,6 @@ export type codeAreaModalPropsType = {
setNodeClass: (Class: APIClassType) => void | undefined;
children: ReactNode;
dynamic?: boolean;
readonly?: boolean;
};
export type chatMessagePropsType = {
@ -501,8 +485,6 @@ export type genericModalPropsType = {
nodeClass?: APIClassType;
setNodeClass?: (Class: APIClassType) => void;
children: ReactNode;
id?: string;
readonly?: boolean;
};
export type buttonBoxPropsType = {

View file

@ -13,14 +13,12 @@ export type NodeType = {
type?: string;
position: XYPosition;
data: NodeDataType;
selected?: boolean;
};
export type NodeDataType = {
type: string;
node?: APIClassType;
id: string;
output_types?: string[];
};
// FlowStyleType is the type of the style object that is used to style the
// Flow card with an emoji and a color.
@ -37,18 +35,3 @@ export type TweaksType = Array<
};
} & FlowStyleType
>;
// right side
export type sourceHandleType = {
dataType: string;
id: string;
baseClasses: string[];
};
//left side
export type targetHandleType = {
inputTypes?: string[];
type: string;
fieldName: string;
id: string;
proxy?: { field: string; id: string };
};

View file

@ -1,7 +1,7 @@
import { tweakType } from "../components";
import { FlowType } from "../flow";
export type FlowsContextType = {
export type TabsContextType = {
saveFlow: (flow: FlowType, silent?: boolean) => Promise<void>;
tabId: string;
isLoading: boolean;
@ -9,8 +9,8 @@ export type FlowsContextType = {
flows: Array<FlowType>;
removeFlow: (id: string) => void;
addFlow: (
newProject: boolean,
flow?: FlowType
flow?: FlowType,
newProject?: Boolean
) => Promise<String | undefined>;
updateFlow: (newFlow: FlowType) => void;
incrementNodeId: () => string;
@ -23,11 +23,11 @@ export type FlowsContextType = {
uploadFlows: () => void;
isBuilt: boolean;
setIsBuilt: (state: boolean) => void;
uploadFlow: (newFlow: boolean, file?: File) => Promise<String | undefined>;
uploadFlow: (newFlow?: boolean, file?: File) => Promise<String | undefined>;
hardReset: () => void;
getNodeId: (nodeType: string) => string;
tabsState: FlowsState;
setTabsState: (state: FlowsState) => void;
tabsState: TabsState;
setTabsState: (state: TabsState) => void;
paste: (
selection: { nodes: any; edges: any },
position: { x: number; y: number; paneX?: number; paneY?: number }
@ -38,7 +38,7 @@ export type FlowsContextType = {
getTweak: tweakType;
};
export type FlowsState = {
export type TabsState = {
[key: string]: {
isPending: boolean;
formKeysData: {

View file

@ -1,5 +1,5 @@
import { Edge, Node } from "reactflow";
import { FlowType, NodeType } from "../flow";
import { NodeType } from "../flow";
export type cleanEdgesType = {
flow: {
@ -11,17 +11,5 @@ export type cleanEdgesType = {
export type unselectAllNodesType = {
updateNodes: (nodes: Node[]) => void;
data: Node[];
};
export type updateEdgesHandleIdsType = {
nodes: NodeType[];
edges: Edge[];
};
export type generateFlowType = { newFlow: FlowType; removedEdges: Edge[] };
export type findLastNodeType = {
nodes: NodeType[];
edges: Edge[];
data: Node[] | null;
};

View file

@ -3,28 +3,17 @@ import {
Connection,
Edge,
Node,
OnSelectionChangeParams,
ReactFlowInstance,
ReactFlowJsonObject,
XYPosition,
} from "reactflow";
import { specialCharsRegex } from "../constants/constants";
import { APITemplateType, TemplateVariableType } from "../types/api";
import {
FlowType,
NodeDataType,
NodeType,
sourceHandleType,
targetHandleType,
} from "../types/flow";
import { APITemplateType } from "../types/api";
import { FlowType, NodeType } from "../types/flow";
import {
cleanEdgesType,
findLastNodeType,
generateFlowType,
unselectAllNodesType,
updateEdgesHandleIdsType,
} from "../types/utils/reactflowUtils";
import { getFieldTitle } from "./utils";
import { toNormalCase } from "./utils";
export function cleanEdges({
flow: { edges, nodes },
@ -43,30 +32,26 @@ export function cleanEdges({
const sourceHandle = edge.sourceHandle; //right
const targetHandle = edge.targetHandle; //left
if (targetHandle) {
const targetHandleObject: targetHandleType =
scapeJSONParse(targetHandle);
const field = targetHandleObject.fieldName;
const id: targetHandleType = {
type: targetNode.data.node!.template[field]?.type,
fieldName: field,
id: targetNode.data.id,
inputTypes: targetNode.data.node!.template[field]?.input_types,
};
if (targetNode.data.node!.template[field]?.proxy) {
id.proxy = targetNode.data.node!.template[field]?.proxy;
}
if (scapedJSONStringfy(id) !== targetHandle) {
const field = targetHandle.split("|")[1];
const id =
(targetNode.data.node?.template[field]?.input_types?.join(";") ??
targetNode.data.node?.template[field]?.type) +
"|" +
field +
"|" +
targetNode.data.id;
if (id !== targetHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
}
}
if (sourceHandle) {
const id: sourceHandleType = {
id: sourceNode.data.id,
baseClasses: sourceNode.data.node!.base_classes,
dataType: sourceNode.data.type,
};
if (scapedJSONStringfy(id) !== sourceHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
const id = [
sourceNode.data.type,
sourceNode.data.id,
...sourceNode.data.node?.base_classes!,
].join("|");
if (id !== sourceHandle) {
newEdges = newEdges.filter((edg) => edg.id !== edge.id);
}
}
}
@ -76,7 +61,7 @@ export function cleanEdges({
export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) {
let newNodes = _.cloneDeep(data);
newNodes.forEach((node: Node) => {
newNodes!.forEach((node: Node) => {
node.selected = false;
});
updateNodes(newNodes!);
@ -86,18 +71,21 @@ export function isValidConnection(
{ source, target, sourceHandle, targetHandle }: Connection,
reactFlowInstance: ReactFlowInstance
) {
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
if (
targetHandleObject.inputTypes?.some(
(n) => n === sourceHandleObject.dataType
) ||
sourceHandleObject.baseClasses.some(
(t) =>
targetHandleObject.inputTypes?.some((n) => n === t) ||
t === targetHandleObject.type
) ||
targetHandleObject.type === "str"
targetHandle
?.split("|")[0]
.split(";")
.some((target) => target === sourceHandle?.split("|")[0]) ||
sourceHandle
?.split("|")
.slice(2)
.some((target) =>
targetHandle
?.split("|")[0]
.split(";")
.some((n) => n === target)
) ||
targetHandle?.split("|")[0] === "str"
) {
let targetNode = reactFlowInstance?.getNode(target!)?.data?.node;
if (!targetNode) {
@ -109,11 +97,11 @@ export function isValidConnection(
return true;
}
} else if (
(!targetNode.template[targetHandleObject.fieldName].list &&
(!targetNode.template[targetHandle?.split("|")[1]!].list &&
!reactFlowInstance
.getEdges()
.find((e) => e.targetHandle === targetHandle)) ||
targetNode.template[targetHandleObject.fieldName].list
targetNode.template[targetHandle?.split("|")[1]!].list
) {
return true;
}
@ -164,36 +152,26 @@ export function updateIds(
newFlow.nodes.forEach((node: NodeType) => {
// Generate a unique node ID
let newId = getNodeId(node.data.node?.flow ? "GroupNode" : node.data.type);
let newId = getNodeId(node.data.type);
idsMap[node.id] = newId;
node.id = newId;
node.data.id = newId;
// Add the new node to the list of nodes in state
});
newFlow.edges.forEach((edge: Edge) => {
newFlow.edges.forEach((edge) => {
edge.source = idsMap[edge.source];
edge.target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle!
);
edge.sourceHandle = scapedJSONStringfy({
...sourceHandleObject,
id: edge.source,
});
if (edge.data?.sourceHandle?.id) {
edge.data.sourceHandle.id = edge.source;
}
const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!
);
edge.targetHandle = scapedJSONStringfy({
...targetHandleObject,
id: edge.target,
});
if (edge.data?.targetHandle?.id) {
edge.data.targetHandle.id = edge.target;
}
let sourceHandleSplitted = edge.sourceHandle!.split("|");
edge.sourceHandle =
sourceHandleSplitted[0] +
"|" +
edge.source +
"|" +
sourceHandleSplitted.slice(2).join("|");
let targetHandleSplitted = edge.targetHandle!.split("|");
edge.targetHandle =
targetHandleSplitted.slice(0, -1).join("|") + "|" + edge.target;
edge.id =
"reactflow__edge-" +
edge.source +
@ -232,13 +210,15 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
template[t].value === "") &&
!edges.some(
(edge) =>
(scapeJSONParse(edge.targetHandle!) as targetHandleType).fieldName ===
t &&
(scapeJSONParse(edge.targetHandle!) as targetHandleType).id ===
node.id
edge.targetHandle?.split("|")[1] === t &&
edge.targetHandle.split("|")[2] === node.id
)
) {
errors.push(`${type} is missing ${getFieldTitle(template, t)}.`);
errors.push(
`${type} is missing ${
template.display_name || toNormalCase(template[t].name)
}.`
);
} else if (
template[t].type === "dict" &&
template[t].required &&
@ -249,14 +229,15 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
) {
if (hasDuplicateKeys(template[t].value))
errors.push(
`${type} (${getFieldTitle(
template,
t
)}) contains duplicate keys with the same values.`
`${type} (${
template.display_name || template[t].name
}) contains duplicate keys with the same values.`
);
if (hasEmptyKey(template[t].value))
errors.push(
`${type} (${getFieldTitle(template, t)}) field must not be empty.`
`${type} (${
template.display_name || template[t].name
}) field must not be empty.`
);
}
return errors;
@ -285,49 +266,6 @@ export function addVersionToDuplicates(flow: FlowType, flows: FlowType[]) {
return newName;
}
export function updateEdgesHandleIds({
edges,
nodes,
}: updateEdgesHandleIdsType): Edge[] {
let newEdges = _.cloneDeep(edges);
newEdges.forEach((edge) => {
const sourceNodeId = edge.source;
const targetNodeId = edge.target;
const sourceNode = nodes.find((node) => node.id === sourceNodeId);
const targetNode = nodes.find((node) => node.id === targetNodeId);
let source = edge.sourceHandle;
let target = edge.targetHandle;
//right
let newSource: sourceHandleType;
//left
let newTarget: targetHandleType;
if (target && targetNode) {
let field = target.split("|")[1];
newTarget = {
type: targetNode.data.node!.template[field].type,
fieldName: field,
id: targetNode.data.id,
inputTypes: targetNode.data.node!.template[field].input_types,
};
}
if (source && sourceNode) {
newSource = {
id: sourceNode.data.id,
baseClasses: sourceNode.data.node!.base_classes,
dataType: sourceNode.data.type,
};
}
edge.sourceHandle = scapedJSONStringfy(newSource!);
edge.targetHandle = scapedJSONStringfy(newTarget!);
const newData = {
sourceHandle: scapeJSONParse(edge.sourceHandle),
targetHandle: scapeJSONParse(edge.targetHandle),
};
edge.data = newData;
});
return newEdges;
}
export function handleKeyDown(
e:
| React.KeyboardEvent<HTMLInputElement>
@ -439,594 +377,3 @@ export function convertValuesToNumbers(arr) {
return newObj;
});
}
export function scapedJSONStringfy(json: object): string {
return customStringify(json).replace(/"/g, "œ");
}
export function scapeJSONParse(json: string): any {
let parsed = json.replace(/œ/g, '"');
return JSON.parse(parsed);
}
// this function receives an array of edges and return true if any of the handles are not a json string
export function checkOldEdgesHandles(edges: Edge[]): boolean {
return edges.some(
(edge) =>
!edge.sourceHandle ||
!edge.targetHandle ||
!edge.sourceHandle.includes("{") ||
!edge.targetHandle.includes("{")
);
}
export function customStringify(obj: any): string {
if (typeof obj === "undefined") {
return "null";
}
if (obj === null || typeof obj !== "object") {
if (obj instanceof Date) {
return `"${obj.toISOString()}"`;
}
return JSON.stringify(obj);
}
if (Array.isArray(obj)) {
const arrayItems = obj.map((item) => customStringify(item)).join(",");
return `[${arrayItems}]`;
}
const keys = Object.keys(obj).sort();
const keyValuePairs = keys.map(
(key) => `"${key}":${customStringify(obj[key])}`
);
return `{${keyValuePairs.join(",")}}`;
}
export function getMiddlePoint(nodes: Node[]) {
let middlePointX = 0;
let middlePointY = 0;
nodes.forEach((node) => {
middlePointX += node.position.x;
middlePointY += node.position.y;
});
const totalNodes = nodes.length;
const averageX = middlePointX / totalNodes;
const averageY = middlePointY / totalNodes;
return { x: averageX, y: averageY };
}
export function generateFlow(
selection: OnSelectionChangeParams,
reactFlowInstance: ReactFlowInstance,
name: string
): generateFlowType {
const newFlowData = reactFlowInstance.toObject();
/* remove edges that are not connected to selected nodes on both ends
in future we can save this edges to when ungrouping reconect to the old nodes
*/
newFlowData.edges = selection.edges.filter(
(edge) =>
selection.nodes.some((node) => node.id === edge.target) &&
selection.nodes.some((node) => node.id === edge.source)
);
newFlowData.nodes = selection.nodes;
const newFlow: FlowType = {
data: newFlowData,
name: name,
description: "",
//generating local id instead of using the id from the server, can change in the future
id: uid(),
};
// filter edges that are not connected to selected nodes on both ends
// using O(n²) aproach because the number of edges is small
// in the future we can use a better aproach using a set
return {
newFlow,
removedEdges: selection.edges.filter(
(edge) => !newFlowData.edges.includes(edge)
),
};
}
export function filterFlow(
selection: OnSelectionChangeParams,
reactFlowInstance: ReactFlowInstance
) {
reactFlowInstance.setNodes((nodes) =>
nodes.filter((node) => !selection.nodes.includes(node))
);
reactFlowInstance.setEdges((edges) =>
edges.filter((edge) => !selection.edges.includes(edge))
);
}
export function findLastNode({ nodes, edges }: findLastNodeType) {
/*
this function receives a flow and return the last node
*/
let lastNode = nodes.find((n) => !edges.some((e) => e.source === n.id));
return lastNode;
}
export function updateFlowPosition(NewPosition: XYPosition, flow: FlowType) {
const middlePoint = getMiddlePoint(flow.data!.nodes);
let deltaPosition = {
x: NewPosition.x - middlePoint.x,
y: NewPosition.y - middlePoint.y,
};
flow.data!.nodes.forEach((node) => {
node.position.x += deltaPosition.x;
node.position.y += deltaPosition.y;
});
}
export function concatFlows(
flow: FlowType,
ReactFlowInstance: ReactFlowInstance
) {
const { nodes, edges } = flow.data!;
ReactFlowInstance.addNodes(nodes);
ReactFlowInstance.addEdges(edges);
}
export function validateSelection(
selection: OnSelectionChangeParams,
edges: Edge[]
): Array<string> {
//add edges to selection if selection mode selected only nodes
if (selection.edges.length === 0) {
selection.edges = edges;
}
// get only edges that are connected to the nodes in the selection
// first creates a set of all the nodes ids
let nodesSet = new Set(selection.nodes.map((n) => n.id));
// then filter the edges that are connected to the nodes in the set
let connectedEdges = selection.edges.filter(
(e) => nodesSet.has(e.source) && nodesSet.has(e.target)
);
// add the edges to the selection
selection.edges = connectedEdges;
let errorsArray: Array<string> = [];
// check if there is more than one node
if (selection.nodes.length < 2) {
errorsArray.push("Please select more than one node");
}
//check if there are two or more nodes with free outputs
if (
selection.nodes.filter(
(n) => !selection.edges.some((e) => e.source === n.id)
).length > 1
) {
errorsArray.push("Please select only one node with free outputs");
}
// check if there is any node that does not have any connection
if (
selection.nodes.some(
(node) =>
!selection.edges.some((edge) => edge.target === node.id) &&
!selection.edges.some((edge) => edge.source === node.id)
)
) {
errorsArray.push("Please select only nodes that are connected");
}
return errorsArray;
}
function updateGroupNodeTemplate(template: APITemplateType) {
/*this function receives a template, iterates for it's items
updating the visibility of all basic types setting it to advanced true*/
Object.keys(template).forEach((key) => {
let type = template[key].type;
let input_types = template[key].input_types;
if (
(type === "str" ||
type === "bool" ||
type === "float" ||
type === "code" ||
type === "prompt" ||
type === "file" ||
type === "int" ||
type === "dict" ||
type === "NestedDict") &&
!template[key].required &&
!input_types
) {
template[key].advanced = true;
}
//prevent code fields from showing on the group node
if (type === "code") {
template[key].show = false;
}
});
return template;
}
export function mergeNodeTemplates({
nodes,
edges,
}: {
nodes: NodeType[];
edges: Edge[];
}): APITemplateType {
/* this function receives a flow and iterate throw each node
and merge the templates with only the visible fields
if there are two keys with the same name in the flow, we will update the display name of each one
to show from which node it came from
*/
let template: APITemplateType = {};
nodes.forEach((node) => {
let nodeTemplate = _.cloneDeep(node.data.node!.template);
Object.keys(nodeTemplate)
.filter((field_name) => field_name.charAt(0) !== "_")
.forEach((key) => {
if (!isHandleConnected(edges, key, nodeTemplate[key], node.id)) {
template[key + "_" + node.id] = nodeTemplate[key];
template[key + "_" + node.id].proxy = { id: node.id, field: key };
if (node.type === "groupNode") {
template[key + "_" + node.id].display_name =
node.data.node!.flow!.name + " - " + nodeTemplate[key].name;
} else {
template[key + "_" + node.id].display_name =
//data id already has the node name on it
nodeTemplate[key].display_name
? nodeTemplate[key].display_name
: nodeTemplate[key].name
? toTitleCase(nodeTemplate[key].name)
: toTitleCase(key);
}
}
});
});
return template;
}
function isHandleConnected(
edges: Edge[],
key: string,
field: TemplateVariableType,
nodeId: string
) {
/*
this function receives a flow and a handleId and check if there is a connection with this handle
*/
scapedJSONStringfy({ type: field.type, fieldName: key, id: nodeId });
if (field.proxy) {
if (
edges.some(
(e) =>
e.targetHandle ===
scapedJSONStringfy({
type: field.type,
fieldName: key,
id: nodeId,
proxy: { id: field.proxy!.id, field: field.proxy!.field },
inputTypes: field.input_types,
} as targetHandleType)
)
) {
return true;
}
} else {
if (
edges.some(
(e) =>
e.targetHandle ===
scapedJSONStringfy({
type: field.type,
fieldName: key,
id: nodeId,
inputTypes: field.input_types,
} as targetHandleType)
)
) {
return true;
}
}
return false;
}
export function generateNodeTemplate(Flow: FlowType) {
/*
this function receives a flow and generate a template for the group node
*/
let template = mergeNodeTemplates({
nodes: Flow.data!.nodes,
edges: Flow.data!.edges,
});
updateGroupNodeTemplate(template);
return template;
}
export function generateNodeFromFlow(
flow: FlowType,
getNodeId: (type: string) => string
): NodeType {
const { nodes } = flow.data!;
const outputNode = _.cloneDeep(findLastNode(flow.data!));
const position = getMiddlePoint(nodes);
let data = _.cloneDeep(flow);
const id = getNodeId(outputNode?.data.type!);
const newGroupNode: NodeType = {
data: {
id,
type: outputNode?.data.type!,
node: {
output_types: outputNode!.data.node!.output_types,
display_name: "Group",
documentation: "",
base_classes: outputNode!.data.node!.base_classes,
description: "double click to edit description",
template: generateNodeTemplate(data),
flow: data,
},
},
id,
position,
type: "genericNode",
};
return newGroupNode;
}
export function connectedInputNodesOnHandle(
nodeId: string,
handleId: string,
{ nodes, edges }: { nodes: NodeType[]; edges: Edge[] }
) {
const connectedNodes: Array<{ name: string; id: string; isGroup: boolean }> =
[];
// return the nodes connected to the input handle of the node
const TargetEdges = edges.filter((e) => e.target === nodeId);
TargetEdges.forEach((edge) => {
if (edge.targetHandle === handleId) {
const sourceNode = nodes.find((n) => n.id === edge.source);
if (sourceNode) {
if (sourceNode.type === "groupNode") {
let lastNode = findLastNode(sourceNode.data.node!.flow!.data!);
while (lastNode && lastNode.type === "groupNode") {
lastNode = findLastNode(lastNode.data.node!.flow!.data!);
}
if (lastNode) {
connectedNodes.push({
name: sourceNode.data.node!.flow!.name,
id: lastNode.id,
isGroup: true,
});
}
} else {
connectedNodes.push({
name: sourceNode.data.type,
id: sourceNode.id,
isGroup: false,
});
}
}
}
});
return connectedNodes;
}
export function ungroupNode(
groupNode: NodeDataType,
BaseFlow: ReactFlowJsonObject
) {
const { template, flow } = groupNode.node!;
const gNodes: NodeType[] = flow!.data!.nodes;
const gEdges = flow!.data!.edges;
//redirect edges to correct proxy node
let updatedEdges: Edge[] = [];
BaseFlow.edges.forEach((edge) => {
let newEdge = _.cloneDeep(edge);
if (newEdge.target === groupNode.id) {
const targetHandle: targetHandleType = newEdge.data.targetHandle;
if (targetHandle.proxy) {
let type = targetHandle.type;
let field = targetHandle.proxy.field;
let proxyId = targetHandle.proxy.id;
let inputTypes = targetHandle.inputTypes;
let node: NodeType = gNodes.find((n) => n.id === proxyId)!;
if (node) {
newEdge.target = proxyId;
let newTargetHandle: targetHandleType = {
fieldName: field,
type,
id: proxyId,
inputTypes: inputTypes,
};
if (node.data.node?.flow) {
newTargetHandle.proxy = {
field: node.data.node.template[field].proxy?.field!,
id: node.data.node.template[field].proxy?.id!,
};
}
newEdge.data.targetHandle = newTargetHandle;
newEdge.targetHandle = scapedJSONStringfy(newTargetHandle);
}
}
}
if (newEdge.source === groupNode.id) {
const lastNode = _.cloneDeep(findLastNode(flow!.data!));
newEdge.source = lastNode!.id;
let newSourceHandle: sourceHandleType = scapeJSONParse(
newEdge.sourceHandle!
);
newSourceHandle.id = lastNode!.id;
newEdge.data.sourceHandle = newSourceHandle;
newEdge.sourceHandle = scapedJSONStringfy(newSourceHandle);
}
if (edge.target === groupNode.id || edge.source === groupNode.id) {
updatedEdges.push(newEdge);
}
});
//update template values
Object.keys(template).forEach((key) => {
let { field, id } = template[key].proxy!;
let nodeIndex = gNodes.findIndex((n) => n.id === id);
if (nodeIndex !== -1) {
let display_name: string;
let show = gNodes[nodeIndex].data.node!.template[field].show;
let advanced = gNodes[nodeIndex].data.node!.template[field].advanced;
if (gNodes[nodeIndex].data.node!.template[field].display_name) {
display_name =
gNodes[nodeIndex].data.node!.template[field].display_name;
} else {
display_name = gNodes[nodeIndex].data.node!.template[field].name;
}
gNodes[nodeIndex].data.node!.template[field] = template[key];
gNodes[nodeIndex].data.node!.template[field].show = show;
gNodes[nodeIndex].data.node!.template[field].advanced = advanced;
gNodes[nodeIndex].data.node!.template[field].display_name = display_name;
}
});
const nodes = [
...BaseFlow.nodes.filter((n) => n.id !== groupNode.id),
...gNodes,
];
const edges = [
...BaseFlow.edges.filter(
(e) => e.target !== groupNode.id && e.source !== groupNode.id
),
...gEdges,
...updatedEdges,
];
BaseFlow.nodes = nodes;
BaseFlow.edges = edges;
}
export function expandGroupNode(
groupNode: NodeDataType,
ReactFlowInstance: ReactFlowInstance
) {
const { template, flow } = _.cloneDeep(groupNode.node!);
const gNodes: NodeType[] = flow?.data?.nodes!;
const gEdges = flow!.data!.edges;
console.log(gEdges);
//redirect edges to correct proxy node
let updatedEdges: Edge[] = [];
ReactFlowInstance.getEdges().forEach((edge) => {
let newEdge = _.cloneDeep(edge);
if (newEdge.target === groupNode.id) {
const targetHandle: targetHandleType = newEdge.data.targetHandle;
if (targetHandle.proxy) {
let type = targetHandle.type;
let field = targetHandle.proxy.field;
let proxyId = targetHandle.proxy.id;
let inputTypes = targetHandle.inputTypes;
let node: NodeType = gNodes.find((n) => n.id === proxyId)!;
if (node) {
newEdge.target = proxyId;
let newTargetHandle: targetHandleType = {
fieldName: field,
type,
id: proxyId,
inputTypes: inputTypes,
};
if (node.data.node?.flow) {
newTargetHandle.proxy = {
field: node.data.node.template[field].proxy?.field!,
id: node.data.node.template[field].proxy?.id!,
};
}
newEdge.data.targetHandle = newTargetHandle;
newEdge.targetHandle = scapedJSONStringfy(newTargetHandle);
}
}
}
if (newEdge.source === groupNode.id) {
const lastNode = _.cloneDeep(findLastNode(flow!.data!));
newEdge.source = lastNode!.id;
let newSourceHandle: sourceHandleType = scapeJSONParse(
newEdge.sourceHandle!
);
newSourceHandle.id = lastNode!.id;
newEdge.data.sourceHandle = newSourceHandle;
newEdge.sourceHandle = scapedJSONStringfy(newSourceHandle);
}
if (edge.target === groupNode.id || edge.source === groupNode.id) {
updatedEdges.push(newEdge);
}
});
//update template values
Object.keys(template).forEach((key) => {
let { field, id } = template[key].proxy!;
let nodeIndex = gNodes.findIndex((n) => n.id === id);
if (nodeIndex !== -1) {
let proxy: { id: string; field: string } | undefined;
let display_name: string;
let show = gNodes[nodeIndex].data.node!.template[field].show;
let advanced = gNodes[nodeIndex].data.node!.template[field].advanced;
if (gNodes[nodeIndex].data.node!.template[field].display_name) {
display_name =
gNodes[nodeIndex].data.node!.template[field].display_name;
} else {
display_name = gNodes[nodeIndex].data.node!.template[field].name;
}
if (gNodes[nodeIndex].data.node!.template[field].proxy) {
proxy = gNodes[nodeIndex].data.node!.template[field].proxy;
}
gNodes[nodeIndex].data.node!.template[field] = template[key];
gNodes[nodeIndex].data.node!.template[field].show = show;
gNodes[nodeIndex].data.node!.template[field].advanced = advanced;
gNodes[nodeIndex].data.node!.template[field].display_name = display_name;
// keep the nodes selected after ungrouping
// gNodes[nodeIndex].selected = false;
if (proxy) {
gNodes[nodeIndex].data.node!.template[field].proxy = proxy;
} else {
delete gNodes[nodeIndex].data.node!.template[field].proxy;
}
}
});
const nodes = [
...ReactFlowInstance.getNodes().filter((n) => n.id !== groupNode.id),
...gNodes,
];
const edges = [
...ReactFlowInstance.getEdges().filter(
(e) => e.target !== groupNode.id && e.source !== groupNode.id
),
...gEdges,
...updatedEdges,
];
console.log(edges);
ReactFlowInstance.setNodes(nodes);
ReactFlowInstance.setEdges(edges);
}
export function processFlow(FlowObject: ReactFlowJsonObject) {
let clonedFLow = _.cloneDeep(FlowObject);
clonedFLow.nodes.forEach((node: NodeType) => {
if (node.data.node?.flow) {
processFlow(node.data.node!.flow!.data!);
ungroupNode(node.data, clonedFLow);
}
});
return clonedFLow;
}
export function getGroupStatus(
flow: FlowType,
ssData: { [key: string]: { valid: boolean; params: string } }
) {
let status = { valid: true, params: "Built sucessfully ✨" };
const { nodes } = flow.data!;
const ids = nodes.map((n: NodeType) => n.data.id);
ids.forEach((id) => console.log(ssData[id]));
ids.forEach((id) => {
if (!ssData[id]) {
status = ssData[id];
return;
}
if (!ssData[id].valid) {
status = { valid: false, params: ssData[id].params };
}
});
return status;
}

View file

@ -34,7 +34,6 @@ import {
Gift,
GitFork,
GithubIcon,
Group,
Hammer,
HelpCircle,
Home,
@ -71,7 +70,6 @@ import {
TerminalSquare,
Trash2,
Undo,
Ungroup,
Unplug,
Upload,
User,
@ -314,10 +312,8 @@ export const nodeIconsLucide: iconsType = {
UserCog2,
Key,
Unplug,
Group,
ChevronUp,
Ungroup,
BookMarked,
ChevronUp,
Minus,
Square,
Minimize2,

View file

@ -1,11 +1,7 @@
import clsx, { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { ADJECTIVES, DESCRIPTIONS, NOUNS } from "../flow_constants";
import {
APIDataType,
APITemplateType,
TemplateVariableType,
} from "../types/api";
import { APIDataType, TemplateVariableType } from "../types/api";
import {
IVarHighlightType,
groupedObjType,
@ -453,29 +449,6 @@ chat_input_field: Input key that you want the chat to send the user message with
></langflow-chat>`;
}
export function truncateLongId(id: string): string {
let [componentName, newId] = id.split("-");
if (componentName.length > 15) {
componentName = componentName.slice(0, 15);
componentName += "...";
return componentName + "-" + newId;
}
return id;
}
export function extractIdFromLongId(id: string): string {
let [_, newId] = id.split("-");
return newId;
}
export function truncateDisplayName(name: string): string {
if (name.length > 15) {
name = name.slice(0, 15);
name += "...";
}
return name;
}
export function tabsArray(codes: string[], method: number) {
if (!method) return;
if (method === 0) {
@ -554,14 +527,3 @@ export function tabsArray(codes: string[], method: number) {
},
];
}
export function getFieldTitle(
template: APITemplateType,
templateField: string
): string {
return template[templateField].display_name
? template[templateField].display_name!
: template[templateField].name
? toTitleCase(template[templateField].name!)
: toTitleCase(templateField);
}

View file

@ -1,140 +0,0 @@
import { expect, test } from "@playwright/test";
test("CodeAreaModalComponent", async ({ page }) => {
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
await page.locator('//*[@id="new-project-btn"]').click();
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("pythonfunctiontool");
await page.waitForTimeout(2000);
await page
.locator('//*[@id="sidePythonFunctionTool"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.locator('//*[@id="code-input-0"]').click();
let value = await page.locator('//*[@id="codeValue"]').inputValue();
if (
value !=
'def python_function(text: str) -> str: """This is a default python function that returns the input text""" return text'
) {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="checkAndSaveBtn"]').click();
await page
.locator('//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div')
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showdescription"]').click();
expect(
await page.locator('//*[@id="showdescription"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showname"]').click();
expect(await page.locator('//*[@id="showname"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showreturn_direct"]').click();
expect(
await page.locator('//*[@id="showreturn_direct"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showdescription"]').click();
expect(
await page.locator('//*[@id="showdescription"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showname"]').click();
expect(await page.locator('//*[@id="showname"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showreturn_direct"]').click();
expect(
await page.locator('//*[@id="showreturn_direct"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showdescription"]').click();
expect(
await page.locator('//*[@id="showdescription"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showname"]').click();
expect(await page.locator('//*[@id="showname"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showreturn_direct"]').click();
expect(
await page.locator('//*[@id="showreturn_direct"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showdescription"]').click();
expect(
await page.locator('//*[@id="showdescription"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showname"]').click();
expect(await page.locator('//*[@id="showname"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showreturn_direct"]').click();
expect(
await page.locator('//*[@id="showreturn_direct"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator('//*[@id="code-input-0"]');
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
await page
.locator('//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div')
.click();
await page.locator('//*[@id="advancedIcon"]').click();
await page.locator('//*[@id="editAdvancedBtn"]').click();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="code-area-edit0"]').click();
let value = await page.locator('//*[@id="codeValue"]').inputValue();
if (
value !=
'def python_function(text: str) -> str: """This is a default python function that returns the input text""" return text'
) {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="checkAndSaveBtn"]').click();
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.locator('//*[@id="code-input-0"]').click();
}
});

View file

@ -1,415 +0,0 @@
import { expect, test } from "@playwright/test";
test("FloatComponent", async ({ page }) => {
await page.goto("http://localhost:3000/");
await page.waitForTimeout(2000);
await page.locator('//*[@id="new-project-btn"]').click();
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("llamacpp");
await page.waitForTimeout(2000);
await page
.locator('//*[@id="sideLlamaCpp"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.locator('//*[@id="float-input"]').click();
await page.locator('//*[@id="float-input"]').fill("3");
let value = await page.locator('//*[@id="float-input"]').inputValue();
if (value != "2") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="float-input"]').click();
await page.locator('//*[@id="float-input"]').fill("-3");
value = await page.locator('//*[@id="float-input"]').inputValue();
if (value != "-2") {
expect(false).toBeTruthy();
}
await page
.locator(
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
)
.click();
await page.locator('//*[@id="editAdvancedIcon"]').click();
await page.locator('//*[@id="showcache"]').click();
expect(await page.locator('//*[@id="showcache"]').isChecked()).toBeTruthy();
// showecho
await page.locator('//*[@id="showecho"]').click();
expect(await page.locator('//*[@id="showecho"]').isChecked()).toBeTruthy();
// showf16_kv
await page.locator('//*[@id="showf16_kv"]').click();
expect(await page.locator('//*[@id="showf16_kv"]').isChecked()).toBeTruthy();
// showgrammar_path
await page.locator('//*[@id="showgrammar_path"]').click();
expect(
await page.locator('//*[@id="showgrammar_path"]').isChecked()
).toBeTruthy();
// showlast_n_tokens_size
await page.locator('//*[@id="showlast_n_tokens_size"]').click();
expect(
await page.locator('//*[@id="showlast_n_tokens_size"]').isChecked()
).toBeTruthy();
// showlogits_all
await page.locator('//*[@id="showlogits_all"]').click();
expect(
await page.locator('//*[@id="showlogits_all"]').isChecked()
).toBeTruthy();
// showlogprobs
await page.locator('//*[@id="showlogprobs"]').click();
expect(
await page.locator('//*[@id="showlogprobs"]').isChecked()
).toBeTruthy();
// showlora_base
await page.locator('//*[@id="showlora_base"]').click();
expect(
await page.locator('//*[@id="showlora_base"]').isChecked()
).toBeTruthy();
// showlora_path
await page.locator('//*[@id="showlora_path"]').click();
expect(
await page.locator('//*[@id="showlora_path"]').isChecked()
).toBeTruthy();
// showmax_tokens
await page.locator('//*[@id="showmax_tokens"]').click();
expect(
await page.locator('//*[@id="showmax_tokens"]').isChecked()
).toBeTruthy();
// showmetadata
await page.locator('//*[@id="showmetadata"]').click();
expect(
await page.locator('//*[@id="showmetadata"]').isChecked()
).toBeTruthy();
// showmodel_kwargs
await page.locator('//*[@id="showmodel_kwargs"]').click();
expect(
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
).toBeTruthy();
// showmodel_path
await page.locator('//*[@id="showmodel_path"]').click();
expect(
await page.locator('//*[@id="showmodel_path"]').isChecked()
).toBeFalsy();
// shown_batch
await page.locator('//*[@id="shown_batch"]').click();
expect(await page.locator('//*[@id="shown_batch"]').isChecked()).toBeTruthy();
// shown_ctx
await page.locator('//*[@id="shown_ctx"]').click();
expect(await page.locator('//*[@id="shown_ctx"]').isChecked()).toBeTruthy();
// shown_gpu_layers
await page.locator('//*[@id="shown_gpu_layers"]').click();
expect(
await page.locator('//*[@id="shown_gpu_layers"]').isChecked()
).toBeTruthy();
// shown_parts
await page.locator('//*[@id="shown_parts"]').click();
expect(await page.locator('//*[@id="shown_parts"]').isChecked()).toBeTruthy();
// shown_threads
await page.locator('//*[@id="shown_threads"]').click();
expect(
await page.locator('//*[@id="shown_threads"]').isChecked()
).toBeTruthy();
// showrepeat_penalty
await page.locator('//*[@id="showrepeat_penalty"]').click();
expect(
await page.locator('//*[@id="showrepeat_penalty"]').isChecked()
).toBeTruthy();
// showrope_freq_base
await page.locator('//*[@id="showrope_freq_base"]').click();
expect(
await page.locator('//*[@id="showrope_freq_base"]').isChecked()
).toBeTruthy();
// showrope_freq_scale
await page.locator('//*[@id="showrope_freq_scale"]').click();
expect(
await page.locator('//*[@id="showrope_freq_scale"]').isChecked()
).toBeTruthy();
// showseed
await page.locator('//*[@id="showseed"]').click();
expect(await page.locator('//*[@id="showseed"]').isChecked()).toBeTruthy();
// showstop
await page.locator('//*[@id="showstop"]').click();
expect(await page.locator('//*[@id="showstop"]').isChecked()).toBeTruthy();
// showstreaming
await page.locator('//*[@id="showstreaming"]').click();
expect(
await page.locator('//*[@id="showstreaming"]').isChecked()
).toBeTruthy();
// showsuffix
await page.locator('//*[@id="showsuffix"]').click();
expect(await page.locator('//*[@id="showsuffix"]').isChecked()).toBeTruthy();
// showtags
await page.locator('//*[@id="showtags"]').click();
expect(await page.locator('//*[@id="showtags"]').isChecked()).toBeTruthy();
// showtemperature
await page.locator('//*[@id="showtemperature"]').click();
expect(
await page.locator('//*[@id="showtemperature"]').isChecked()
).toBeFalsy();
// showtop_k
await page.locator('//*[@id="showtop_k"]').click();
expect(await page.locator('//*[@id="showtop_k"]').isChecked()).toBeTruthy();
// showtop_p
await page.locator('//*[@id="showtop_p"]').click();
expect(await page.locator('//*[@id="showtop_p"]').isChecked()).toBeTruthy();
// showuse_mlock
await page.locator('//*[@id="showuse_mlock"]').click();
expect(
await page.locator('//*[@id="showuse_mlock"]').isChecked()
).toBeTruthy();
// showuse_mmap
await page.locator('//*[@id="showuse_mmap"]').click();
expect(
await page.locator('//*[@id="showuse_mmap"]').isChecked()
).toBeTruthy();
// showverbose
await page.locator('//*[@id="showverbose"]').click();
expect(await page.locator('//*[@id="showverbose"]').isChecked()).toBeTruthy();
// showvocab_only
await page.locator('//*[@id="showvocab_only"]').click();
expect(
await page.locator('//*[@id="showvocab_only"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showcache"]').click();
expect(await page.locator('//*[@id="showcache"]').isChecked()).toBeFalsy();
// showecho
await page.locator('//*[@id="showecho"]').click();
expect(await page.locator('//*[@id="showecho"]').isChecked()).toBeFalsy();
// showf16_kv
await page.locator('//*[@id="showf16_kv"]').click();
expect(await page.locator('//*[@id="showf16_kv"]').isChecked()).toBeFalsy();
// showgrammar_path
await page.locator('//*[@id="showgrammar_path"]').click();
expect(
await page.locator('//*[@id="showgrammar_path"]').isChecked()
).toBeFalsy();
// showlast_n_tokens_size
await page.locator('//*[@id="showlast_n_tokens_size"]').click();
expect(
await page.locator('//*[@id="showlast_n_tokens_size"]').isChecked()
).toBeFalsy();
// showlogits_all
await page.locator('//*[@id="showlogits_all"]').click();
expect(
await page.locator('//*[@id="showlogits_all"]').isChecked()
).toBeFalsy();
// showlogprobs
await page.locator('//*[@id="showlogprobs"]').click();
expect(await page.locator('//*[@id="showlogprobs"]').isChecked()).toBeFalsy();
// showlora_base
await page.locator('//*[@id="showlora_base"]').click();
expect(
await page.locator('//*[@id="showlora_base"]').isChecked()
).toBeFalsy();
// showlora_path
await page.locator('//*[@id="showlora_path"]').click();
expect(
await page.locator('//*[@id="showlora_path"]').isChecked()
).toBeFalsy();
// showmax_tokens
await page.locator('//*[@id="showmax_tokens"]').click();
expect(
await page.locator('//*[@id="showmax_tokens"]').isChecked()
).toBeFalsy();
// showmetadata
await page.locator('//*[@id="showmetadata"]').click();
expect(await page.locator('//*[@id="showmetadata"]').isChecked()).toBeFalsy();
// showmodel_kwargs
await page.locator('//*[@id="showmodel_kwargs"]').click();
expect(
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
).toBeFalsy();
// showmodel_path
await page.locator('//*[@id="showmodel_path"]').click();
expect(
await page.locator('//*[@id="showmodel_path"]').isChecked()
).toBeTruthy();
// shown_batch
await page.locator('//*[@id="shown_batch"]').click();
expect(await page.locator('//*[@id="shown_batch"]').isChecked()).toBeFalsy();
// shown_ctx
await page.locator('//*[@id="shown_ctx"]').click();
expect(await page.locator('//*[@id="shown_ctx"]').isChecked()).toBeFalsy();
// shown_gpu_layers
await page.locator('//*[@id="shown_gpu_layers"]').click();
expect(
await page.locator('//*[@id="shown_gpu_layers"]').isChecked()
).toBeFalsy();
// shown_parts
await page.locator('//*[@id="shown_parts"]').click();
expect(await page.locator('//*[@id="shown_parts"]').isChecked()).toBeFalsy();
// shown_threads
await page.locator('//*[@id="shown_threads"]').click();
expect(
await page.locator('//*[@id="shown_threads"]').isChecked()
).toBeFalsy();
// showrepeat_penalty
await page.locator('//*[@id="showrepeat_penalty"]').click();
expect(
await page.locator('//*[@id="showrepeat_penalty"]').isChecked()
).toBeFalsy();
// showrope_freq_base
await page.locator('//*[@id="showrope_freq_base"]').click();
expect(
await page.locator('//*[@id="showrope_freq_base"]').isChecked()
).toBeFalsy();
// showrope_freq_scale
await page.locator('//*[@id="showrope_freq_scale"]').click();
expect(
await page.locator('//*[@id="showrope_freq_scale"]').isChecked()
).toBeFalsy();
// showseed
await page.locator('//*[@id="showseed"]').click();
expect(await page.locator('//*[@id="showseed"]').isChecked()).toBeFalsy();
// showstop
await page.locator('//*[@id="showstop"]').click();
expect(await page.locator('//*[@id="showstop"]').isChecked()).toBeFalsy();
// showstreaming
await page.locator('//*[@id="showstreaming"]').click();
expect(
await page.locator('//*[@id="showstreaming"]').isChecked()
).toBeFalsy();
// showsuffix
await page.locator('//*[@id="showsuffix"]').click();
expect(await page.locator('//*[@id="showsuffix"]').isChecked()).toBeFalsy();
// showtags
await page.locator('//*[@id="showtags"]').click();
expect(await page.locator('//*[@id="showtags"]').isChecked()).toBeFalsy();
// showtop_k
await page.locator('//*[@id="showtop_k"]').click();
expect(await page.locator('//*[@id="showtop_k"]').isChecked()).toBeFalsy();
// showtop_p
await page.locator('//*[@id="showtop_p"]').click();
expect(await page.locator('//*[@id="showtop_p"]').isChecked()).toBeFalsy();
// showuse_mlock
await page.locator('//*[@id="showuse_mlock"]').click();
expect(
await page.locator('//*[@id="showuse_mlock"]').isChecked()
).toBeFalsy();
// showuse_mmap
await page.locator('//*[@id="showuse_mmap"]').click();
expect(await page.locator('//*[@id="showuse_mmap"]').isChecked()).toBeFalsy();
// showverbose
await page.locator('//*[@id="showverbose"]').click();
expect(await page.locator('//*[@id="showverbose"]').isChecked()).toBeFalsy();
// showvocab_only
await page.locator('//*[@id="showvocab_only"]').click();
expect(
await page.locator('//*[@id="showvocab_only"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator('//*[@id="float-input"]');
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
await page
.locator(
'//*[@id="react-flow-id"]/div[1]/div[1]/div[1]/div/div[2]/div/div/div[1]/div/div[1]/div'
)
.click();
await page.locator('//*[@id="editAdvancedIcon"]').click();
// showtemperature
await page.locator('//*[@id="showtemperature"]').click();
expect(
await page.locator('//*[@id="showtemperature"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.locator('//*[@id="float-input"]').click();
await page.locator('//*[@id="float-input"]').fill("3");
let value = await page.locator('//*[@id="float-input"]').inputValue();
if (value != "2") {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="float-input"]').click();
await page.locator('//*[@id="float-input"]').fill("-3");
value = await page.locator('//*[@id="float-input"]').inputValue();
if (value != "-2") {
expect(false).toBeTruthy();
}
}
});

Some files were not shown because too many files have changed in this diff Show more