feat: create an IO Category (#8229)
* create an IO bundle * Update styleUtils.ts * [autofix.ci] apply automated fixes * change folder names * [autofix.ci] apply automated fixes * Update test_endpoints.py * refactor: update test IDs for input/output components in specs * test: update test IDs for chat input/output components in decisionFlow.spec.ts * test: update test IDs for input/output components in generalBugs-shard-5.spec.ts * Update styleUtils.ts * 🐛 (typescript_test.yml): increase the maximum shard count to 40 to improve test distribution and efficiency * fix FE Tests Input output component Ids * [autofix.ci] apply automated fixes * ✅ (filterEdge-shard-1.spec.ts): update test to log error message when element is not visible before asserting visibility * ✅ (notifications.spec.ts): update test selectors for better clarity and consistency in the notifications feature test suite * 🐛 (typescript_test.yml): adjust the maximum shard count to 10 to prevent exceeding the limit and optimize test execution. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: deon-sanchez <deon.sanchez@datastax.com> Co-authored-by: Deon Sanchez <69873175+deon-sanchez@users.noreply.github.com> Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
parent
6582f06a21
commit
dc222d93f5
62 changed files with 130 additions and 138 deletions
|
|
@ -0,0 +1,6 @@
|
|||
from .chat import ChatInput
|
||||
from .chat_output import ChatOutput
|
||||
from .text import TextInputComponent
|
||||
from .text_output import TextOutputComponent
|
||||
|
||||
__all__ = ["ChatInput", "ChatOutput", "TextInputComponent", "TextOutputComponent"]
|
||||
119
src/backend/base/langflow/components/input_output/chat.py
Normal file
119
src/backend/base/langflow/components/input_output/chat.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES
|
||||
from langflow.base.io.chat import ChatComponent
|
||||
from langflow.inputs import BoolInput
|
||||
from langflow.io import (
|
||||
DropdownInput,
|
||||
FileInput,
|
||||
MessageTextInput,
|
||||
MultilineInput,
|
||||
Output,
|
||||
)
|
||||
from langflow.schema.message import Message
|
||||
from langflow.utils.constants import (
|
||||
MESSAGE_SENDER_AI,
|
||||
MESSAGE_SENDER_NAME_USER,
|
||||
MESSAGE_SENDER_USER,
|
||||
)
|
||||
|
||||
|
||||
class ChatInput(ChatComponent):
|
||||
display_name = "Chat Input"
|
||||
description = "Get chat inputs from the Playground."
|
||||
icon = "MessagesSquare"
|
||||
name = "ChatInput"
|
||||
minimized = True
|
||||
|
||||
inputs = [
|
||||
MultilineInput(
|
||||
name="input_value",
|
||||
display_name="Text",
|
||||
value="",
|
||||
info="Message to be passed as input.",
|
||||
input_types=[],
|
||||
),
|
||||
BoolInput(
|
||||
name="should_store_message",
|
||||
display_name="Store Messages",
|
||||
info="Store the message in the history.",
|
||||
value=True,
|
||||
advanced=True,
|
||||
),
|
||||
DropdownInput(
|
||||
name="sender",
|
||||
display_name="Sender Type",
|
||||
options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],
|
||||
value=MESSAGE_SENDER_USER,
|
||||
info="Type of sender.",
|
||||
advanced=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="sender_name",
|
||||
display_name="Sender Name",
|
||||
info="Name of the sender.",
|
||||
value=MESSAGE_SENDER_NAME_USER,
|
||||
advanced=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="session_id",
|
||||
display_name="Session ID",
|
||||
info="The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
advanced=True,
|
||||
),
|
||||
FileInput(
|
||||
name="files",
|
||||
display_name="Files",
|
||||
file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,
|
||||
info="Files to be sent with the message.",
|
||||
advanced=True,
|
||||
is_list=True,
|
||||
temp_file=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="background_color",
|
||||
display_name="Background Color",
|
||||
info="The background color of the icon.",
|
||||
advanced=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="chat_icon",
|
||||
display_name="Icon",
|
||||
info="The icon of the message.",
|
||||
advanced=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="text_color",
|
||||
display_name="Text Color",
|
||||
info="The text color of the name",
|
||||
advanced=True,
|
||||
),
|
||||
]
|
||||
outputs = [
|
||||
Output(display_name="Message", name="message", method="message_response"),
|
||||
]
|
||||
|
||||
async def message_response(self) -> Message:
|
||||
background_color = self.background_color
|
||||
text_color = self.text_color
|
||||
icon = self.chat_icon
|
||||
|
||||
message = await Message.create(
|
||||
text=self.input_value,
|
||||
sender=self.sender,
|
||||
sender_name=self.sender_name,
|
||||
session_id=self.session_id,
|
||||
files=self.files,
|
||||
properties={
|
||||
"background_color": background_color,
|
||||
"text_color": text_color,
|
||||
"icon": icon,
|
||||
},
|
||||
)
|
||||
if self.session_id and isinstance(message, Message) and self.should_store_message:
|
||||
stored_message = await self.send_message(
|
||||
message,
|
||||
)
|
||||
self.message.value = stored_message
|
||||
message = stored_message
|
||||
|
||||
self.status = message
|
||||
return message
|
||||
230
src/backend/base/langflow/components/input_output/chat_output.py
Normal file
230
src/backend/base/langflow/components/input_output/chat_output.py
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
from collections.abc import Generator
|
||||
from typing import Any
|
||||
|
||||
import orjson
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
from langflow.base.io.chat import ChatComponent
|
||||
from langflow.inputs import BoolInput
|
||||
from langflow.inputs.inputs import HandleInput
|
||||
from langflow.io import DropdownInput, MessageTextInput, Output
|
||||
from langflow.schema.data import Data
|
||||
from langflow.schema.dataframe import DataFrame
|
||||
from langflow.schema.message import Message
|
||||
from langflow.schema.properties import Source
|
||||
from langflow.utils.constants import (
|
||||
MESSAGE_SENDER_AI,
|
||||
MESSAGE_SENDER_NAME_AI,
|
||||
MESSAGE_SENDER_USER,
|
||||
)
|
||||
|
||||
|
||||
class ChatOutput(ChatComponent):
|
||||
display_name = "Chat Output"
|
||||
description = "Display a chat message in the Playground."
|
||||
icon = "MessagesSquare"
|
||||
name = "ChatOutput"
|
||||
minimized = True
|
||||
|
||||
inputs = [
|
||||
HandleInput(
|
||||
name="input_value",
|
||||
display_name="Text",
|
||||
info="Message to be passed as output.",
|
||||
input_types=["Data", "DataFrame", "Message"],
|
||||
required=True,
|
||||
),
|
||||
BoolInput(
|
||||
name="should_store_message",
|
||||
display_name="Store Messages",
|
||||
info="Store the message in the history.",
|
||||
value=True,
|
||||
advanced=True,
|
||||
),
|
||||
DropdownInput(
|
||||
name="sender",
|
||||
display_name="Sender Type",
|
||||
options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],
|
||||
value=MESSAGE_SENDER_AI,
|
||||
advanced=True,
|
||||
info="Type of sender.",
|
||||
),
|
||||
MessageTextInput(
|
||||
name="sender_name",
|
||||
display_name="Sender Name",
|
||||
info="Name of the sender.",
|
||||
value=MESSAGE_SENDER_NAME_AI,
|
||||
advanced=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="session_id",
|
||||
display_name="Session ID",
|
||||
info="The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
advanced=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="data_template",
|
||||
display_name="Data Template",
|
||||
value="{text}",
|
||||
advanced=True,
|
||||
info="Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
||||
),
|
||||
MessageTextInput(
|
||||
name="background_color",
|
||||
display_name="Background Color",
|
||||
info="The background color of the icon.",
|
||||
advanced=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="chat_icon",
|
||||
display_name="Icon",
|
||||
info="The icon of the message.",
|
||||
advanced=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="text_color",
|
||||
display_name="Text Color",
|
||||
info="The text color of the name",
|
||||
advanced=True,
|
||||
),
|
||||
BoolInput(
|
||||
name="clean_data",
|
||||
display_name="Basic Clean Data",
|
||||
value=True,
|
||||
info="Whether to clean the data",
|
||||
advanced=True,
|
||||
),
|
||||
]
|
||||
outputs = [
|
||||
Output(
|
||||
display_name="Message",
|
||||
name="message",
|
||||
method="message_response",
|
||||
),
|
||||
]
|
||||
|
||||
def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:
|
||||
source_dict = {}
|
||||
if id_:
|
||||
source_dict["id"] = id_
|
||||
if display_name:
|
||||
source_dict["display_name"] = display_name
|
||||
if source:
|
||||
# Handle case where source is a ChatOpenAI object
|
||||
if hasattr(source, "model_name"):
|
||||
source_dict["source"] = source.model_name
|
||||
elif hasattr(source, "model"):
|
||||
source_dict["source"] = str(source.model)
|
||||
else:
|
||||
source_dict["source"] = str(source)
|
||||
return Source(**source_dict)
|
||||
|
||||
async def message_response(self) -> Message:
|
||||
# First convert the input to string if needed
|
||||
text = self.convert_to_string()
|
||||
|
||||
# Get source properties
|
||||
source, icon, display_name, source_id = self.get_properties_from_source_component()
|
||||
background_color = self.background_color
|
||||
text_color = self.text_color
|
||||
if self.chat_icon:
|
||||
icon = self.chat_icon
|
||||
|
||||
# Create or use existing Message object
|
||||
if isinstance(self.input_value, Message):
|
||||
message = self.input_value
|
||||
# Update message properties
|
||||
message.text = text
|
||||
else:
|
||||
message = Message(text=text)
|
||||
|
||||
# Set message properties
|
||||
message.sender = self.sender
|
||||
message.sender_name = self.sender_name
|
||||
message.session_id = self.session_id
|
||||
message.flow_id = self.graph.flow_id if hasattr(self, "graph") else None
|
||||
message.properties.source = self._build_source(source_id, display_name, source)
|
||||
message.properties.icon = icon
|
||||
message.properties.background_color = background_color
|
||||
message.properties.text_color = text_color
|
||||
|
||||
# Store message if needed
|
||||
if self.session_id and self.should_store_message:
|
||||
stored_message = await self.send_message(message)
|
||||
self.message.value = stored_message
|
||||
message = stored_message
|
||||
|
||||
self.status = message
|
||||
return message
|
||||
|
||||
def _validate_input(self) -> None:
|
||||
"""Validate the input data and raise ValueError if invalid."""
|
||||
if self.input_value is None:
|
||||
msg = "Input data cannot be None"
|
||||
raise ValueError(msg)
|
||||
if isinstance(self.input_value, list) and not all(
|
||||
isinstance(item, Message | Data | DataFrame | str) for item in self.input_value
|
||||
):
|
||||
invalid_types = [
|
||||
type(item).__name__
|
||||
for item in self.input_value
|
||||
if not isinstance(item, Message | Data | DataFrame | str)
|
||||
]
|
||||
msg = f"Expected Data or DataFrame or Message or str, got {invalid_types}"
|
||||
raise TypeError(msg)
|
||||
if not isinstance(
|
||||
self.input_value,
|
||||
Message | Data | DataFrame | str | list | Generator | type(None),
|
||||
):
|
||||
type_name = type(self.input_value).__name__
|
||||
msg = f"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}"
|
||||
raise TypeError(msg)
|
||||
|
||||
def _serialize_data(self, data: Data) -> str:
|
||||
"""Serialize Data object to JSON string."""
|
||||
# Convert data.data to JSON-serializable format
|
||||
serializable_data = jsonable_encoder(data.data)
|
||||
# Serialize with orjson, enabling pretty printing with indentation
|
||||
json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)
|
||||
# Convert bytes to string and wrap in Markdown code blocks
|
||||
return "```json\n" + json_bytes.decode("utf-8") + "\n```"
|
||||
|
||||
def _safe_convert(self, data: Any) -> str:
|
||||
"""Safely convert input data to string."""
|
||||
try:
|
||||
if isinstance(data, str):
|
||||
return data
|
||||
if isinstance(data, Message):
|
||||
return data.get_text()
|
||||
if isinstance(data, Data):
|
||||
return self._serialize_data(data)
|
||||
if isinstance(data, DataFrame):
|
||||
if self.clean_data:
|
||||
# Remove empty rows
|
||||
data = data.dropna(how="all")
|
||||
# Remove empty lines in each cell
|
||||
data = data.replace(r"^\s*$", "", regex=True)
|
||||
# Replace multiple newlines with a single newline
|
||||
data = data.replace(r"\n+", "\n", regex=True)
|
||||
|
||||
# Replace pipe characters to avoid markdown table issues
|
||||
processed_data = data.replace(r"\|", r"\\|", regex=True)
|
||||
|
||||
processed_data = processed_data.map(
|
||||
lambda x: str(x).replace("\n", "<br/>") if isinstance(x, str) else x
|
||||
)
|
||||
|
||||
return processed_data.to_markdown(index=False)
|
||||
return str(data)
|
||||
except (ValueError, TypeError, AttributeError) as e:
|
||||
msg = f"Error converting data: {e!s}"
|
||||
raise ValueError(msg) from e
|
||||
|
||||
def convert_to_string(self) -> str | Generator[Any, None, None]:
|
||||
"""Convert input data to string with proper error handling."""
|
||||
self._validate_input()
|
||||
if isinstance(self.input_value, list):
|
||||
return "\n".join([self._safe_convert(item) for item in self.input_value])
|
||||
if isinstance(self.input_value, Generator):
|
||||
return self.input_value
|
||||
return self._safe_convert(self.input_value)
|
||||
26
src/backend/base/langflow/components/input_output/text.py
Normal file
26
src/backend/base/langflow/components/input_output/text.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
from langflow.base.io.text import TextComponent
|
||||
from langflow.io import MultilineInput, Output
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
||||
class TextInputComponent(TextComponent):
|
||||
display_name = "Text Input"
|
||||
description = "Get text inputs from the Playground."
|
||||
icon = "type"
|
||||
name = "TextInput"
|
||||
|
||||
inputs = [
|
||||
MultilineInput(
|
||||
name="input_value",
|
||||
display_name="Text",
|
||||
info="Text to be passed as input.",
|
||||
),
|
||||
]
|
||||
outputs = [
|
||||
Output(display_name="Message", name="text", method="text_response"),
|
||||
]
|
||||
|
||||
def text_response(self) -> Message:
|
||||
return Message(
|
||||
text=self.input_value,
|
||||
)
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
from langflow.base.io.text import TextComponent
|
||||
from langflow.io import MultilineInput, Output
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
||||
class TextOutputComponent(TextComponent):
|
||||
display_name = "Text Output"
|
||||
description = "Display a text output in the Playground."
|
||||
icon = "type"
|
||||
name = "TextOutput"
|
||||
|
||||
inputs = [
|
||||
MultilineInput(
|
||||
name="input_value",
|
||||
display_name="Text",
|
||||
info="Text to be passed as output.",
|
||||
),
|
||||
]
|
||||
outputs = [
|
||||
Output(display_name="Message", name="text", method="text_response"),
|
||||
]
|
||||
|
||||
def text_response(self) -> Message:
|
||||
message = Message(
|
||||
text=self.input_value,
|
||||
)
|
||||
self.status = self.input_value
|
||||
return message
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
from .chat import ChatInput
|
||||
from .text import TextInputComponent
|
||||
|
||||
__all__ = ["ChatInput", "TextInputComponent"]
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
from .chat import ChatOutput
|
||||
from .text import TextOutputComponent
|
||||
|
||||
__all__ = ["ChatOutput", "TextOutputComponent"]
|
||||
|
|
@ -158,8 +158,8 @@ def create_state_model(model_name: str = "State", *, validate: bool = True, **kw
|
|||
ValueError: If the provided field value is invalid or cannot be processed.
|
||||
|
||||
Examples:
|
||||
>>> from langflow.components.inputs import ChatInput
|
||||
>>> from langflow.components.outputs.ChatOutput import ChatOutput
|
||||
>>> from langflow.components.io import ChatInput
|
||||
>>> from langflow.components.io.ChatOutput import ChatOutput
|
||||
>>> from pydantic import Field
|
||||
>>>
|
||||
>>> chat_input = ChatInput()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from langflow.components.inputs import ChatInput
|
||||
from langflow.components.input_output import ChatInput, ChatOutput
|
||||
from langflow.components.languagemodels import OpenAIModelComponent
|
||||
from langflow.components.outputs import ChatOutput
|
||||
from langflow.components.prompts import PromptComponent
|
||||
from langflow.graph import Graph
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
from textwrap import dedent
|
||||
|
||||
from langflow.components.data import URLComponent
|
||||
from langflow.components.inputs import TextInputComponent
|
||||
from langflow.components.input_output import ChatOutput, TextInputComponent
|
||||
from langflow.components.languagemodels import OpenAIModelComponent
|
||||
from langflow.components.outputs import ChatOutput
|
||||
from langflow.components.processing import ParseDataComponent
|
||||
from langflow.components.prompts import PromptComponent
|
||||
from langflow.graph import Graph
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
from langflow.components.crewai.crewai import CrewAIAgentComponent
|
||||
from langflow.components.crewai.hierarchical_crew import HierarchicalCrewComponent
|
||||
from langflow.components.crewai.hierarchical_task import HierarchicalTaskComponent
|
||||
from langflow.components.inputs import ChatInput
|
||||
from langflow.components.input_output import ChatInput, ChatOutput
|
||||
from langflow.components.languagemodels import OpenAIModelComponent
|
||||
from langflow.components.outputs import ChatOutput
|
||||
from langflow.components.prompts import PromptComponent
|
||||
from langflow.components.tools import SearchAPIComponent, YfinanceToolComponent
|
||||
from langflow.graph import Graph
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from langflow.components.data import FileComponent
|
||||
from langflow.components.inputs import ChatInput
|
||||
from langflow.components.input_output import ChatInput, ChatOutput
|
||||
from langflow.components.languagemodels import OpenAIModelComponent
|
||||
from langflow.components.outputs import ChatOutput
|
||||
from langflow.components.processing import ParseDataComponent
|
||||
from langflow.components.prompts import PromptComponent
|
||||
from langflow.graph import Graph
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
from langflow.components.crewai.crewai import CrewAIAgentComponent
|
||||
from langflow.components.crewai.hierarchical_crew import HierarchicalCrewComponent
|
||||
from langflow.components.crewai.hierarchical_task import HierarchicalTaskComponent
|
||||
from langflow.components.inputs import ChatInput
|
||||
from langflow.components.input_output import ChatInput, ChatOutput
|
||||
from langflow.components.languagemodels import OpenAIModelComponent
|
||||
from langflow.components.outputs import ChatOutput
|
||||
from langflow.components.prompts import PromptComponent
|
||||
from langflow.components.tools import SearchAPIComponent
|
||||
from langflow.graph import Graph
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from langflow.components.helpers.memory import MemoryComponent
|
||||
from langflow.components.inputs import ChatInput
|
||||
from langflow.components.input_output import ChatInput, ChatOutput
|
||||
from langflow.components.languagemodels import OpenAIModelComponent
|
||||
from langflow.components.outputs import ChatOutput
|
||||
from langflow.components.prompts import PromptComponent
|
||||
from langflow.graph import Graph
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
from langflow.components.crewai.sequential_crew import SequentialCrewComponent
|
||||
from langflow.components.crewai.sequential_task_agent import SequentialTaskAgentComponent
|
||||
from langflow.components.inputs import TextInputComponent
|
||||
from langflow.components.input_output import ChatOutput, TextInputComponent
|
||||
from langflow.components.languagemodels import OpenAIModelComponent
|
||||
from langflow.components.outputs import ChatOutput
|
||||
from langflow.components.prompts import PromptComponent
|
||||
from langflow.components.tools import SearchAPIComponent
|
||||
from langflow.graph import Graph
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ from textwrap import dedent
|
|||
|
||||
from langflow.components.data import FileComponent
|
||||
from langflow.components.embeddings import OpenAIEmbeddingsComponent
|
||||
from langflow.components.inputs import ChatInput
|
||||
from langflow.components.input_output import ChatInput, ChatOutput
|
||||
from langflow.components.languagemodels import OpenAIModelComponent
|
||||
from langflow.components.outputs import ChatOutput
|
||||
from langflow.components.processing import ParseDataComponent
|
||||
from langflow.components.processing.split_text import SplitTextComponent
|
||||
from langflow.components.prompts import PromptComponent
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ from blockbuster import blockbuster_ctx
|
|||
from dotenv import load_dotenv
|
||||
from fastapi.testclient import TestClient
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
from langflow.components.inputs import ChatInput
|
||||
from langflow.components.input_output import ChatInput
|
||||
from langflow.graph import Graph
|
||||
from langflow.initial_setup.constants import STARTER_FOLDER_NAME
|
||||
from langflow.main import create_app
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from langflow.components.inputs import ChatInput
|
||||
from langflow.components.input_output import ChatInput
|
||||
from langflow.components.processing.parse_json_data import ParseJSONDataComponent
|
||||
from langflow.schema import Data
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from langflow.components.inputs import ChatInput
|
||||
from langflow.components.input_output import ChatInput
|
||||
from langflow.memory import aget_messages
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from langflow.components.inputs import TextInputComponent
|
||||
from langflow.components.input_output import TextInputComponent
|
||||
from langflow.schema.message import Message
|
||||
|
||||
from tests.integration.utils import run_single_component
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue