Merge branch 'db' into merge/mp

This commit is contained in:
Cristhian Zanforlin Lousa 2023-06-14 21:47:53 -03:00
commit 3d69d76c6f
63 changed files with 833 additions and 646 deletions

2
.vscode/launch.json vendored
View file

@ -35,7 +35,7 @@
"name": "Debug Frontend",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000/*",
"url": "http://localhost:3000/",
"webRoot": "${workspaceRoot}/src/frontend"
}
]

View file

@ -51,7 +51,6 @@ build_and_run:
rm -rf dist
make build && poetry run pip install dist/*.tar.gz && poetry run langflow
build_frontend:
cd src/frontend && CI='' npm run build
cp -r src/frontend/build src/backend/langflow/frontend

View file

@ -1,4 +1,12 @@
from importlib import metadata
from langflow.cache import cache_manager
from langflow.processing.process import load_flow_from_json
try:
__version__ = metadata.version(__package__)
except metadata.PackageNotFoundError:
# Case where package metadata is not available.
__version__ = ""
del metadata # optional, avoids polluting the results of dir(__package__)
__all__ = ["load_flow_from_json", "cache_manager"]

View file

@ -16,7 +16,7 @@ from langflow.main import create_app
from langflow.settings import settings
from langflow.utils.logger import configure, logger
import webbrowser
from dotenv import load_dotenv
app = typer.Typer()
@ -88,6 +88,10 @@ def serve(
timeout: int = typer.Option(60, help="Worker timeout in seconds."),
port: int = typer.Option(7860, help="Port to listen on."),
config: str = typer.Option("config.yaml", help="Path to the configuration file."),
# .env file param
env_file: Path = typer.Option(
".env", help="Path to the .env file containing environment variables."
),
log_level: str = typer.Option("critical", help="Logging level."),
log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file."),
jcloud: bool = typer.Option(False, help="Deploy on Jina AI Cloud"),
@ -106,11 +110,27 @@ def serve(
):
"""
Run the Langflow server.
Args:
host (str): Host to bind the server to.
workers (int): Number of worker processes.
timeout (int): Worker timeout in seconds.
port (int): Port to listen on.
config (str): Path to the configuration file.
env_file (Path): Path to the .env file containing environment variables.
log_level (str): Logging level.
log_file (Path): Path to the log file.
jcloud (bool): Deploy on Jina AI Cloud.
dev (bool): Run in development mode (may contain bugs).
path (str): Path to the frontend directory containing build files. This is for development purposes only.
open_browser (bool): Open the browser after starting the server.
"""
if jcloud:
return serve_on_jcloud()
load_dotenv(env_file)
configure(log_level=log_level, log_file=log_file)
update_settings(config, dev=dev, database_url=database_url)
app = create_app()

View file

@ -1,7 +1,6 @@
from langflow.database.models.flow import Flow
from langflow.processing.process import process_graph_cached, process_tweaks
from langflow.utils.logger import logger
from importlib.metadata import version
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import HTTPBearer
@ -70,4 +69,6 @@ async def predict_flow(
# get endpoint to return version of langflow
@router.get("/version")
def get_version():
return {"version": version("langflow")}
from langflow import __version__
return {"version": __version__}

View file

@ -16,6 +16,10 @@ chains:
- MidJourneyPromptChain
- TimeTravelGuideChain
- SQLDatabaseChain
- RetrievalQA
- RetrievalQAWithSourcesChain
- ConversationalRetrievalChain
- CombineDocsChain
documentloaders:
- AirbyteJSONLoader
- CoNLLULoader
@ -53,7 +57,7 @@ llms:
# - AzureOpenAI
# - AzureChatOpenAI
- ChatOpenAI
- LlamaCpp
- LlamaCpp
- CTransformers
- Cohere
- Anthropic
@ -69,7 +73,7 @@ prompts:
- ZeroShotPrompt
textsplitters:
- CharacterTextSplitter
# - RecursiveCharacterTextSplitter
- RecursiveCharacterTextSplitter
# - LatexTextSplitter
# - PythonCodeTextSplitter
toolkits:

View file

@ -2,7 +2,9 @@ from langflow.template import frontend_node
# These should always be instantiated
CUSTOM_NODES = {
"prompts": {"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode()},
"prompts": {
"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(),
},
"tools": {
"PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(),
"PythonFunction": frontend_node.tools.PythonFunctionNode(),
@ -23,6 +25,7 @@ CUSTOM_NODES = {
"SeriesCharacterChain": frontend_node.chains.SeriesCharacterChainNode(),
"TimeTravelGuideChain": frontend_node.chains.TimeTravelGuideChainNode(),
"MidJourneyPromptChain": frontend_node.chains.MidJourneyPromptChainNode(),
"load_qa_chain": frontend_node.chains.CombineDocsChainNode(),
},
}

View file

@ -24,7 +24,6 @@ class Graph:
self._edges = edges
self._build_graph()
@classmethod
@classmethod
def from_payload(cls, payload: Dict) -> "Graph":
"""
@ -43,7 +42,9 @@ class Graph:
edges = payload["edges"]
return cls(nodes, edges)
except KeyError as exc:
raise ValueError("Invalid payload") from exc
raise ValueError(
f"Invalid payload. Expected keys 'nodes' and 'edges'. Found {list(payload.keys())}"
) from exc
def _build_graph(self) -> None:
"""Builds the graph from the nodes and edges."""

View file

@ -49,8 +49,11 @@ class Vertex:
template_dict = self.data["node"]["template"]
self.vertex_type = (
self.data["type"] if "Tool" not in self.output else template_dict["_type"]
self.data["type"]
if "Tool" not in self.output or template_dict["_type"].islower()
else template_dict["_type"]
)
if self.base_type is None:
for base_type, value in ALL_TYPES_DICT.items():
if self.vertex_type in value:

View file

@ -1,4 +1,3 @@
from abc import ABC
from typing import Any, List, Optional
from langchain import LLMChain
@ -33,24 +32,7 @@ from langchain.memory.chat_memory import BaseChatMemory
from langchain.sql_database import SQLDatabase
from langchain.tools.python.tool import PythonAstREPLTool
from langchain.tools.sql_database.prompt import QUERY_CHECKER
class CustomAgentExecutor(AgentExecutor, ABC):
"""Custom agent executor"""
@staticmethod
def function_name():
return "CustomAgentExecutor"
@classmethod
def initialize(cls, *args, **kwargs):
pass
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def run(self, *args, **kwargs):
return super().run(*args, **kwargs)
from langflow.interface.base import CustomAgentExecutor
class JsonAgent(CustomAgentExecutor):

View file

@ -1,6 +1,7 @@
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Type, Union
from langchain.chains.base import Chain
from langchain.agents import AgentExecutor
from pydantic import BaseModel
from langflow.template.field.base import TemplateField
@ -81,5 +82,42 @@ class LangChainTypeCreator(BaseModel, ABC):
)
signature.add_extra_fields()
signature.add_extra_base_classes()
return signature
class CustomChain(Chain, ABC):
"""Custom chain"""
@staticmethod
def function_name():
return "CustomChain"
@classmethod
def initialize(cls, *args, **kwargs):
pass
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def run(self, *args, **kwargs):
return super().run(*args, **kwargs)
class CustomAgentExecutor(AgentExecutor, ABC):
"""Custom chain"""
@staticmethod
def function_name():
return "CustomChain"
@classmethod
def initialize(cls, *args, **kwargs):
pass
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def run(self, *args, **kwargs):
return super().run(*args, **kwargs)

View file

@ -1,12 +1,13 @@
from typing import Dict, List, Optional, Type
from typing import Any, Dict, List, Optional, Type
from langflow.custom.customs import get_custom_nodes
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import chain_type_to_cls_dict
from langflow.interface.importing.utils import import_class
from langflow.settings import settings
from langflow.template.frontend_node.chains import ChainFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
from langflow.utils.util import build_template_from_class, build_template_from_method
from langchain import chains
# Assuming necessary imports for Field, Template, and FrontendNode classes
@ -18,10 +19,16 @@ class ChainCreator(LangChainTypeCreator):
def frontend_node_class(self) -> Type[ChainFrontendNode]:
return ChainFrontendNode
#! We need to find a better solution for this
from_method_nodes = {"ConversationalRetrievalChain": "from_llm"}
@property
def type_to_loader_dict(self) -> Dict:
if self.type_dict is None:
self.type_dict = chain_type_to_cls_dict
self.type_dict: dict[str, Any] = {
chain_name: import_class(f"langchain.chains.{chain_name}")
for chain_name in chains.__all__
}
from langflow.interface.chains.custom import CUSTOM_CHAINS
self.type_dict.update(CUSTOM_CHAINS)
@ -37,20 +44,32 @@ class ChainCreator(LangChainTypeCreator):
try:
if name in get_custom_nodes(self.type_name).keys():
return get_custom_nodes(self.type_name)[name]
elif name in self.from_method_nodes.keys():
return build_template_from_method(
name,
type_to_cls_dict=self.type_to_loader_dict,
method_name=self.from_method_nodes[name],
add_function=True,
)
return build_template_from_class(
name, self.type_to_loader_dict, add_function=True
)
except ValueError as exc:
raise ValueError("Chain not found") from exc
raise ValueError(f"Chain {name} not found: {exc}") from exc
except AttributeError as exc:
logger.error(f"Chain {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
custom_chains = list(get_custom_nodes("chains").keys())
default_chains = list(self.type_to_loader_dict.keys())
return default_chains + custom_chains
names = []
for _, chain in self.type_to_loader_dict.items():
chain_name = (
chain.function_name()
if hasattr(chain, "function_name")
else chain.__name__
)
names.append(chain_name)
return names
chain_creator = ChainCreator()

View file

@ -1,11 +1,13 @@
from typing import Dict, Optional, Type
from typing import Dict, Optional, Type, Union
from langchain.chains import ConversationChain
from langchain.memory.buffer import ConversationBufferMemory
from langchain.schema import BaseMemory
from langflow.interface.base import CustomChain
from pydantic import Field, root_validator
from langchain.chains.question_answering import load_qa_chain
from langflow.interface.utils import extract_input_variables_from_prompt
from langchain.base_language import BaseLanguageModel
DEFAULT_SUFFIX = """"
Current conversation:
@ -14,7 +16,7 @@ Human: {input}
{ai_prefix}"""
class BaseCustomChain(ConversationChain):
class BaseCustomConversationChain(ConversationChain):
"""BaseCustomChain is a chain you can use to have a conversation with a custom character."""
template: Optional[str]
@ -47,7 +49,7 @@ class BaseCustomChain(ConversationChain):
return values
class SeriesCharacterChain(BaseCustomChain):
class SeriesCharacterChain(BaseCustomConversationChain):
"""SeriesCharacterChain is a chain you can use to have a conversation with a character from a series."""
character: str
@ -66,7 +68,7 @@ Human: {input}
"""Default memory store."""
class MidJourneyPromptChain(BaseCustomChain):
class MidJourneyPromptChain(BaseCustomConversationChain):
"""MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."""
template: Optional[
@ -84,7 +86,7 @@ class MidJourneyPromptChain(BaseCustomChain):
AI:""" # noqa: E501
class TimeTravelGuideChain(BaseCustomChain):
class TimeTravelGuideChain(BaseCustomConversationChain):
template: Optional[
str
] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information.
@ -94,7 +96,26 @@ class TimeTravelGuideChain(BaseCustomChain):
AI:""" # noqa: E501
CUSTOM_CHAINS: Dict[str, Type[ConversationChain]] = {
class CombineDocsChain(CustomChain):
"""Implementation of initialize_agent function"""
@staticmethod
def function_name():
return "load_qa_chain"
@classmethod
def initialize(cls, llm: BaseLanguageModel, chain_type: str):
return load_qa_chain(llm=llm, chain_type=chain_type)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def run(self, *args, **kwargs):
return super().run(*args, **kwargs)
CUSTOM_CHAINS: Dict[str, Type[Union[ConversationChain, CustomChain]]] = {
"CombineDocsChain": CombineDocsChain,
"SeriesCharacterChain": SeriesCharacterChain,
"MidJourneyPromptChain": MidJourneyPromptChain,
"TimeTravelGuideChain": TimeTravelGuideChain,

View file

@ -2,7 +2,6 @@ import inspect
from typing import Any
from langchain import (
chains,
document_loaders,
embeddings,
llms,
@ -15,6 +14,8 @@ from langchain.chat_models import AzureChatOpenAI, ChatOpenAI
from langchain.chat_models import ChatAnthropic
from langflow.interface.importing.utils import import_class
from langflow.interface.agents.custom import CUSTOM_AGENTS
from langflow.interface.chains.custom import CUSTOM_CHAINS
## LLMs
llm_type_to_cls_dict = llms.type_to_cls_dict
@ -22,11 +23,6 @@ llm_type_to_cls_dict["anthropic-chat"] = ChatAnthropic # type: ignore
llm_type_to_cls_dict["azure-chat"] = AzureChatOpenAI # type: ignore
llm_type_to_cls_dict["openai-chat"] = ChatOpenAI # type: ignore
## Chains
chain_type_to_cls_dict: dict[str, Any] = {
chain_name: import_class(f"langchain.chains.{chain_name}")
for chain_name in chains.__all__
}
## Toolkits
toolkit_type_to_loader_dict: dict[str, Any] = {
@ -73,3 +69,6 @@ documentloaders_type_to_cls_dict: dict[str, Any] = {
textsplitter_type_to_cls_dict: dict[str, Any] = dict(
inspect.getmembers(text_splitter, inspect.isclass)
)
# merge CUSTOM_AGENTS and CUSTOM_CHAINS
CUSTOM_NODES = {**CUSTOM_AGENTS, **CUSTOM_CHAINS} # type: ignore

View file

@ -1,30 +1,20 @@
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Type
from langflow.interface.base import LangChainTypeCreator
from langflow.template.frontend_node.documentloaders import DocumentLoaderFrontNode
from langflow.interface.custom_lists import documentloaders_type_to_cls_dict
from langflow.settings import settings
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
def build_file_path_template(
suffixes: list, fileTypes: list, name: str = "file_path"
) -> Dict:
"""Build a file path template for a document loader."""
return {
"type": "file",
"required": True,
"show": True,
"name": name,
"value": "",
"suffixes": suffixes,
"fileTypes": fileTypes,
}
class DocumentLoaderCreator(LangChainTypeCreator):
type_name: str = "documentloaders"
@property
def frontend_node_class(self) -> Type[DocumentLoaderFrontNode]:
return DocumentLoaderFrontNode
@property
def type_to_loader_dict(self) -> Dict:
return documentloaders_type_to_cls_dict
@ -32,106 +22,7 @@ class DocumentLoaderCreator(LangChainTypeCreator):
def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of a document loader."""
try:
signature = build_template_from_class(
name, documentloaders_type_to_cls_dict
)
file_path_templates = {
"AirbyteJSONLoader": build_file_path_template(
suffixes=[".json"], fileTypes=["json"]
),
"CoNLLULoader": build_file_path_template(
suffixes=[".csv"], fileTypes=["csv"]
),
"CSVLoader": build_file_path_template(
suffixes=[".csv"], fileTypes=["csv"]
),
"UnstructuredEmailLoader": build_file_path_template(
suffixes=[".eml"], fileTypes=["eml"]
),
"EverNoteLoader": build_file_path_template(
suffixes=[".xml"], fileTypes=["xml"]
),
"FacebookChatLoader": build_file_path_template(
suffixes=[".json"], fileTypes=["json"]
),
"GutenbergLoader": build_file_path_template(
suffixes=[".txt"], fileTypes=["txt"]
),
"BSHTMLLoader": build_file_path_template(
suffixes=[".html"], fileTypes=["html"]
),
"UnstructuredHTMLLoader": build_file_path_template(
suffixes=[".html"], fileTypes=["html"]
),
"UnstructuredImageLoader": build_file_path_template(
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
),
"UnstructuredMarkdownLoader": build_file_path_template(
suffixes=[".md"], fileTypes=["md"]
),
"PyPDFLoader": build_file_path_template(
suffixes=[".pdf"], fileTypes=["pdf"]
),
"UnstructuredPowerPointLoader": build_file_path_template(
suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]
),
"SRTLoader": build_file_path_template(
suffixes=[".srt"], fileTypes=["srt"]
),
"TelegramChatLoader": build_file_path_template(
suffixes=[".json"], fileTypes=["json"]
),
"TextLoader": build_file_path_template(
suffixes=[".txt"], fileTypes=["txt"]
),
"UnstructuredWordDocumentLoader": build_file_path_template(
suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]
),
"SlackDirectoryLoader": build_file_path_template(
suffixes=[".zip"], fileTypes=["zip"]
),
}
if name in file_path_templates:
signature["template"]["file_path"] = file_path_templates[name]
elif name in {
"WebBaseLoader",
"AZLyricsLoader",
"CollegeConfidentialLoader",
"HNLoader",
"IFixitLoader",
"IMSDbLoader",
}:
signature["template"]["web_path"] = {
"type": "str",
"required": True,
"show": True,
"name": "web_path",
"value": "",
"display_name": "Web Page",
}
elif name in {"GitbookLoader"}:
signature["template"]["web_page"] = {
"type": "str",
"required": True,
"show": True,
"name": "web_page",
"value": "",
"display_name": "Web Page",
}
elif name in {"ReadTheDocsLoader", "NotionDirectoryLoader"}:
signature["template"]["path"] = {
"type": "str",
"required": True,
"show": True,
"name": "path",
"value": "",
"display_name": "Web Page",
}
return signature
return build_template_from_class(name, documentloaders_type_to_cls_dict)
except ValueError as exc:
raise ValueError(f"Documment Loader {name} not found") from exc
except AttributeError as exc:

View file

@ -19,9 +19,10 @@ from langchain.chains.loading import load_chain_from_config
from langchain.llms.loading import load_llm_from_config
from pydantic import ValidationError
from langflow.interface.agents.custom import CUSTOM_AGENTS
from langflow.interface.custom_lists import CUSTOM_NODES
from langflow.interface.importing.utils import get_function, import_by_type
from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.chains.base import chain_creator
from langflow.interface.types import get_type_list
from langflow.interface.utils import load_file_into_dict
from langflow.utils import util, validate
@ -31,10 +32,11 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
"""Instantiate class from module type and key, and params"""
params = convert_params_to_sets(params)
params = convert_kwargs(params)
if node_type in CUSTOM_AGENTS:
custom_agent = CUSTOM_AGENTS.get(node_type)
if custom_agent:
return custom_agent.initialize(**params)
if node_type in CUSTOM_NODES:
if custom_node := CUSTOM_NODES.get(node_type):
if hasattr(custom_node, "initialize"):
return custom_node.initialize(**params)
return custom_node(**params)
class_object = import_by_type(_type=base_type, name=node_type)
return instantiate_based_on_type(class_object, base_type, node_type, params)
@ -78,10 +80,24 @@ def instantiate_based_on_type(class_object, base_type, node_type, params):
return instantiate_textsplitter(class_object, params)
elif base_type == "utilities":
return instantiate_utility(node_type, class_object, params)
elif base_type == "chains":
return instantiate_chains(node_type, class_object, params)
else:
return class_object(**params)
def instantiate_chains(node_type, class_object, params):
if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
params["retriever"] = params["retriever"].as_retriever()
if node_type in chain_creator.from_method_nodes:
method = chain_creator.from_method_nodes[node_type]
if class_method := getattr(class_object, method, None):
return class_method(**params)
raise ValueError(f"Method {method} not found in {class_object}")
return class_object(**params)
def instantiate_agent(class_object, params):
return load_agent_executor(class_object, params)
@ -142,6 +158,14 @@ def instantiate_vectorstore(class_object, params):
"The source you provided did not load correctly or was empty."
"This may cause an error in the vectorstore."
)
# Chroma requires all metadata values to not be None
if class_object.__name__ == "Chroma":
for doc in params["documents"]:
if doc.metadata is None:
doc.metadata = {}
for key, value in doc.metadata.items():
if value is None:
doc.metadata[key] = ""
return class_object.from_documents(**params)

View file

@ -71,7 +71,3 @@ Human: {input}
CUSTOM_PROMPTS: Dict[str, Type[BaseCustomPrompt]] = {
"SeriesCharacterPrompt": SeriesCharacterPrompt
}
if __name__ == "__main__":
prompt = SeriesCharacterPrompt(character="Harry Potter", series="Harry Potter")
print(prompt.template)

View file

@ -1,6 +1,7 @@
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Type
from langflow.interface.base import LangChainTypeCreator
from langflow.template.frontend_node.textsplitters import TextSplittersFrontendNode
from langflow.interface.custom_lists import textsplitter_type_to_cls_dict
from langflow.settings import settings
from langflow.utils.logger import logger
@ -10,6 +11,10 @@ from langflow.utils.util import build_template_from_class
class TextSplitterCreator(LangChainTypeCreator):
type_name: str = "textsplitters"
@property
def frontend_node_class(self) -> Type[TextSplittersFrontendNode]:
return TextSplittersFrontendNode
@property
def type_to_loader_dict(self) -> Dict:
return textsplitter_type_to_cls_dict
@ -17,43 +22,7 @@ class TextSplitterCreator(LangChainTypeCreator):
def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of a text splitter."""
try:
signature = build_template_from_class(name, textsplitter_type_to_cls_dict)
signature["template"]["documents"] = {
"type": "BaseLoader",
"required": True,
"show": True,
"name": "documents",
}
signature["template"]["separator"] = {
"type": "str",
"required": True,
"show": True,
"value": ".",
"name": "separator",
"display_name": "Separator",
}
signature["template"]["chunk_size"] = {
"type": "int",
"required": True,
"show": True,
"value": 1000,
"name": "chunk_size",
"display_name": "Chunk Size",
}
signature["template"]["chunk_overlap"] = {
"type": "int",
"required": True,
"show": True,
"value": 200,
"name": "chunk_overlap",
"display_name": "Chunk Overlap",
}
return signature
return build_template_from_class(name, textsplitter_type_to_cls_dict)
except ValueError as exc:
raise ValueError(f"Text Splitter {name} not found") from exc
except AttributeError as exc:

View file

@ -7,6 +7,8 @@ from langflow.template.frontend_node import (
prompts,
tools,
vectorstores,
documentloaders,
textsplitters,
)
__all__ = [
@ -18,4 +20,6 @@ __all__ = [
"llms",
"prompts",
"vectorstores",
"documentloaders",
"textsplitters",
]

View file

@ -154,9 +154,9 @@ class CSVAgentNode(FrontendNode):
class InitializeAgentNode(FrontendNode):
name: str = "initialize_agent"
name: str = "AgentInitializer"
template: Template = Template(
type_name="initailize_agent",
type_name="initialize_agent",
fields=[
TemplateField(
field_type="str",

View file

@ -14,6 +14,7 @@ class FrontendNode(BaseModel):
description: str
base_classes: List[str]
name: str = ""
display_name: str = ""
def to_dict(self) -> dict:
return {
@ -21,12 +22,16 @@ class FrontendNode(BaseModel):
"template": self.template.to_dict(self.format_field),
"description": self.description,
"base_classes": self.base_classes,
}
"display_name": self.display_name or self.name,
},
}
def add_extra_fields(self) -> None:
pass
def add_extra_base_classes(self) -> None:
pass
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
"""Formats a given field based on its attributes and value."""

View file

@ -2,10 +2,24 @@ from typing import Optional
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode
from langflow.template.frontend_node.constants import QA_CHAIN_TYPES
from langflow.template.template.base import Template
class ChainFrontendNode(FrontendNode):
def add_extra_fields(self) -> None:
if self.template.type_name == "ConversationalRetrievalChain":
# add memory
self.template.add_field(
TemplateField(
field_type="BaseChatMemory",
required=False,
show=True,
name="memory",
advanced=False,
)
)
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
FrontendNode.format_field(field, name)
@ -155,3 +169,41 @@ class MidJourneyPromptChainNode(FrontendNode):
"ConversationChain",
"MidJourneyPromptChain",
]
class CombineDocsChainNode(FrontendNode):
name: str = "CombineDocsChain"
template: Template = Template(
type_name="load_qa_chain",
fields=[
TemplateField(
field_type="str",
required=True,
is_list=True,
show=True,
multiline=False,
options=QA_CHAIN_TYPES,
value=QA_CHAIN_TYPES[0],
name="chain_type",
advanced=False,
),
TemplateField(
field_type="BaseLanguageModel",
required=True,
show=True,
name="llm",
display_name="LLM",
advanced=False,
),
],
)
description: str = """Construct a zero shot agent from an LLM and tools."""
base_classes: list[str] = ["BaseCombineDocumentsChain", "function"]
def to_dict(self):
return super().to_dict()
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
# do nothing and don't return anything
pass

View file

@ -30,3 +30,5 @@ You are a good listener and you can talk about anything.
"""
HUMAN_PROMPT = "{input}"
QA_CHAIN_TYPES = ["stuff", "map_reduce", "map_rerank", "refine"]

View file

@ -0,0 +1,79 @@
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode
def build_template(
suffixes: list, fileTypes: list, name: str = "file_path"
) -> TemplateField:
"""Build a template field for a document loader."""
return TemplateField(
field_type="file",
required=True,
show=True,
name=name,
value="",
suffixes=suffixes,
fileTypes=fileTypes,
)
class DocumentLoaderFrontNode(FrontendNode):
file_path_templates = {
"AirbyteJSONLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
"CoNLLULoader": build_template(suffixes=[".csv"], fileTypes=["csv"]),
"CSVLoader": build_template(suffixes=[".csv"], fileTypes=["csv"]),
"UnstructuredEmailLoader": build_template(suffixes=[".eml"], fileTypes=["eml"]),
"EverNoteLoader": build_template(suffixes=[".xml"], fileTypes=["xml"]),
"FacebookChatLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
"GutenbergLoader": build_template(suffixes=[".txt"], fileTypes=["txt"]),
"BSHTMLLoader": build_template(suffixes=[".html"], fileTypes=["html"]),
"UnstructuredHTMLLoader": build_template(
suffixes=[".html"], fileTypes=["html"]
),
"UnstructuredImageLoader": build_template(
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
),
"UnstructuredMarkdownLoader": build_template(
suffixes=[".md"], fileTypes=["md"]
),
"PyPDFLoader": build_template(suffixes=[".pdf"], fileTypes=["pdf"]),
"UnstructuredPowerPointLoader": build_template(
suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]
),
"SRTLoader": build_template(suffixes=[".srt"], fileTypes=["srt"]),
"TelegramChatLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
"TextLoader": build_template(suffixes=[".txt"], fileTypes=["txt"]),
"UnstructuredWordDocumentLoader": build_template(
suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]
),
}
def add_extra_fields(self) -> None:
name = None
if self.template.type_name in self.file_path_templates:
self.template.add_field(self.file_path_templates[self.template.type_name])
elif self.template.type_name in {
"WebBaseLoader",
"AZLyricsLoader",
"CollegeConfidentialLoader",
"HNLoader",
"IFixitLoader",
"IMSDbLoader",
}:
name = "web_path"
elif self.template.type_name in {"GitbookLoader"}:
name = "web_page"
elif self.template.type_name in {"ReadTheDocsLoader"}:
name = "path"
if name:
self.template.add_field(
TemplateField(
field_type="str",
required=True,
show=True,
name=name,
value="",
display_name="Web Page",
)
)

View file

@ -5,6 +5,20 @@ from langflow.template.frontend_node.base import FrontendNode
class MemoryFrontendNode(FrontendNode):
#! Needs testing
def add_extra_fields(self) -> None:
# add return_messages field
self.template.add_field(
TemplateField(
field_type="bool",
required=False,
show=True,
name="return_messages",
advanced=False,
value=False,
)
)
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
FrontendNode.format_field(field, name)
@ -18,3 +32,7 @@ class MemoryFrontendNode(FrontendNode):
field.value = 10
field.display_name = "Memory Size"
field.password = False
if field.name == "return_messages":
field.required = False
field.show = True
field.advanced = False

View file

@ -74,7 +74,7 @@ class BasePromptFrontendNode(FrontendNode):
class ZeroShotPromptNode(BasePromptFrontendNode):
name: str = "ZeroShotPrompt"
template: Template = Template(
type_name="zero_shot",
type_name="ZeroShotPrompt",
fields=[
TemplateField(
field_type="str",

View file

@ -0,0 +1,49 @@
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode
class TextSplittersFrontendNode(FrontendNode):
def add_extra_fields(self) -> None:
self.template.add_field(
TemplateField(
field_type="BaseLoader",
required=True,
show=True,
name="documents",
)
)
name = "separator"
if self.template.type_name == "CharacterTextSplitter":
name = "separator"
elif self.template.type_name == "RecursiveCharacterTextSplitter":
name = "separators"
self.template.add_field(
TemplateField(
field_type="str",
required=True,
show=True,
value=".",
name=name,
display_name="Separator",
)
)
self.template.add_field(
TemplateField(
field_type="int",
required=True,
show=True,
value=1000,
name="chunk_size",
display_name="Chunk Size",
)
)
self.template.add_field(
TemplateField(
field_type="int",
required=True,
show=True,
value=200,
name="chunk_overlap",
display_name="Chunk Overlap",
)
)

View file

@ -108,7 +108,7 @@ class PythonFunctionToolNode(FrontendNode):
class PythonFunctionNode(FrontendNode):
name: str = "PythonFunction"
template: Template = Template(
type_name="python_function",
type_name="PythonFunction",
fields=[
TemplateField(
field_type="code",

View file

@ -20,6 +20,9 @@ class VectorStoreFrontendNode(FrontendNode):
self.template.add_field(extra_field)
def add_extra_base_classes(self) -> None:
self.base_classes.append("BaseRetriever")
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
FrontendNode.format_field(field, name)

View file

@ -53,6 +53,7 @@
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"shadcn-ui": "^0.1.3",
"short-unique-id": "^4.4.4",
"switch": "^0.0.0",
"table": "^6.8.1",
"tailwind-merge": "^1.13.0",
@ -8642,6 +8643,15 @@
"node": ">=8"
}
},
"node_modules/short-unique-id": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-4.4.4.tgz",
"integrity": "sha512-oLF1NCmtbiTWl2SqdXZQbo5KM1b7axdp0RgQLq8qCBBLoq+o3A5wmLrNM6bZIh54/a8BJ3l69kTXuxwZ+XCYuw==",
"bin": {
"short-unique-id": "bin/short-unique-id",
"suid": "bin/short-unique-id"
}
},
"node_modules/side-channel": {
"version": "1.0.4",
"dev": true,

View file

@ -48,6 +48,7 @@
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"shadcn-ui": "^0.1.3",
"short-unique-id": "^4.4.4",
"switch": "^0.0.0",
"table": "^6.8.1",
"tailwind-merge": "^1.13.0",

View file

@ -25,6 +25,7 @@ import React from "react";
import { nodeColors } from "../../../../utils";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import { PopUpContext } from "../../../../contexts/popUpContext";
import ToggleShadComponent from "../../../../components/toggleShadComponent";
export default function ParameterComponent({
left,
@ -39,7 +40,6 @@ export default function ParameterComponent({
}: ParameterComponentType) {
const ref = useRef(null);
const refHtml = useRef(null);
const refData = useRef(null);
const updateNodeInternals = useUpdateNodeInternals();
const [position, setPosition] = useState(0);
const { closePopUp } = useContext(PopUpContext);
@ -64,11 +64,11 @@ export default function ParameterComponent({
const { reactFlowInstance } = useContext(typesContext);
let disabled =
reactFlowInstance?.getEdges().some((e) => e.targetHandle === id) ?? false;
const [myData, setMyData] = useState(useContext(typesContext).data);
useEffect(() => {
const groupedObj = groupByFamily(myData, tooltipTitle);
refHtml.current = groupedObj.map((item, i) => (
<span
key={i}
@ -108,169 +108,166 @@ export default function ParameterComponent({
}, [tooltipTitle]);
return (
<>
<div
ref={ref}
className="w-full flex flex-wrap justify-between items-center bg-muted dark:bg-gray-800 dark:text-white mt-1 px-5 py-2"
>
<>
<div
className={"text-sm truncate w-full " + (left ? "" : "text-end")}
<div
ref={ref}
className="w-full flex flex-wrap justify-between items-center bg-muted dark:bg-gray-800 dark:text-white mt-1 px-5 py-2"
>
<>
<div className={"text-sm truncate w-full " + (left ? "" : "text-end")}>
{title}
<span className="text-red-600">{required ? " *" : ""}</span>
</div>
{left &&
(type === "str" ||
type === "bool" ||
type === "float" ||
type === "code" ||
type === "prompt" ||
type === "file" ||
type === "int") ? (
<></>
) : (
<ShadTooltip
delayDuration={0}
content={refHtml.current}
side={left ? "left" : "right"}
open={refHtml?.current?.length > 0}
>
{title}
<span className="text-red-600">{required ? " *" : ""}</span>
</div>
{left &&
(type === "str" ||
type === "bool" ||
type === "float" ||
type === "code" ||
type === "prompt" ||
type === "file" ||
type === "int") ? (
<></>
) : (
<ShadTooltip
delayDuration={0}
content={refHtml.current}
side={left ? "left" : "right"}
open={refHtml?.current?.length > 0}
>
<Handle
type={left ? "target" : "source"}
position={left ? Position.Left : Position.Right}
id={id}
isValidConnection={(connection) =>
isValidConnection(connection, reactFlowInstance)
}
className={classNames(
left ? "-ml-0.5 " : "-mr-0.5 ",
"w-3 h-3 rounded-full border-2 bg-white dark:bg-gray-800"
)}
style={{
borderColor: color,
top: position,
}}
></Handle>
</ShadTooltip>
)}
{left === true &&
type === "str" &&
!data.node.template[name].options ? (
<div className="mt-2 w-full">
{data.node.template[name].list ? (
<InputListComponent
disabled={disabled}
value={
!data.node.template[name].value ||
data.node.template[name].value === ""
? [""]
: data.node.template[name].value
}
onChange={(t: string[]) => {
data.node.template[name].value = t;
}}
/>
) : data.node.template[name].multiline ? (
<TextAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
) : (
<InputComponent
disabled={disabled}
disableCopyPaste={true}
password={data.node.template[name].password ?? false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
<Handle
type={left ? "target" : "source"}
position={left ? Position.Left : Position.Right}
id={id}
isValidConnection={(connection) =>
isValidConnection(connection, reactFlowInstance)
}
className={classNames(
left ? "-ml-0.5 " : "-mr-0.5 ",
"w-3 h-3 rounded-full border-2 bg-white dark:bg-gray-800"
)}
</div>
) : left === true && type === "bool" ? (
<div className="mt-2">
<ToggleComponent
style={{
borderColor: color,
top: position,
}}
></Handle>
</ShadTooltip>
)}
{left === true &&
type === "str" &&
!data.node.template[name].options ? (
<div className="mt-2 w-full">
{data.node.template[name].list ? (
<InputListComponent
disabled={disabled}
enabled={enabled}
setEnabled={(t) => {
data.node.template[name].value = t;
setEnabled(t);
}}
/>
</div>
) : left === true && type === "float" ? (
<div className="mt-2 w-full">
<FloatComponent
disabled={disabled}
disableCopyPaste={true}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
</div>
) : left === true &&
type === "str" &&
data.node.template[name].options ? (
<div className="w-full">
<Dropdown
options={data.node.template[name].options}
onSelect={(newValue) =>
(data.node.template[name].value = newValue)
value={
!data.node.template[name].value ||
data.node.template[name].value === ""
? [""]
: data.node.template[name].value
}
value={data.node.template[name].value ?? "Choose an option"}
></Dropdown>
</div>
) : left === true && type === "code" ? (
<CodeAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
) : left === true && type === "file" ? (
<InputFileComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
fileTypes={data.node.template[name].fileTypes}
suffixes={data.node.template[name].suffixes}
onFileChange={(t: string) => {
data.node.template[name].content = t;
}}
></InputFileComponent>
) : left === true && type === "int" ? (
<div className="mt-2 w-full">
<IntComponent
onChange={(t: string[]) => {
data.node.template[name].value = t;
}}
/>
) : data.node.template[name].multiline ? (
<TextAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
) : (
<InputComponent
disabled={disabled}
disableCopyPaste={true}
password={data.node.template[name].password ?? false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
</div>
) : left === true && type === "prompt" ? (
<PromptAreaComponent
)}
</div>
) : left === true && type === "bool" ? (
<div className="mt-2">
<ToggleShadComponent
disabled={disabled}
enabled={enabled}
setEnabled={(t) => {
data.node.template[name].value = t;
setEnabled(t);
}}
size="large"
/>
</div>
) : left === true && type === "float" ? (
<div className="mt-2 w-full">
<FloatComponent
disabled={disabled}
disableCopyPaste={true}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
) : (
<></>
)}
</>
</div>
</>
</div>
) : left === true &&
type === "str" &&
data.node.template[name].options ? (
<div className="w-full">
<Dropdown
options={data.node.template[name].options}
onSelect={(newValue) =>
(data.node.template[name].value = newValue)
}
value={data.node.template[name].value ?? "Choose an option"}
></Dropdown>
</div>
) : left === true && type === "code" ? (
<CodeAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
) : left === true && type === "file" ? (
<InputFileComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
fileTypes={data.node.template[name].fileTypes}
suffixes={data.node.template[name].suffixes}
onFileChange={(t: string) => {
data.node.template[name].content = t;
}}
></InputFileComponent>
) : left === true && type === "int" ? (
<div className="mt-2 w-full">
<IntComponent
disabled={disabled}
disableCopyPaste={true}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
</div>
) : left === true && type === "prompt" ? (
<PromptAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
) : (
<></>
)}
</>
</div>
);
}

View file

@ -1,16 +1,3 @@
import {
BugAntIcon,
Cog6ToothIcon,
InformationCircleIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import {
CheckCircleIcon,
EllipsisHorizontalCircleIcon,
ExclamationCircleIcon,
} from "@heroicons/react/24/solid";
import {
classNames,
nodeColors,
@ -33,6 +20,7 @@ import { NodeToolbar } from "reactflow";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import ShadTooltip from "../../components/ShadTooltipComponent";
import { postValidateNode } from "../../controllers/API";
export default function GenericNode({
data,
selected,
@ -62,17 +50,13 @@ export default function GenericNode({
const validateNode = useCallback(
debounce(async () => {
try {
const response = await fetch(`/validate/node/${data.id}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(reactFlowInstance.toObject()),
});
const response = await postValidateNode(
data.id,
reactFlowInstance.toObject()
);
if (response.status === 200) {
let jsonResponse = await response.json();
let jsonResponseParsed = await JSON.parse(jsonResponse);
let jsonResponseParsed = await JSON.parse(response.data);
setValidationStatus(jsonResponseParsed);
}
} catch (error) {
@ -150,11 +134,10 @@ export default function GenericNode({
"Validating..."
) : (
<div className="max-h-96 overflow-auto">
{validationStatus.params
.split("\n")
.map((line, index) => (
<div key={index}>{line}</div>
))}
{validationStatus.params ||
""
.split("\n")
.map((line, index) => <div key={index}>{line}</div>)}
</div>
)
}
@ -191,7 +174,7 @@ export default function GenericNode({
</div>
<div className="h-full w-full py-5 text-gray-800">
<div className="w-full px-5 pb-3 text-sm text-gray-500 dark:text-gray-300">
<div className="w-full px-5 pb-3 text-sm text-muted-foreground">
{data.node.description}
</div>

View file

@ -1,7 +1,7 @@
import React, { useState, ChangeEvent } from "react";
import { Textarea } from "../ui/textarea";
import { Label } from "../ui/label";
import { Input } from "../ui/input";
import { Textarea } from "../../components/ui/textarea";
import { Label } from "../../components/ui/label";
import { Input } from "../../components/ui/input";
type InputProps = {
name: string | null;
@ -62,7 +62,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
)}
</div>
<Input
className="mt-2"
className="mt-2 font-normal"
onChange={handleNameChange}
type="text"
name="name"
@ -80,7 +80,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
onChange={handleDescriptionChange}
value={description ?? ""}
placeholder="Flow description"
className="max-h-[100px] mt-2"
className="max-h-[100px] mt-2 font-normal"
rows={3}
/>
</Label>

View file

@ -40,7 +40,7 @@ export const CardComponent = ({
</div>
{onDelete && (
<button onClick={onDelete}>
<Trash2 className="w-5 text-primary opacity-0 group-hover:opacity-100 transition-all" />
<Trash2 className="w-4 h-4 text-primary opacity-0 group-hover:opacity-100 transition-all" />
</button>
)}
</CardTitle>

View file

@ -70,7 +70,7 @@ export default function Dropdown({
className={({ active }) =>
classNames(
active
? "text-white bg-slate-400 dark:bg-white dark:text-gray-500"
? " bg-accent dark:bg-white dark:text-gray-500"
: "",
editNode
? "relative cursor-default select-none py-0.5 pl-3 pr-12 dark:text-gray-300 dark:bg-gray-800"

View file

@ -45,7 +45,7 @@ export const MenuBar = ({ flows, tabId }) => {
return (
<div className="flex gap-2 items-center">
<Link to="/">
<ChevronLeft className="w-5" />
<ChevronLeft className="w-4" />
</Link>
<div className="flex items-center font-medium text-sm rounded-md py-1 px-1.5 gap-0.5">
<DropdownMenu>
@ -62,7 +62,7 @@ export const MenuBar = ({ flows, tabId }) => {
openPopUp(<FlowSettingsModal />);
}}
>
<Settings2 className="w-4 h-4 mr-2" />
<Settings2 className="w-4 h-4 mr-2 dark:text-gray-300" />
Settings
</DropdownMenuItem>
<DropdownMenuItem
@ -70,7 +70,7 @@ export const MenuBar = ({ flows, tabId }) => {
undo();
}}
>
<Undo className="w-4 h-4 mr-2" />
<Undo className="w-4 h-4 mr-2 dark:text-gray-300" />
Undo
</DropdownMenuItem>
<DropdownMenuItem
@ -78,7 +78,7 @@ export const MenuBar = ({ flows, tabId }) => {
redo();
}}
>
<Redo className="w-4 h-4 mr-2" />
<Redo className="w-4 h-4 mr-2 dark:text-gray-300" />
Redo
</DropdownMenuItem>
<DropdownMenuSeparator />

View file

@ -72,7 +72,7 @@ export default function Header() {
Join The Community
</a>
</Button>
<button
{/* <button
className="text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200"
onClick={() => {
setDark(!dark);
@ -83,7 +83,7 @@ export default function Header() {
) : (
<MoonIcon className="h-5 w-5" />
)}
</button>
</button> */}
<button
className="text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200 relative"
onClick={(event: React.MouseEvent<HTMLElement>) => {

View file

@ -41,7 +41,7 @@ export default function IntComponent({
if (disableCopyPaste) setDisableCopyPaste(false);
}}
onKeyDown={(event) => {
console.log(event);
// console.log(event);
if (
event.key !== "Backspace" &&
event.key !== "Enter" &&

View file

@ -1,4 +1,3 @@
import { classNames } from "../../utils";
import { useEffect } from "react";
import { ToggleComponentType } from "../../types/components";
import { Switch } from "../ui/switch";
@ -7,17 +6,36 @@ export default function ToggleShadComponent({
enabled,
setEnabled,
disabled,
size,
}: ToggleComponentType) {
useEffect(() => {
if (disabled) {
setEnabled(false);
}
}, [disabled, setEnabled]);
let scaleX, scaleY;
switch (size) {
case "small":
scaleX = 0.6;
scaleY = 0.6;
break;
case "medium":
scaleX = 0.8;
scaleY = 0.8;
break;
case "large":
scaleX = 1;
scaleY = 1;
break;
default:
scaleX = 1;
scaleY = 1;
}
return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
<Switch
style={{
transform: "scaleX(0.6) scaleY(0.6)",
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
}}
className="data-[state=unchecked]:bg-slate-500"
checked={enabled}

View file

@ -1,5 +1,8 @@
// src/constants.tsx
import { FlowType } from "./types/flow";
import { buildTweaks } from "./utils";
/**
* The base text for subtitle of Export Dialog (Toolbar)
* @constant
@ -51,10 +54,21 @@ export const TEXT_DIALOG_SUBTITLE = "Edit your text.";
* @param {string} flowId - The id of the flow
* @returns {string} - The python code
*/
export const getPythonApiCode = (flowId: string): string => {
export const getPythonApiCode = (flow: FlowType): string => {
const flowId = flow.id;
// create a dictionary of node ids and the values is an empty dictionary
// flow.data.nodes.forEach((node) => {
// node.data.id
// }
const tweaks = buildTweaks(flow);
return `import requests
BASE_API_URL = "${window.location.protocol}//${window.location.host}/predict"
BASE_API_URL = "${window.location.protocol}//${
window.location.host
}/ap1/v1/predict"
FLOW_ID = "${flowId}"
TWEAKS = ${JSON.stringify(tweaks, null, 2)}
def run_flow(message: str, flow_id: str, tweaks: dict = None) -> dict:
"""
@ -76,32 +90,35 @@ def run_flow(message: str, flow_id: str, tweaks: dict = None) -> dict:
return response.json()
# Setup any tweaks you want to apply to the flow
tweaks = {} # {"nodeId": {"key": "value"}, "nodeId2": {"key": "value"}}
FLOW_ID = "${flowId}"
print(run_flow("Your message", flow_id=FLOW_ID, tweaks=tweaks))`;
print(run_flow("Your message", flow_id=FLOW_ID, tweaks=TWEAKS))`;
};
/**
* Function to get the curl code for the API
* @param {string} flowId - The id of the flow
* @returns {string} - The curl code
*/
export const getCurlCode = (flowId: string): string => {
export const getCurlCode = (flow: FlowType): string => {
const flowId = flow.id;
const tweaks = buildTweaks(flow);
return `curl -X POST \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer ${flowId}" \\
-d '{"message": "Your message"}' \\
${window.location.protocol}//${window.location.host}/predict`;
${window.location.protocol}//${
window.location.host
}/api/v1/predict/${flowId} \\
-H 'Content-Type: application/json' \\
-d '{"message": "Your message", "tweaks": ${JSON.stringify(
tweaks,
null,
2
)}}'`;
};
/**
* Function to get the python code for the API
* @param {string} flowName - The name of the flow
* @returns {string} - The python code
*/
export const getPythonCode = (flowName: string): string => {
export const getPythonCode = (flow: FlowType): string => {
const flowName = flow.name;
return `from langflow import load_flow_from_json
flow = load_flow_from_json("${flowName}.json")

View file

@ -12,7 +12,7 @@ import { updateIds, updateTemplate } from "../utils";
import { alertContext } from "./alertContext";
import { typesContext } from "./typesContext";
import { APITemplateType } from "../types/api";
import { v4 as uuidv4 } from "uuid";
import ShortUniqueId from "short-unique-id";
import { addEdge } from "reactflow";
import {
readFlowsFromDatabase,
@ -23,14 +23,17 @@ import {
} from "../controllers/API";
import _ from "lodash";
const uid = new ShortUniqueId({ length: 5 });
const TabsContextInitialValue: TabsContextType = {
save: () => {},
tabId: "",
setTabId: (index: string) => {},
flows: [],
removeFlow: (id: string) => {},
addFlow: async (flowData?: any) => "",
updateFlow: (newFlow: FlowType) => {},
incrementNodeId: () => uuidv4(),
incrementNodeId: () => uid(),
downloadFlow: (flow: FlowType) => {},
downloadFlows: () => {},
uploadFlows: () => {},
@ -41,7 +44,7 @@ const TabsContextInitialValue: TabsContextType = {
lastCopiedSelection: null,
setLastCopiedSelection: (selection: any) => {},
getNodeId: () => "",
getNodeId: (nodeType: string) => "",
paste: (
selection: { nodes: any; edges: any },
position: { x: number; y: number; paneX?: number; paneY?: number }
@ -54,18 +57,46 @@ export const TabsContext = createContext<TabsContextType>(
export function TabsProvider({ children }: { children: ReactNode }) {
const { setErrorData, setNoticeData } = useContext(alertContext);
const [tabId, setTabId] = useState("");
const [flows, setFlows] = useState([]);
const [id, setId] = useState(uuidv4());
const [flows, setFlows] = useState<Array<FlowType>>([]);
const [id, setId] = useState(uid());
const { templates, reactFlowInstance } = useContext(typesContext);
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
const newNodeId = useRef(uuidv4());
const newNodeId = useRef(uid());
function incrementNodeId() {
newNodeId.current = uuidv4();
newNodeId.current = uid();
return newNodeId.current;
}
function save() {
// added clone deep to avoid mutating the original object
let Saveflows = _.cloneDeep(flows);
if (Saveflows.length !== 0) {
Saveflows.forEach((flow) => {
if (flow.data && flow.data?.nodes)
flow.data?.nodes.forEach((node) => {
// console.log(node.data.type);
//looking for file fields to prevent saving the content and breaking the flow for exceeding the the data limite for local storage
Object.keys(node.data.node.template).forEach((key) => {
// console.log(node.data.node.template[key].type);
if (node.data.node.template[key].type === "file") {
// console.log(node.data.node.template[key]);
node.data.node.template[key].content = null;
node.data.node.template[key].value = "";
}
});
});
});
window.localStorage.setItem(
"tabsData",
JSON.stringify({ tabId, flows: Saveflows, id })
);
}
}
// function loadCookie(cookie: string) {
// if (cookie && Object.keys(templates).length > 0) {
// let cookieObject: LangFlowState = JSON.parse(cookie);
@ -206,10 +237,11 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
function hardReset() {
newNodeId.current = uuidv4();
newNodeId.current = uid();
setTabId("");
setFlows([]);
setId(uuidv4());
setId(uid());
}
/**
@ -249,8 +281,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
}
function getNodeId() {
return `dndnode_` + incrementNodeId();
function getNodeId(nodeType: string) {
return nodeType + "-" + incrementNodeId();
}
/**
@ -262,6 +294,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
// create a file input
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
// add a change event listener to the file input
input.onchange = (e: Event) => {
// check if the file type is application/json
@ -343,9 +376,9 @@ export function TabsProvider({ children }: { children: ReactNode }) {
? { x: position.paneX + position.x, y: position.paneY + position.y }
: reactFlowInstance.project({ x: position.x, y: position.y });
selectionInstance.nodes.forEach((n) => {
selectionInstance.nodes.forEach((n: NodeType) => {
// Generate a unique node ID
let newId = getNodeId();
let newId = getNodeId(n.data.type);
idsMap[n.id] = newId;
// Create a new node object
@ -500,7 +533,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const createNewFlow = (flowData, flow) => ({
description: flowData.description,
name: flow?.name ?? "New Flow",
id: uuidv4(),
data: flowData.data,
});
@ -540,6 +572,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
tabId,
setTabId,
flows,
save,
incrementNodeId,
removeFlow,
addFlow,

View file

@ -2,6 +2,7 @@ import { PromptTypeAPI, errorsTypeAPI } from "./../../types/api/index";
import { APIObjectType, sendAllProps } from "../../types/api/index";
import axios, { AxiosResponse } from "axios";
import { FlowStyleType, FlowType } from "../../types/flow";
import { ReactFlowJsonObject } from "reactflow";
/**
* Fetches all objects from the API endpoint.
@ -22,18 +23,19 @@ export async function sendAll(data: sendAllProps) {
return await axios.post(`/api/v1/predict`, data);
}
/**
* Validates code by sending it to an API endpoint.
*
* @param {string} code - The code to validate.
* @returns {Promise<AxiosResponse<errorsTypeAPI>>} A promise that resolves to an AxiosResponse containing the validation results.
*/
export async function checkCode(
export async function postValidateCode(
code: string
): Promise<AxiosResponse<errorsTypeAPI>> {
return await axios.post("/api/v1/validate/code", { code });
}
export async function postValidateNode(
nodeId: string,
data: any
): Promise<AxiosResponse<string>> {
return await axios.post(`/api/v1/validate/node/${nodeId}`, { data });
}
/**
* Checks the prompt for the code block by sending it to an API endpoint.
*
@ -75,7 +77,13 @@ export async function getExamples(): Promise<FlowType[]> {
* @returns {Promise<any>} The saved flow data.
* @throws Will throw an error if saving fails.
*/
export async function saveFlowToDatabase(newFlow: FlowType): Promise<FlowType> {
export async function saveFlowToDatabase(newFlow: {
name: string;
id: string;
data: ReactFlowJsonObject;
description: string;
style?: FlowStyleType;
}): Promise<FlowType> {
try {
const response = await axios.post("/api/v1/flows/", {
name: newFlow.name,

View file

@ -72,47 +72,85 @@
:root {
--background: 0 0% 100%;
/* hsl(0 0% 100%) */
--foreground: 222.2 47.4% 11.2%;
--muted: 210 30% 98.5%;
/* hsl(222 47% 11%) */
--muted: 210 40% 98%;
/* hsl(210 40% 98%) */
--muted-foreground: 215.4 16.3% 46.9%;
/* hsl(215 16% 46%) */
--popover: 0 0% 100%;
/* hsl(0 0% 100%) */
--popover-foreground: 222.2 47.4% 11.2%;
/* hsl(222 47% 11%) */
--card: 0 0% 100%;
/* hsl(0 0% 100%) */
--card-foreground: 222.2 47.4% 11.2%;
--border: 214.3 31.8% 93.4%;
/* hsl(222 47% 11%) */
--border: 214.3 31.8% 91.4%;
/* hsl(214 32% 91%) */
--input: 214.3 31.8% 91.4%;
--primary: 222.2 37.4% 18%;
/* hsl(214 32% 91%) */
--primary: 222.2 47.4% 18%;
/* hsl(222 47% 18%) */
--primary-foreground: 210 40% 98%;
--secondary: 210 30% 96.1%;
/* hsl(210 40% 98%) */
--secondary: 210 40% 96.1%;
/* hsl(210 40% 96%) */
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 30% 96.1%;
--accent-foreground: 222.2 37.4% 11.2%;
/* hsl(222 47% 11%) */
--accent: 210 40% 96.1%;
/* hsl(210 40% 96%) */
--accent-foreground: 222.2 47.4% 11.2%;
/* hsl(222 47% 11%) */
--destructive: 0 100% 50%;
/* hsl(0 100% 50%) */
--destructive-foreground: 210 40% 98%;
/* hsl(210 40% 98%) */
--ring: 215 20.2% 65.1%;
/* hsl(215 20% 65%) */
--radius: 0.5rem;
}
.dark {
-background: 224 71% 4%;
/* hsl(224 71% 4%) */
-foreground: 213 31% 91%;
/* hsl(213 31% 91%) */
-muted: 223 47% 11%;
/* hsl(223 47% 11%) */
-muted-foreground: 215.4 16.3% 56.9%;
/* hsl(215 16% 56%) */
-popover: 224 71% 4%;
/* hsl(224 71% 4%) */
-popover-foreground: 215 20.2% 65.1%;
/* hsl(215 20% 65%) */
-card: 224 71% 4%;
/* hsl(224 71% 4%) */
-card-foreground: 213 31% 91%;
/* hsl(213 31% 91%) */
-border: 216 34% 17%;
/* hsl(216 34% 17%) */
-input: 216 34% 17%;
/* hsl(216 34% 17%) */
-primary: 210 40% 98%;
/* hsl(210 40% 98%) */
-primary-foreground: 222.2 47.4% 1.2%;
/* hsl(222 47% 1%) */
-secondary: 222.2 47.4% 11.2%;
/* hsl(222 47% 11%) */
-secondary-foreground: 210 40% 98%;
/* hsl(210 40% 98%) */
-accent: 216 34% 17%;
/* hsl(216 34% 17%) */
-accent-foreground: 210 40% 98%;
/* hsl(210 40% 98%) */
-destructive: 0 63% 31%;
/* hsl(0 63% 31%) */
-destructive-foreground: 210 40% 98%;
/* hsl(210 40% 98%) */
-ring: 216 34% 17%;
/* hsl(216 34% 17%) */
-radius: 0.5rem;
}

View file

@ -1,10 +1,6 @@
import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-react";
import {
XMarkIcon,
CommandLineIcon,
CodeBracketSquareIcon,
} from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react";
import { IconCheck, IconClipboard } from "@tabler/icons-react";
import { CodeBracketSquareIcon } from "@heroicons/react/24/outline";
import { useContext, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/theme-github";
@ -18,13 +14,11 @@ import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import { Button } from "../../components/ui/button";
import { FlowType } from "../../types/flow";
import { FlowType } from "../../types/flow/index";
import { getCurlCode, getPythonApiCode, getPythonCode } from "../../constants";
import { EXPORT_CODE_DIALOG } from "../../constants";
@ -55,10 +49,10 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
}
}
const pythonApiCode = getPythonApiCode(flow.id);
const pythonApiCode = getPythonApiCode(flow);
const curl_code = getCurlCode(flow.id);
const pythonCode = getPythonCode(flow.name);
const curl_code = getCurlCode(flow);
const pythonCode = getPythonCode(flow);
const tabs = [
{
@ -96,8 +90,8 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
<DialogDescription>{EXPORT_CODE_DIALOG}</DialogDescription>
</DialogHeader>
<div className="flex flex-col h-full w-full ">
<div className="flex px-5 z-10">
<div className="flex flex-col h-full w-full overflow-hidden">
<div className="flex px-5 z-10 w-full">
{tabs.map((tab, index) => (
<button
key={index}
@ -117,7 +111,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
))}
</div>
<div className=" overflow-hidden px-4 sm:p-4 sm:pb-0 sm:pt-0 w-full h-full rounded-lg border-gray-200 border-[1px] bg-muted dark:bg-gray-800">
<div className="items-center mb-2">
<div className="items-center mb-2 w-full">
<div className="float-right">
<button
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
@ -133,7 +127,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
</div>
</div>
<SyntaxHighlighter
className="h-[350px] w-full"
className="h-[350px] w-full overflow-auto"
language={tabs[activeTab].mode}
style={oneDark}
customStyle={{ margin: 0 }}

View file

@ -1,30 +1,21 @@
import {
ChevronDoubleLeftIcon,
ChevronDoubleRightIcon,
PencilSquareIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
import { Fragment, useContext, useEffect, useRef, useState } from "react";
import { useContext, useEffect, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { NodeDataType } from "../../types/flow";
import { classNames, limitScrollFieldsModal, nodeIcons } from "../../utils";
import { classNames, limitScrollFieldsModal } from "../../utils";
import { typesContext } from "../../contexts/typesContext";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "../../components/ui/table";
import { Switch } from "../../components/ui/switch";
import ToggleShadComponent from "../../components/toggleShadComponent";
import { VariableIcon } from "@heroicons/react/24/outline";
import InputListComponent from "../../components/inputListComponent";
import TextAreaComponent from "../../components/textAreaComponent";
import InputComponent from "../../components/inputComponent";
import ToggleComponent from "../../components/toggleComponent";
import FloatComponent from "../../components/floatComponent";
import Dropdown from "../../components/dropdownComponent";
import IntComponent from "../../components/intComponent";
@ -42,8 +33,8 @@ import {
DialogTrigger,
} from "../../components/ui/dialog";
import { Button } from "../../components/ui/button";
import { EDIT_DIALOG_SUBTITLE } from "../../constants";
import { Edit } from "lucide-react";
import { Badge } from "../../components/ui/badge";
export default function EditNodeModal({ data }: { data: NodeDataType }) {
const [open, setOpen] = useState(true);
@ -95,14 +86,11 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
<DialogContent className="lg:max-w-[700px] ">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">Edit Node</span>
<Edit
className="h-5 w-5 text-gray-800 pl-1 dark:text-white"
aria-hidden="true"
/>
<span className="pr-2">{data.type}</span>
<Badge variant="secondary">ID: {data.id}</Badge>
</DialogTitle>
<DialogDescription>
{EDIT_DIALOG_SUBTITLE}
{data.node?.description}
<div className="flex pt-4">
<VariableIcon className="w-5 h-5 pe-1 text-gray-700 stroke-2 dark:text-slate-200">
&nbsp;
@ -151,7 +139,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
)
.map((n, i) => (
<TableRow key={i} className="h-10 dark:border-b-muted">
<TableCell className="p-0 text-center text-gray-900 text-xs dark:text-gray-300 text-sm">
<TableCell className="p-0 text-center text-gray-900 dark:text-gray-300 text-sm">
{data.node.template[n].name
? data.node.template[n].name
: data.node.template[n].display_name}
@ -206,6 +194,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
data.node.template[n].value = e;
setEnabled(e);
}}
size="small"
disabled={false}
/>
</div>
@ -299,6 +288,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
changeAdvanced(data.node.template[n])
}
disabled={false}
size="small"
/>
</div>
</TableCell>
@ -319,7 +309,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
}}
type="submit"
>
Save changes
Save Changes
</Button>
</DialogFooter>
</DialogContent>

View file

@ -44,7 +44,7 @@ export default function ChatInput({
}}
className={classNames(
lockChat
? " bg-gray-300 text-black dark:bg-gray-700 dark:text-gray-300"
? " bg-input text-black dark:bg-gray-700 dark:text-gray-300"
: " bg-white-200 text-black dark:bg-gray-900 dark:text-gray-300",
"form-input block w-full custom-scroll rounded-md border-gray-300 dark:border-gray-600 pr-10 sm:text-sm" +
INPUT_STYLE

View file

@ -34,8 +34,8 @@ export default function ChatMessage({
className={classNames(
"w-full py-2 pl-2 flex",
chat.isSend
? "bg-white dark:bg-gray-900 "
: "bg-gray-200 dark:bg-gray-800"
? "bg-background dark:bg-gray-900 "
: "bg-input dark:bg-gray-800"
)}
>
<div

View file

@ -8,7 +8,7 @@ import "ace-builds/src-noconflict/theme-twilight";
import "ace-builds/src-noconflict/ext-language_tools";
// import "ace-builds/webpack-resolver";
import { darkContext } from "../../contexts/darkContext";
import { checkCode } from "../../controllers/API";
import { postValidateCode } from "../../controllers/API";
import { alertContext } from "../../contexts/alertContext";
import { TabsContext } from "../../contexts/tabsContext";
import {
@ -81,7 +81,7 @@ export default function CodeAreaModal({
<Button
className="mt-3"
onClick={() => {
checkCode(code)
postValidateCode(code)
.then((apiReturn) => {
if (apiReturn.data) {
let importsErrors = apiReturn.data.imports.errors;

View file

@ -16,11 +16,8 @@ import {
import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox";
import { EXPORT_DIALOG_SUBTITLE } from "../../constants";
import EditFlowSettings from "../../components/nameInputComponent";
import { Label } from "../../components/ui/label";
import { Input } from "../../components/ui/input";
import { Textarea } from "../../components/ui/textarea";
import { Download } from "lucide-react";
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
export default function ExportModal() {
const [open, setOpen] = useState(true);
@ -57,44 +54,15 @@ export default function ExportModal() {
<DialogDescription>{EXPORT_DIALOG_SUBTITLE}</DialogDescription>
</DialogHeader>
<Label>
<span className="font-medium">Name</span>
<Input
className="mt-2 focus-visible:ring-1"
onChange={(event) => {
if (event.target.value != "") {
let newFlow = flows.find((f) => f.id === tabId);
newFlow.name = event.target.value;
setName(event.target.value);
updateFlow(newFlow);
} else {
setName(event.target.value);
}
}}
type="text"
name="name"
value={name ?? null}
placeholder="File name"
id="name"
/>
</Label>
<Label>
<span className="font-medium">Description (optional)</span>
<Textarea
name="description"
id="description"
onChange={(event) => {
let newFlow = flows.find((f) => f.id === tabId);
newFlow.description = event.target.value;
updateFlow(newFlow);
}}
value={flows.find((f) => f.id === tabId).description ?? null}
placeholder="Flow description"
className="max-h-[100px] mt-2 focus-visible:ring-1"
rows={3}
/>
</Label>
<EditFlowSettings
name={name}
description={description}
flows={flows}
tabId={tabId}
setName={setName}
setDescription={setDescription}
updateFlow={updateFlow}
/>
<div className="flex items-center space-x-2">
<Checkbox
id="terms"

View file

@ -15,7 +15,7 @@ import {
import { Button } from "../../components/ui/button";
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants";
import { updateFlowInDatabase } from "../../controllers/API";
import EditFlowSettings from "../../components/nameInputComponent";
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
export default function FlowSettingsModal() {
const [open, setOpen] = useState(true);
@ -52,10 +52,6 @@ export default function FlowSettingsModal() {
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">Settings</span>
<ArrowDownTrayIcon
className="h-6 w-6 text-gray-800 pl-1 dark:text-white"
aria-hidden="true"
/>
</DialogTitle>
<DialogDescription>{SETTINGS_DIALOG_SUBTITLE}</DialogDescription>
</DialogHeader>

View file

@ -43,7 +43,7 @@ export default function PromptAreaModal({
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
<div className="fixed inset-0 bg-ring dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -87,7 +87,7 @@ export default function PromptAreaModal({
</Dialog.Title>
</div>
</div>
<div className="h-full w-full bg-gray-200 overflow-auto dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
<div className="h-full w-full bg-accent overflow-auto dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
<div className="flex h-full w-full">
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full h-full rounded-lg bg-white dark:bg-gray-800 shadow">
<textarea

View file

@ -1,42 +1,10 @@
import {
Tabs,
TabsList,
TabsTrigger,
TabsContent,
} from "../../components/ui/tabs";
import ExtraSidebar from "../../components/ExtraSidebarComponent";
import { ReactFlowProvider } from "reactflow";
import FlowPage from "../FlowPage";
import { useContext, useEffect, useState } from "react";
import {
SunIcon,
MoonIcon,
BellIcon,
GithubIcon,
Download,
Upload,
Plus,
Home,
Users2,
GitFork,
} from "lucide-react";
import { GithubIcon, Users2, GitFork } from "lucide-react";
import { TabsContext } from "../../contexts/tabsContext";
import AlertDropdown from "../../alerts/alertDropDown";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { typesContext } from "../../contexts/typesContext";
import { Button } from "../../components/ui/button";
import { FaGithub } from "react-icons/fa";
import _ from "lodash";
import {
getExamples,
updateFlowInDatabase,
uploadFlowsToDatabase,
} from "../../controllers/API";
import { MenuBar } from "../../components/headerComponent/components/menuBar";
import { getExamples } from "../../controllers/API";
import { FlowType } from "../../types/flow";
import { CardComponent } from "../../components/cardComponent";
import { useNavigate } from "react-router-dom";

View file

@ -127,6 +127,7 @@ export default function Page({ flow }: { flow: FlowType }) {
setEdges(flow?.data?.edges ?? []);
if (reactFlowInstance) {
setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 });
reactFlowInstance.fitView();
}
}, [flow, reactFlowInstance, setEdges, setNodes, setViewport]);
//set extra sidebar
@ -213,7 +214,8 @@ export default function Page({ flow }: { flow: FlowType }) {
});
// Generate a unique node ID
let newId = getNodeId();
let { type } = data;
let newId = getNodeId(type);
let newNode: NodeType;
if (data.type !== "groupNode") {

View file

@ -1,9 +1,4 @@
import {
Bars2Icon,
PencilSquareIcon,
Square2StackIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Bars2Icon } from "@heroicons/react/24/outline";
import DisclosureComponent from "../DisclosureComponent";
import {
classNames,
@ -11,26 +6,27 @@ import {
nodeIcons,
nodeNames,
} from "../../../../utils";
import { useContext, useEffect, useState, useRef } from "react";
import { useContext, useState } from "react";
import { typesContext } from "../../../../contexts/typesContext";
import { APIClassType, APIObjectType } from "../../../../types/api";
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import { Code, Code2, FileDown, FileUp, Import, Save } from "lucide-react";
import { Code2, FileDown, FileUp, Save } from "lucide-react";
import { PopUpContext } from "../../../../contexts/popUpContext";
import ImportModal from "../../../../modals/importModal";
import ExportModal from "../../../../modals/exportModal";
import ApiModal from "../../../../modals/ApiModal";
import { TabsContext } from "../../../../contexts/tabsContext";
import { Separator } from "../../../../components/ui/separator";
import { alertContext } from "../../../../contexts/alertContext";
import { updateFlowInDatabase } from "../../../../controllers/API";
import { INPUT_STYLE } from "../../../../constants";
import { Input } from "../../../../components/ui/input";
import { Separator } from "../../../../components/ui/separator";
export default function ExtraSidebar() {
const { data } = useContext(typesContext);
const { openPopUp } = useContext(PopUpContext);
const { flows, tabId } = useContext(TabsContext);
const { flows, tabId, uploadFlow } = useContext(TabsContext);
const { setSuccessData, setErrorData } = useContext(alertContext);
const [dataFilter, setFilterData] = useState(data);
const [search, setSearch] = useState("");
@ -82,7 +78,8 @@ export default function ExtraSidebar() {
<button
className="hover:dark:hover:bg-[#242f47] text-gray-700 w-full justify-center shadow-sm transition-all duration-500 ease-in-out dark:bg-gray-800 dark:text-gray-300 relative inline-flex items-center rounded-md bg-white px-2 py-2 ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
onClick={() => {
openPopUp(<ImportModal />);
// openPopUp(<ImportModal />);
uploadFlow();
}}
>
<FileUp className="w-5 h-5 dark:text-gray-300"></FileUp>
@ -132,7 +129,7 @@ export default function ExtraSidebar() {
type="text"
name="search"
id="search"
placeholder="Search nodes"
placeholder="Search Nodes"
className={
INPUT_STYLE +
"border-1 dark:border-slate-600 dark:border-0.5 dark:ring-0 focus-visible:dark:ring-0 focus-visible:dark:ring-offset-1 rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"

View file

@ -1,21 +1,9 @@
import React, { useContext, useState } from "react";
import { Menu, Transition } from "@headlessui/react";
import { EllipsisVerticalIcon } from "@heroicons/react/20/solid";
import {
Cog6ToothIcon,
TrashIcon,
PencilSquareIcon,
DocumentDuplicateIcon,
DocumentPlusIcon,
Square2StackIcon,
} from "@heroicons/react/24/outline";
import { useContext, useState } from "react";
import { Settings2, Copy, Trash2 } from "lucide-react";
import { classNames } from "../../../../utils";
import { Fragment } from "react";
import NodeModal from "../../../../modals/NodeModal";
import { TabsContext } from "../../../../contexts/tabsContext";
import { useReactFlow } from "reactflow";
import EditNodeModal from "../../../../modals/EditNodeModal";
import TooltipReact from "../../../../components/ReactTooltipComponent";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
const NodeToolbarComponent = (props) => {
@ -48,7 +36,7 @@ const NodeToolbarComponent = (props) => {
props.deleteNode(props.data.id);
}}
>
<TrashIcon className="w-5 h-5 dark:text-gray-300"></TrashIcon>
<Trash2 className="w-4 h-4 dark:text-gray-300"></Trash2>
</button>
</ShadTooltip>
@ -61,7 +49,7 @@ const NodeToolbarComponent = (props) => {
)}
onClick={(event) => {
event.preventDefault();
console.log(reactFlowInstance.getNode(props.data.id));
// console.log(reactFlowInstance.getNode(props.data.id));
paste(
{
nodes: [reactFlowInstance.getNode(props.data.id)],
@ -76,7 +64,7 @@ const NodeToolbarComponent = (props) => {
);
}}
>
<Square2StackIcon className="w-5 h-5 dark:text-gray-300"></Square2StackIcon>
<Copy className="w-4 h-4 dark:text-gray-300"></Copy>
</button>
</ShadTooltip>
@ -89,12 +77,12 @@ const NodeToolbarComponent = (props) => {
props.openPopUp(<EditNodeModal data={props.data} />);
}}
>
<PencilSquareIcon className="w-5 h-5 dark:text-gray-300"></PencilSquareIcon>
<Settings2 className="w-4 h-4 dark:text-gray-300"></Settings2>
</button>
</ShadTooltip>
)}
{/*
{/*
<Menu as="div" className="relative inline-block text-left z-100">
<button className="hover:dark:hover:bg-[#242f47] text-gray-700 transition-all duration-500 ease-in-out dark:bg-gray-800 dark:text-gray-300 shadow-md relative -ml-px inline-flex items-center bg-white px-2 py-2 ring-1 ring-inset ring-gray-300 hover:bg-muted focus:z-10 rounded-r-md">
<div>
@ -133,7 +121,7 @@ const NodeToolbarComponent = (props) => {
"w-full group flex items-center px-4 py-2 text-sm"
)}
>
<PencilSquareIcon
<Settings
className="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500"
aria-hidden="true"
/>

View file

@ -26,7 +26,7 @@ export default function HomePage() {
}}
>
<Download className="w-4 mr-2" />
Download Database
Download Collection
</Button>
<Button
variant="primary"
@ -35,7 +35,7 @@ export default function HomePage() {
}}
>
<Upload className="w-4 mr-2" />
Upload Database
Upload Collection
</Button>
<Button
variant="primary"

View file

@ -21,6 +21,7 @@ export type ToggleComponentType = {
enabled: boolean;
setEnabled: (state: boolean) => void;
disabled: boolean;
size: "small" | "medium" | "large";
};
export type DropDownComponentType = {
value: string;

View file

@ -1,6 +1,7 @@
import { FlowType } from "../flow";
export type TabsContextType = {
save: () => void;
tabId: string;
setTabId: (index: string) => void;
flows: Array<FlowType>;
@ -16,7 +17,7 @@ export type TabsContextType = {
//disable CopyPaste
disableCopyPaste: boolean;
setDisableCopyPaste: (value: boolean) => void;
getNodeId: () => string;
getNodeId: (nodeType: string) => string;
paste: (
selection: { nodes: any; edges: any },
position: { x: number; y: number; paneX?: number; paneY?: number }

View file

@ -16,14 +16,13 @@ import {
Squares2X2Icon,
Bars3CenterLeftIcon,
} from "@heroicons/react/24/outline";
import { Connection, Edge, Node, ReactFlowInstance, addEdge } from "reactflow";
import { FlowType, NodeType } from "./types/flow";
import { APITemplateType, TemplateVariableType } from "./types/api";
import { Connection, Edge, Node, ReactFlowInstance } from "reactflow";
import { FlowType, NodeDataType, NodeType } from "./types/flow";
import { APITemplateType } from "./types/api";
import _ from "lodash";
import { ChromaIcon } from "./icons/ChromaIcon";
import { AnthropicIcon } from "./icons/Anthropic";
import { AirbyteIcon } from "./icons/Airbyte";
import { AzIcon } from "./icons/AzLogo";
import { BingIcon } from "./icons/Bing";
import { CohereIcon } from "./icons/Cohere";
import { EvernoteIcon } from "./icons/Evernote";
@ -37,17 +36,9 @@ import { MetaIcon } from "./icons/Meta";
import { MidjorneyIcon } from "./icons/Midjorney";
import { NotionIcon } from "./icons/Notion";
import { OpenAiIcon } from "./icons/OpenAi";
import { PowerPointIcon } from "./icons/PowerPoint";
import { QDrantIcon } from "./icons/QDrant";
import { ReadTheDocsIcon } from "./icons/ReadTheDocs";
import { SearxIcon } from "./icons/Searx";
import { SlackIcon } from "./icons/Slack";
import { WeaviateIcon } from "./icons/Weaviate";
import { WikipediaIcon } from "./icons/Wikipedia";
import { WolframIcon } from "./icons/Wolfram";
import { WordIcon } from "./icons/Word";
import { SerperIcon } from "./icons/Serper";
import { v4 as uuidv4 } from "uuid";
import clsx, { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
@ -634,9 +625,9 @@ export function checkUpperWords(str: string) {
export function updateIds(newFlow, getNodeId) {
let idsMap = {};
newFlow.nodes.forEach((n) => {
newFlow.nodes.forEach((n: NodeType) => {
// Generate a unique node ID
let newId = getNodeId();
let newId = getNodeId(n.data.type);
idsMap[n.id] = newId;
n.id = newId;
n.data.id = newId;
@ -720,3 +711,10 @@ export function groupByFamily(data, baseClasses) {
return groupedObj;
}
export function buildTweaks(flow) {
return flow.data.nodes.reduce((acc, node) => {
acc[node.data.id] = {};
return acc;
}, {});
}

View file

@ -1,7 +1,7 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import svgr from "vite-plugin-svgr";
const apiRoutes = ["^/api/v1/"];
const apiRoutes = ["^/api/v1/", "/health"];
// Use environment variable to determine the target.
const target = process.env.VITE_PROXY_TARGET || "http://127.0.0.1:7860";

View file

@ -352,7 +352,7 @@
"type": "str",
"list": false
},
"_type": "zero_shot"
"_type": "ZeroShotPrompt"
},
"description": "Prompt template for Zero Shot Agent.",
"base_classes": [

View file

@ -1,15 +1,4 @@
from fastapi.testclient import TestClient
from langflow.settings import settings
# check that all agents are in settings.agents
# are in json_response["agents"]
def test_agents_settings(client: TestClient):
response = client.get("api/v1/all")
assert response.status_code == 200
json_response = response.json()
agents = json_response["agents"]
assert set(agents.keys()) == set(settings.agents)
def test_zero_shot_agent(client: TestClient):
@ -131,7 +120,7 @@ def test_initialize_agent(client: TestClient):
json_response = response.json()
agents = json_response["agents"]
initialize_agent = agents["initialize_agent"]
initialize_agent = agents["AgentInitializer"]
assert initialize_agent["base_classes"] == ["AgentExecutor", "function"]
template = initialize_agent["template"]