Merge remote-tracking branch 'origin/form_io' into python_custom_node_component

This commit is contained in:
gustavoschaedler 2023-06-30 16:44:11 +01:00
commit 6416c43ff2
37 changed files with 669 additions and 423 deletions

View file

@ -24,24 +24,30 @@ def remove_api_keys(flow: dict):
return flow return flow
def build_input_keys_response(langchain_object): def build_input_keys_response(langchain_object, artifacts):
"""Build the input keys response.""" """Build the input keys response."""
input_keys_response = { input_keys_response = {
"input_keys": {key: "" for key in langchain_object.input_keys}, "input_keys": {key: "" for key in langchain_object.input_keys},
"memory_keys": [], "memory_keys": [],
"handle_keys": artifacts.get("handle_keys", []),
} }
# Set the input keys values from artifacts
for key, value in artifacts.items():
if key in input_keys_response["input_keys"]:
input_keys_response["input_keys"][key] = value
# If the object has memory, that memory will have a memory_variables attribute # If the object has memory, that memory will have a memory_variables attribute
# memory variables should be removed from the input keys # memory variables should be removed from the input keys
if hasattr(langchain_object, "memory") and hasattr( if hasattr(langchain_object, "memory") and hasattr(
langchain_object.memory, "memory_variables" langchain_object.memory, "memory_variables"
): ):
# Remove memory variables from input keys # Remove memory variables from input keys
input_keys_response["input_keys"] = [ input_keys_response["input_keys"] = {
key key: value
for key in input_keys_response["input_keys"] for key, value in input_keys_response["input_keys"].items()
if key not in langchain_object.memory.memory_variables if key not in langchain_object.memory.memory_variables
] }
# Add memory variables to memory_keys # Add memory variables to memory_keys
input_keys_response["memory_keys"] = langchain_object.memory.memory_variables input_keys_response["memory_keys"] = langchain_object.memory.memory_variables

View file

@ -72,6 +72,7 @@ async def stream_build(flow_id: str):
async def event_stream(flow_id): async def event_stream(flow_id):
final_response = {"end_of_stream": True} final_response = {"end_of_stream": True}
artifacts = {}
try: try:
if flow_id not in flow_data_store: if flow_id not in flow_data_store:
error_message = "Invalid session ID" error_message = "Invalid session ID"
@ -108,6 +109,11 @@ async def stream_build(flow_id: str):
logger.debug( logger.debug(
f"Building node {params[:50]}{'...' if len(params) > 50 else ''}" f"Building node {params[:50]}{'...' if len(params) > 50 else ''}"
) )
if vertex.artifacts:
# The artifacts will be prompt variables
# passed to build_input_keys_response
# to set the input_keys values
artifacts.update(vertex.artifacts)
except Exception as exc: except Exception as exc:
params = str(exc) params = str(exc)
valid = False valid = False
@ -124,13 +130,16 @@ async def stream_build(flow_id: str):
langchain_object = graph.build() langchain_object = graph.build()
# Now we need to check the input_keys to send them to the client # Now we need to check the input_keys to send them to the client
if hasattr(langchain_object, "input_keys"): if hasattr(langchain_object, "input_keys"):
input_keys_response = build_input_keys_response(langchain_object) input_keys_response = build_input_keys_response(
langchain_object, artifacts
)
yield str(StreamData(event="message", data=input_keys_response)) yield str(StreamData(event="message", data=input_keys_response))
chat_manager.set_cache(flow_id, langchain_object) chat_manager.set_cache(flow_id, langchain_object)
# We need to reset the chat history # We need to reset the chat history
chat_manager.chat_history.empty_history(flow_id) chat_manager.chat_history.empty_history(flow_id)
except Exception as exc: except Exception as exc:
logger.exception(exc)
logger.error("Error while building the flow: %s", exc) logger.error("Error while building the flow: %s", exc)
yield str(StreamData(event="error", data={"error": str(exc)})) yield str(StreamData(event="error", data={"error": str(exc)}))
finally: finally:

View file

@ -39,10 +39,11 @@ def post_validate_prompt(prompt: ValidatePromptRequest):
try: try:
template_field = TemplateField( template_field = TemplateField(
name=variable, name=variable,
display_name=variable,
field_type="str", field_type="str",
show=True, show=True,
advanced=False, advanced=False,
input_types=["BaseLoader"], input_types=["BaseLoader", "BaseOutputParser"],
) )
prompt.frontend_node.template[variable] = template_field.to_dict() prompt.frontend_node.template[variable] = template_field.to_dict()

View file

@ -142,6 +142,6 @@ vectorstores:
- MongoDBAtlasVectorSearch - MongoDBAtlasVectorSearch
wrappers: wrappers:
- RequestsWrapper - RequestsWrapper
# - ChatPromptTemplate output_parsers:
# - SystemMessagePromptTemplate - StructuredOutputParser
# - HumanMessagePromptTemplate - ResponseSchema

View file

@ -6,9 +6,15 @@ if TYPE_CHECKING:
class Edge: class Edge:
def __init__(self, source: "Vertex", target: "Vertex"): def __init__(self, source: "Vertex", target: "Vertex", edge: dict):
self.source: "Vertex" = source self.source: "Vertex" = source
self.target: "Vertex" = target self.target: "Vertex" = target
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]
self.validate_edge() self.validate_edge()
def validate_edge(self) -> None: def validate_edge(self) -> None:

View file

@ -179,7 +179,7 @@ class Graph:
raise ValueError(f"Source node {edge['source']} not found") raise ValueError(f"Source node {edge['source']} not found")
if target is None: if target is None:
raise ValueError(f"Target node {edge['target']} not found") raise ValueError(f"Target node {edge['target']} not found")
edges.append(Edge(source, target)) edges.append(Edge(source, target, edge))
return edges return edges
def _get_vertex_class(self, node_type: str, node_lc_type: str) -> Type[Vertex]: def _get_vertex_class(self, node_type: str, node_lc_type: str) -> Type[Vertex]:

View file

@ -1,18 +1,5 @@
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.types import ( from langflow.graph.vertex import types
AgentVertex,
ChainVertex,
DocumentLoaderVertex,
EmbeddingVertex,
LLMVertex,
MemoryVertex,
PromptVertex,
TextSplitterVertex,
ToolVertex,
ToolkitVertex,
VectorStoreVertex,
WrapperVertex,
)
from langflow.interface.agents.base import agent_creator from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator from langflow.interface.chains.base import chain_creator
from langflow.interface.document_loaders.base import documentloader_creator from langflow.interface.document_loaders.base import documentloader_creator
@ -25,22 +12,24 @@ from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.tools.base import tool_creator from langflow.interface.tools.base import tool_creator
from langflow.interface.vector_store.base import vectorstore_creator from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.wrappers.base import wrapper_creator
from langflow.interface.output_parsers.base import output_parser_creator
from typing import Dict, Type from typing import Dict, Type
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = { VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
**{t: PromptVertex for t in prompt_creator.to_list()}, **{t: types.PromptVertex for t in prompt_creator.to_list()},
**{t: AgentVertex for t in agent_creator.to_list()}, **{t: types.AgentVertex for t in agent_creator.to_list()},
**{t: ChainVertex for t in chain_creator.to_list()}, **{t: types.ChainVertex for t in chain_creator.to_list()},
**{t: ToolVertex for t in tool_creator.to_list()}, **{t: types.ToolVertex for t in tool_creator.to_list()},
**{t: ToolkitVertex for t in toolkits_creator.to_list()}, **{t: types.ToolkitVertex for t in toolkits_creator.to_list()},
**{t: WrapperVertex for t in wrapper_creator.to_list()}, **{t: types.WrapperVertex for t in wrapper_creator.to_list()},
**{t: LLMVertex for t in llm_creator.to_list()}, **{t: types.LLMVertex for t in llm_creator.to_list()},
**{t: MemoryVertex for t in memory_creator.to_list()}, **{t: types.MemoryVertex for t in memory_creator.to_list()},
**{t: EmbeddingVertex for t in embedding_creator.to_list()}, **{t: types.EmbeddingVertex for t in embedding_creator.to_list()},
**{t: VectorStoreVertex for t in vectorstore_creator.to_list()}, **{t: types.VectorStoreVertex for t in vectorstore_creator.to_list()},
**{t: DocumentLoaderVertex for t in documentloader_creator.to_list()}, **{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
**{t: TextSplitterVertex for t in textsplitter_creator.to_list()}, **{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
} }

View file

@ -1,14 +1,11 @@
from langflow.utils.constants import DIRECT_TYPES
from langflow.interface.initialize import loading from langflow.interface.initialize import loading
from langflow.interface.listing import ALL_TYPES_DICT from langflow.interface.listing import ALL_TYPES_DICT
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.utils.util import sync_to_async from langflow.utils.util import sync_to_async
import contextlib
import inspect import inspect
import types import types
import warnings
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -25,6 +22,7 @@ class Vertex:
self._parse_data() self._parse_data()
self._built_object = None self._built_object = None
self._built = False self._built = False
self.artifacts: Dict[str, Any] = {}
def _parse_data(self) -> None: def _parse_data(self) -> None:
self.data = self._data["data"] self.data = self._data["data"]
@ -45,6 +43,14 @@ class Vertex:
for key, value in template_dicts.items() for key, value in template_dicts.items()
if not value["required"] if not value["required"]
] ]
# Add the template_dicts[key]["input_types"] to the optional_inputs
self.optional_inputs.extend(
[
input_type
for value in template_dicts.values()
for input_type in value.get("input_types", [])
]
)
template_dict = self.data["node"]["template"] template_dict = self.data["node"]["template"]
self.vertex_type = ( self.vertex_type = (
@ -60,6 +66,7 @@ class Vertex:
break break
def _build_params(self): def _build_params(self):
# sourcery skip: merge-list-append, remove-redundant-if
# Some params are required, some are optional # Some params are required, some are optional
# but most importantly, some params are python base classes # but most importantly, some params are python base classes
# like str and others are LangChain objects like LLMChain, BasePromptTemplate # like str and others are LangChain objects like LLMChain, BasePromptTemplate
@ -80,8 +87,19 @@ class Vertex:
if isinstance(value, dict) if isinstance(value, dict)
} }
params = {} params = {}
for edge in self.edges:
param_key = edge.target_param
if param_key in template_dict:
if template_dict[param_key]["list"]:
if param_key not in params:
params[param_key] = []
params[param_key].append(edge.source)
else:
params[param_key] = edge.source
for key, value in template_dict.items(): for key, value in template_dict.items():
if key == "_type": if key == "_type" or not value.get("show"):
continue continue
# If the type is not transformable to a python base class # If the type is not transformable to a python base class
# then we need to get the edge that connects to this node # then we need to get the edge that connects to this node
@ -92,45 +110,8 @@ class Vertex:
file_path = value.get("file_path") file_path = value.get("file_path")
params[key] = file_path params[key] = file_path
elif value.get("type") in ["str", "prompt"] and params.get(key) is None:
elif value.get("type") not in DIRECT_TYPES: params[key] = value.get("value")
# Get the edge that connects to this node
edges = [
edge
for edge in self.edges
if edge.target == self and edge.matched_type in value["type"]
]
# Get the output of the node that the edge connects to
# if the value['list'] is True, then there will be more
# than one time setting to params[key]
# so we need to append to a list if it exists
# or create a new list if it doesn't
if value["required"] and not edges:
# If a required parameter is not found, raise an error
raise ValueError(
f"Required input {key} for module {self.vertex_type} not found"
)
elif value["list"]:
# If this is a list parameter, append all sources to a list
params[key] = [edge.source for edge in edges]
elif edges:
# If a single parameter is found, use its source
params[key] = edges[0].source
elif value["required"] or value.get("value"):
# If value does not have value this still passes
# but then gives a keyError
# so we need to check if value has value
new_value = value.get("value")
if new_value is None:
warnings.warn(f"Value for {key} in {self.vertex_type} is None. ")
if value.get("type") == "int":
with contextlib.suppress(TypeError, ValueError):
new_value = int(new_value) # type: ignore
params[key] = new_value
# Add _type to params # Add _type to params
self.params = params self.params = params
@ -195,11 +176,17 @@ class Vertex:
# and return the instance # and return the instance
try: try:
self._built_object = loading.instantiate_class( result = loading.instantiate_class(
node_type=self.vertex_type, node_type=self.vertex_type,
base_type=self.base_type, base_type=self.base_type,
params=self.params, params=self.params,
) )
# Result could be the _built_object or
# (_built_object, dict) tuple
if isinstance(result, tuple):
self._built_object, self.artifacts = result
else:
self._built_object = result
except Exception as exc: except Exception as exc:
raise ValueError( raise ValueError(
f"Error building node {self.vertex_type}: {str(exc)}" f"Error building node {self.vertex_type}: {str(exc)}"

View file

@ -194,3 +194,8 @@ class PromptVertex(Vertex):
self._build() self._build()
return self._built_object return self._built_object
class OutputParserVertex(Vertex):
def __init__(self, data: Dict):
super().__init__(data, base_type="output_parsers")

View file

@ -45,6 +45,7 @@ def import_by_type(_type: str, name: str) -> Any:
"documentloaders": import_documentloader, "documentloaders": import_documentloader,
"textsplitters": import_textsplitter, "textsplitters": import_textsplitter,
"utilities": import_utility, "utilities": import_utility,
"output_parsers": import_output_parser,
} }
if _type == "llms": if _type == "llms":
key = "chat" if "chat" in name.lower() else "llm" key = "chat" if "chat" in name.lower() else "llm"
@ -55,6 +56,11 @@ def import_by_type(_type: str, name: str) -> Any:
return loaded_func(name) return loaded_func(name)
def import_output_parser(output_parser: str) -> Any:
"""Import output parser from output parser name"""
return import_module(f"from langchain.output_parsers import {output_parser}")
def import_chat_llm(llm: str) -> BaseChatModel: def import_chat_llm(llm: str) -> BaseChatModel:
"""Import chat llm from llm name""" """Import chat llm from llm name"""
return import_class(f"langchain.chat_models.{llm}") return import_class(f"langchain.chat_models.{llm}")

View file

@ -1,5 +1,5 @@
import json import json
from typing import Any, Callable, Dict, Sequence from typing import Any, Callable, Dict, List, Sequence
from langchain.agents import ZeroShotAgent from langchain.agents import ZeroShotAgent
from langchain.agents import agent as agent_module from langchain.agents import agent as agent_module
@ -8,6 +8,7 @@ from langchain.agents.agent_toolkits.base import BaseToolkit
from langchain.agents.tools import BaseTool from langchain.agents.tools import BaseTool
from langflow.interface.initialize.vector_store import vecstore_initializer from langflow.interface.initialize.vector_store import vecstore_initializer
from langchain.schema import Document, BaseOutputParser
from pydantic import ValidationError from pydantic import ValidationError
from langflow.interface.importing.utils import ( from langflow.interface.importing.utils import (
@ -18,6 +19,7 @@ from langflow.interface.importing.utils import (
from langflow.interface.custom_lists import CUSTOM_NODES from langflow.interface.custom_lists import CUSTOM_NODES
from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.chains.base import chain_creator from langflow.interface.chains.base import chain_creator
from langflow.interface.output_parsers.base import output_parser_creator
from langflow.interface.utils import load_file_into_dict from langflow.interface.utils import load_file_into_dict
from langflow.utils import validate from langflow.utils import validate
@ -76,10 +78,21 @@ def instantiate_based_on_type(class_object, base_type, node_type, params):
return instantiate_utility(node_type, class_object, params) return instantiate_utility(node_type, class_object, params)
elif base_type == "chains": elif base_type == "chains":
return instantiate_chains(node_type, class_object, params) return instantiate_chains(node_type, class_object, params)
elif base_type == "output_parsers":
return instantiate_output_parser(node_type, class_object, params)
else: else:
return class_object(**params) return class_object(**params)
def instantiate_output_parser(node_type, class_object, params):
if node_type in output_parser_creator.from_method_nodes:
method = output_parser_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_chains(node_type, class_object, params): def instantiate_chains(node_type, class_object, params):
if "retriever" in params and hasattr(params["retriever"], "as_retriever"): if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
params["retriever"] = params["retriever"].as_retriever() params["retriever"] = params["retriever"].as_retriever()
@ -116,19 +129,45 @@ def instantiate_prompt(node_type, class_object, params):
prompt = class_object(**params) prompt = class_object(**params)
# Now we go through input_variables
# Check if they are in params, if so
# get their values and set them
format_kwargs = {} format_kwargs = {}
for input_variable in prompt.input_variables: for input_variable in prompt.input_variables:
if input_variable in params: if input_variable in params:
input_value = params[input_variable] variable = params[input_variable]
format_kwargs[input_variable] = input_value if isinstance(variable, str):
format_kwargs[input_variable] = variable
elif isinstance(variable, BaseOutputParser) and hasattr(
variable, "get_format_instructions"
):
format_kwargs[input_variable] = variable.get_format_instructions()
# check if is a list of Document
elif isinstance(variable, List) and all(
isinstance(item, Document) for item in variable
):
# Format document to contain page_content and metadata
# as one string separated by a newline
format_kwargs[input_variable] = "\n".join(
[
f"Document:{item.page_content}\nMetadata:{item.metadata}"
for item in variable
]
)
# handle_keys will be a list but it does not exist yet
# so we need to create it
if format_kwargs: if (
prompt = prompt.partial(**format_kwargs) isinstance(variable, List)
and all(isinstance(item, Document) for item in variable)
) or (
isinstance(variable, BaseOutputParser)
and hasattr(variable, "get_format_instructions")
):
if "handle_keys" not in format_kwargs:
format_kwargs["handle_keys"] = []
return prompt # Add the handle_keys to the list
format_kwargs["handle_keys"].append(input_variable)
return prompt, format_kwargs
def instantiate_tool(node_type, class_object, params): def instantiate_tool(node_type, class_object, params):

View file

@ -11,6 +11,7 @@ from langflow.interface.tools.base import tool_creator
from langflow.interface.utilities.base import utility_creator from langflow.interface.utilities.base import utility_creator
from langflow.interface.vector_store.base import vectorstore_creator from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.wrappers.base import wrapper_creator
from langflow.interface.output_parsers.base import output_parser_creator
def get_type_dict(): def get_type_dict():
@ -28,6 +29,7 @@ def get_type_dict():
"embeddings": embedding_creator.to_list(), "embeddings": embedding_creator.to_list(),
"textSplitters": textsplitter_creator.to_list(), "textSplitters": textsplitter_creator.to_list(),
"utilities": utility_creator.to_list(), "utilities": utility_creator.to_list(),
"outputParsers": output_parser_creator.to_list(),
} }

View file

@ -0,0 +1,64 @@
from typing import Dict, List, Optional, Type
from langchain import output_parsers
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class
from langflow.settings import settings
from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class, build_template_from_method
class OutputParserCreator(LangChainTypeCreator):
type_name: str = "output_parsers"
from_method_nodes = {
"StructuredOutputParser": "from_response_schemas",
}
@property
def frontend_node_class(self) -> Type[OutputParserFrontendNode]:
return OutputParserFrontendNode
@property
def type_to_loader_dict(self) -> Dict:
if self.type_dict is None:
self.type_dict = {
output_parser_name: import_class(
f"langchain.output_parsers.{output_parser_name}"
)
# if output_parser_name is not lower case it is a class
for output_parser_name in output_parsers.__all__
}
self.type_dict = {
name: output_parser
for name, output_parser in self.type_dict.items()
if name in settings.output_parsers or settings.dev
}
return self.type_dict
def get_signature(self, name: str) -> Optional[Dict]:
try:
if name in self.from_method_nodes:
return build_template_from_method(
name,
type_to_cls_dict=self.type_to_loader_dict,
method_name=self.from_method_nodes[name],
)
else:
return build_template_from_class(
name,
type_to_cls_dict=self.type_to_loader_dict,
)
except ValueError as exc:
# raise ValueError("OutputParser not found") from exc
logger.error(f"OutputParser {name} not found: {exc}")
except AttributeError as exc:
logger.error(f"OutputParser {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return list(self.type_to_loader_dict.keys())
output_parser_creator = OutputParserCreator()

View file

@ -14,6 +14,23 @@ def build_langchain_object_with_caching(data_graph):
return graph.build() return graph.build()
@memoize_dict(maxsize=10)
def build_sorted_vertices_with_caching(data_graph):
"""
Build langchain object from data_graph.
"""
logger.debug("Building langchain object")
graph = Graph.from_payload(data_graph)
sorted_vertices = graph.topological_sort()
artifacts = {}
for vertex in sorted_vertices:
vertex.build()
if vertex.artifacts:
artifacts.update(vertex.artifacts)
return graph.build(), artifacts
def build_langchain_object(data_graph): def build_langchain_object(data_graph):
""" """
Build langchain object from data_graph. Build langchain object from data_graph.

View file

@ -11,6 +11,7 @@ from langflow.interface.tools.base import tool_creator
from langflow.interface.utilities.base import utility_creator from langflow.interface.utilities.base import utility_creator
from langflow.interface.vector_store.base import vectorstore_creator from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.wrappers.base import wrapper_creator
from langflow.interface.output_parsers.base import output_parser_creator
from langflow.template.field.base import TemplateField from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.tools import CustomComponentNode from langflow.template.frontend_node.tools import CustomComponentNode
@ -47,6 +48,7 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
documentloader_creator, documentloader_creator,
textsplitter_creator, textsplitter_creator,
utility_creator, utility_creator,
output_parser_creator,
] ]
all_types = {} all_types = {}

View file

@ -2,7 +2,7 @@ from pathlib import Path
from langchain.schema import AgentAction from langchain.schema import AgentAction
import json import json
from langflow.interface.run import ( from langflow.interface.run import (
build_langchain_object_with_caching, build_sorted_vertices_with_caching,
get_memory_key, get_memory_key,
update_memory_keys, update_memory_keys,
) )
@ -88,8 +88,16 @@ def process_graph_cached(data_graph: Dict[str, Any], inputs: Optional[dict] = No
with PromptTemplate,then run the graph and return the result and thought. with PromptTemplate,then run the graph and return the result and thought.
""" """
# Load langchain object # Load langchain object
langchain_object = build_langchain_object_with_caching(data_graph) langchain_object, artifacts = build_sorted_vertices_with_caching(data_graph)
logger.debug("Loaded LangChain object") logger.debug("Loaded LangChain object")
if inputs is None:
inputs = {}
for (
key,
value,
) in artifacts.items():
if key not in inputs or not inputs[key]:
inputs[key] = value
if langchain_object is None: if langchain_object is None:
# Raise user facing error # Raise user facing error

View file

@ -19,6 +19,7 @@ class Settings(BaseSettings):
toolkits: List[str] = [] toolkits: List[str] = []
textsplitters: List[str] = [] textsplitters: List[str] = []
utilities: List[str] = [] utilities: List[str] = []
output_parsers: List[str] = []
dev: bool = False dev: bool = False
database_url: str = "sqlite:///./langflow.db" database_url: str = "sqlite:///./langflow.db"
cache: str = "InMemoryCache" cache: str = "InMemoryCache"
@ -48,6 +49,10 @@ class Settings(BaseSettings):
self.toolkits = new_settings.toolkits or [] self.toolkits = new_settings.toolkits or []
self.textsplitters = new_settings.textsplitters or [] self.textsplitters = new_settings.textsplitters or []
self.utilities = new_settings.utilities or [] self.utilities = new_settings.utilities or []
self.embeddings = new_settings.embeddings or []
self.vectorstores = new_settings.vectorstores or []
self.documentloaders = new_settings.documentloaders or []
self.output_parsers = new_settings.output_parsers or []
self.dev = dev self.dev = dev
def update_settings(self, **kwargs): def update_settings(self, **kwargs):

View file

@ -0,0 +1,10 @@
from typing import Optional
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode
class OutputParserFrontendNode(FrontendNode):
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
FrontendNode.format_field(field, name)
field.show = True

View file

@ -25,6 +25,7 @@ import { nodeColors } from "../../../../utils";
import ShadTooltip from "../../../../components/ShadTooltipComponent"; import ShadTooltip from "../../../../components/ShadTooltipComponent";
import { PopUpContext } from "../../../../contexts/popUpContext"; import { PopUpContext } from "../../../../contexts/popUpContext";
import ToggleShadComponent from "../../../../components/toggleShadComponent"; import ToggleShadComponent from "../../../../components/toggleShadComponent";
import * as _ from "lodash";
export default function ParameterComponent({ export default function ParameterComponent({
left, left,
@ -36,6 +37,7 @@ export default function ParameterComponent({
type, type,
name = "", name = "",
required = false, required = false,
optionalHandle = null,
}: ParameterComponentType) { }: ParameterComponentType) {
const ref = useRef(null); const ref = useRef(null);
const refHtml = useRef(null); const refHtml = useRef(null);
@ -73,6 +75,7 @@ export default function ParameterComponent({
return { return {
...prev, ...prev,
[tabId]: { [tabId]: {
...prev[tabId],
isPending: true, isPending: true,
formKeysData: prev[tabId].formKeysData, formKeysData: prev[tabId].formKeysData,
}, },
@ -85,7 +88,7 @@ export default function ParameterComponent({
refHtml.current = groupedObj.map((item, i) => ( refHtml.current = groupedObj.map((item, i) => (
<span <span
key={getRandomKeyByssmm()} key={getRandomKeyByssmm() + item.family + i}
className={classNames( className={classNames(
i > 0 ? "items-center flex mt-3" : "items-center flex" i > 0 ? "items-center flex mt-3" : "items-center flex"
)} )}
@ -132,13 +135,14 @@ export default function ParameterComponent({
<span className="text-red-600">{required ? " *" : ""}</span> <span className="text-red-600">{required ? " *" : ""}</span>
</div> </div>
{left && {left &&
(type === "str" || ((type === "str" ||
type === "bool" || type === "bool" ||
type === "float" || type === "float" ||
type === "code" || type === "code" ||
type === "prompt" || type === "prompt" ||
type === "file" || type === "file" ||
type === "int") ? ( type === "int") && !optionalHandle
) ? (
<></> <></>
) : ( ) : (
<ShadTooltip <ShadTooltip

View file

@ -102,7 +102,7 @@ export default function GenericNode({
color: nodeColors[types[data.type]] ?? nodeColors.unknown, color: nodeColors[types[data.type]] ?? nodeColors.unknown,
}} }}
/> />
<div className="ml-2 truncate"> <div className="ml-2 truncate flex-1">
<ShadTooltip <ShadTooltip
delayDuration={1500} delayDuration={1500}
content={data.node.display_name} content={data.node.display_name}
@ -204,6 +204,7 @@ export default function GenericNode({
data={data} data={data}
color={ color={
nodeColors[types[data.node.template[t].type]] ?? nodeColors[types[data.node.template[t].type]] ??
nodeColors[data.node.template[t].type] ??
nodeColors.unknown nodeColors.unknown
} }
title={ title={
@ -214,11 +215,12 @@ export default function GenericNode({
: toTitleCase(t) : toTitleCase(t)
} }
name={t} name={t}
tooltipTitle={data.node.template[t].type} tooltipTitle={data.node.template[t].input_types?.join("\n") ?? data.node.template[t].type}
required={data.node.template[t].required} required={data.node.template[t].required}
id={data.node.template[t].type + "|" + t + "|" + data.id} id={(data.node.template[t].input_types?.join(";") ?? data.node.template[t].type) + "|" + t + "|" + data.id}
left={true} left={true}
type={data.node.template[t].type} type={data.node.template[t].type}
optionalHandle={data.node.template[t].input_types}
/> />
) : ( ) : (
<></> <></>

View file

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react"; import { useContext, useEffect, useRef, useState } from "react";
import { useNodes } from "reactflow"; import { useNodes } from "reactflow";
import { ChatType } from "../../types/chat"; import { ChatType } from "../../types/chat";
import ChatTrigger from "./chatTrigger"; import ChatTrigger from "./chatTrigger";
@ -7,11 +7,13 @@ import BuildTrigger from "./buildTrigger";
import { getBuildStatus } from "../../controllers/API"; import { getBuildStatus } from "../../controllers/API";
import { NodeType } from "../../types/flow"; import { NodeType } from "../../types/flow";
import FormModal from "../../modals/formModal"; import FormModal from "../../modals/formModal";
import { TabsContext } from "../../contexts/tabsContext";
import * as _ from "lodash";
export default function Chat({ flow }: ChatType) { export default function Chat({ flow }: ChatType) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [openForm, setOpenForm] = useState(false);
const [isBuilt, setIsBuilt] = useState(false); const [isBuilt, setIsBuilt] = useState(false);
const {tabsState} = useContext(TabsContext);
useEffect(() => { useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => { const handleKeyDown = (event: KeyboardEvent) => {
@ -44,21 +46,19 @@ export default function Chat({ flow }: ChatType) {
const prevNodesRef = useRef<any[] | undefined>(); const prevNodesRef = useRef<any[] | undefined>();
const nodes = useNodes(); const nodes = useNodes();
useEffect(() => { useEffect(() => {
const prevNodes = prevNodesRef.current; const prevNodes = prevNodesRef.current;
const currentNodes = nodes.map( const currentNodes = nodes.map(
(node: NodeType) => node.data.node.template (node: NodeType) => _.cloneDeep(node.data.node.template)
); );
if ( if (
prevNodes && tabsState && tabsState[flow.id] && tabsState[flow.id].isPending
JSON.stringify(prevNodes) !== JSON.stringify(currentNodes) && JSON.stringify(prevNodes) !== JSON.stringify(currentNodes)
) { ) {
setIsBuilt(false); setIsBuilt(false);
} }
prevNodesRef.current = currentNodes; prevNodesRef.current = currentNodes;
}, [nodes]); }, [tabsState]);
return ( return (
<> <>

View file

@ -5,6 +5,9 @@ import GenericModal from "../../modals/genericModal";
import { TypeModal } from "../../utils"; import { TypeModal } from "../../utils";
import { INPUT_STYLE } from "../../constants"; import { INPUT_STYLE } from "../../constants";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { postValidatePrompt } from "../../controllers/API";
import { typesContext } from "../../contexts/typesContext";
import * as _ from "lodash";
export default function PromptAreaComponent({ export default function PromptAreaComponent({
setNodeClass, setNodeClass,
@ -14,8 +17,10 @@ export default function PromptAreaComponent({
disabled, disabled,
editNode = false, editNode = false,
}: TextAreaComponentType) { }: TextAreaComponentType) {
const [myValue, setMyValue] = useState(value); const [myValue, setMyValue] = useState("");
const { openPopUp } = useContext(PopUpContext); const { openPopUp } = useContext(PopUpContext);
const { reactFlowInstance } = useContext(typesContext);
useEffect(() => { useEffect(() => {
if (disabled) { if (disabled) {
setMyValue(""); setMyValue("");
@ -24,8 +29,19 @@ export default function PromptAreaComponent({
}, [disabled, onChange]); }, [disabled, onChange]);
useEffect(() => { useEffect(() => {
if (value !== "" && myValue !== value && reactFlowInstance) { // only executed once
setMyValue(value); setMyValue(value);
}, [value]); postValidatePrompt(value, nodeClass)
.then((apiReturn) => {
if (apiReturn.data) {
setNodeClass(apiReturn.data.frontend_node);
// need to update reactFlowInstance to re-render the nodes.
reactFlowInstance.setEdges(_.cloneDeep(reactFlowInstance.getEdges()));
}
})
.catch((error) => {});
}
}, [reactFlowInstance]);
return ( return (
<div <div

View file

@ -8,11 +8,6 @@ export default function ToggleShadComponent({
disabled, disabled,
size, size,
}: ToggleComponentType) { }: ToggleComponentType) {
useEffect(() => {
if (disabled) {
setEnabled(false);
}
}, [disabled, setEnabled]);
let scaleX, scaleY; let scaleX, scaleY;
switch (size) { switch (size) {
case "small": case "small":
@ -32,11 +27,12 @@ export default function ToggleShadComponent({
scaleY = 1; scaleY = 1;
} }
return ( return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}> <div className={disabled ? "pointer-events-none cursor-not-allowed " : ""}>
<Switch <Switch
style={{ style={{
transform: `scaleX(${scaleX}) scaleY(${scaleY})`, transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
}} }}
disabled={disabled}
className="data-[state=unchecked]:bg-slate-500" className="data-[state=unchecked]:bg-slate-500"
checked={enabled} checked={enabled}
onCheckedChange={(x: boolean) => { onCheckedChange={(x: boolean) => {

View file

@ -173,7 +173,7 @@ export const DESCRIPTIONS: string[] = [
"Your Hub for Text Generation.", "Your Hub for Text Generation.",
"Promptly Ingenious!", "Promptly Ingenious!",
"Building Linguistic Labyrinths.", "Building Linguistic Labyrinths.",
"LangFlow: Create, Chain, Communicate.", "Create, Chain, Communicate.",
"Connect the Dots, Craft Language.", "Connect the Dots, Craft Language.",
"Interactive Language Weaving.", "Interactive Language Weaving.",
"Generate, Innovate, Communicate.", "Generate, Innovate, Communicate.",
@ -183,6 +183,46 @@ export const DESCRIPTIONS: string[] = [
"Nurture NLP Nodes Here.", "Nurture NLP Nodes Here.",
"Conversational Cartography Unlocked.", "Conversational Cartography Unlocked.",
"Design, Develop, Dialogize.", "Design, Develop, Dialogize.",
"Unleashing Linguistic Creativity.",
"Graph Your Way to Great Conversations.",
"The Power of Language at Your Fingertips.",
"Sculpting Language with Precision.",
"Where Language Meets Logic.",
"Building Intelligent Interactions.",
"Your Passport to Linguistic Landscapes.",
"Create, Curate, Communicate with LangFlow.",
"Flow into the Future of Language.",
"Mapping Meaningful Conversations.",
"Unravel the Art of Articulation.",
"Language Engineering Excellence.",
"Navigate the Networks of Conversation.",
"Crafting Conversations, One Node at a Time.",
"The Pinnacle of Prompt Generation.",
"Language Models, Mapped and Mastered.",
"Powerful Prompts, Perfectly Positioned.",
"Innovation in Interaction with LangFlow.",
"Your Toolkit for Text Generation.",
"Unfolding Linguistic Possibilities.",
"Building Powerful Solutions with Language Models.",
"Uncover Business Opportunities with NLP.",
"Harness the Power of Conversational AI.",
"Transform Your Business with Smart Dialogues.",
"Craft Meaningful Interactions, Generate Value.",
"Unleashing Business Potential through Language Engineering.",
"Empowering Enterprises with Intelligent Interactions.",
"Driving Innovation in Business Communication.",
"Catalyzing Business Growth through Conversational AI.",
"Text Generation Meets Business Transformation.",
"Navigate the Linguistic Landscape, Discover Opportunities.",
"Create Powerful Connections, Boost Business Value.",
"Empowering Communication, Enabling Opportunities.",
"Advanced NLP for Groundbreaking Business Solutions.",
"Innovation in Interaction, Revolution in Revenue.",
"Maximize Impact with Intelligent Conversations.",
"Beyond Text Generation - Unleashing Business Opportunities.",
"Unlock the Power of AI in Your Business Conversations.",
"Crafting Dialogues that Drive Business Success.",
"Engineered for Excellence, Built for Business.",
]; ];
/** /**
@ -256,8 +296,49 @@ export const ADJECTIVES: string[] = [
"thirsty", "thirsty",
"tiny", "tiny",
"trusting", "trusting",
"bubbly",
"charming",
"cheerful",
"comical",
"dazzling",
"delighted",
"dynamic",
"effervescent",
"enthusiastic",
"exuberant",
"fluffy",
"friendly",
"funky",
"giddy",
"giggly",
"gleeful",
"goofy",
"graceful",
"grinning",
"hilarious",
"inquisitive",
"joyous",
"jubilant",
"lively",
"mirthful",
"mischievous",
"optimistic",
"peppy",
"perky",
"playful",
"quirky",
"radiant",
"sassy",
"silly",
"spirited",
"sprightly",
"twinkly",
"upbeat",
"vibrant",
"witty",
"zany",
"zealous",
]; ];
/** /**
* Nouns for the name of the flow * Nouns for the name of the flow
* @constant * @constant
@ -406,6 +487,38 @@ export const NOUNS: string[] = [
"wright", "wright",
"yalow", "yalow",
"yonath", "yonath",
"coulomb",
"degrasse",
"dewey",
"edison",
"eratosthenes",
"faraday",
"galton",
"gauss",
"herschel",
"hubble",
"joule",
"kaku",
"kepler",
"khayyam",
"lavoisier",
"maxwell",
"mendel",
"mendeleev",
"ohm",
"pascal",
"planck",
"riemann",
"schrodinger",
"sagan",
"tesla",
"tyson",
"volta",
"watt",
"weber",
"wien",
"zoBell",
"zuse",
]; ];
/** /**

View file

@ -189,26 +189,28 @@ export function TabsProvider({ children }: { children: ReactNode }) {
} }
function processFlowEdges(flow) { function processFlowEdges(flow) {
if(!flow.data || !flow.data.edges) return; if (!flow.data || !flow.data.edges) return;
flow.data.edges.forEach((edge) => { flow.data.edges.forEach((edge) => {
edge.className = ""; edge.className = "";
edge.style = { stroke: "#555555" }; edge.style = { stroke: "#555555" };
}); });
} }
function updateDisplay_name(node:NodeType,template:APIClassType) { function updateDisplay_name(node: NodeType, template: APIClassType) {
node.data.node.display_name = template["display_name"]?template["display_name"]:node.data.type; node.data.node.display_name = template["display_name"]
? template["display_name"]
: node.data.type;
} }
function processFlowNodes(flow) { function processFlowNodes(flow) {
if(!flow.data || !flow.data.nodes) return; if (!flow.data || !flow.data.nodes) return;
flow.data.nodes.forEach((node:NodeType) => { flow.data.nodes.forEach((node: NodeType) => {
const template = templates[node.data.type]; const template = templates[node.data.type];
if (!template) { if (!template) {
setErrorData({ title: `Unknown node type: ${node.data.type}` }); setErrorData({ title: `Unknown node type: ${node.data.type}` });
return; return;
} }
if (Object.keys(template["template"]).length > 0) { if (Object.keys(template["template"]).length > 0) {
updateDisplay_name(node,template); updateDisplay_name(node, template);
updateNodeBaseClasses(node, template); updateNodeBaseClasses(node, template);
updateNodeEdges(flow, node, template); updateNodeEdges(flow, node, template);
updateNodeDescription(node, template); updateNodeDescription(node, template);
@ -217,11 +219,15 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}); });
} }
function updateNodeBaseClasses(node:NodeType,template:APIClassType) { function updateNodeBaseClasses(node: NodeType, template: APIClassType) {
node.data.node.base_classes = template["base_classes"]; node.data.node.base_classes = template["base_classes"];
} }
function updateNodeEdges(flow:FlowType, node:NodeType,template:APIClassType) { function updateNodeEdges(
flow: FlowType,
node: NodeType,
template: APIClassType
) {
flow.data.edges.forEach((edge) => { flow.data.edges.forEach((edge) => {
if (edge.source === node.id) { if (edge.source === node.id) {
edge.sourceHandle = edge.sourceHandle edge.sourceHandle = edge.sourceHandle
@ -233,11 +239,11 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}); });
} }
function updateNodeDescription(node:NodeType,template:APIClassType) { function updateNodeDescription(node: NodeType, template: APIClassType) {
node.data.node.description = template["description"]; node.data.node.description = template["description"];
} }
function updateNodeTemplate(node:NodeType,template:APIClassType) { function updateNodeTemplate(node: NodeType, template: APIClassType) {
node.data.node.template = updateTemplate( node.data.node.template = updateTemplate(
template["template"] as unknown as APITemplateType, template["template"] as unknown as APITemplateType,
node.data.node.template as APITemplateType node.data.node.template as APITemplateType
@ -302,7 +308,15 @@ export function TabsProvider({ children }: { children: ReactNode }) {
* If the file type is application/json, the file is read and parsed into a JSON object. * If the file type is application/json, the file is read and parsed into a JSON object.
* The resulting JSON object is passed to the addFlow function. * The resulting JSON object is passed to the addFlow function.
*/ */
function uploadFlow(newProject?: boolean) { function uploadFlow(newProject?: boolean, file?: File) {
if (file) {
file.text().then((text) => {
// parse the text into a JSON object
let flow: FlowType = JSON.parse(text);
addFlow(flow, newProject);
});
} else {
// create a file input // create a file input
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
@ -310,11 +324,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
// add a change event listener to the file input // add a change event listener to the file input
input.onchange = (e: Event) => { input.onchange = (e: Event) => {
// check if the file type is application/json // check if the file type is application/json
if ((e.target as HTMLInputElement).files[0].type === "application/json") { if (
(e.target as HTMLInputElement).files[0].type === "application/json"
) {
// get the file from the file input // get the file from the file input
const file = (e.target as HTMLInputElement).files[0]; const currentfile = (e.target as HTMLInputElement).files[0];
// read the file as text // read the file as text
file.text().then((text) => { currentfile.text().then((text) => {
// parse the text into a JSON object // parse the text into a JSON object
let flow: FlowType = JSON.parse(text); let flow: FlowType = JSON.parse(text);
@ -325,6 +341,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
// trigger the file input click event to open the file dialog // trigger the file input click event to open the file dialog
input.click(); input.click();
} }
}
function uploadFlows() { function uploadFlows() {
// create a file input // create a file input

View file

@ -53,7 +53,7 @@ export default function ChatInput({
lockChat lockChat
? " bg-input 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", : " bg-white-200 text-black dark:bg-gray-900 dark:text-gray-300",
"p-4 form-input block w-full custom-scroll rounded-md border-gray-300 dark:border-gray-600 pr-12 sm:text-sm" + "p-4 form-input block w-full custom-scroll rounded-md border-gray-300 dark:border-gray-600 pr-16 sm:text-sm" +
INPUT_STYLE INPUT_STYLE
)} )}
placeholder={"Send a message..."} placeholder={"Send a message..."}
@ -62,7 +62,7 @@ export default function ChatInput({
<button className={classNames("p-2 px-1 transition-all duration-300 rounded-md",chatValue == "" ? "text-primary" : " bg-indigo-600 text-background")} disabled={lockChat} onClick={() => sendMessage()}> <button className={classNames("p-2 px-1 transition-all duration-300 rounded-md",chatValue == "" ? "text-primary" : " bg-indigo-600 text-background")} disabled={lockChat} onClick={() => sendMessage()}>
{lockChat ? ( {lockChat ? (
<Lock <Lock
className="h-5 w-5 ml-1 mr-11 animate-pulse" className="h-5 w-5 ml-1 mr-1 animate-pulse"
aria-hidden="true" aria-hidden="true"
/> />
) : ( ) : (

View file

@ -69,7 +69,7 @@ export const CodeBlock: FC<Props> = memo(({ language, value }) => {
</div> </div>
<SyntaxHighlighter <SyntaxHighlighter
className=" w-[570px]" className=" w-[47vw]"
language={language} language={language}
style={oneDark} style={oneDark}
customStyle={{ margin: 0 }} customStyle={{ margin: 0 }}

View file

@ -92,7 +92,7 @@ export default function ChatMessage({
<ReactMarkdown <ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]} remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]} rehypePlugins={[rehypeMathjax]}
className="markdown prose dark:prose-invert text-primary" className="markdown prose dark:prose-invert text-primary max-w-full"
components={{ components={{
code({ node, inline, className, children, ...props }) { code({ node, inline, className, children, ...props }) {
if (children.length) { if (children.length) {

View file

@ -4,7 +4,13 @@ import { alertContext } from "../../contexts/alertContext";
import { classNames, validateNodes } from "../../utils"; import { classNames, validateNodes } from "../../utils";
import { typesContext } from "../../contexts/typesContext"; import { typesContext } from "../../contexts/typesContext";
import ChatMessage from "./chatMessage"; import ChatMessage from "./chatMessage";
import { TerminalSquare, MessageSquare, Variable, Eraser } from "lucide-react"; import {
TerminalSquare,
MessageSquare,
Variable,
Eraser,
MessageSquarePlus,
} from "lucide-react";
import { sendAllProps } from "../../types/api"; import { sendAllProps } from "../../types/api";
import { ChatMessageType } from "../../types/chat"; import { ChatMessageType } from "../../types/chat";
import ChatInput from "./chatInput"; import ChatInput from "./chatInput";
@ -30,6 +36,14 @@ import {
import { Textarea } from "../../components/ui/textarea"; import { Textarea } from "../../components/ui/textarea";
import { Badge } from "../../components/ui/badge"; import { Badge } from "../../components/ui/badge";
import ToggleShadComponent from "../../components/toggleShadComponent"; import ToggleShadComponent from "../../components/toggleShadComponent";
import Dropdown from "../../components/dropdownComponent";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../../components/ui/dropdown-menu";
import { Button } from "../../components/ui/button";
export default function FormModal({ export default function FormModal({
flow, flow,
@ -40,18 +54,27 @@ export default function FormModal({
setOpen: Function; setOpen: Function;
flow: FlowType; flow: FlowType;
}) { }) {
const [chatValue, setChatValue] = useState(""); const { tabsState, setTabsState } = useContext(TabsContext);
const [keysValue, setKeysValue] = useState([]); const [chatValue, setChatValue] = useState(
tabsState[flow.id].formKeysData.input_keys[
Object.keys(tabsState[flow.id].formKeysData.input_keys).find(
(k) => !tabsState[flow.id].formKeysData.handle_keys.some((j) => j === k)
)
]
);
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]); const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
const { setErrorData, setNoticeData } = useContext(alertContext); const { setErrorData, setNoticeData } = useContext(alertContext);
const { tabsState } = useContext(TabsContext);
const ws = useRef<WebSocket | null>(null); const ws = useRef<WebSocket | null>(null);
const [lockChat, setLockChat] = useState(false); const [lockChat, setLockChat] = useState(false);
const isOpen = useRef(open); const isOpen = useRef(open);
const messagesRef = useRef(null); const messagesRef = useRef(null);
const id = useRef(flow.id); const id = useRef(flow.id);
const [chatKey, setChatKey] = useState(0); const [chatKey, setChatKey] = useState(
Object.keys(tabsState[flow.id].formKeysData.input_keys).find(
(k) => !tabsState[flow.id].formKeysData.handle_keys.some((j) => j === k)
)
);
useEffect(() => { useEffect(() => {
if (messagesRef.current) { if (messagesRef.current) {
@ -64,12 +87,6 @@ export default function FormModal({
}, [open]); }, [open]);
useEffect(() => { useEffect(() => {
id.current = flow.id; id.current = flow.id;
if (tabsState[flow.id].formKeysData) {
setKeysValue(
Array(tabsState[flow.id].formKeysData.input_keys.length).fill("")
);
}
}, [flow.id, tabsState[flow.id], tabsState[flow.id].formKeysData]); }, [flow.id, tabsState[flow.id], tabsState[flow.id].formKeysData]);
var isStream = false; var isStream = false;
@ -166,13 +183,13 @@ export default function FormModal({
chatItem.files chatItem.files
? { ? {
isSend: !chatItem.is_bot, isSend: !chatItem.is_bot,
message: chatItem.is_bot ? chatItem.message : formatMessage(chatItem.message), message: formatMessage(chatItem.message),
thought: chatItem.intermediate_steps, thought: chatItem.intermediate_steps,
files: chatItem.files, files: chatItem.files,
} }
: { : {
isSend: !chatItem.is_bot, isSend: !chatItem.is_bot,
message: chatItem.is_bot ? chatItem.message : formatMessage(chatItem.message), message: formatMessage(chatItem.message),
thought: chatItem.intermediate_steps, thought: chatItem.intermediate_steps,
} }
); );
@ -305,6 +322,7 @@ export default function FormModal({
}, [open]); }, [open]);
function formatMessage(inputs: any): string { function formatMessage(inputs: any): string {
if (inputs) { if (inputs) {
if (typeof inputs == "string") return inputs;
// inputs is a object with the keys and values being input_keys and keysValue // inputs is a object with the keys and values being input_keys and keysValue
// so the formated message is a string with the keys and values separated by ": " // so the formated message is a string with the keys and values separated by ": "
let message = ""; let message = "";
@ -325,13 +343,7 @@ export default function FormModal({
let nodeValidationErrors = validateNodes(reactFlowInstance); let nodeValidationErrors = validateNodes(reactFlowInstance);
if (nodeValidationErrors.length === 0) { if (nodeValidationErrors.length === 0) {
setLockChat(true); setLockChat(true);
// Message variable makes a object with the keys being the names from tabsState[flow.id].formKeysData.input_keys and the values being the keysValue of the correspondent index let inputs = tabsState[id.current].formKeysData.input_keys;
let keys = tabsState[flow.id].formKeysData.input_keys; // array of keys
let values = keysValue.map((k, i) => (i == chatKey ? chatValue : k)); // array of values
let inputs = keys.reduce((object, key, index) => {
object[key] = values[index];
return object;
}, {});
setChatValue(""); setChatValue("");
const message = formatMessage(inputs); const message = formatMessage(inputs);
addChatHistory(message, true); addChatHistory(message, true);
@ -342,6 +354,11 @@ export default function FormModal({
name: flow.name, name: flow.name,
description: flow.description, description: flow.description,
}); });
setTabsState((old) => {
let newTabsState = _.cloneDeep(old);
newTabsState[id.current].formKeysData.input_keys[chatKey] = "";
return newTabsState;
});
} else { } else {
setErrorData({ setErrorData({
title: "Oops! Looks like you missed some required information:", title: "Oops! Looks like you missed some required information:",
@ -365,13 +382,12 @@ export default function FormModal({
setOpen(x); setOpen(x);
} }
function handleOnCheckedChange(checked: boolean, index: number) { function handleOnCheckedChange(checked: boolean, i: string) {
if (checked === true) { if (checked === true) {
setChatKey(index); setChatKey(i);
setChatValue(tabsState[flow.id].formKeysData.input_keys[i]);
} }
} }
console.log(chatHistory)
return ( return (
<Dialog open={open} onOpenChange={setModalOpen}> <Dialog open={open} onOpenChange={setModalOpen}>
<DialogTrigger className="hidden"></DialogTrigger> <DialogTrigger className="hidden"></DialogTrigger>
@ -388,47 +404,90 @@ export default function FormModal({
<DialogDescription>{CHAT_FORM_DIALOG_SUBTITLE}</DialogDescription> <DialogDescription>{CHAT_FORM_DIALOG_SUBTITLE}</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex h-[80vh] w-full mt-2"> <div className="flex h-[80vh] w-full mt-2 ">
<div className="w-1/4 h-full flex flex-col justify-start mr-6"> <div className="w-2/5 h-full overflow-auto scrollbar-hide flex flex-col justify-start mr-6">
<div className="flex py-2"> <div className="flex justify-between items-center">
<Variable className="w-6 h-6 pe-1 text-gray-700 stroke-2 dark:text-slate-200"></Variable> <div className="flex py-2 items-center">
<span className="text-md font-semibold text-gray-800 dark:text-white"> <Variable className="w-4 h-4 mr-1 text-primary"></Variable>
<span className="text-sm font-semibold text-primary">
Input Variables Input Variables
</span> </span>
</div> </div>
<Accordion type="single" collapsible className="w-full"> <div className="flex mr-2.5 py-2 items-center">
{tabsState[id.current].formKeysData.input_keys.map((i, k) => ( <span className="text-sm font-semibold text-primary">
<AccordionItem key={k} value={i}> Chat Input
<AccordionTrigger><Badge variant="gray" size="md">{i}</Badge></AccordionTrigger> </span>
<AccordionContent> </div>
<div className="p-1 flex flex-col gap-2"> </div>
<div className="flex items-center space-x-2"> <Accordion type="multiple" className="w-full">
<Label htmlFor="airplane-mode" className="-mt-1">From Chat</Label> {Object.keys(tabsState[id.current].formKeysData.input_keys).map(
(i, k) => (
<div className="flex items-start gap-3" key={k}>
<AccordionItem className="w-full" key={k} value={i}>
<AccordionTrigger className="flex gap-2">
<div className="flex items-center w-full justify-between">
<Badge variant="gray" size="md">
{i}
</Badge>
<div
className="-mb-1"
onClick={(event) => {
event.stopPropagation();
}}
>
<ToggleShadComponent <ToggleShadComponent
enabled={chatKey === k} enabled={chatKey === i}
setEnabled={(value) => setEnabled={(value) =>
handleOnCheckedChange(value, k) handleOnCheckedChange(value, i)
} }
size="small" size="small"
disabled={false} disabled={tabsState[
id.current
].formKeysData.handle_keys.some((t) => t === i)}
/> />
</div> </div>
</div>
</AccordionTrigger>
<AccordionContent>
<div className="p-1 flex flex-col gap-2">
{tabsState[
id.current
].formKeysData.handle_keys.some((t) => t === i) && (
<div className="font-normal text-muted-foreground ">
Source: Component
</div>
)}
<Textarea <Textarea
value={keysValue[k]} value={
onChange={(e) => tabsState[id.current].formKeysData.input_keys[i]
setKeysValue((old) => [...old.slice(0, k), e.target.value, ...old.slice(k + 1)])
} }
disabled={chatKey === k} onChange={(e) => {
setTabsState((old) => {
let newTabsState = _.cloneDeep(old);
newTabsState[
id.current
].formKeysData.input_keys[i] = e.target.value;
return newTabsState;
});
}}
disabled={chatKey === i}
placeholder="Enter text..." placeholder="Enter text..."
></Textarea> ></Textarea>
</div> </div>
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
))} </div>
)
)}
{tabsState[id.current].formKeysData.memory_keys.map((i, k) => ( {tabsState[id.current].formKeysData.memory_keys.map((i, k) => (
<AccordionItem key={k} value={i}> <AccordionItem key={k} value={i}>
<div className="flex flex-1 items-center justify-between py-4 font-normal transition-all group text-muted-foreground text-sm"> <div className="flex flex-1 items-center justify-between py-4 font-normal transition-all group text-muted-foreground text-sm">
<div className="group-hover:underline"><Badge size="md" variant="gray">{i}</Badge></div> <div className="group-hover:underline">
<Badge size="md" variant="gray">
{i}
</Badge>
</div>
Used as Memory Key Used as Memory Key
</div> </div>
</AccordionItem> </AccordionItem>
@ -440,7 +499,12 @@ export default function FormModal({
<div className="absolute right-3 top-3 z-50"> <div className="absolute right-3 top-3 z-50">
<button disabled={lockChat} onClick={() => clearChat()}> <button disabled={lockChat} onClick={() => clearChat()}>
<Eraser <Eraser
className={classNames("h-5 w-5", lockChat ? "text-primary animate-pulse" : "text-primary hover:text-gray-600")} className={classNames(
"h-5 w-5",
lockChat
? "text-primary animate-pulse"
: "text-primary hover:text-gray-600"
)}
aria-hidden="true" aria-hidden="true"
/> />
</button> </button>
@ -486,7 +550,16 @@ export default function FormModal({
chatValue={chatValue} chatValue={chatValue}
lockChat={lockChat} lockChat={lockChat}
sendMessage={sendMessage} sendMessage={sendMessage}
setChatValue={setChatValue} setChatValue={(value) => {
setChatValue(value);
setTabsState((old) => {
let newTabsState = _.cloneDeep(old);
newTabsState[id.current].formKeysData.input_keys[
chatKey
] = chatValue;
return newTabsState;
});
}}
inputRef={ref} inputRef={ref}
/> />
</div> </div>

View file

@ -1,153 +0,0 @@
import { Dialog, Transition } from "@headlessui/react";
import { XMarkIcon, DocumentTextIcon } from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { darkContext } from "../../contexts/darkContext";
import { postValidatePrompt } from "../../controllers/API";
import { alertContext } from "../../contexts/alertContext";
export default function PromptAreaModal({
value,
setValue,
}: {
setValue: (value: string) => void;
value: string;
}) {
const [open, setOpen] = useState(true);
const [myValue, setMyValue] = useState(value);
const { dark } = useContext(darkContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
const { closePopUp } = useContext(PopUpContext);
const ref = useRef();
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setTimeout(() => {
closePopUp();
}, 300);
}
}
return (
<Transition.Root show={open} appear={true} as={Fragment}>
<Dialog
as="div"
className="relative z-10"
onClose={setModalOpen}
initialFocus={ref}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<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">
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button
type="button"
className="rounded-md text-gray-400 hover:text-gray-500"
onClick={() => {
setModalOpen(false);
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="h-full w-full flex flex-col justify-center items-center">
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
<div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-gray-900 sm:mx-0 sm:h-10 sm:w-10">
<DocumentTextIcon
className="h-6 w-6 text-blue-600"
aria-hidden="true"
/>
</div>
<div className="mt-4 text-center sm:ml-4 sm:text-left">
<Dialog.Title
as="h3"
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
>
Edit Prompt
</Dialog.Title>
</div>
</div>
<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
ref={ref}
className="form-input h-full w-full rounded-lg border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
value={myValue}
onChange={(e) => {
setMyValue(e.target.value);
setValue(e.target.value);
}}
/>
</div>
</div>
</div>
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
<button
type="button"
className="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-ring focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:ring-offset-1 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => {
postValidatePrompt(myValue)
.then((apiReturn) => {
if (apiReturn.data) {
let inputVariables =
apiReturn.data.input_variables;
if (inputVariables.length === 0) {
setErrorData({
title:
"The template you are attempting to use does not contain any variables for data entry.",
});
} else {
setSuccessData({
title: "Prompt is ready",
});
setModalOpen(false);
setValue(myValue);
}
} else {
setErrorData({
title: "Something went wrong, please try again",
});
}
})
.catch((error) => {
return setErrorData({
title:
"There is something wrong with this prompt, please review it",
list: [error.response.data.detail],
});
});
}}
>
Check & Save
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}

View file

@ -37,6 +37,7 @@ const nodeTypes = {
export default function Page({ flow }: { flow: FlowType }) { export default function Page({ flow }: { flow: FlowType }) {
let { let {
updateFlow, updateFlow,
uploadFlow,
disableCopyPaste, disableCopyPaste,
addFlow, addFlow,
getNodeId, getNodeId,
@ -151,6 +152,7 @@ export default function Page({ flow }: { flow: FlowType }) {
return { return {
...prev, ...prev,
[tabId]: { [tabId]: {
...prev[tabId],
isPending: true, isPending: true,
}, },
}; };
@ -166,6 +168,7 @@ export default function Page({ flow }: { flow: FlowType }) {
return { return {
...prev, ...prev,
[tabId]: { [tabId]: {
...prev[tabId],
isPending: true, isPending: true,
}, },
}; };
@ -217,20 +220,26 @@ export default function Page({ flow }: { flow: FlowType }) {
const onDragOver = useCallback((event: React.DragEvent) => { const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault(); event.preventDefault();
if (event.dataTransfer.types.some((t) => t === "nodedata")) {
event.dataTransfer.dropEffect = "move"; event.dataTransfer.dropEffect = "move";
} else {
event.dataTransfer.dropEffect = "copy";
}
}, []); }, []);
const onDrop = useCallback( const onDrop = useCallback(
(event: React.DragEvent) => { (event: React.DragEvent) => {
event.preventDefault(); event.preventDefault();
if (event.dataTransfer.types.some((t) => t === "nodedata")) {
takeSnapshot(); takeSnapshot();
// Get the current bounds of the ReactFlow wrapper element // Get the current bounds of the ReactFlow wrapper element
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect(); const reactflowBounds =
reactFlowWrapper.current.getBoundingClientRect();
// Extract the data from the drag event and parse it as a JSON object // Extract the data from the drag event and parse it as a JSON object
let data: { type: string; node?: APIClassType } = JSON.parse( let data: { type: string; node?: APIClassType } = JSON.parse(
event.dataTransfer.getData("json") 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 // If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
@ -273,6 +282,10 @@ export default function Page({ flow }: { flow: FlowType }) {
// Add the new node to the list of nodes in state // Add the new node to the list of nodes in state
} }
setNodes((nds) => nds.concat(newNode)); setNodes((nds) => nds.concat(newNode));
} else if (event.dataTransfer.types.some((t) => t === "Files")) {
takeSnapshot();
uploadFlow(false, event.dataTransfer.files.item(0));
}
}, },
// Specify dependencies for useCallback // Specify dependencies for useCallback
[getNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot] [getNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]

View file

@ -40,7 +40,7 @@ export default function ExtraSidebar() {
crt.classList.add("cursor-grabbing"); crt.classList.add("cursor-grabbing");
document.body.appendChild(crt); document.body.appendChild(crt);
event.dataTransfer.setDragImage(crt, 0, 0); event.dataTransfer.setDragImage(crt, 0, 0);
event.dataTransfer.setData("json", JSON.stringify(data)); event.dataTransfer.setData("nodedata", JSON.stringify(data));
} }
function handleSearchInput(e: string) { function handleSearchInput(e: string) {

View file

@ -12,6 +12,7 @@ export type APIClassType = {
description: string; description: string;
template: APITemplateType; template: APITemplateType;
display_name: string; display_name: string;
input_types?: Array<string>;
[key: string]: Array<string> | string | APITemplateType; [key: string]: Array<string> | string | APITemplateType;
}; };

View file

@ -36,6 +36,7 @@ export type ParameterComponentType = {
name?: string; name?: string;
tooltipTitle: string; tooltipTitle: string;
dataContext?: typesContextType; dataContext?: typesContextType;
optionalHandle?: Array<String>;
}; };
export type InputListComponentType = { export type InputListComponentType = {
value: string[]; value: string[];

View file

@ -14,7 +14,7 @@ export type TabsContextType = {
downloadFlow: (flow: FlowType) => void; downloadFlow: (flow: FlowType) => void;
downloadFlows: () => void; downloadFlows: () => void;
uploadFlows: () => void; uploadFlows: () => void;
uploadFlow: (newFlow?: boolean) => void; uploadFlow: (newFlow?: boolean, file?: File) => void;
hardReset: () => void; hardReset: () => void;
//disable CopyPaste //disable CopyPaste
disableCopyPaste: boolean; disableCopyPaste: boolean;
@ -33,6 +33,6 @@ export type TabsContextType = {
export type TabsState = { export type TabsState = {
[key: string]: { [key: string]: {
isPending: boolean; isPending: boolean;
formKeysData: {input_keys?: Array<string>, memory_keys?: Array<string>}; formKeysData: {input_keys?: Object, memory_keys?: Array<string>, handle_keys?: Array<string>};
}; };
}; };

View file

@ -45,6 +45,7 @@ import { twMerge } from "tailwind-merge";
import { ADJECTIVES, DESCRIPTIONS, NOUNS } from "./constants"; import { ADJECTIVES, DESCRIPTIONS, NOUNS } from "./constants";
import { ComponentType, SVGProps } from "react"; import { ComponentType, SVGProps } from "react";
import { import {
Compass,
Cpu, Cpu,
Fingerprint, Fingerprint,
Gift, Gift,
@ -139,6 +140,8 @@ export const nodeColors: { [char: string]: string } = {
toolkits: "#DB2C2C", toolkits: "#DB2C2C",
wrappers: "#E6277A", wrappers: "#E6277A",
utilities: "#31A3CC", utilities: "#31A3CC",
output_parsers: "#E6A627",
str: "#049524",
unknown: "#9CA3AF", unknown: "#9CA3AF",
}; };
@ -158,6 +161,7 @@ export const nodeNames: { [char: string]: string } = {
wrappers: "Wrappers", wrappers: "Wrappers",
textsplitters: "Text Splitters", textsplitters: "Text Splitters",
utilities: "Utilities", utilities: "Utilities",
output_parsers: "Output Parsers",
unknown: "Unknown", unknown: "Unknown",
}; };
@ -363,6 +367,9 @@ export const nodeIconsLucide: {
utilities: Wand2 as React.ForwardRefExoticComponent< utilities: Wand2 as React.ForwardRefExoticComponent<
ComponentType<SVGProps<SVGSVGElement>> ComponentType<SVGProps<SVGSVGElement>>
>, >,
output_parsers: Compass as React.ForwardRefExoticComponent<
ComponentType<SVGProps<SVGSVGElement>>
>,
unknown: HelpCircle as React.ForwardRefExoticComponent< unknown: HelpCircle as React.ForwardRefExoticComponent<
ComponentType<SVGProps<SVGSVGElement>> ComponentType<SVGProps<SVGSVGElement>>
>, >,
@ -625,11 +632,11 @@ export function isValidConnection(
reactFlowInstance: ReactFlowInstance reactFlowInstance: ReactFlowInstance
) { ) {
if ( if (
sourceHandle.split("|")[0] === targetHandle.split("|")[0] || targetHandle.split("|")[0].split(";").some((n) => n === sourceHandle.split("|")[0]) ||
sourceHandle sourceHandle
.split("|") .split("|")
.slice(2) .slice(2)
.some((t) => t === targetHandle.split("|")[0]) || .some((t) => targetHandle.split("|")[0].split(";").some((n) => n === t)) ||
targetHandle.split("|")[0] === "str" targetHandle.split("|")[0] === "str"
) { ) {
let targetNode = reactFlowInstance.getNode(target).data.node; let targetNode = reactFlowInstance.getNode(target).data.node;