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
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

View file

@ -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:

View file

@ -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()

View file

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

View file

@ -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:

View file

@ -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]:

View file

@ -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()},
}

View file

@ -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)}"

View file

@ -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")

View file

@ -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}")

View file

@ -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):

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.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(),
}

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()
@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.

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.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 = {}

View file

@ -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

View file

@ -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):

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 { 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

View file

@ -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}
/>
) : (
<></>

View file

@ -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 (
<>

View file

@ -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

View file

@ -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) => {

View file

@ -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",
];
/**

View file

@ -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() {

View file

@ -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"
/>
) : (

View file

@ -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 }}

View file

@ -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) {

View file

@ -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>

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 }) {
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]

View file

@ -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) {

View file

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

View file

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

View file

@ -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>};
};
};

View file

@ -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;