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:
Edwin Jose 2025-05-30 13:58:34 -04:00 • committed by GitHub
commit dc222d93f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 130 additions and 138 deletions

View file

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

View 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

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

View 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,
)

View file

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

View file

@ -1,4 +0,0 @@
from .chat import ChatInput
from .text import TextInputComponent
__all__ = ["ChatInput", "TextInputComponent"]

View file

@ -1,4 +0,0 @@
from .chat import ChatOutput
from .text import TextOutputComponent
__all__ = ["ChatOutput", "TextOutputComponent"]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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