refactor: Update langflow custom imports and base classes

This commit updates the langflow custom imports and base classes in the code. It adds the "Component" import and base class to the langflow custom __init__.py file. It also updates the langflow template __init__.py file to include the "Input", "Output", "FrontendNode", and "Template" imports and base classes. Additionally, it modifies the langflow base ChatComponent class to inherit from the Component class. These changes improve the organization and functionality of the langflow custom and template modules.

Note: The commit message has been generated based on the provided code changes and recent commits.
This commit is contained in:
ogabrielluiz 2024-06-02 20:35:59 -03:00
commit 7fb5644a87
18 changed files with 6760 additions and 6719 deletions

View file

@ -20,6 +20,7 @@ from langflow.api.v1.schemas import (
UploadFileResponse, UploadFileResponse,
) )
from langflow.custom import CustomComponent from langflow.custom import CustomComponent
from langflow.custom.custom_component.component import Component
from langflow.custom.utils import build_custom_component_template from langflow.custom.utils import build_custom_component_template
from langflow.graph.graph.base import Graph from langflow.graph.graph.base import Graph
from langflow.graph.schema import RunOutputs from langflow.graph.schema import RunOutputs
@ -475,7 +476,7 @@ async def custom_component(
raw_code: CustomComponentRequest, raw_code: CustomComponentRequest,
user: User = Depends(get_current_active_user), user: User = Depends(get_current_active_user),
): ):
component = CustomComponent(code=raw_code.code) component = Component(code=raw_code.code)
built_frontend_node, _ = build_custom_component_template(component, user_id=user.id) built_frontend_node, _ = build_custom_component_template(component, user_id=user.id)

View file

@ -1,13 +1,13 @@
from typing import Optional, Union from typing import Optional, Union
from langflow.custom import CustomComponent from langflow.custom import Component
from langflow.field_typing import Text from langflow.field_typing import Text
from langflow.helpers.record import records_to_text from langflow.helpers.record import records_to_text
from langflow.memory import store_message from langflow.memory import store_message
from langflow.schema import Record from langflow.schema import Record
class ChatComponent(CustomComponent): class ChatComponent(Component):
display_name = "Chat Component" display_name = "Chat Component"
description = "Use as base for chat components." description = "Use as base for chat components."

View file

@ -1,8 +1,7 @@
from typing import Optional, Union
from langflow.base.io.chat import ChatComponent from langflow.base.io.chat import ChatComponent
from langflow.field_typing import Text from langflow.field_typing import Text
from langflow.schema import Record from langflow.schema import Record
from langflow.template import Input, Output
class ChatInput(ChatComponent): class ChatInput(ChatComponent):
@ -10,28 +9,32 @@ class ChatInput(ChatComponent):
description = "Get chat inputs from the Playground." description = "Get chat inputs from the Playground."
icon = "ChatInput" icon = "ChatInput"
def build_config(self): inputs = [
build_config = super().build_config() Input(name="input_value", type=str, display_name="Message", multiline=True, input_types=[]),
build_config["input_value"] = { Input(name="sender", type=str, display_name="Sender Type", options=["Machine", "User"]),
"input_types": [], Input(name="sender_name", type=str, display_name="Sender Name"),
"display_name": "Message", Input(name="session_id", type=str, display_name="Session ID"),
"multiline": True, ]
outputs = [
Output(name="Message", method="text_response"),
Output(name="Record", method="record_response"),
]
def text_response(self) -> Text:
result = self.message
if self.session_id and isinstance(result, (Record, str)):
self.store_message(result, self.session_id, self.sender, self.sender_name)
return result
def record_response(self) -> Record:
record = Record(
data={
"message": self.message,
"sender": self.sender,
"sender_name": self.sender_name,
"session_id": self.session_id,
} }
return build_config
def build(
self,
sender: Optional[str] = "User",
sender_name: Optional[str] = "User",
input_value: Optional[str] = None,
session_id: Optional[str] = None,
return_record: Optional[bool] = False,
) -> Union[Text, Record]:
return super().build_no_record(
sender=sender,
sender_name=sender_name,
input_value=input_value,
session_id=session_id,
return_record=return_record,
) )
if self.session_id and isinstance(record, (Record, str)):
self.store_message(record, self.session_id, self.sender, self.sender_name)
return record

View file

@ -1,3 +1,4 @@
from langflow.custom.custom_component import CustomComponent from langflow.custom.custom_component import CustomComponent
from langflow.custom.custom_component.component import Component
__all__ = ["CustomComponent"] __all__ = ["CustomComponent", "Component"]

View file

@ -0,0 +1,94 @@
import operator
import warnings
from typing import Any, ClassVar, Optional
from cachetools import TTLCache, cachedmethod
from fastapi import HTTPException
from langflow.custom.attributes import ATTR_FUNC_MAPPING
from langflow.custom.code_parser import CodeParser
from langflow.custom.eval import eval_custom_component_code
from langflow.utils import validate
class ComponentCodeNullError(HTTPException):
pass
class ComponentFunctionEntrypointNameNullError(HTTPException):
pass
class BaseComponent:
ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided."
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = "The name of the entrypoint function must be provided."
code: Optional[str] = None
_function_entrypoint_name: str = "build"
field_config: dict = {}
_user_id: Optional[str]
def __init__(self, **data):
self.cache = TTLCache(maxsize=1024, ttl=60)
for key, value in data.items():
if key == "user_id":
setattr(self, "_user_id", value)
else:
setattr(self, key, value)
def __setattr__(self, key, value):
if key == "_user_id" and hasattr(self, "_user_id"):
warnings.warn("user_id is immutable and cannot be changed.")
super().__setattr__(key, value)
@cachedmethod(cache=operator.attrgetter("cache"))
def get_code_tree(self, code: str):
parser = CodeParser(code)
return parser.parse_code()
def get_function(self):
if not self.code:
raise ComponentCodeNullError(
status_code=400,
detail={"error": self.ERROR_CODE_NULL, "traceback": ""},
)
if not self._function_entrypoint_name:
raise ComponentFunctionEntrypointNameNullError(
status_code=400,
detail={
"error": self.ERROR_FUNCTION_ENTRYPOINT_NAME_NULL,
"traceback": "",
},
)
return validate.create_function(self.code, self._function_entrypoint_name)
def build_template_config(self) -> dict:
"""
Builds the template configuration for the custom component.
Returns:
A dictionary representing the template configuration.
"""
if not self.code:
return {}
cc_class = eval_custom_component_code(self.code)
component_instance = cc_class()
template_config = {}
for attribute, func in ATTR_FUNC_MAPPING.items():
if hasattr(component_instance, attribute):
value = getattr(component_instance, attribute)
if value is not None:
template_config[attribute] = func(value=value)
for key in template_config.copy():
if key not in ATTR_FUNC_MAPPING.keys():
template_config.pop(key, None)
return template_config
def build(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError

View file

@ -1,94 +1,17 @@
import operator from typing import ClassVar, List, Optional
import warnings
from typing import Any, ClassVar, Optional
from cachetools import TTLCache, cachedmethod from langflow.template.field.base import Input, Output
from fastapi import HTTPException
from langflow.custom.attributes import ATTR_FUNC_MAPPING from .custom_component import CustomComponent
from langflow.custom.code_parser import CodeParser
from langflow.custom.eval import eval_custom_component_code
from langflow.utils import validate
class ComponentCodeNullError(HTTPException): class Component(CustomComponent):
pass inputs: Optional[List[Input]] = None
outputs: Optional[List[Output]] = None
code_class_base_inheritance: ClassVar[str] = "Component"
def set_attributes(self, params: dict):
class ComponentFunctionEntrypointNameNullError(HTTPException): for key, value in params.items():
pass if key in self.__dict__:
raise ValueError(f"Key {key} already exists in {self.__class__.__name__}")
class Component:
ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided."
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = "The name of the entrypoint function must be provided."
code: Optional[str] = None
_function_entrypoint_name: str = "build"
field_config: dict = {}
_user_id: Optional[str]
def __init__(self, **data):
self.cache = TTLCache(maxsize=1024, ttl=60)
for key, value in data.items():
if key == "user_id":
setattr(self, "_user_id", value)
else:
setattr(self, key, value) setattr(self, key, value)
def __setattr__(self, key, value):
if key == "_user_id" and hasattr(self, "_user_id"):
warnings.warn("user_id is immutable and cannot be changed.")
super().__setattr__(key, value)
@cachedmethod(cache=operator.attrgetter("cache"))
def get_code_tree(self, code: str):
parser = CodeParser(code)
return parser.parse_code()
def get_function(self):
if not self.code:
raise ComponentCodeNullError(
status_code=400,
detail={"error": self.ERROR_CODE_NULL, "traceback": ""},
)
if not self._function_entrypoint_name:
raise ComponentFunctionEntrypointNameNullError(
status_code=400,
detail={
"error": self.ERROR_FUNCTION_ENTRYPOINT_NAME_NULL,
"traceback": "",
},
)
return validate.create_function(self.code, self._function_entrypoint_name)
def build_template_config(self) -> dict:
"""
Builds the template configuration for the custom component.
Returns:
A dictionary representing the template configuration.
"""
if not self.code:
return {}
cc_class = eval_custom_component_code(self.code)
component_instance = cc_class()
template_config = {}
for attribute, func in ATTR_FUNC_MAPPING.items():
if hasattr(component_instance, attribute):
value = getattr(component_instance, attribute)
if value is not None:
template_config[attribute] = func(value=value)
for key in template_config.copy():
if key not in ATTR_FUNC_MAPPING.keys():
template_config.pop(key, None)
return template_config
def build(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError

View file

@ -12,13 +12,12 @@ from langflow.custom.code_parser.utils import (
extract_inner_type_from_generic_alias, extract_inner_type_from_generic_alias,
extract_union_types_from_generic_alias, extract_union_types_from_generic_alias,
) )
from langflow.custom.custom_component.component import Component from langflow.custom.custom_component.base_component import BaseComponent
from langflow.helpers.flow import list_flows, load_flow, run_flow from langflow.helpers.flow import list_flows, load_flow, run_flow
from langflow.schema import Record from langflow.schema import Record
from langflow.schema.dotdict import dotdict from langflow.schema.dotdict import dotdict
from langflow.services.deps import get_storage_service, get_variable_service, session_scope from langflow.services.deps import get_storage_service, get_variable_service, session_scope
from langflow.services.storage.service import StorageService from langflow.services.storage.service import StorageService
from langflow.template.field.base import Input, Output
from langflow.utils import validate from langflow.utils import validate
if TYPE_CHECKING: if TYPE_CHECKING:
@ -27,7 +26,7 @@ if TYPE_CHECKING:
from langflow.services.storage.service import StorageService from langflow.services.storage.service import StorageService
class CustomComponent(Component): class CustomComponent(BaseComponent):
""" """
Represents a custom component in Langflow. Represents a custom component in Langflow.
@ -80,9 +79,6 @@ class CustomComponent(Component):
"""The status of the component. This is displayed on the frontend. Defaults to None.""" """The status of the component. This is displayed on the frontend. Defaults to None."""
_flows_records: Optional[List[Record]] = None _flows_records: Optional[List[Record]] = None
inputs: Optional[List[Input]] = None
outputs: Optional[List[Output]] = None
def build_inputs(self, user_id: Optional[Union[str, UUID]] = None): def build_inputs(self, user_id: Optional[Union[str, UUID]] = None):
""" """
Builds the inputs for the custom component. Builds the inputs for the custom component.
@ -100,12 +96,6 @@ class CustomComponent(Component):
build_config = {_input.name: _input.model_dump(by_alias=True, exclude_none=True) for _input in self.inputs} build_config = {_input.name: _input.model_dump(by_alias=True, exclude_none=True) for _input in self.inputs}
return build_config return build_config
def set_attributes(self, params: dict):
for key, value in params.items():
if key in self.__dict__:
raise ValueError(f"Key {key} already exists in {self.__class__.__name__}")
setattr(self, key, value)
def update_state(self, name: str, value: Any): def update_state(self, name: str, value: Any):
if not self.vertex: if not self.vertex:
raise ValueError("Vertex is not set") raise ValueError("Vertex is not set")
@ -493,4 +483,3 @@ class CustomComponent(Component):
Any: The result of the build process. Any: The result of the build process.
""" """
raise NotImplementedError raise NotImplementedError
raise NotImplementedError

View file

@ -12,6 +12,7 @@ from pydantic import BaseModel
from langflow.custom import CustomComponent from langflow.custom import CustomComponent
from langflow.custom.code_parser.utils import extract_inner_type from langflow.custom.code_parser.utils import extract_inner_type
from langflow.custom.custom_component.component import Component
from langflow.custom.directory_reader.utils import ( from langflow.custom.directory_reader.utils import (
abuild_custom_component_list_from_path, abuild_custom_component_list_from_path,
build_custom_component_list_from_path, build_custom_component_list_from_path,
@ -24,7 +25,7 @@ from langflow.field_typing.range_spec import RangeSpec
from langflow.helpers.custom import format_type from langflow.helpers.custom import format_type
from langflow.schema import dotdict from langflow.schema import dotdict
from langflow.template.field.base import Input from langflow.template.field.base import Input
from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode from langflow.template.frontend_node.custom_components import ComponentFrontendNode, CustomComponentFrontendNode
from langflow.utils import validate from langflow.utils import validate
from langflow.utils.util import get_base_classes from langflow.utils.util import get_base_classes
@ -325,7 +326,7 @@ def build_custom_component_template_from_inputs(
custom_component: CustomComponent, user_id: Optional[Union[str, UUID]] = None custom_component: CustomComponent, user_id: Optional[Union[str, UUID]] = None
): ):
# The List of Inputs fills the role of the build_config and the entrypoint_args # The List of Inputs fills the role of the build_config and the entrypoint_args
frontend_node = CustomComponentFrontendNode.from_inputs(**custom_component.template_config) frontend_node = ComponentFrontendNode.from_inputs(**custom_component.template_config)
field_config = run_build_inputs( field_config = run_build_inputs(
custom_component, custom_component,
user_id=user_id, user_id=user_id,
@ -336,6 +337,8 @@ def build_custom_component_template_from_inputs(
return_types = custom_component.get_method_return_type(output.method) return_types = custom_component.get_method_return_type(output.method)
return_types = [format_type(return_type) for return_type in return_types] return_types = [format_type(return_type) for return_type in return_types]
output.add_types(return_types) output.add_types(return_types)
# ! This should be removed when we have a better way to handle this
frontend_node.get_base_classes_from_outputs()
return frontend_node.to_dict(add_name=False), custom_component return frontend_node.to_dict(add_name=False), custom_component
@ -384,7 +387,7 @@ def create_component_template(component):
component_code = component["code"] component_code = component["code"]
component_output_types = component["output_types"] component_output_types = component["output_types"]
component_extractor = CustomComponent(code=component_code) component_extractor = Component(code=component_code)
component_template, _ = build_custom_component_template(component_extractor) component_template, _ = build_custom_component_template(component_extractor)
if not component_template["output_types"] and component_output_types: if not component_template["output_types"] and component_output_types:

View file

@ -11,10 +11,11 @@ if TYPE_CHECKING:
class SourceHandle(BaseModel): class SourceHandle(BaseModel):
baseClasses: List[str] = Field(..., description="List of base classes for the source handle.") baseClasses: Optional[List[str]] = Field(None, description="List of base classes for the source handle.")
dataType: str = Field(..., description="Data type for the source handle.") dataType: str = Field(..., description="Data type for the source handle.")
id: str = Field(..., description="Unique identifier for the source handle.") id: str = Field(..., description="Unique identifier for the source handle.")
conditionalPath: Optional[bool] = Field(None, description="Conditional path for the source handle.") name: str = Field(..., description="Name of the source handle.")
output_types: List[str] = Field(..., description="List of output types for the source handle.")
class TargetHandle(BaseModel): class TargetHandle(BaseModel):
@ -49,11 +50,11 @@ class Edge:
def validate_handles(self, source, target) -> None: def validate_handles(self, source, target) -> None:
if self.target_handle.inputTypes is None: if self.target_handle.inputTypes is None:
self.valid_handles = self.target_handle.type in self.source_handle.baseClasses self.valid_handles = self.target_handle.type in self.source_handle.output_types
else: else:
self.valid_handles = ( self.valid_handles = (
any(baseClass in self.target_handle.inputTypes for baseClass in self.source_handle.baseClasses) any(output_type in self.target_handle.inputTypes for output_type in self.source_handle.output_types)
or self.target_handle.type in self.source_handle.baseClasses or self.target_handle.type in self.source_handle.output_types
) )
if not self.valid_handles: if not self.valid_handles:
logger.debug(self.source_handle) logger.debug(self.source_handle)
@ -70,16 +71,29 @@ class Edge:
def validate_edge(self, source, target) -> None: def validate_edge(self, source, target) -> None:
# Validate that the outputs of the source node are valid inputs # Validate that the outputs of the source node are valid inputs
# for the target node # for the target node
self.source_types = source.output # .outputs is a list of Output objects as dictionaries
# meaning: check for "types" key in each dictionary
self.source_types = [output for output in source.outputs if output["name"] == self.source_handle.name]
self.target_reqs = target.required_inputs + target.optional_inputs self.target_reqs = target.required_inputs + target.optional_inputs
# Both lists contain strings and sometimes a string contains the value we are # Both lists contain strings and sometimes a string contains the value we are
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"] # looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
# so we need to check if any of the strings in source_types is in target_reqs # so we need to check if any of the strings in source_types is in target_reqs
self.valid = any(output in target_req for output in self.source_types for target_req in self.target_reqs) self.valid = any(
any(output_type in target_req for output_type in output["types"])
for output in self.source_types
for target_req in self.target_reqs
)
# Get what type of input the target node is expecting # Get what type of input the target node is expecting
# Update the matched type to be the first found match
self.matched_type = next( self.matched_type = next(
(output for output in self.source_types if output in self.target_reqs), (
output_type
for output in self.source_types
for output_type in output["types"]
for target_req in self.target_reqs
if output_type in target_req
),
None, None,
) )
no_matched_type = self.matched_type is None no_matched_type = self.matched_type is None

View file

@ -618,7 +618,7 @@
"list": false, "list": false,
"show": true, "show": true,
"multiline": true, "multiline": true,
"value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"ChatInput\"\n\n def build_config(self):\n build_config = super().build_config()\n build_config[\"input_value\"] = {\n \"input_types\": [],\n \"display_name\": \"Message\",\n \"multiline\": True,\n }\n\n return build_config\n\n def build(\n self,\n sender: Optional[str] = \"User\",\n sender_name: Optional[str] = \"User\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n ) -> Union[Text, Record]:\n return super().build_no_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n )\n", "value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\nfrom langflow.template import Input, Output\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"ChatInput\"\n\n inputs = [\n Input(name=\"input_value\", type=str, display_name=\"Message\", multiline=True, input_types=[]),\n Input(name=\"sender\", type=str, display_name=\"Sender Type\", options=[\"Machine\", \"User\"]),\n Input(name=\"sender_name\", type=str, display_name=\"Sender Name\"),\n Input(name=\"session_id\", type=str, display_name=\"Session ID\"),\n ]\n outputs = [\n Output(name=\"Message\", method=\"text_response\"),\n Output(name=\"Record\", method=\"record_response\"),\n ]\n\n def build_config(self):\n build_config = super().build_config()\n build_config[\"input_value\"] = {\n \"input_types\": [],\n \"display_name\": \"Message\",\n \"multiline\": True,\n }\n\n return build_config\n\n def text_response(self):\n result = self.message\n if self.session_id and isinstance(result, (Record, str)):\n self.store_message(result, self.session_id, self.sender, self.sender_name)\n return result\n\n def record_response(self):\n record = Record(\n data={\n \"message\": self.message,\n \"sender\": self.sender,\n \"sender_name\": self.sender_name,\n \"session_id\": self.session_id,\n }\n )\n if self.session_id and isinstance(record, (Record, str)):\n self.store_message(record, self.session_id, self.sender, self.sender_name)\n return record\n\n def build(\n self,\n sender: Optional[str] = \"User\",\n sender_name: Optional[str] = \"User\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n ) -> Union[Text, Record]:\n return super().build_no_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n )\n",
"fileTypes": [], "fileTypes": [],
"file_path": "", "file_path": "",
"password": false, "password": false,
@ -672,7 +672,7 @@
"type": "str", "type": "str",
"required": false, "required": false,
"placeholder": "", "placeholder": "",
"list": true, "list": false,
"show": true, "show": true,
"multiline": false, "multiline": false,
"value": "User", "value": "User",
@ -730,7 +730,7 @@
"display_name": "Session ID", "display_name": "Session ID",
"advanced": true, "advanced": true,
"dynamic": false, "dynamic": false,
"info": "If provided, the message will be stored in the memory.", "info": "",
"load_from_db": false, "load_from_db": false,
"title_case": false, "title_case": false,
"input_types": [ "input_types": [
@ -780,9 +780,9 @@
"edges": [ "edges": [
{ {
"source": "OpenAIModel-k39HS", "source": "OpenAIModel-k39HS",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-k39HS\u0153}", "sourceHandle": "{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-k39HSœ}",
"target": "ChatOutput-njtka", "target": "ChatOutput-njtka",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-njtka\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-njtkaœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -806,13 +806,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-k39HS{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-k39HS\u0153}-ChatOutput-njtka{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-njtka\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}" "id": "reactflow__edge-OpenAIModel-k39HS{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-k39HSœ}-ChatOutput-njtka{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-njtkaœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
}, },
{ {
"source": "Prompt-uxBqP", "source": "Prompt-uxBqP",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-uxBqP\u0153}", "sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-uxBqPœ}",
"target": "OpenAIModel-k39HS", "target": "OpenAIModel-k39HS",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-k39HS\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-k39HSœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -836,13 +836,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-uxBqP{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-uxBqP\u0153}-OpenAIModel-k39HS{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-k39HS\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}" "id": "reactflow__edge-Prompt-uxBqP{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-uxBqPœ}-OpenAIModel-k39HS{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-k39HSœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
}, },
{ {
"source": "ChatInput-P3fgL", "source": "ChatInput-P3fgL",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Record\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-P3fgL\u0153}", "sourceHandle": "{œbaseClassesœ:[œobjectœ,œRecordœ,œstrœ,œTextœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-P3fgLœ}",
"target": "Prompt-uxBqP", "target": "Prompt-uxBqP",
"targetHandle": "{\u0153fieldName\u0153:\u0153user_input\u0153,\u0153id\u0153:\u0153Prompt-uxBqP\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "targetHandle": "{œfieldNameœ:œuser_inputœ,œidœ:œPrompt-uxBqPœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "user_input", "fieldName": "user_input",
@ -870,7 +870,7 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-ChatInput-P3fgL{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Record\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-P3fgL\u0153}-Prompt-uxBqP{\u0153fieldName\u0153:\u0153user_input\u0153,\u0153id\u0153:\u0153Prompt-uxBqP\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}" "id": "reactflow__edge-ChatInput-P3fgL{œbaseClassesœ:[œobjectœ,œRecordœ,œstrœ,œTextœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-P3fgLœ}-Prompt-uxBqP{œfieldNameœ:œuser_inputœ,œidœ:œPrompt-uxBqPœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}"
} }
], ],
"viewport": { "viewport": {

View file

@ -277,7 +277,7 @@
"list": false, "list": false,
"show": true, "show": true,
"multiline": true, "multiline": true,
"value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"ChatInput\"\n\n def build_config(self):\n build_config = super().build_config()\n build_config[\"input_value\"] = {\n \"input_types\": [],\n \"display_name\": \"Message\",\n \"multiline\": True,\n }\n\n return build_config\n\n def build(\n self,\n sender: Optional[str] = \"User\",\n sender_name: Optional[str] = \"User\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n ) -> Union[Text, Record]:\n return super().build_no_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n )\n", "value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\nfrom langflow.template import Input, Output\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"ChatInput\"\n\n inputs = [\n Input(name=\"input_value\", type=str, display_name=\"Message\", multiline=True, input_types=[]),\n Input(name=\"sender\", type=str, display_name=\"Sender Type\", options=[\"Machine\", \"User\"]),\n Input(name=\"sender_name\", type=str, display_name=\"Sender Name\"),\n Input(name=\"session_id\", type=str, display_name=\"Session ID\"),\n ]\n outputs = [\n Output(name=\"Message\", method=\"text_response\"),\n Output(name=\"Record\", method=\"record_response\"),\n ]\n\n def build_config(self):\n build_config = super().build_config()\n build_config[\"input_value\"] = {\n \"input_types\": [],\n \"display_name\": \"Message\",\n \"multiline\": True,\n }\n\n return build_config\n\n def text_response(self):\n result = self.message\n if self.session_id and isinstance(result, (Record, str)):\n self.store_message(result, self.session_id, self.sender, self.sender_name)\n return result\n\n def record_response(self):\n record = Record(\n data={\n \"message\": self.message,\n \"sender\": self.sender,\n \"sender_name\": self.sender_name,\n \"session_id\": self.session_id,\n }\n )\n if self.session_id and isinstance(record, (Record, str)):\n self.store_message(record, self.session_id, self.sender, self.sender_name)\n return record\n\n def build(\n self,\n sender: Optional[str] = \"User\",\n sender_name: Optional[str] = \"User\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n ) -> Union[Text, Record]:\n return super().build_no_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n )\n",
"fileTypes": [], "fileTypes": [],
"file_path": "", "file_path": "",
"password": false, "password": false,
@ -331,7 +331,7 @@
"type": "str", "type": "str",
"required": false, "required": false,
"placeholder": "", "placeholder": "",
"list": true, "list": false,
"show": true, "show": true,
"multiline": false, "multiline": false,
"value": "User", "value": "User",
@ -389,7 +389,7 @@
"display_name": "Session ID", "display_name": "Session ID",
"advanced": true, "advanced": true,
"dynamic": false, "dynamic": false,
"info": "If provided, the message will be stored in the memory.", "info": "",
"load_from_db": false, "load_from_db": false,
"title_case": false, "title_case": false,
"input_types": [ "input_types": [
@ -892,9 +892,9 @@
"edges": [ "edges": [
{ {
"source": "ChatInput-MsSJ9", "source": "ChatInput-MsSJ9",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Record\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-MsSJ9\u0153}", "sourceHandle": "{œbaseClassesœ:[œstrœ,œRecordœ,œTextœ,œobjectœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-MsSJ9œ}",
"target": "Prompt-tHwPf", "target": "Prompt-tHwPf",
"targetHandle": "{\u0153fieldName\u0153:\u0153Question\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "targetHandle": "{œfieldNameœ:œQuestionœ,œidœ:œPrompt-tHwPfœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "Question", "fieldName": "Question",
@ -922,13 +922,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-ChatInput-MsSJ9{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Record\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-MsSJ9\u0153}-Prompt-tHwPf{\u0153fieldName\u0153:\u0153Question\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}" "id": "reactflow__edge-ChatInput-MsSJ9{œbaseClassesœ:[œstrœ,œRecordœ,œTextœ,œobjectœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-MsSJ9œ}-Prompt-tHwPf{œfieldNameœ:œQuestionœ,œidœ:œPrompt-tHwPfœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}"
}, },
{ {
"source": "File-6TEsD", "source": "File-6TEsD",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153File\u0153,\u0153id\u0153:\u0153File-6TEsD\u0153}", "sourceHandle": "{œbaseClassesœ:[œRecordœ],œdataTypeœ:œFileœ,œidœ:œFile-6TEsDœ}",
"target": "Prompt-tHwPf", "target": "Prompt-tHwPf",
"targetHandle": "{\u0153fieldName\u0153:\u0153Document\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "targetHandle": "{œfieldNameœ:œDocumentœ,œidœ:œPrompt-tHwPfœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "Document", "fieldName": "Document",
@ -953,13 +953,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-File-6TEsD{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153File\u0153,\u0153id\u0153:\u0153File-6TEsD\u0153}-Prompt-tHwPf{\u0153fieldName\u0153:\u0153Document\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}" "id": "reactflow__edge-File-6TEsD{œbaseClassesœ:[œRecordœ],œdataTypeœ:œFileœ,œidœ:œFile-6TEsDœ}-Prompt-tHwPf{œfieldNameœ:œDocumentœ,œidœ:œPrompt-tHwPfœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}"
}, },
{ {
"source": "Prompt-tHwPf", "source": "Prompt-tHwPf",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153}", "sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-tHwPfœ}",
"target": "OpenAIModel-Bt067", "target": "OpenAIModel-Bt067",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-Bt067\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-Bt067œ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -983,13 +983,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-tHwPf{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153}-OpenAIModel-Bt067{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-Bt067\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}" "id": "reactflow__edge-Prompt-tHwPf{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-tHwPfœ}-OpenAIModel-Bt067{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-Bt067œ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
}, },
{ {
"source": "OpenAIModel-Bt067", "source": "OpenAIModel-Bt067",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-Bt067\u0153}", "sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-Bt067œ}",
"target": "ChatOutput-F5Awj", "target": "ChatOutput-F5Awj",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-F5Awj\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-F5Awjœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1013,7 +1013,7 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-Bt067{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-Bt067\u0153}-ChatOutput-F5Awj{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-F5Awj\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}" "id": "reactflow__edge-OpenAIModel-Bt067{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-Bt067œ}-ChatOutput-F5Awj{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-F5Awjœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
} }
], ],
"viewport": { "viewport": {

View file

@ -1,6 +1,6 @@
{ {
"id": "08d5cccf-d098-4367-b14b-1078429c9ed9", "id": "08d5cccf-d098-4367-b14b-1078429c9ed9",
"icon": "\ud83e\udd16", "icon": "🤖",
"icon_bg_color": "#FFD700", "icon_bg_color": "#FFD700",
"data": { "data": {
"nodes": [ "nodes": [
@ -22,7 +22,7 @@
"list": false, "list": false,
"show": true, "show": true,
"multiline": true, "multiline": true,
"value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"ChatInput\"\n\n def build_config(self):\n build_config = super().build_config()\n build_config[\"input_value\"] = {\n \"input_types\": [],\n \"display_name\": \"Message\",\n \"multiline\": True,\n }\n\n return build_config\n\n def build(\n self,\n sender: Optional[str] = \"User\",\n sender_name: Optional[str] = \"User\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n ) -> Union[Text, Record]:\n return super().build_no_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n )\n", "value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\nfrom langflow.template import Input, Output\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"ChatInput\"\n\n inputs = [\n Input(name=\"input_value\", type=str, display_name=\"Message\", multiline=True, input_types=[]),\n Input(name=\"sender\", type=str, display_name=\"Sender Type\", options=[\"Machine\", \"User\"]),\n Input(name=\"sender_name\", type=str, display_name=\"Sender Name\"),\n Input(name=\"session_id\", type=str, display_name=\"Session ID\"),\n ]\n outputs = [\n Output(name=\"Message\", method=\"text_response\"),\n Output(name=\"Record\", method=\"record_response\"),\n ]\n\n def build_config(self):\n build_config = super().build_config()\n build_config[\"input_value\"] = {\n \"input_types\": [],\n \"display_name\": \"Message\",\n \"multiline\": True,\n }\n\n return build_config\n\n def text_response(self):\n result = self.message\n if self.session_id and isinstance(result, (Record, str)):\n self.store_message(result, self.session_id, self.sender, self.sender_name)\n return result\n\n def record_response(self):\n record = Record(\n data={\n \"message\": self.message,\n \"sender\": self.sender,\n \"sender_name\": self.sender_name,\n \"session_id\": self.session_id,\n }\n )\n if self.session_id and isinstance(record, (Record, str)):\n self.store_message(record, self.session_id, self.sender, self.sender_name)\n return record\n\n def build(\n self,\n sender: Optional[str] = \"User\",\n sender_name: Optional[str] = \"User\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n ) -> Union[Text, Record]:\n return super().build_no_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n )\n",
"fileTypes": [], "fileTypes": [],
"file_path": "", "file_path": "",
"password": false, "password": false,
@ -76,7 +76,7 @@
"type": "str", "type": "str",
"required": false, "required": false,
"placeholder": "", "placeholder": "",
"list": true, "list": false,
"show": true, "show": true,
"multiline": false, "multiline": false,
"value": "User", "value": "User",
@ -134,7 +134,7 @@
"display_name": "Session ID", "display_name": "Session ID",
"advanced": false, "advanced": false,
"dynamic": false, "dynamic": false,
"info": "If provided, the message will be stored in the memory.", "info": "",
"load_from_db": false, "load_from_db": false,
"title_case": false, "title_case": false,
"input_types": [ "input_types": [
@ -1100,9 +1100,9 @@
"edges": [ "edges": [
{ {
"source": "MemoryComponent-cdA1J", "source": "MemoryComponent-cdA1J",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153MemoryComponent\u0153,\u0153id\u0153:\u0153MemoryComponent-cdA1J\u0153}", "sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œMemoryComponentœ,œidœ:œMemoryComponent-cdA1Jœ}",
"target": "Prompt-ODkUx", "target": "Prompt-ODkUx",
"targetHandle": "{\u0153fieldName\u0153:\u0153context\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "targetHandle": "{œfieldNameœ:œcontextœ,œidœ:œPrompt-ODkUxœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "context", "fieldName": "context",
@ -1129,14 +1129,14 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-MemoryComponent-cdA1J{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153MemoryComponent\u0153,\u0153id\u0153:\u0153MemoryComponent-cdA1J\u0153}-Prompt-ODkUx{\u0153fieldName\u0153:\u0153context\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "id": "reactflow__edge-MemoryComponent-cdA1J{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œMemoryComponentœ,œidœ:œMemoryComponent-cdA1Jœ}-Prompt-ODkUx{œfieldNameœ:œcontextœ,œidœ:œPrompt-ODkUxœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"selected": false "selected": false
}, },
{ {
"source": "ChatInput-t7F8v", "source": "ChatInput-t7F8v",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153Text\u0153,\u0153object\u0153,\u0153Record\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-t7F8v\u0153}", "sourceHandle": "{œbaseClassesœ:[œTextœ,œobjectœ,œRecordœ,œstrœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-t7F8vœ}",
"target": "Prompt-ODkUx", "target": "Prompt-ODkUx",
"targetHandle": "{\u0153fieldName\u0153:\u0153user_message\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "targetHandle": "{œfieldNameœ:œuser_messageœ,œidœ:œPrompt-ODkUxœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "user_message", "fieldName": "user_message",
@ -1164,14 +1164,14 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-ChatInput-t7F8v{\u0153baseClasses\u0153:[\u0153Text\u0153,\u0153object\u0153,\u0153Record\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-t7F8v\u0153}-Prompt-ODkUx{\u0153fieldName\u0153:\u0153user_message\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "id": "reactflow__edge-ChatInput-t7F8v{œbaseClassesœ:[œTextœ,œobjectœ,œRecordœ,œstrœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-t7F8vœ}-Prompt-ODkUx{œfieldNameœ:œuser_messageœ,œidœ:œPrompt-ODkUxœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"selected": false "selected": false
}, },
{ {
"source": "Prompt-ODkUx", "source": "Prompt-ODkUx",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153Text\u0153,\u0153str\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153}", "sourceHandle": "{œbaseClassesœ:[œTextœ,œstrœ,œobjectœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-ODkUxœ}",
"target": "OpenAIModel-9RykF", "target": "OpenAIModel-9RykF",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-9RykF\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-9RykFœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1195,13 +1195,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-ODkUx{\u0153baseClasses\u0153:[\u0153Text\u0153,\u0153str\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153}-OpenAIModel-9RykF{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-9RykF\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}" "id": "reactflow__edge-Prompt-ODkUx{œbaseClassesœ:[œTextœ,œstrœ,œobjectœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-ODkUxœ}-OpenAIModel-9RykF{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-9RykFœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
}, },
{ {
"source": "OpenAIModel-9RykF", "source": "OpenAIModel-9RykF",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153object\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-9RykF\u0153}", "sourceHandle": "{œbaseClassesœ:[œstrœ,œobjectœ,œTextœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-9RykFœ}",
"target": "ChatOutput-P1jEe", "target": "ChatOutput-P1jEe",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-P1jEe\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-P1jEeœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1225,13 +1225,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-9RykF{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153object\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-9RykF\u0153}-ChatOutput-P1jEe{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-P1jEe\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}" "id": "reactflow__edge-OpenAIModel-9RykF{œbaseClassesœ:[œstrœ,œobjectœ,œTextœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-9RykFœ}-ChatOutput-P1jEe{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-P1jEeœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
}, },
{ {
"source": "MemoryComponent-cdA1J", "source": "MemoryComponent-cdA1J",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153MemoryComponent\u0153,\u0153id\u0153:\u0153MemoryComponent-cdA1J\u0153}", "sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œMemoryComponentœ,œidœ:œMemoryComponent-cdA1Jœ}",
"target": "TextOutput-vrs6T", "target": "TextOutput-vrs6T",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-vrs6T\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}", "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-vrs6Tœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1256,7 +1256,7 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-foreground stroke-connection", "className": "stroke-foreground stroke-connection",
"id": "reactflow__edge-MemoryComponent-cdA1J{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153MemoryComponent\u0153,\u0153id\u0153:\u0153MemoryComponent-cdA1J\u0153}-TextOutput-vrs6T{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-vrs6T\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}" "id": "reactflow__edge-MemoryComponent-cdA1J{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œMemoryComponentœ,œidœ:œMemoryComponent-cdA1Jœ}-TextOutput-vrs6T{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-vrs6Tœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}"
} }
], ],
"viewport": { "viewport": {

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,10 @@
from langflow.template.field.base import Input, Output
from langflow.template.frontend_node.base import FrontendNode
from langflow.template.template.base import Template
__all__ = [
"Input",
"Output",
"FrontendNode",
"Template",
]

View file

@ -101,6 +101,9 @@ class FrontendNode(BaseModel):
def add_extra_base_classes(self) -> None: def add_extra_base_classes(self) -> None:
pass pass
def get_base_classes_from_outputs(self) -> list[str]:
self.base_classes = [output_type for output in self.outputs for output_type in output.types]
def add_base_class(self, base_class: Union[str, List[str]]) -> None: def add_base_class(self, base_class: Union[str, List[str]]) -> None:
"""Adds a base class to the frontend node.""" """Adds a base class to the frontend node."""
if isinstance(base_class, str): if isinstance(base_class, str):

View file

@ -62,6 +62,8 @@ export default function ParameterComponent({
index, index,
outputName, outputName,
}: ParameterComponentType): JSX.Element { }: ParameterComponentType): JSX.Element {
console.log("title", title);
console.log("data", data);
const infoHtml = useRef<HTMLDivElement & ReactNode>(null); const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
const nodes = useFlowStore((state) => state.nodes); const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges); const edges = useFlowStore((state) => state.edges);
@ -72,7 +74,6 @@ export default function ParameterComponent({
const updateNodeInternals = useUpdateNodeInternals(); const updateNodeInternals = useUpdateNodeInternals();
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge); const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue( const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue(
data, data,
name, name,

View file

@ -55,14 +55,14 @@ export default function GenericNode({
const [nodeName, setNodeName] = useState(data.node!.display_name); const [nodeName, setNodeName] = useState(data.node!.display_name);
const [inputDescription, setInputDescription] = useState(false); const [inputDescription, setInputDescription] = useState(false);
const [nodeDescription, setNodeDescription] = useState( const [nodeDescription, setNodeDescription] = useState(
data.node?.description!, data.node?.description!
); );
const [isOutdated, setIsOutdated] = useState(false); const [isOutdated, setIsOutdated] = useState(false);
const buildStatus = useFlowStore( const buildStatus = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.status, (state) => state.flowBuildStatus[data.id]?.status
); );
const lastRunTime = useFlowStore( const lastRunTime = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.timestamp, (state) => state.flowBuildStatus[data.id]?.timestamp
); );
const [validationStatus, setValidationStatus] = const [validationStatus, setValidationStatus] =
useState<validationStatusType | null>(null); useState<validationStatusType | null>(null);
@ -115,7 +115,7 @@ export default function GenericNode({
updateNodeInternals(data.id); updateNodeInternals(data.id);
}, },
[data.id, data.node, setNode, setIsOutdated], [data.id, data.node, setNode, setIsOutdated]
); );
if (!data.node!.template) { if (!data.node!.template) {
@ -255,7 +255,7 @@ export default function GenericNode({
const isDark = useDarkStore((state) => state.dark); const isDark = useDarkStore((state) => state.dark);
const renderIconStatus = ( const renderIconStatus = (
buildStatus: BuildStatus | undefined, buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null, validationStatus: validationStatusType | null
) => { ) => {
if (buildStatus === BuildStatus.BUILDING) { if (buildStatus === BuildStatus.BUILDING) {
return <Loading className="text-medium-indigo" />; return <Loading className="text-medium-indigo" />;
@ -296,7 +296,7 @@ export default function GenericNode({
}; };
const getSpecificClassFromBuildStatus = ( const getSpecificClassFromBuildStatus = (
buildStatus: BuildStatus | undefined, buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null, validationStatus: validationStatusType | null
) => { ) => {
let isInvalid = validationStatus && !validationStatus.valid; let isInvalid = validationStatus && !validationStatus.valid;
@ -320,11 +320,11 @@ export default function GenericNode({
selected: boolean, selected: boolean,
showNode: boolean, showNode: boolean,
buildStatus: BuildStatus | undefined, buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null, validationStatus: validationStatusType | null
) => { ) => {
const specificClassFromBuildStatus = getSpecificClassFromBuildStatus( const specificClassFromBuildStatus = getSpecificClassFromBuildStatus(
buildStatus, buildStatus,
validationStatus, validationStatus
); );
const baseBorderClass = getBaseBorderClass(selected); const baseBorderClass = getBaseBorderClass(selected);
@ -333,7 +333,7 @@ export default function GenericNode({
baseBorderClass, baseBorderClass,
nodeSizeClass, nodeSizeClass,
"generic-node-div", "generic-node-div",
specificClassFromBuildStatus, specificClassFromBuildStatus
); );
return names; return names;
}; };
@ -393,7 +393,7 @@ export default function GenericNode({
selected, selected,
showNode, showNode,
buildStatus, buildStatus,
validationStatus, validationStatus
)} )}
> >
{data.node?.beta && showNode && ( {data.node?.beta && showNode && (
@ -538,7 +538,7 @@ export default function GenericNode({
} }
title={getFieldTitle( title={getFieldTitle(
data.node?.template!, data.node?.template!,
templateField, templateField
)} )}
info={data.node?.template[templateField].info} info={data.node?.template[templateField].info}
name={templateField} name={templateField}
@ -566,7 +566,7 @@ export default function GenericNode({
proxy={data.node?.template[templateField].proxy} proxy={data.node?.template[templateField].proxy}
showNode={showNode} showNode={showNode}
/> />
), )
)} )}
{/* <ParameterComponent {/* <ParameterComponent
index={0} index={0}
@ -725,7 +725,7 @@ export default function GenericNode({
!data.node?.description) && !data.node?.description) &&
nameEditable nameEditable
? "font-light italic" ? "font-light italic"
: "", : ""
)} )}
onDoubleClick={(e) => { onDoubleClick={(e) => {
setInputDescription(true); setInputDescription(true);
@ -787,13 +787,13 @@ export default function GenericNode({
} }
title={getFieldTitle( title={getFieldTitle(
data.node?.template!, data.node?.template!,
templateField, templateField
)} )}
info={data.node?.template[templateField].info} info={data.node?.template[templateField].info}
name={templateField} name={templateField}
tooltipTitle={ tooltipTitle={
data.node?.template[templateField].input_types?.join( data.node?.template[templateField].input_types?.join(
"\n", "\n"
) ?? data.node?.template[templateField].type ) ?? data.node?.template[templateField].type
} }
required={data.node!.template[templateField].required} required={data.node!.template[templateField].required}
@ -820,7 +820,7 @@ export default function GenericNode({
<div <div
className={classNames( className={classNames(
Object.keys(data.node!.template).length < 1 ? "hidden" : "", Object.keys(data.node!.template).length < 1 ? "hidden" : "",
"flex-max-width justify-center", "flex-max-width justify-center"
)} )}
> >
{" "} {" "}
@ -842,7 +842,7 @@ export default function GenericNode({
nodeColors[types[data.type]] ?? nodeColors[types[data.type]] ??
nodeColors.unknown nodeColors.unknown
} }
title={output.selected ?? output.types[0]} title={output.name}
tooltipTitle={output.selected ?? output.types[0]} tooltipTitle={output.selected ?? output.types[0]}
id={{ id={{
output_types: [output.selected ?? output.types[0]], output_types: [output.selected ?? output.types[0]],

View file

@ -4,10 +4,9 @@ from uuid import uuid4
import pytest import pytest
from langchain_core.documents import Document from langchain_core.documents import Document
from langflow.custom import CustomComponent from langflow.custom import CustomComponent
from langflow.custom.code_parser.code_parser import CodeParser, CodeSyntaxError from langflow.custom.code_parser.code_parser import CodeParser, CodeSyntaxError
from langflow.custom.custom_component.component import Component, ComponentCodeNullError from langflow.custom.custom_component.base_component import BaseComponent, ComponentCodeNullError
from langflow.custom.utils import build_custom_component_template from langflow.custom.utils import build_custom_component_template
from langflow.services.database.models.flow import Flow, FlowCreate from langflow.services.database.models.flow import Flow, FlowCreate
@ -77,7 +76,7 @@ def test_component_init():
""" """
Test the initialization of the Component class. Test the initialization of the Component class.
""" """
component = Component(code=code_default, function_entrypoint_name="build") component = BaseComponent(code=code_default, function_entrypoint_name="build")
assert component.code == code_default assert component.code == code_default
assert component.function_entrypoint_name == "build" assert component.function_entrypoint_name == "build"
@ -86,7 +85,7 @@ def test_component_get_code_tree():
""" """
Test the get_code_tree method of the Component class. Test the get_code_tree method of the Component class.
""" """
component = Component(code=code_default, function_entrypoint_name="build") component = BaseComponent(code=code_default, function_entrypoint_name="build")
tree = component.get_code_tree(component.code) tree = component.get_code_tree(component.code)
assert "imports" in tree assert "imports" in tree
@ -96,7 +95,7 @@ def test_component_code_null_error():
Test the get_function method raises the Test the get_function method raises the
ComponentCodeNullError when the code is empty. ComponentCodeNullError when the code is empty.
""" """
component = Component(code="", function_entrypoint_name="") component = BaseComponent(code="", function_entrypoint_name="")
with pytest.raises(ComponentCodeNullError): with pytest.raises(ComponentCodeNullError):
component.get_function() component.get_function()
@ -200,7 +199,7 @@ def test_component_get_function_valid():
Test the get_function method of the Component Test the get_function method of the Component
class with valid code and function_entrypoint_name. class with valid code and function_entrypoint_name.
""" """
component = Component(code="def build(): pass", function_entrypoint_name="build") component = BaseComponent(code="def build(): pass", function_entrypoint_name="build")
my_function = component.get_function() my_function = component.get_function()
assert callable(my_function) assert callable(my_function)
@ -357,7 +356,7 @@ def test_component_get_code_tree_syntax_error():
Test the get_code_tree method of the Component class Test the get_code_tree method of the Component class
raises the CodeSyntaxError when given incorrect syntax. raises the CodeSyntaxError when given incorrect syntax.
""" """
component = Component(code="import os as", function_entrypoint_name="build") component = BaseComponent(code="import os as", function_entrypoint_name="build")
with pytest.raises(CodeSyntaxError): with pytest.raises(CodeSyntaxError):
component.get_code_tree(component.code) component.get_code_tree(component.code)