Merge remote-tracking branch 'origin/form_io' into python_custom_node_component
This commit is contained in:
commit
6416c43ff2
37 changed files with 669 additions and 423 deletions
|
|
@ -24,24 +24,30 @@ def remove_api_keys(flow: dict):
|
|||
return flow
|
||||
|
||||
|
||||
def build_input_keys_response(langchain_object):
|
||||
def build_input_keys_response(langchain_object, artifacts):
|
||||
"""Build the input keys response."""
|
||||
|
||||
input_keys_response = {
|
||||
"input_keys": {key: "" for key in langchain_object.input_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
|
||||
# memory variables should be removed from the input keys
|
||||
if hasattr(langchain_object, "memory") and hasattr(
|
||||
langchain_object.memory, "memory_variables"
|
||||
):
|
||||
# Remove memory variables from input keys
|
||||
input_keys_response["input_keys"] = [
|
||||
key
|
||||
for key in input_keys_response["input_keys"]
|
||||
input_keys_response["input_keys"] = {
|
||||
key: value
|
||||
for key, value in input_keys_response["input_keys"].items()
|
||||
if key not in langchain_object.memory.memory_variables
|
||||
]
|
||||
}
|
||||
# Add memory variables to memory_keys
|
||||
input_keys_response["memory_keys"] = langchain_object.memory.memory_variables
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ async def stream_build(flow_id: str):
|
|||
|
||||
async def event_stream(flow_id):
|
||||
final_response = {"end_of_stream": True}
|
||||
artifacts = {}
|
||||
try:
|
||||
if flow_id not in flow_data_store:
|
||||
error_message = "Invalid session ID"
|
||||
|
|
@ -108,6 +109,11 @@ async def stream_build(flow_id: str):
|
|||
logger.debug(
|
||||
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:
|
||||
params = str(exc)
|
||||
valid = False
|
||||
|
|
@ -124,13 +130,16 @@ async def stream_build(flow_id: str):
|
|||
langchain_object = graph.build()
|
||||
# Now we need to check the input_keys to send them to the client
|
||||
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))
|
||||
|
||||
chat_manager.set_cache(flow_id, langchain_object)
|
||||
# We need to reset the chat history
|
||||
chat_manager.chat_history.empty_history(flow_id)
|
||||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
logger.error("Error while building the flow: %s", exc)
|
||||
yield str(StreamData(event="error", data={"error": str(exc)}))
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -39,10 +39,11 @@ def post_validate_prompt(prompt: ValidatePromptRequest):
|
|||
try:
|
||||
template_field = TemplateField(
|
||||
name=variable,
|
||||
display_name=variable,
|
||||
field_type="str",
|
||||
show=True,
|
||||
advanced=False,
|
||||
input_types=["BaseLoader"],
|
||||
input_types=["BaseLoader", "BaseOutputParser"],
|
||||
)
|
||||
|
||||
prompt.frontend_node.template[variable] = template_field.to_dict()
|
||||
|
|
|
|||
|
|
@ -142,6 +142,6 @@ vectorstores:
|
|||
- MongoDBAtlasVectorSearch
|
||||
wrappers:
|
||||
- RequestsWrapper
|
||||
# - ChatPromptTemplate
|
||||
# - SystemMessagePromptTemplate
|
||||
# - HumanMessagePromptTemplate
|
||||
output_parsers:
|
||||
- StructuredOutputParser
|
||||
- ResponseSchema
|
||||
|
|
|
|||
|
|
@ -6,9 +6,15 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class Edge:
|
||||
def __init__(self, source: "Vertex", target: "Vertex"):
|
||||
def __init__(self, source: "Vertex", target: "Vertex", edge: dict):
|
||||
self.source: "Vertex" = source
|
||||
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()
|
||||
|
||||
def validate_edge(self) -> None:
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ class Graph:
|
|||
raise ValueError(f"Source node {edge['source']} not found")
|
||||
if target is None:
|
||||
raise ValueError(f"Target node {edge['target']} not found")
|
||||
edges.append(Edge(source, target))
|
||||
edges.append(Edge(source, target, edge))
|
||||
return edges
|
||||
|
||||
def _get_vertex_class(self, node_type: str, node_lc_type: str) -> Type[Vertex]:
|
||||
|
|
|
|||
|
|
@ -1,18 +1,5 @@
|
|||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.graph.vertex.types import (
|
||||
AgentVertex,
|
||||
ChainVertex,
|
||||
DocumentLoaderVertex,
|
||||
EmbeddingVertex,
|
||||
LLMVertex,
|
||||
MemoryVertex,
|
||||
PromptVertex,
|
||||
TextSplitterVertex,
|
||||
ToolVertex,
|
||||
ToolkitVertex,
|
||||
VectorStoreVertex,
|
||||
WrapperVertex,
|
||||
)
|
||||
from langflow.graph.vertex import types
|
||||
from langflow.interface.agents.base import agent_creator
|
||||
from langflow.interface.chains.base import chain_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.vector_store.base import vectorstore_creator
|
||||
from langflow.interface.wrappers.base import wrapper_creator
|
||||
from langflow.interface.output_parsers.base import output_parser_creator
|
||||
|
||||
|
||||
from typing import Dict, Type
|
||||
|
||||
|
||||
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
|
||||
**{t: PromptVertex for t in prompt_creator.to_list()},
|
||||
**{t: AgentVertex for t in agent_creator.to_list()},
|
||||
**{t: ChainVertex for t in chain_creator.to_list()},
|
||||
**{t: ToolVertex for t in tool_creator.to_list()},
|
||||
**{t: ToolkitVertex for t in toolkits_creator.to_list()},
|
||||
**{t: WrapperVertex for t in wrapper_creator.to_list()},
|
||||
**{t: LLMVertex for t in llm_creator.to_list()},
|
||||
**{t: MemoryVertex for t in memory_creator.to_list()},
|
||||
**{t: EmbeddingVertex for t in embedding_creator.to_list()},
|
||||
**{t: VectorStoreVertex for t in vectorstore_creator.to_list()},
|
||||
**{t: DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
||||
**{t: TextSplitterVertex for t in textsplitter_creator.to_list()},
|
||||
**{t: types.PromptVertex for t in prompt_creator.to_list()},
|
||||
**{t: types.AgentVertex for t in agent_creator.to_list()},
|
||||
**{t: types.ChainVertex for t in chain_creator.to_list()},
|
||||
**{t: types.ToolVertex for t in tool_creator.to_list()},
|
||||
**{t: types.ToolkitVertex for t in toolkits_creator.to_list()},
|
||||
**{t: types.WrapperVertex for t in wrapper_creator.to_list()},
|
||||
**{t: types.LLMVertex for t in llm_creator.to_list()},
|
||||
**{t: types.MemoryVertex for t in memory_creator.to_list()},
|
||||
**{t: types.EmbeddingVertex for t in embedding_creator.to_list()},
|
||||
**{t: types.VectorStoreVertex for t in vectorstore_creator.to_list()},
|
||||
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
||||
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
|
||||
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
from langflow.utils.constants import DIRECT_TYPES
|
||||
from langflow.interface.initialize import loading
|
||||
from langflow.interface.listing import ALL_TYPES_DICT
|
||||
from langflow.utils.logger import logger
|
||||
from langflow.utils.util import sync_to_async
|
||||
|
||||
|
||||
import contextlib
|
||||
import inspect
|
||||
import types
|
||||
import warnings
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
|
@ -25,6 +22,7 @@ class Vertex:
|
|||
self._parse_data()
|
||||
self._built_object = None
|
||||
self._built = False
|
||||
self.artifacts: Dict[str, Any] = {}
|
||||
|
||||
def _parse_data(self) -> None:
|
||||
self.data = self._data["data"]
|
||||
|
|
@ -45,6 +43,14 @@ class Vertex:
|
|||
for key, value in template_dicts.items()
|
||||
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"]
|
||||
self.vertex_type = (
|
||||
|
|
@ -60,6 +66,7 @@ class Vertex:
|
|||
break
|
||||
|
||||
def _build_params(self):
|
||||
# sourcery skip: merge-list-append, remove-redundant-if
|
||||
# Some params are required, some are optional
|
||||
# but most importantly, some params are python base classes
|
||||
# like str and others are LangChain objects like LLMChain, BasePromptTemplate
|
||||
|
|
@ -80,8 +87,19 @@ class Vertex:
|
|||
if isinstance(value, dict)
|
||||
}
|
||||
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():
|
||||
if key == "_type":
|
||||
if key == "_type" or not value.get("show"):
|
||||
continue
|
||||
# If the type is not transformable to a python base class
|
||||
# then we need to get the edge that connects to this node
|
||||
|
|
@ -92,45 +110,8 @@ class Vertex:
|
|||
file_path = value.get("file_path")
|
||||
|
||||
params[key] = file_path
|
||||
|
||||
elif value.get("type") not in DIRECT_TYPES:
|
||||
# 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
|
||||
|
||||
elif value.get("type") in ["str", "prompt"] and params.get(key) is None:
|
||||
params[key] = value.get("value")
|
||||
# Add _type to params
|
||||
self.params = params
|
||||
|
||||
|
|
@ -195,11 +176,17 @@ class Vertex:
|
|||
# and return the instance
|
||||
|
||||
try:
|
||||
self._built_object = loading.instantiate_class(
|
||||
result = loading.instantiate_class(
|
||||
node_type=self.vertex_type,
|
||||
base_type=self.base_type,
|
||||
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:
|
||||
raise ValueError(
|
||||
f"Error building node {self.vertex_type}: {str(exc)}"
|
||||
|
|
|
|||
|
|
@ -194,3 +194,8 @@ class PromptVertex(Vertex):
|
|||
|
||||
self._build()
|
||||
return self._built_object
|
||||
|
||||
|
||||
class OutputParserVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="output_parsers")
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ def import_by_type(_type: str, name: str) -> Any:
|
|||
"documentloaders": import_documentloader,
|
||||
"textsplitters": import_textsplitter,
|
||||
"utilities": import_utility,
|
||||
"output_parsers": import_output_parser,
|
||||
}
|
||||
if _type == "llms":
|
||||
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)
|
||||
|
||||
|
||||
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:
|
||||
"""Import chat llm from llm name"""
|
||||
return import_class(f"langchain.chat_models.{llm}")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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 agent as agent_module
|
||||
|
|
@ -8,6 +8,7 @@ from langchain.agents.agent_toolkits.base import BaseToolkit
|
|||
from langchain.agents.tools import BaseTool
|
||||
from langflow.interface.initialize.vector_store import vecstore_initializer
|
||||
|
||||
from langchain.schema import Document, BaseOutputParser
|
||||
from pydantic import ValidationError
|
||||
|
||||
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.toolkits.base import toolkits_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.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)
|
||||
elif base_type == "chains":
|
||||
return instantiate_chains(node_type, class_object, params)
|
||||
elif base_type == "output_parsers":
|
||||
return instantiate_output_parser(node_type, class_object, params)
|
||||
else:
|
||||
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):
|
||||
if "retriever" in params and hasattr(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)
|
||||
|
||||
# Now we go through input_variables
|
||||
# Check if they are in params, if so
|
||||
# get their values and set them
|
||||
format_kwargs = {}
|
||||
for input_variable in prompt.input_variables:
|
||||
if input_variable in params:
|
||||
input_value = params[input_variable]
|
||||
format_kwargs[input_variable] = input_value
|
||||
variable = params[input_variable]
|
||||
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:
|
||||
prompt = prompt.partial(**format_kwargs)
|
||||
if (
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from langflow.interface.tools.base import tool_creator
|
|||
from langflow.interface.utilities.base import utility_creator
|
||||
from langflow.interface.vector_store.base import vectorstore_creator
|
||||
from langflow.interface.wrappers.base import wrapper_creator
|
||||
from langflow.interface.output_parsers.base import output_parser_creator
|
||||
|
||||
|
||||
def get_type_dict():
|
||||
|
|
@ -28,6 +29,7 @@ def get_type_dict():
|
|||
"embeddings": embedding_creator.to_list(),
|
||||
"textSplitters": textsplitter_creator.to_list(),
|
||||
"utilities": utility_creator.to_list(),
|
||||
"outputParsers": output_parser_creator.to_list(),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
64
src/backend/langflow/interface/output_parsers/base.py
Normal file
64
src/backend/langflow/interface/output_parsers/base.py
Normal 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()
|
||||
|
|
@ -14,6 +14,23 @@ def build_langchain_object_with_caching(data_graph):
|
|||
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):
|
||||
"""
|
||||
Build langchain object from data_graph.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from langflow.interface.tools.base import tool_creator
|
|||
from langflow.interface.utilities.base import utility_creator
|
||||
from langflow.interface.vector_store.base import vectorstore_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.frontend_node.tools import CustomComponentNode
|
||||
|
|
@ -47,6 +48,7 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
|
|||
documentloader_creator,
|
||||
textsplitter_creator,
|
||||
utility_creator,
|
||||
output_parser_creator,
|
||||
]
|
||||
|
||||
all_types = {}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from pathlib import Path
|
|||
from langchain.schema import AgentAction
|
||||
import json
|
||||
from langflow.interface.run import (
|
||||
build_langchain_object_with_caching,
|
||||
build_sorted_vertices_with_caching,
|
||||
get_memory_key,
|
||||
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.
|
||||
"""
|
||||
# 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")
|
||||
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:
|
||||
# Raise user facing error
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class Settings(BaseSettings):
|
|||
toolkits: List[str] = []
|
||||
textsplitters: List[str] = []
|
||||
utilities: List[str] = []
|
||||
output_parsers: List[str] = []
|
||||
dev: bool = False
|
||||
database_url: str = "sqlite:///./langflow.db"
|
||||
cache: str = "InMemoryCache"
|
||||
|
|
@ -48,6 +49,10 @@ class Settings(BaseSettings):
|
|||
self.toolkits = new_settings.toolkits or []
|
||||
self.textsplitters = new_settings.textsplitters 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
|
||||
|
||||
def update_settings(self, **kwargs):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -25,6 +25,7 @@ import { nodeColors } from "../../../../utils";
|
|||
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
||||
import { PopUpContext } from "../../../../contexts/popUpContext";
|
||||
import ToggleShadComponent from "../../../../components/toggleShadComponent";
|
||||
import * as _ from "lodash";
|
||||
|
||||
export default function ParameterComponent({
|
||||
left,
|
||||
|
|
@ -36,6 +37,7 @@ export default function ParameterComponent({
|
|||
type,
|
||||
name = "",
|
||||
required = false,
|
||||
optionalHandle = null,
|
||||
}: ParameterComponentType) {
|
||||
const ref = useRef(null);
|
||||
const refHtml = useRef(null);
|
||||
|
|
@ -73,6 +75,7 @@ export default function ParameterComponent({
|
|||
return {
|
||||
...prev,
|
||||
[tabId]: {
|
||||
...prev[tabId],
|
||||
isPending: true,
|
||||
formKeysData: prev[tabId].formKeysData,
|
||||
},
|
||||
|
|
@ -85,7 +88,7 @@ export default function ParameterComponent({
|
|||
|
||||
refHtml.current = groupedObj.map((item, i) => (
|
||||
<span
|
||||
key={getRandomKeyByssmm()}
|
||||
key={getRandomKeyByssmm() + item.family + i}
|
||||
className={classNames(
|
||||
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>
|
||||
</div>
|
||||
{left &&
|
||||
(type === "str" ||
|
||||
((type === "str" ||
|
||||
type === "bool" ||
|
||||
type === "float" ||
|
||||
type === "code" ||
|
||||
type === "prompt" ||
|
||||
type === "file" ||
|
||||
type === "int") ? (
|
||||
type === "int") && !optionalHandle
|
||||
) ? (
|
||||
<></>
|
||||
) : (
|
||||
<ShadTooltip
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export default function GenericNode({
|
|||
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
|
||||
}}
|
||||
/>
|
||||
<div className="ml-2 truncate">
|
||||
<div className="ml-2 truncate flex-1">
|
||||
<ShadTooltip
|
||||
delayDuration={1500}
|
||||
content={data.node.display_name}
|
||||
|
|
@ -204,6 +204,7 @@ export default function GenericNode({
|
|||
data={data}
|
||||
color={
|
||||
nodeColors[types[data.node.template[t].type]] ??
|
||||
nodeColors[data.node.template[t].type] ??
|
||||
nodeColors.unknown
|
||||
}
|
||||
title={
|
||||
|
|
@ -214,11 +215,12 @@ export default function GenericNode({
|
|||
: toTitleCase(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}
|
||||
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}
|
||||
type={data.node.template[t].type}
|
||||
optionalHandle={data.node.template[t].input_types}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { useNodes } from "reactflow";
|
||||
import { ChatType } from "../../types/chat";
|
||||
import ChatTrigger from "./chatTrigger";
|
||||
|
|
@ -7,11 +7,13 @@ import BuildTrigger from "./buildTrigger";
|
|||
import { getBuildStatus } from "../../controllers/API";
|
||||
import { NodeType } from "../../types/flow";
|
||||
import FormModal from "../../modals/formModal";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import * as _ from "lodash";
|
||||
|
||||
export default function Chat({ flow }: ChatType) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [openForm, setOpenForm] = useState(false);
|
||||
const [isBuilt, setIsBuilt] = useState(false);
|
||||
const {tabsState} = useContext(TabsContext);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
|
|
@ -44,21 +46,19 @@ export default function Chat({ flow }: ChatType) {
|
|||
const prevNodesRef = useRef<any[] | undefined>();
|
||||
const nodes = useNodes();
|
||||
useEffect(() => {
|
||||
|
||||
const prevNodes = prevNodesRef.current;
|
||||
const currentNodes = nodes.map(
|
||||
(node: NodeType) => node.data.node.template
|
||||
(node: NodeType) => _.cloneDeep(node.data.node.template)
|
||||
);
|
||||
|
||||
if (
|
||||
prevNodes &&
|
||||
JSON.stringify(prevNodes) !== JSON.stringify(currentNodes)
|
||||
tabsState && tabsState[flow.id] && tabsState[flow.id].isPending
|
||||
&& JSON.stringify(prevNodes) !== JSON.stringify(currentNodes)
|
||||
) {
|
||||
setIsBuilt(false);
|
||||
}
|
||||
|
||||
prevNodesRef.current = currentNodes;
|
||||
}, [nodes]);
|
||||
}, [tabsState]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import GenericModal from "../../modals/genericModal";
|
|||
import { TypeModal } from "../../utils";
|
||||
import { INPUT_STYLE } from "../../constants";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { postValidatePrompt } from "../../controllers/API";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import * as _ from "lodash";
|
||||
|
||||
export default function PromptAreaComponent({
|
||||
setNodeClass,
|
||||
|
|
@ -14,8 +17,10 @@ export default function PromptAreaComponent({
|
|||
disabled,
|
||||
editNode = false,
|
||||
}: TextAreaComponentType) {
|
||||
const [myValue, setMyValue] = useState(value);
|
||||
const [myValue, setMyValue] = useState("");
|
||||
const { openPopUp } = useContext(PopUpContext);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setMyValue("");
|
||||
|
|
@ -24,8 +29,19 @@ export default function PromptAreaComponent({
|
|||
}, [disabled, onChange]);
|
||||
|
||||
useEffect(() => {
|
||||
setMyValue(value);
|
||||
}, [value]);
|
||||
if (value !== "" && myValue !== value && reactFlowInstance) { // only executed once
|
||||
setMyValue(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 (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -8,11 +8,6 @@ export default function ToggleShadComponent({
|
|||
disabled,
|
||||
size,
|
||||
}: ToggleComponentType) {
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setEnabled(false);
|
||||
}
|
||||
}, [disabled, setEnabled]);
|
||||
let scaleX, scaleY;
|
||||
switch (size) {
|
||||
case "small":
|
||||
|
|
@ -32,11 +27,12 @@ export default function ToggleShadComponent({
|
|||
scaleY = 1;
|
||||
}
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed " : ""}>
|
||||
<Switch
|
||||
style={{
|
||||
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
|
||||
}}
|
||||
disabled={disabled}
|
||||
className="data-[state=unchecked]:bg-slate-500"
|
||||
checked={enabled}
|
||||
onCheckedChange={(x: boolean) => {
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ export const DESCRIPTIONS: string[] = [
|
|||
"Your Hub for Text Generation.",
|
||||
"Promptly Ingenious!",
|
||||
"Building Linguistic Labyrinths.",
|
||||
"LangFlow: Create, Chain, Communicate.",
|
||||
"Create, Chain, Communicate.",
|
||||
"Connect the Dots, Craft Language.",
|
||||
"Interactive Language Weaving.",
|
||||
"Generate, Innovate, Communicate.",
|
||||
|
|
@ -183,6 +183,46 @@ export const DESCRIPTIONS: string[] = [
|
|||
"Nurture NLP Nodes Here.",
|
||||
"Conversational Cartography Unlocked.",
|
||||
"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",
|
||||
"tiny",
|
||||
"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
|
||||
* @constant
|
||||
|
|
@ -406,6 +487,38 @@ export const NOUNS: string[] = [
|
|||
"wright",
|
||||
"yalow",
|
||||
"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",
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -189,26 +189,28 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
|
||||
function processFlowEdges(flow) {
|
||||
if(!flow.data || !flow.data.edges) return;
|
||||
if (!flow.data || !flow.data.edges) return;
|
||||
flow.data.edges.forEach((edge) => {
|
||||
edge.className = "";
|
||||
edge.style = { stroke: "#555555" };
|
||||
});
|
||||
}
|
||||
function updateDisplay_name(node:NodeType,template:APIClassType) {
|
||||
node.data.node.display_name = template["display_name"]?template["display_name"]:node.data.type;
|
||||
function updateDisplay_name(node: NodeType, template: APIClassType) {
|
||||
node.data.node.display_name = template["display_name"]
|
||||
? template["display_name"]
|
||||
: node.data.type;
|
||||
}
|
||||
|
||||
function processFlowNodes(flow) {
|
||||
if(!flow.data || !flow.data.nodes) return;
|
||||
flow.data.nodes.forEach((node:NodeType) => {
|
||||
if (!flow.data || !flow.data.nodes) return;
|
||||
flow.data.nodes.forEach((node: NodeType) => {
|
||||
const template = templates[node.data.type];
|
||||
if (!template) {
|
||||
setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
||||
return;
|
||||
}
|
||||
if (Object.keys(template["template"]).length > 0) {
|
||||
updateDisplay_name(node,template);
|
||||
updateDisplay_name(node, template);
|
||||
updateNodeBaseClasses(node, template);
|
||||
updateNodeEdges(flow, 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"];
|
||||
}
|
||||
|
||||
function updateNodeEdges(flow:FlowType, node:NodeType,template:APIClassType) {
|
||||
function updateNodeEdges(
|
||||
flow: FlowType,
|
||||
node: NodeType,
|
||||
template: APIClassType
|
||||
) {
|
||||
flow.data.edges.forEach((edge) => {
|
||||
if (edge.source === node.id) {
|
||||
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"];
|
||||
}
|
||||
|
||||
function updateNodeTemplate(node:NodeType,template:APIClassType) {
|
||||
function updateNodeTemplate(node: NodeType, template: APIClassType) {
|
||||
node.data.node.template = updateTemplate(
|
||||
template["template"] as unknown as APITemplateType,
|
||||
node.data.node.template as APITemplateType
|
||||
|
|
@ -302,28 +308,39 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
* 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.
|
||||
*/
|
||||
function uploadFlow(newProject?: boolean) {
|
||||
// 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
|
||||
if ((e.target as HTMLInputElement).files[0].type === "application/json") {
|
||||
// get the file from the file input
|
||||
const file = (e.target as HTMLInputElement).files[0];
|
||||
// read the file as text
|
||||
file.text().then((text) => {
|
||||
// parse the text into a JSON object
|
||||
let flow: FlowType = JSON.parse(text);
|
||||
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);
|
||||
});
|
||||
}
|
||||
};
|
||||
// trigger the file input click event to open the file dialog
|
||||
input.click();
|
||||
addFlow(flow, newProject);
|
||||
});
|
||||
} else {
|
||||
// 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
|
||||
if (
|
||||
(e.target as HTMLInputElement).files[0].type === "application/json"
|
||||
) {
|
||||
// get the file from the file input
|
||||
const currentfile = (e.target as HTMLInputElement).files[0];
|
||||
// read the file as text
|
||||
currentfile.text().then((text) => {
|
||||
// parse the text into a JSON object
|
||||
let flow: FlowType = JSON.parse(text);
|
||||
|
||||
addFlow(flow, newProject);
|
||||
});
|
||||
}
|
||||
};
|
||||
// trigger the file input click event to open the file dialog
|
||||
input.click();
|
||||
}
|
||||
}
|
||||
|
||||
function uploadFlows() {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export default function ChatInput({
|
|||
lockChat
|
||||
? " 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",
|
||||
"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
|
||||
)}
|
||||
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()}>
|
||||
{lockChat ? (
|
||||
<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"
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
|||
</div>
|
||||
|
||||
<SyntaxHighlighter
|
||||
className=" w-[570px]"
|
||||
className=" w-[47vw]"
|
||||
language={language}
|
||||
style={oneDark}
|
||||
customStyle={{ margin: 0 }}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export default function ChatMessage({
|
|||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeMathjax]}
|
||||
className="markdown prose dark:prose-invert text-primary"
|
||||
className="markdown prose dark:prose-invert text-primary max-w-full"
|
||||
components={{
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
if (children.length) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,13 @@ import { alertContext } from "../../contexts/alertContext";
|
|||
import { classNames, validateNodes } from "../../utils";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
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 { ChatMessageType } from "../../types/chat";
|
||||
import ChatInput from "./chatInput";
|
||||
|
|
@ -30,6 +36,14 @@ import {
|
|||
import { Textarea } from "../../components/ui/textarea";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
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({
|
||||
flow,
|
||||
|
|
@ -40,18 +54,27 @@ export default function FormModal({
|
|||
setOpen: Function;
|
||||
flow: FlowType;
|
||||
}) {
|
||||
const [chatValue, setChatValue] = useState("");
|
||||
const [keysValue, setKeysValue] = useState([]);
|
||||
const { tabsState, setTabsState } = useContext(TabsContext);
|
||||
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 { reactFlowInstance } = useContext(typesContext);
|
||||
const { setErrorData, setNoticeData } = useContext(alertContext);
|
||||
const { tabsState } = useContext(TabsContext);
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
const [lockChat, setLockChat] = useState(false);
|
||||
const isOpen = useRef(open);
|
||||
const messagesRef = useRef(null);
|
||||
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(() => {
|
||||
if (messagesRef.current) {
|
||||
|
|
@ -64,12 +87,6 @@ export default function FormModal({
|
|||
}, [open]);
|
||||
useEffect(() => {
|
||||
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]);
|
||||
|
||||
var isStream = false;
|
||||
|
|
@ -166,13 +183,13 @@ export default function FormModal({
|
|||
chatItem.files
|
||||
? {
|
||||
isSend: !chatItem.is_bot,
|
||||
message: chatItem.is_bot ? chatItem.message : formatMessage(chatItem.message),
|
||||
message: formatMessage(chatItem.message),
|
||||
thought: chatItem.intermediate_steps,
|
||||
files: chatItem.files,
|
||||
}
|
||||
: {
|
||||
isSend: !chatItem.is_bot,
|
||||
message: chatItem.is_bot ? chatItem.message : formatMessage(chatItem.message),
|
||||
message: formatMessage(chatItem.message),
|
||||
thought: chatItem.intermediate_steps,
|
||||
}
|
||||
);
|
||||
|
|
@ -305,6 +322,7 @@ export default function FormModal({
|
|||
}, [open]);
|
||||
function formatMessage(inputs: any): string {
|
||||
if (inputs) {
|
||||
if (typeof inputs == "string") return inputs;
|
||||
// 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 ": "
|
||||
let message = "";
|
||||
|
|
@ -325,13 +343,7 @@ export default function FormModal({
|
|||
let nodeValidationErrors = validateNodes(reactFlowInstance);
|
||||
if (nodeValidationErrors.length === 0) {
|
||||
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 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;
|
||||
}, {});
|
||||
let inputs = tabsState[id.current].formKeysData.input_keys;
|
||||
setChatValue("");
|
||||
const message = formatMessage(inputs);
|
||||
addChatHistory(message, true);
|
||||
|
|
@ -342,6 +354,11 @@ export default function FormModal({
|
|||
name: flow.name,
|
||||
description: flow.description,
|
||||
});
|
||||
setTabsState((old) => {
|
||||
let newTabsState = _.cloneDeep(old);
|
||||
newTabsState[id.current].formKeysData.input_keys[chatKey] = "";
|
||||
return newTabsState;
|
||||
});
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Oops! Looks like you missed some required information:",
|
||||
|
|
@ -365,13 +382,12 @@ export default function FormModal({
|
|||
setOpen(x);
|
||||
}
|
||||
|
||||
function handleOnCheckedChange(checked: boolean, index: number) {
|
||||
function handleOnCheckedChange(checked: boolean, i: string) {
|
||||
if (checked === true) {
|
||||
setChatKey(index);
|
||||
setChatKey(i);
|
||||
setChatValue(tabsState[flow.id].formKeysData.input_keys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chatHistory)
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setModalOpen}>
|
||||
<DialogTrigger className="hidden"></DialogTrigger>
|
||||
|
|
@ -388,47 +404,90 @@ export default function FormModal({
|
|||
<DialogDescription>{CHAT_FORM_DIALOG_SUBTITLE}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<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="flex py-2">
|
||||
<Variable className="w-6 h-6 pe-1 text-gray-700 stroke-2 dark:text-slate-200"></Variable>
|
||||
<span className="text-md font-semibold text-gray-800 dark:text-white">
|
||||
Input Variables
|
||||
</span>
|
||||
<div className="flex h-[80vh] w-full mt-2 ">
|
||||
<div className="w-2/5 h-full overflow-auto scrollbar-hide flex flex-col justify-start mr-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex py-2 items-center">
|
||||
<Variable className="w-4 h-4 mr-1 text-primary"></Variable>
|
||||
<span className="text-sm font-semibold text-primary">
|
||||
Input Variables
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex mr-2.5 py-2 items-center">
|
||||
<span className="text-sm font-semibold text-primary">
|
||||
Chat Input
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
{tabsState[id.current].formKeysData.input_keys.map((i, k) => (
|
||||
<AccordionItem key={k} value={i}>
|
||||
<AccordionTrigger><Badge variant="gray" size="md">{i}</Badge></AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="p-1 flex flex-col gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="airplane-mode" className="-mt-1">From Chat</Label>
|
||||
<ToggleShadComponent
|
||||
enabled={chatKey === k}
|
||||
setEnabled={(value) =>
|
||||
handleOnCheckedChange(value, k)
|
||||
}
|
||||
size="small"
|
||||
disabled={false}
|
||||
/>
|
||||
</div>
|
||||
<Textarea
|
||||
value={keysValue[k]}
|
||||
onChange={(e) =>
|
||||
setKeysValue((old) => [...old.slice(0, k), e.target.value, ...old.slice(k + 1)])
|
||||
}
|
||||
disabled={chatKey === k}
|
||||
placeholder="Enter text..."
|
||||
></Textarea>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
<Accordion type="multiple" className="w-full">
|
||||
{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
|
||||
enabled={chatKey === i}
|
||||
setEnabled={(value) =>
|
||||
handleOnCheckedChange(value, i)
|
||||
}
|
||||
size="small"
|
||||
disabled={tabsState[
|
||||
id.current
|
||||
].formKeysData.handle_keys.some((t) => t === i)}
|
||||
/>
|
||||
</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
|
||||
value={
|
||||
tabsState[id.current].formKeysData.input_keys[i]
|
||||
}
|
||||
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..."
|
||||
></Textarea>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
{tabsState[id.current].formKeysData.memory_keys.map((i, k) => (
|
||||
<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="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
|
||||
</div>
|
||||
</AccordionItem>
|
||||
|
|
@ -437,14 +496,19 @@ export default function FormModal({
|
|||
</div>
|
||||
<div className="w-full">
|
||||
<div className="flex flex-col rounded-md border bg-muted w-full h-full relative">
|
||||
<div className="absolute right-3 top-3 z-50">
|
||||
<button disabled={lockChat} onClick={() => clearChat()}>
|
||||
<Eraser
|
||||
className={classNames("h-5 w-5", lockChat ? "text-primary animate-pulse" : "text-primary hover:text-gray-600")}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="absolute right-3 top-3 z-50">
|
||||
<button disabled={lockChat} onClick={() => clearChat()}>
|
||||
<Eraser
|
||||
className={classNames(
|
||||
"h-5 w-5",
|
||||
lockChat
|
||||
? "text-primary animate-pulse"
|
||||
: "text-primary hover:text-gray-600"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
ref={messagesRef}
|
||||
className="w-full h-full flex-col flex items-center overflow-scroll scrollbar-hide"
|
||||
|
|
@ -486,7 +550,16 @@ export default function FormModal({
|
|||
chatValue={chatValue}
|
||||
lockChat={lockChat}
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ const nodeTypes = {
|
|||
export default function Page({ flow }: { flow: FlowType }) {
|
||||
let {
|
||||
updateFlow,
|
||||
uploadFlow,
|
||||
disableCopyPaste,
|
||||
addFlow,
|
||||
getNodeId,
|
||||
|
|
@ -151,6 +152,7 @@ export default function Page({ flow }: { flow: FlowType }) {
|
|||
return {
|
||||
...prev,
|
||||
[tabId]: {
|
||||
...prev[tabId],
|
||||
isPending: true,
|
||||
},
|
||||
};
|
||||
|
|
@ -166,6 +168,7 @@ export default function Page({ flow }: { flow: FlowType }) {
|
|||
return {
|
||||
...prev,
|
||||
[tabId]: {
|
||||
...prev[tabId],
|
||||
isPending: true,
|
||||
},
|
||||
};
|
||||
|
|
@ -217,62 +220,72 @@ export default function Page({ flow }: { flow: FlowType }) {
|
|||
|
||||
const onDragOver = useCallback((event: React.DragEvent) => {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = "move";
|
||||
if (event.dataTransfer.types.some((t) => t === "nodedata")) {
|
||||
event.dataTransfer.dropEffect = "move";
|
||||
} else {
|
||||
event.dataTransfer.dropEffect = "copy";
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onDrop = useCallback(
|
||||
(event: React.DragEvent) => {
|
||||
event.preventDefault();
|
||||
takeSnapshot();
|
||||
if (event.dataTransfer.types.some((t) => t === "nodedata")) {
|
||||
takeSnapshot();
|
||||
|
||||
// Get the current bounds of the ReactFlow wrapper element
|
||||
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
// Get the current bounds of the ReactFlow wrapper element
|
||||
const reactflowBounds =
|
||||
reactFlowWrapper.current.getBoundingClientRect();
|
||||
|
||||
// Extract the data from the drag event and parse it as a JSON object
|
||||
let data: { type: string; node?: APIClassType } = JSON.parse(
|
||||
event.dataTransfer.getData("json")
|
||||
);
|
||||
// Extract the data from the drag event and parse it as a JSON object
|
||||
let data: { type: string; node?: APIClassType } = JSON.parse(
|
||||
event.dataTransfer.getData("nodedata")
|
||||
);
|
||||
|
||||
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
|
||||
// Calculate the position where the node should be created
|
||||
const position = reactFlowInstance.project({
|
||||
x: event.clientX - reactflowBounds.left,
|
||||
y: event.clientY - reactflowBounds.top,
|
||||
});
|
||||
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
|
||||
// Calculate the position where the node should be created
|
||||
const position = reactFlowInstance.project({
|
||||
x: event.clientX - reactflowBounds.left,
|
||||
y: event.clientY - reactflowBounds.top,
|
||||
});
|
||||
|
||||
// Generate a unique node ID
|
||||
let { type } = data;
|
||||
let newId = getNodeId(type);
|
||||
let newNode: NodeType;
|
||||
// Generate a unique node ID
|
||||
let { type } = data;
|
||||
let newId = getNodeId(type);
|
||||
let newNode: NodeType;
|
||||
|
||||
if (data.type !== "groupNode") {
|
||||
// Create a new node object
|
||||
newNode = {
|
||||
id: newId,
|
||||
type: "genericNode",
|
||||
position,
|
||||
data: {
|
||||
...data,
|
||||
if (data.type !== "groupNode") {
|
||||
// Create a new node object
|
||||
newNode = {
|
||||
id: newId,
|
||||
value: null,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// Create a new node object
|
||||
newNode = {
|
||||
id: newId,
|
||||
type: "genericNode",
|
||||
position,
|
||||
data: {
|
||||
...data,
|
||||
type: "genericNode",
|
||||
position,
|
||||
data: {
|
||||
...data,
|
||||
id: newId,
|
||||
value: null,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// Create a new node object
|
||||
newNode = {
|
||||
id: newId,
|
||||
value: null,
|
||||
},
|
||||
};
|
||||
type: "genericNode",
|
||||
position,
|
||||
data: {
|
||||
...data,
|
||||
id: newId,
|
||||
value: null,
|
||||
},
|
||||
};
|
||||
|
||||
// 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));
|
||||
} else if (event.dataTransfer.types.some((t) => t === "Files")) {
|
||||
takeSnapshot();
|
||||
uploadFlow(false, event.dataTransfer.files.item(0));
|
||||
}
|
||||
setNodes((nds) => nds.concat(newNode));
|
||||
},
|
||||
// Specify dependencies for useCallback
|
||||
[getNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default function ExtraSidebar() {
|
|||
crt.classList.add("cursor-grabbing");
|
||||
document.body.appendChild(crt);
|
||||
event.dataTransfer.setDragImage(crt, 0, 0);
|
||||
event.dataTransfer.setData("json", JSON.stringify(data));
|
||||
event.dataTransfer.setData("nodedata", JSON.stringify(data));
|
||||
}
|
||||
|
||||
function handleSearchInput(e: string) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export type APIClassType = {
|
|||
description: string;
|
||||
template: APITemplateType;
|
||||
display_name: string;
|
||||
input_types?: Array<string>;
|
||||
[key: string]: Array<string> | string | APITemplateType;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export type ParameterComponentType = {
|
|||
name?: string;
|
||||
tooltipTitle: string;
|
||||
dataContext?: typesContextType;
|
||||
optionalHandle?: Array<String>;
|
||||
};
|
||||
export type InputListComponentType = {
|
||||
value: string[];
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export type TabsContextType = {
|
|||
downloadFlow: (flow: FlowType) => void;
|
||||
downloadFlows: () => void;
|
||||
uploadFlows: () => void;
|
||||
uploadFlow: (newFlow?: boolean) => void;
|
||||
uploadFlow: (newFlow?: boolean, file?: File) => void;
|
||||
hardReset: () => void;
|
||||
//disable CopyPaste
|
||||
disableCopyPaste: boolean;
|
||||
|
|
@ -33,6 +33,6 @@ export type TabsContextType = {
|
|||
export type TabsState = {
|
||||
[key: string]: {
|
||||
isPending: boolean;
|
||||
formKeysData: {input_keys?: Array<string>, memory_keys?: Array<string>};
|
||||
formKeysData: {input_keys?: Object, memory_keys?: Array<string>, handle_keys?: Array<string>};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import { twMerge } from "tailwind-merge";
|
|||
import { ADJECTIVES, DESCRIPTIONS, NOUNS } from "./constants";
|
||||
import { ComponentType, SVGProps } from "react";
|
||||
import {
|
||||
Compass,
|
||||
Cpu,
|
||||
Fingerprint,
|
||||
Gift,
|
||||
|
|
@ -139,6 +140,8 @@ export const nodeColors: { [char: string]: string } = {
|
|||
toolkits: "#DB2C2C",
|
||||
wrappers: "#E6277A",
|
||||
utilities: "#31A3CC",
|
||||
output_parsers: "#E6A627",
|
||||
str: "#049524",
|
||||
unknown: "#9CA3AF",
|
||||
};
|
||||
|
||||
|
|
@ -158,6 +161,7 @@ export const nodeNames: { [char: string]: string } = {
|
|||
wrappers: "Wrappers",
|
||||
textsplitters: "Text Splitters",
|
||||
utilities: "Utilities",
|
||||
output_parsers: "Output Parsers",
|
||||
unknown: "Unknown",
|
||||
};
|
||||
|
||||
|
|
@ -363,6 +367,9 @@ export const nodeIconsLucide: {
|
|||
utilities: Wand2 as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
output_parsers: Compass as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
unknown: HelpCircle as React.ForwardRefExoticComponent<
|
||||
ComponentType<SVGProps<SVGSVGElement>>
|
||||
>,
|
||||
|
|
@ -625,11 +632,11 @@ export function isValidConnection(
|
|||
reactFlowInstance: ReactFlowInstance
|
||||
) {
|
||||
if (
|
||||
sourceHandle.split("|")[0] === targetHandle.split("|")[0] ||
|
||||
targetHandle.split("|")[0].split(";").some((n) => n === sourceHandle.split("|")[0]) ||
|
||||
sourceHandle
|
||||
.split("|")
|
||||
.slice(2)
|
||||
.some((t) => t === targetHandle.split("|")[0]) ||
|
||||
.some((t) => targetHandle.split("|")[0].split(";").some((n) => n === t)) ||
|
||||
targetHandle.split("|")[0] === "str"
|
||||
) {
|
||||
let targetNode = reactFlowInstance.getNode(target).data.node;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue