feature: add new playground events (#4280)

* Refactor sessionSelector component to improve session management and interaction

* Refactor IOModal component to remove unused code and improve session management

* fix typing error

* fix run chat input on component level

* prevent toogle visibility on session menu

* fix bug on rename session while in table view mode

* chore: Update setSelectedView prop type in sessionSelector component

* add first test version not working yet

* fix bug for renaming and deleting session

* refactor: Update sessionSelector component to handle session changes

* improve test

* fix rename session multiple session bugs

* change visible session from array to string

* chore: Update editMessageField component to include margin-right for text span

* [autofix.ci] apply automated fixes

* Update down_revision in Alembic migration script

* Refactor IOModal component to simplify session visibility handling

* Add new schema for playground events with various event types

- Introduce `PlaygroundEvent` base class and specific event types: `MessageEvent`, `ErrorEvent`, `WarningEvent`, `InfoEvent`, and `TokenEvent`.
- Implement `ContentBlock` class for flexible content handling.
- Add factory functions to create different event types.
- Utilize Pydantic for data validation and serialization.

* Add PlaygroundEvent to LoggableType in log schema

* Add timestamp validation utility to schema utils and refactor Message class to use it

* Add custom serialization for datetime and Decimal in data schema

* Add session_id handling to graph and chat API

- Introduce `_session_id` attribute in the graph base class.
- Implement getter and setter for `session_id` property.
- Update chat API to set `session_id` from inputs if available.

* Add error handling for event creation in EventManager

- Integrate `create_event_by_type` to generate playground events.
- Add try-except block to handle `TypeError` during event creation and log the error using `loguru`.
- Raise any other exceptions encountered during event creation.

* Enhance session ID handling in vertex base logic

* Add message handling and streaming capabilities to custom component

- Introduced `send_message` method to handle message sending with optional formatting and content blocks.
- Implemented `_store_message`, `_send_message_event`, and `_should_stream_message` for message storage and event management.
- Added `_update_stored_message` and `_stream_message` to support message streaming and updating.
- Included `_handle_async_iterator` and `_process_chunk` for processing message chunks from iterators.

* Add new schema types and properties for chat and messages components

* Add customizable chat message styles and icons in IOModal component

* Add support for handling Playground events in chat view

- Introduced `PlaygroundEvent` type to manage different event types such as message, error, warning, info, and token.
- Implemented `handlePlaygroundEvent` function to process and update chat history based on event type.
- Enhanced file parsing logic to handle empty strings and parsing errors.
- Added default values for `background_color` and `text_color` in chat messages.

* revert changes made to bypass ruff-check

* revert changes made to bypass ruff-check

* Refactor file parsing logic in ChatView component

* fix ui problem

* Refactor feature flags and chat view components

- Enable new IO modal in feature flags
- Update imports and remove unused code in newChatMessage component
- Add error icon and error message in ChatMessage component
- Update lockChat icon position in newChatView component
- Import OctagonAlert icon in styleUtils

* Refactor IOModal newModal component styles

* Refactor styles in EditMessageField and ChatMessage components

* Refactor styles in EditMessageField component

* Refactor styles in ChatMessage component to fix UI problem

* Refactor styles in UploadFileButton component and add image attachment tooltip

* Refactor styles in ChatCodeTabComponent to update language text color

* fix padding in other field views

* Refactor styles in ChatComponent and add tooltip to the playground

* Refactor styles in constants.ts to remove unused input and output types

* Refactor session selector styles

* Refactor chatView component styles

* update colors to use variables

* Refactor code tab component and update code styling

* Refactor buttonSendWrapper component styles

* update colors to use variables

* update colors to use variables

* Refactor chatComponent and chatInput styles

* update colors to use variables

* chore: add new attributes to model definition

* chore: generate database migration

* chore: add MetaData and ContentBlock models and update Message schema

* chore: add error handling to build_results and adjust message event handling

* chore: deserialize meta_data and content_blocks fields in add_messagetables

* raw

* Refactor message and chat types to include category, meta_data, and content_blocks

* raw

* Refactor error handling in component.py and newChatMessage.tsx

* add source to bot messages

* raw

* Refactor chatMessage component to include meta_data icon and source

* improve icon, background color, text color handling in chat message

* add edit flag correctly

* use svg icon logo instead of chain emoji

* add text effect

* update error colors

* fix error aligment

* add text shimmer effect

* improve is running style

* Add support for importing SVG files in Vite environment

* Add chatLogoIcon component with Chain logo

* Refactor chat message component to use chatLogoIcon component

* Refactor chatView component to use chatLogoIcon component

* remove from empty chat

* update text sizes and style

* remove icon in message logs

* fix stop button color

* update background color and sender name size

* prevent list index out of range error

* Fix onBlur event in EditMessageField component

* improve edit message field

* improve edit background behaviour

* Update chat input styling for better user experience

* Refactor ChatCodeTabComponent to remove redundant "Copy" text in button

* Refactor code to use CSS variables for background color in code blocks

* Refactor ChatCodeTabComponent to update code block styling

* Refactor code to update code block styling and improve chat input styling

* prevent Flow Running after error rendering

* Refactor code to add IOModalOpen state and setter function

* Refactor code to add IOModalOpen state and setter function

* Refactor code to remove console.log statement

* update icon and padding

* Refactor code to retrieve connected model name in ChatComponent

* run formatter

* fix stop button glitch

* update max width

* Refactor GithubStarButton component to handle null value for stars

* Refactor tailwind.config.mjs to add utility classes for fading text truncation

* improve faded truncate function

* Refactor ChatView component to handle null value for chat history category

* [autofix.ci] apply automated fixes

* Refactor DialogContent component to improve close button styling

* Refactor IOModal component to improve sidebar behavior and styling

* Refactor ChatView component to remove unused code and improve performance

* Refactor session selector component to add tooltip for options

* [autofix.ci] apply automated fixes

* update several icon sizes and hover states

* [autofix.ci] apply automated fixes

* Refactor IOModal component to improve sidebar behavior and styling

* Refactor IOModal component to handle sidebar behavior on window resize

* [autofix.ci] apply automated fixes

* fix error related to content blocks being null

* [autofix.ci] apply automated fixes

* add component id as sender to the message

* add component id as source to the message

* Add fitViewNode function to FlowStoreType

* enable closing chat on error message

* Add ClickableLinks component for rendering clickable URLs in chat messages

* add error message transition

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* use IsPlayground flag

* Remove console.log statement in setIOModalOpen function

* update max-w

* prevent breaking the buttons on small screen

* [autofix.ci] apply automated fixes

* remove underline when component is not clicable

* Refactor IOModal component to remove unnecessary props and update usage

- Remove the "isPlayground" prop from the IOModal component in cardComponent and storeCardComponent
- Update the usage of the IOModal component in chatComponent and newModal to use the "canvasOpen" prop instead of "isPlayground"

* fix html missing closing tag error

* [autofix.ci] apply automated fixes

* Refactor PlaygroundPage component to improve flow initialization and data fetching

* [autofix.ci] apply automated fixes

* Refactor cardComponent and storeCardComponent to comment out playground buttons

* Refactor message model to replace 'meta_data' with 'properties' and update related schema and components

* Add new schema for content types with Pydantic models

* Add ContentBlock schema with support for multiple content types

* Refactor: Import ContentBlock from langflow.schema.content_block instead of defining locally

* Add SendMessageFunctionType protocol to log schema for message handling

* Refactor chat message model to replace 'meta_data' with 'properties' and update related components

* Add ContentBlock import and enhance send_message method in component.py

- Import ContentBlock from langflow.schema.content_block.
- Replace 'meta_data' with 'properties' in message construction.
- Update send_message method to handle optional Message and text parameters.

* Add async on_chain_start method to callback for logging chain start events

* Refactor add_messagetables function to replace 'meta_data' with 'properties' in message processing

* Fix typo in 'traceback' property name in ErrorDetails interface

* Add condition to check chat history length before rendering chat lock

* Refactor error content structure and enhance message event handling

* Add `update_messages` function to handle message updates in memory module

* Add check for 'id' attribute in stored_message for message validation

* Add new content types and TypeAlias for schema in content_types.py

* Add custom serialization for UUID and BaseModel types in data schema

* Refactor `ContentType` to use `ContentTypes` union and update `content` field in `ContentBlock` model

* Update model to use specific types for properties and content_blocks fields

* Change return type of 'serialize_params' method to string

* Add sender fields and properties handling to PlaygroundEvent and create_message function

* Refactor chat message component to use 'properties' instead of 'meta_data' for chat attributes

* Refactor message handling to improve error categorization and session management

* Enhance ContentBlock initialization to update model fields with defaults

* Add new content types and update ContentBlock interface in chat types

- Introduced BaseContent interface and various content types: ErrorContent, TextContent, MediaContent, JSONContent, CodeContent, ToolStartContent, ToolEndContent, and ToolErrorContent.
- Created a union type ContentType for all content types.
- Updated ContentBlock interface to include new content structure and additional fields.

* Refactor error handling in chat messages to use `ErrorContent` type

* Add ErrorMessage class for handling error-specific messages in schema

* Add type annotations for cache and logs in custom_component.py

* Refactor error handling in custom component to use `send_error` method

* Add customizable properties for chat components

- Introduced new properties: `background_color`, `chat_icon`, and `text_color` to chat components across various starter projects.
- These properties allow for customization of the chat message appearance, including icon and text styling.
- Updated the `ChatInput` and `ChatOutput` components to handle these new properties and include them in the message response.

* Add 'category' field to MessageEvent and update create_message function

* Remove unused message handling methods from ChatComponent

* Refactor message event handling in chat module

* Add 'category' field to Message creation and pass 'id_' in _send_message_event

* Add 'category' field with Literal type to Message schema and default handling in chat IO

* Refactor message event handling in chat module

* Refactor buttonSendWrapper component to add data-testid attribute

* Refactor ChatComponent to replace 'store_message' with 'send_message' method

* refactor: streamline message handling in Component

Update message processing to utilize new methods for improved clarity and efficiency. Enhance event data management to ensure accuracy and consistency in message updates.

* feat: Update input handling and output storage for chat components

Enhance chat input and output components to improve message handling, including updates to storage and display properties. This facilitates better user experience and message management in various agent projects.

* Ensure 'id' field is set in message data dictionary if not present

* refactor: Simplify property retrieval from source component in ChatComponent

* refactor: Replace property retrieval logic with a dedicated method in ChatOutput

* Refactor `TokenEvent` class and add `timestamp` field with default factory

* refactor: Simplify message processing by removing unused parameters in async handling

* Refactor message text update logic in messagesStore

* Refactor border styling in ChatInput component

* refactor: Consolidate properties handling in PlaygroundEvent and create_message function

* refactor: Update timestamp formatting to include timezone information across multiple models

* refactor: Add StreamingError exception for better error handling in streaming components

* refactor: Add source_display_name attribute to Properties model for enhanced data representation

* refactor: Enhance ChatComponent to return source display name and update properties handling in ChatOutput

* refactor: Improve error handling in Component class and enhance create_error function with timestamp support

* feat: enhance ChatOutput component for improved message handling

Update the message_response method to include source display name and improve message storage functionality across various project templates. This enhances clarity and ensures consistent behavior of chat components.

* feat: add delete_message function to remove messages by ID from the monitor service

* Add error handling for message streaming and implement message deletion on failure

* feat: Enhance message streaming and event handling

Improve the streaming process to send message events only for non-streaming messages and ensure the initial message is sent correctly when processing chunks, enhancing the overall message handling flow.

* improve UI

* Refactor buttonSendWrapper component to add data-testid attribute and update tests

* fix edited tag

* add tooltip to add button

* fix bug after merge

* update examples

* updateinput without chat

* fix some frontend tests

* fix test

* update test

* Update fileUploadComponent.spec.ts to fix test and update input without chat

* refactor event creation functions and enhance ErrorEvent structure

* refactor ErrorMessage to use Source for sender and component details

* refactor Properties to use Source model for source details and add validation

* refactor ChatMessage component for improved readability and structure

* refactor ChatMessageType to use PropertiesType for improved structure and clarity

* Add Source ID to message properties and update exception handling

- Updated `message_response` method to include `source_id` in the `Properties` object for better traceability.
- Modified `StreamingError` to use `Source` object instead of `component_name` for more detailed error context.
- Adjusted `get_properties_from_source_component` to return `source_id` along with other properties.

* Add 'Source' property to error handling in custom_component module

- Introduced 'Source' property to enhance error message details.
- Updated error handling to include 'source' instead of 'display_name'.
- Modified exception raising to use 'source' for better traceability.

* Enhance event ID generation with event type prefix in `send_event` method

* Refactor ChatOutput class to use Properties and Source schemas for message properties

* update style in tailwind and add fallback for nullable values

* fix playground test

* Update message handling in Component class to send modified message copy on first chunk

* Add flow_id and session_id to ErrorEvent creation function

* Update content_blocks initialization and add flow_id parameter to ErrorMessage

* feat: include vertex ID in class instantiation

Pass the vertex ID to the custom component during instantiation to facilitate event tracking or management capabilities.

* fix: update message streaming logic to handle None messages and refactor _stream_message parameters

* feat: include flow_id in ChatOutput initialization

Add flow_id to the ChatOutput class to improve the tracking of conversational flows, enhancing the context management within the chat system.

* fix: update chat message source display to use 'source' instead of 'display_name'

* feat: add flow_id to message initialization in ChatOutput class

* fix: handle JSON parsing with type checks for message properties

Refine JSON loading to ensure proper type handling for message properties and content blocks, enhancing stability and preventing potential errors with non-string inputs.

* refactor: update logging structure and message handling

Improve the output logs to support logging multiple entries and ensure robust message streaming with type checking. Adjust error handling parameters for consistency.

* feat: restrict event types in registration and sending

Enhance event management by explicitly defining allowed event types, improving code clarity and reducing potential errors. This ensures only valid event types are registered and sent, leading to more robust event handling.

* Update `logs` attribute to store lists of `Log` objects in `Vertex` class

* feat: introduce TypedDicts for ContentBlock and Properties, update default values in Message model

* fix: restrict event types in send_event method to improve data validation

* Set default values for 'id', 'display_name', and 'source' fields in Source schema

* [autofix.ci] apply automated fixes

* fix: update query to use equality check for error messages

* make lint

* update simple agent test

* test: enhance EventManager tests for event_id validation

* feat: add background, chat icon, and text color properties to component toolkit

* fix: update LogComponent name to display_name for consistency

* remove playground from new cards on main page

---------

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: italojohnny <italojohnnydosanjos@gmail.com>
This commit is contained in:
anovazzi1 2024-11-04 20:58:16 -03:00 • committed by GitHub
commit 0d4ecd4601
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
106 changed files with 3938 additions and 1162 deletions

View file

@ -0,0 +1,53 @@
"""event_error
Revision ID: 1eab2c3eb45e
Revises: eb5e72293a8e
Create Date: 2024-10-24 12:03:24.118937
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import sqlite
from sqlalchemy.engine.reflection import Inspector
# revision identifiers, used by Alembic.
revision: str = '1eab2c3eb45e'
down_revision: Union[str, None] = 'eb5e72293a8e'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
table_names = inspector.get_table_names() # noqa
column_names = [column["name"] for column in inspector.get_columns("message")]
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('message', schema=None) as batch_op:
if "properties" not in column_names:
batch_op.add_column(sa.Column('properties', sa.JSON(), nullable=True))
if "category" not in column_names:
batch_op.add_column(sa.Column('category', sa.Text(), nullable=True))
if "content_blocks" not in column_names:
batch_op.add_column(sa.Column('content_blocks', sa.JSON(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
table_names = inspector.get_table_names() # noqa
column_names = [column["name"] for column in inspector.get_columns("message")]
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('message', schema=None) as batch_op:
if "content_blocks" in column_names:
batch_op.drop_column('content_blocks')
if "category" in column_names:
batch_op.drop_column('category')
if "properties" in column_names:
batch_op.drop_column('properties')
# ### end Alembic commands ###

View file

@ -177,6 +177,9 @@ async def build_flow(
else: else:
first_layer = graph.sort_vertices() first_layer = graph.sort_vertices()
if inputs is not None and hasattr(inputs, "session") and inputs.session is not None:
graph.session_id = inputs.session
for vertex_id in first_layer: for vertex_id in first_layer:
graph.run_manager.add_to_vertices_being_run(vertex_id) graph.run_manager.add_to_vertices_being_run(vertex_id)

View file

@ -13,6 +13,33 @@ class AgentAsyncHandler(AsyncCallbackHandler):
def __init__(self, log_function: LogFunctionType | None = None): def __init__(self, log_function: LogFunctionType | None = None):
self.log_function = log_function self.log_function = log_function
async def on_chain_start(
self,
serialized: dict[str, Any],
inputs: dict[str, Any],
*,
run_id: UUID,
parent_run_id: UUID | None = None,
tags: list[str] | None = None,
metadata: dict[str, Any] | None = None,
**kwargs: Any,
) -> None:
if self.log_function is None:
return
self.log_function(
{
"type": "chain_start",
"serialized": serialized,
"inputs": inputs,
"run_id": run_id,
"parent_run_id": parent_run_id,
"tags": tags,
"metadata": metadata,
**kwargs,
},
name="Chain Start",
)
async def on_tool_start( async def on_tool_start(
self, self,
serialized: dict[str, Any], serialized: dict[str, Any],

View file

@ -1,86 +1,15 @@
from collections.abc import AsyncIterator, Iterator
from typing import cast from typing import cast
from langflow.custom import Component from langflow.custom import Component
from langflow.memory import store_message from langflow.memory import store_message
from langflow.schema import Data from langflow.schema import Data
from langflow.schema.message import Message from langflow.schema.message import Message
from langflow.services.database.models.message.crud import update_message
from langflow.utils.async_helpers import run_until_complete
class ChatComponent(Component): 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."
def store_message(self, message: Message) -> Message:
messages = store_message(message, flow_id=self.graph.flow_id)
if len(messages) != 1:
msg = "Only one message can be stored at a time."
raise ValueError(msg)
stored_message = messages[0]
self._send_message_event(stored_message)
if self._should_stream_message(stored_message, message):
complete_message = self._stream_message(message, stored_message.id)
stored_message = self._update_stored_message(stored_message.id, complete_message)
self.status = stored_message
return stored_message
def _send_message_event(self, message: Message) -> None:
if hasattr(self, "_event_manager") and self._event_manager:
self._event_manager.on_message(data=message.data)
def _should_stream_message(self, stored_message: Message, original_message: Message) -> bool:
return bool(
hasattr(self, "_event_manager")
and self._event_manager
and stored_message.id
and not isinstance(original_message.text, str)
)
def _update_stored_message(self, message_id: str, complete_message: str) -> Message:
message_table = update_message(message_id=message_id, message={"text": complete_message})
updated_message = Message(**message_table.model_dump())
self.vertex.added_message = updated_message
return updated_message
def _process_chunk(self, chunk: str, complete_message: str, message: Message, message_id: str) -> str:
complete_message += chunk
if self._event_manager:
self._event_manager.on_token(
data={
"text": complete_message,
"chunk": chunk,
"sender": message.sender,
"sender_name": message.sender_name,
"id": str(message_id),
}
)
return complete_message
async def _handle_async_iterator(self, iterator: AsyncIterator, message: Message, message_id: str) -> str:
complete_message = ""
async for chunk in iterator:
complete_message = self._process_chunk(chunk.content, complete_message, message, message_id)
return complete_message
def _stream_message(self, message: Message, message_id: str) -> str:
iterator = message.text
if not isinstance(iterator, AsyncIterator | Iterator):
msg = "The message must be an iterator or an async iterator."
raise TypeError(msg)
if isinstance(iterator, AsyncIterator):
return run_until_complete(self._handle_async_iterator(iterator, message, message_id))
complete_message = ""
for chunk in iterator:
complete_message = self._process_chunk(chunk.content, complete_message, message, message_id)
return complete_message
def build_with_data( def build_with_data(
self, self,
*, *,
@ -105,9 +34,31 @@ class ChatComponent(Component):
def _create_message(self, input_value, sender, sender_name, files, session_id) -> Message: def _create_message(self, input_value, sender, sender_name, files, session_id) -> Message:
if isinstance(input_value, Data): if isinstance(input_value, Data):
return Message.from_data(input_value) return Message.from_data(input_value)
return Message(text=input_value, sender=sender, sender_name=sender_name, files=files, session_id=session_id) return Message(
text=input_value,
sender=sender,
sender_name=sender_name,
files=files,
session_id=session_id,
category="message",
)
def _send_messages_events(self, messages) -> None: def _send_messages_events(self, messages) -> None:
if hasattr(self, "_event_manager") and self._event_manager: if hasattr(self, "_event_manager") and self._event_manager:
for stored_message in messages: for stored_message in messages:
self._event_manager.on_message(data=stored_message.data) id_ = stored_message.id
self._send_message_event(message=stored_message, id_=id_)
def get_properties_from_source_component(self):
if self.vertex.incoming_edges:
source_id = self.vertex.incoming_edges[0].source_id
_source_vertex = self.graph.get_vertex(source_id)
component = _source_vertex.custom_component
source = component.display_name
icon = component.icon
possible_attributes = ["model_name", "model_id", "model"]
for attribute in possible_attributes:
if hasattr(component, attribute) and getattr(component, attribute):
return getattr(component, attribute), icon, source, component._id
return source, icon, component.display_name, component._id
return None, None, None, None

View file

@ -55,21 +55,43 @@ class ChatInput(ChatComponent):
advanced=True, advanced=True,
is_list=True, is_list=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 = [ outputs = [
Output(display_name="Message", name="message", method="message_response"), Output(display_name="Message", name="message", method="message_response"),
] ]
def message_response(self) -> Message: def message_response(self) -> Message:
_background_color = self.background_color
_text_color = self.text_color
_icon = self.chat_icon
message = Message( message = Message(
text=self.input_value, text=self.input_value,
sender=self.sender, sender=self.sender,
sender_name=self.sender_name, sender_name=self.sender_name,
session_id=self.session_id, session_id=self.session_id,
files=self.files, 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: if self.session_id and isinstance(message, Message) and self.should_store_message:
stored_message = self.store_message( stored_message = self.send_message(
message, message,
) )
self.message.value = stored_message self.message.value = stored_message

View file

@ -2,6 +2,7 @@ from langflow.base.io.chat import ChatComponent
from langflow.inputs import BoolInput from langflow.inputs import BoolInput
from langflow.io import DropdownInput, MessageTextInput, Output from langflow.io import DropdownInput, MessageTextInput, Output
from langflow.schema.message import Message from langflow.schema.message import Message
from langflow.schema.properties import Properties, Source
from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER
@ -52,20 +53,64 @@ class ChatOutput(ChatComponent):
advanced=True, advanced=True,
info="Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.", 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,
),
] ]
outputs = [ outputs = [
Output(display_name="Message", name="message", method="message_response"), 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:
source_dict["source"] = source
return Source(**source_dict)
def message_response(self) -> Message: def message_response(self) -> Message:
_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
message = Message( message = Message(
text=self.input_value, text=self.input_value,
sender=self.sender, sender=self.sender,
sender_name=self.sender_name, sender_name=self.sender_name,
session_id=self.session_id, session_id=self.session_id,
flow_id=self.graph.flow_id,
properties=Properties(
source=self._build_source(_source_id, _display_name, _source),
icon=_icon,
background_color=_background_color,
text_color=_text_color,
),
) )
if self.session_id and isinstance(message, Message) and self.should_store_message: if self.session_id and isinstance(message, Message) and self.should_store_message:
stored_message = self.store_message( stored_message = self.send_message(
message, message,
) )
self.message.value = stored_message self.message.value = stored_message

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import ast import ast
import asyncio import asyncio
import inspect import inspect
from collections.abc import AsyncIterator, Iterator
from copy import deepcopy from copy import deepcopy
from textwrap import dedent from textwrap import dedent
from typing import TYPE_CHECKING, Any, ClassVar, get_type_hints from typing import TYPE_CHECKING, Any, ClassVar, get_type_hints
@ -13,12 +14,15 @@ from pydantic import BaseModel, ValidationError
from langflow.base.tools.constants import TOOL_OUTPUT_NAME from langflow.base.tools.constants import TOOL_OUTPUT_NAME
from langflow.custom.tree_visitor import RequiredInputsVisitor from langflow.custom.tree_visitor import RequiredInputsVisitor
from langflow.exceptions.component import StreamingError
from langflow.field_typing import Tool # noqa: TCH001 Needed by _add_toolkit_output from langflow.field_typing import Tool # noqa: TCH001 Needed by _add_toolkit_output
from langflow.graph.state.model import create_state_model from langflow.graph.state.model import create_state_model
from langflow.helpers.custom import format_type from langflow.helpers.custom import format_type
from langflow.memory import delete_message, store_message, update_messages
from langflow.schema.artifact import get_artifact_type, post_process_raw from langflow.schema.artifact import get_artifact_type, post_process_raw
from langflow.schema.data import Data from langflow.schema.data import Data
from langflow.schema.message import Message from langflow.schema.message import ErrorMessage, Message
from langflow.schema.properties import Source
from langflow.services.settings.feature_flags import FEATURE_FLAGS from langflow.services.settings.feature_flags import FEATURE_FLAGS
from langflow.services.tracing.schema import Log from langflow.services.tracing.schema import Log
from langflow.template.field.base import UNDEFINED, Input, Output from langflow.template.field.base import UNDEFINED, Input, Output
@ -59,7 +63,7 @@ class Component(CustomComponent):
inputs: list[InputTypes] = [] inputs: list[InputTypes] = []
outputs: list[Output] = [] outputs: list[Output] = []
code_class_base_inheritance: ClassVar[str] = "Component" code_class_base_inheritance: ClassVar[str] = "Component"
_output_logs: dict[str, Log] = {} _output_logs: dict[str, list[Log]] = {}
_current_output: str = "" _current_output: str = ""
_metadata: dict = {} _metadata: dict = {}
@ -736,14 +740,30 @@ class Component(CustomComponent):
async def _build_without_tracing(self): async def _build_without_tracing(self):
return await self._build_results() return await self._build_results()
async def build_results( async def build_results(self):
self, """Build the results of the component."""
): try:
if self._tracing_service: if self._tracing_service:
return await self._build_with_tracing() return await self._build_with_tracing()
return await self._build_without_tracing() return await self._build_without_tracing()
except StreamingError as e:
self.send_error(
exception=e.cause,
session_id=self.graph.session_id,
trace_name=getattr(self, "trace_name", None),
source=e.source,
)
raise e.cause # noqa: B904
except Exception as e:
self.send_error(
exception=e,
session_id=self.graph.session_id,
source=Source(id=self._id, display_name=self.display_name, source=self.display_name),
trace_name=getattr(self, "trace_name", None),
)
raise
async def _build_results(self): async def _build_results(self) -> tuple[dict, dict]:
_results = {} _results = {}
_artifacts = {} _artifacts = {}
if hasattr(self, "outputs"): if hasattr(self, "outputs"):
@ -881,3 +901,132 @@ class Component(CustomComponent):
def _append_tool_output(self) -> None: def _append_tool_output(self) -> None:
if next((output for output in self.outputs if output.name == TOOL_OUTPUT_NAME), None) is None: if next((output for output in self.outputs if output.name == TOOL_OUTPUT_NAME), None) is None:
self.outputs.append(Output(name=TOOL_OUTPUT_NAME, display_name="Tool", method="to_toolkit", types=["Tool"])) self.outputs.append(Output(name=TOOL_OUTPUT_NAME, display_name="Tool", method="to_toolkit", types=["Tool"]))
def send_message(self, message: Message, id_: str | None = None):
if self.graph.session_id and message is not None and message.session_id is None:
message.session_id = self.graph.session_id
stored_message = self._store_message(message)
self._stored_message_id = stored_message.id
try:
complete_message = ""
if (
self._should_stream_message(stored_message, message)
and message is not None
and isinstance(message.text, AsyncIterator | Iterator)
):
complete_message = self._stream_message(message.text, stored_message)
stored_message.text = complete_message
stored_message = self._update_stored_message(stored_message)
else:
# Only send message event for non-streaming messages
self._send_message_event(stored_message, id_=id_)
except Exception:
# remove the message from the database
delete_message(stored_message.id)
raise
self.status = stored_message
return stored_message
def _store_message(self, message: Message) -> Message:
messages = store_message(message, flow_id=self.graph.flow_id)
if len(messages) != 1:
msg = "Only one message can be stored at a time."
raise ValueError(msg)
return messages[0]
def _send_message_event(self, message: Message, id_: str | None = None):
if hasattr(self, "_event_manager") and self._event_manager:
data_dict = message.data.copy() if hasattr(message, "data") else message.model_dump()
if id_ and not data_dict.get("id"):
data_dict["id"] = id_
category = data_dict.get("category", None)
match category:
case "error":
self._event_manager.on_error(data=data_dict)
case _:
self._event_manager.on_message(data=data_dict)
def _should_stream_message(self, stored_message: Message, original_message: Message) -> bool:
return bool(
hasattr(self, "_event_manager")
and self._event_manager
and stored_message.id
and not isinstance(original_message.text, str)
)
def _update_stored_message(self, stored_message: Message) -> Message:
message_tables = update_messages(stored_message)
if len(message_tables) != 1:
msg = "Only one message can be updated at a time."
raise ValueError(msg)
message_table = message_tables[0]
updated_message = Message(**message_table.model_dump())
self.vertex._added_message = updated_message
return updated_message
def _stream_message(self, iterator: AsyncIterator | Iterator, message: Message) -> str:
if not isinstance(iterator, AsyncIterator | Iterator):
msg = "The message must be an iterator or an async iterator."
raise TypeError(msg)
if isinstance(iterator, AsyncIterator):
return run_until_complete(self._handle_async_iterator(iterator, message.id, message))
try:
complete_message = ""
first_chunk = True
for chunk in iterator:
complete_message = self._process_chunk(
chunk.content, complete_message, message.id, message, first_chunk=first_chunk
)
first_chunk = False
except Exception as e:
raise StreamingError(cause=e, source=message.properties.source) from e
else:
return complete_message
async def _handle_async_iterator(self, iterator: AsyncIterator, message_id: str, message: Message) -> str:
complete_message = ""
first_chunk = True
async for chunk in iterator:
complete_message = self._process_chunk(
chunk.content, complete_message, message_id, message, first_chunk=first_chunk
)
first_chunk = False
return complete_message
def _process_chunk(
self, chunk: str, complete_message: str, message_id: str, message: Message, *, first_chunk: bool = False
) -> str:
complete_message += chunk
if self._event_manager:
if first_chunk:
# Send the initial message only on the first chunk
msg_copy = message.model_copy()
msg_copy.text = complete_message
self._send_message_event(msg_copy, id_=message_id)
self._event_manager.on_token(
data={
"chunk": chunk,
"id": str(message_id),
}
)
return complete_message
def send_error(
self,
exception: Exception,
session_id: str,
trace_name: str,
source: Source,
) -> None:
"""Send an error message to the frontend."""
error_message = ErrorMessage(
flow_id=self.graph.flow_id,
exception=exception,
session_id=session_id,
trace_name=trace_name,
source=source,
)
self.send_message(error_message)

View file

@ -83,7 +83,7 @@ class CustomComponent(BaseComponent):
_flows_data: list[Data] | None = None _flows_data: list[Data] | None = None
_outputs: list[OutputValue] = [] _outputs: list[OutputValue] = []
_logs: list[Log] = [] _logs: list[Log] = []
_output_logs: dict[str, Log] = {} _output_logs: dict[str, list[Log] | Log] = {}
_tracing_service: TracingService | None = None _tracing_service: TracingService | None = None
_tree: dict | None = None _tree: dict | None = None
@ -93,8 +93,8 @@ class CustomComponent(BaseComponent):
Args: Args:
**data: Additional keyword arguments to initialize the custom component. **data: Additional keyword arguments to initialize the custom component.
""" """
self.cache = TTLCache(maxsize=1024, ttl=60) self.cache: TTLCache = TTLCache(maxsize=1024, ttl=60)
self._logs = [] self._logs: list[Log] = []
self._results: dict = {} self._results: dict = {}
self._artifacts: dict = {} self._artifacts: dict = {}
super().__init__(**data) super().__init__(**data)

View file

@ -4,12 +4,14 @@ import json
import time import time
import uuid import uuid
from functools import partial from functools import partial
from typing import Literal
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from loguru import logger
from typing_extensions import Protocol from typing_extensions import Protocol
from langflow.schema.artifact import CUSTOM_ENCODERS
from langflow.schema.log import LoggableType from langflow.schema.log import LoggableType
from langflow.schema.playground_events import create_event_by_type
class EventCallback(Protocol): class EventCallback(Protocol):
@ -40,7 +42,12 @@ class EventManager:
msg = "Callback must have exactly 3 parameters: manager, event_type, and data" msg = "Callback must have exactly 3 parameters: manager, event_type, and data"
raise ValueError(msg) raise ValueError(msg)
def register_event(self, name: str, event_type: str, callback: EventCallback | None = None) -> None: def register_event(
self,
name: str,
event_type: Literal["message", "error", "warning", "info", "token"],
callback: EventCallback | None = None,
) -> None:
if not name: if not name:
msg = "Event name cannot be empty" msg = "Event name cannot be empty"
raise ValueError(msg) raise ValueError(msg)
@ -53,10 +60,17 @@ class EventManager:
_callback = partial(callback, manager=self, event_type=event_type) _callback = partial(callback, manager=self, event_type=event_type)
self.events[name] = _callback self.events[name] = _callback
def send_event(self, *, event_type: str, data: LoggableType) -> None: def send_event(self, *, event_type: Literal["message", "error", "warning", "info", "token"], data: LoggableType):
jsonable_data = jsonable_encoder(data, custom_encoder=CUSTOM_ENCODERS) try:
if isinstance(data, dict) and event_type in ["message", "error", "warning", "info", "token"]:
data = create_event_by_type(event_type, **data)
except TypeError as e:
logger.debug(f"Error creating playground event: {e}")
except Exception:
raise
jsonable_data = jsonable_encoder(data)
json_data = {"event": event_type, "data": jsonable_data} json_data = {"event": event_type, "data": jsonable_data}
event_id = uuid.uuid4() event_id = f"{event_type}-{uuid.uuid4()}"
str_data = json.dumps(json_data) + "\n\n" str_data = json.dumps(json_data) + "\n\n"
self.queue.put_nowait((event_id, str_data.encode("utf-8"), time.time())) self.queue.put_nowait((event_id, str_data.encode("utf-8"), time.time()))

View file

@ -1,6 +1,17 @@
# Create an exception class that receives the message and the formatted traceback # Create an exception class that receives the message and the formatted traceback
from langflow.schema.properties import Source
class ComponentBuildError(Exception): class ComponentBuildError(Exception):
def __init__(self, message: str, formatted_traceback: str): def __init__(self, message: str, formatted_traceback: str):
self.message = message self.message = message
self.formatted_traceback = formatted_traceback self.formatted_traceback = formatted_traceback
super().__init__(message) super().__init__(message)
class StreamingError(Exception):
def __init__(self, cause: Exception, source: Source):
self.cause = cause
self.source = source
super().__init__(cause)

View file

@ -93,6 +93,7 @@ class Graph:
self.has_session_id_vertices: list[str] = [] self.has_session_id_vertices: list[str] = []
self._sorted_vertices_layers: list[list[str]] = [] self._sorted_vertices_layers: list[list[str]] = []
self._run_id = "" self._run_id = ""
self._session_id = ""
self._start_time = datetime.now(timezone.utc) self._start_time = datetime.now(timezone.utc)
self.inactivated_vertices: set = set() self.inactivated_vertices: set = set()
self.activated_vertices: list[str] = [] self.activated_vertices: list[str] = []
@ -134,6 +135,14 @@ class Graph:
msg = "You must provide both input and output components" msg = "You must provide both input and output components"
raise ValueError(msg) raise ValueError(msg)
@property
def session_id(self):
return self._session_id
@session_id.setter
def session_id(self, value: str):
self._session_id = value
@property @property
def state_model(self): def state_model(self):
if not self._state_model: if not self._state_model:
@ -780,7 +789,7 @@ class Graph:
Returns: Returns:
dict: The metadata of the graph. dict: The metadata of the graph.
""" """
time_format = "%Y-%m-%d %H:%M:%S" time_format = "%Y-%m-%d %H:%M:%S %Z"
return { return {
"start_time": self._start_time.strftime(time_format), "start_time": self._start_time.strftime(time_format),
"end_time": self._end_time.strftime(time_format), "end_time": self._end_time.strftime(time_format),

View file

@ -94,7 +94,7 @@ class Vertex:
self.result: ResultData | None = None self.result: ResultData | None = None
self.results: dict[str, Any] = {} self.results: dict[str, Any] = {}
self.outputs_logs: dict[str, OutputValue] = {} self.outputs_logs: dict[str, OutputValue] = {}
self.logs: dict[str, Log] = {} self.logs: dict[str, list[Log]] = {}
self.has_cycle_edges = False self.has_cycle_edges = False
try: try:
self.is_interface_component = self.vertex_type in InterfaceComponentTypes self.is_interface_component = self.vertex_type in InterfaceComponentTypes
@ -227,6 +227,7 @@ class Vertex:
self.output = self.data["node"]["base_classes"] self.output = self.data["node"]["base_classes"]
self.display_name: str = self.data["node"].get("display_name", self.id.split("-")[0]) self.display_name: str = self.data["node"].get("display_name", self.id.split("-")[0])
self.icon: str = self.data["node"].get("icon", self.id.split("-")[0])
self.description: str = self.data["node"].get("description", "") self.description: str = self.data["node"].get("description", "")
self.frozen: bool = self.data["node"].get("frozen", False) self.frozen: bool = self.data["node"].get("frozen", False)
@ -803,9 +804,9 @@ class Vertex:
inputs inputs
and isinstance(inputs, dict) and isinstance(inputs, dict)
and "input_value" in inputs and "input_value" in inputs
and inputs["input_value"] is not None and inputs.get("input_value") is not None
): ):
chat_input.update({"input_value": inputs[INPUT_FIELD_NAME]}) chat_input.update({"input_value": inputs.get(INPUT_FIELD_NAME, "")})
if files: if files:
chat_input.update({"files": files}) chat_input.update({"files": files})

View file

@ -159,6 +159,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -175,7 +217,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n _background_color = self.background_color\n _text_color = self.text_color\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\"background_color\": _background_color, \"text_color\": _text_color, \"icon\": _icon},\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"files": { "files": {
"_input_type": "FileInput", "_input_type": "FileInput",
@ -318,6 +360,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },
@ -382,6 +445,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -398,7 +503,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Properties, Source\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, _id: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if _id:\n source_dict[\"id\"] = _id\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n def message_response(self) -> Message:\n _source, _icon, _display_name, _source_id = self.get_properties_from_source_component()\n _background_color = self.background_color\n _text_color = self.text_color\n if self.chat_icon:\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n flow_id=self.graph.flow_id,\n properties=Properties(\n source=self._build_source(_source_id, _display_name, _source),\n icon=_icon,\n background_color=_background_color,\n text_color=_text_color,\n ),\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"data_template": { "data_template": {
"_input_type": "MessageTextInput", "_input_type": "MessageTextInput",
@ -519,6 +624,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },

View file

@ -127,6 +127,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -143,7 +185,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n _background_color = self.background_color\n _text_color = self.text_color\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\"background_color\": _background_color, \"text_color\": _text_color, \"icon\": _icon},\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"files": { "files": {
"advanced": true, "advanced": true,
@ -280,6 +322,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },
@ -466,6 +529,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -482,7 +587,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Properties, Source\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, _id: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if _id:\n source_dict[\"id\"] = _id\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n def message_response(self) -> Message:\n _source, _icon, _display_name, _source_id = self.get_properties_from_source_component()\n _background_color = self.background_color\n _text_color = self.text_color\n if self.chat_icon:\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n flow_id=self.graph.flow_id,\n properties=Properties(\n source=self._build_source(_source_id, _display_name, _source),\n icon=_icon,\n background_color=_background_color,\n text_color=_text_color,\n ),\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"data_template": { "data_template": {
"advanced": true, "advanced": true,
@ -597,6 +702,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },

View file

@ -689,6 +689,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -705,7 +747,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Properties, Source\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, _id: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if _id:\n source_dict[\"id\"] = _id\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n def message_response(self) -> Message:\n _source, _icon, _display_name, _source_id = self.get_properties_from_source_component()\n _background_color = self.background_color\n _text_color = self.text_color\n if self.chat_icon:\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n flow_id=self.graph.flow_id,\n properties=Properties(\n source=self._build_source(_source_id, _display_name, _source),\n icon=_icon,\n background_color=_background_color,\n text_color=_text_color,\n ),\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"data_template": { "data_template": {
"advanced": true, "advanced": true,
@ -820,6 +862,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },

View file

@ -1223,6 +1223,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -1239,7 +1281,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Properties, Source\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, _id: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if _id:\n source_dict[\"id\"] = _id\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n def message_response(self) -> Message:\n _source, _icon, _display_name, _source_id = self.get_properties_from_source_component()\n _background_color = self.background_color\n _text_color = self.text_color\n if self.chat_icon:\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n flow_id=self.graph.flow_id,\n properties=Properties(\n source=self._build_source(_source_id, _display_name, _source),\n icon=_icon,\n background_color=_background_color,\n text_color=_text_color,\n ),\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"data_template": { "data_template": {
"advanced": true, "advanced": true,
@ -1353,6 +1395,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },
@ -2515,6 +2578,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -2531,7 +2636,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n _background_color = self.background_color\n _text_color = self.text_color\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\"background_color\": _background_color, \"text_color\": _text_color, \"icon\": _icon},\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"files": { "files": {
"advanced": true, "advanced": true,
@ -2667,6 +2772,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },

View file

@ -326,6 +326,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -342,7 +384,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n _background_color = self.background_color\n _text_color = self.text_color\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\"background_color\": _background_color, \"text_color\": _text_color, \"icon\": _icon},\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"files": { "files": {
"advanced": true, "advanced": true,
@ -479,6 +521,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },
@ -544,6 +607,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -560,7 +665,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Properties, Source\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, _id: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if _id:\n source_dict[\"id\"] = _id\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n def message_response(self) -> Message:\n _source, _icon, _display_name, _source_id = self.get_properties_from_source_component()\n _background_color = self.background_color\n _text_color = self.text_color\n if self.chat_icon:\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n flow_id=self.graph.flow_id,\n properties=Properties(\n source=self._build_source(_source_id, _display_name, _source),\n icon=_icon,\n background_color=_background_color,\n text_color=_text_color,\n ),\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"data_template": { "data_template": {
"advanced": true, "advanced": true,
@ -675,6 +780,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },

View file

@ -927,6 +927,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -943,7 +985,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Properties, Source\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, _id: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if _id:\n source_dict[\"id\"] = _id\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n def message_response(self) -> Message:\n _source, _icon, _display_name, _source_id = self.get_properties_from_source_component()\n _background_color = self.background_color\n _text_color = self.text_color\n if self.chat_icon:\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n flow_id=self.graph.flow_id,\n properties=Properties(\n source=self._build_source(_source_id, _display_name, _source),\n icon=_icon,\n background_color=_background_color,\n text_color=_text_color,\n ),\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"data_template": { "data_template": {
"advanced": true, "advanced": true,
@ -1057,6 +1099,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },
@ -2243,6 +2306,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -2259,7 +2364,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n _background_color = self.background_color\n _text_color = self.text_color\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\"background_color\": _background_color, \"text_color\": _text_color, \"icon\": _icon},\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"files": { "files": {
"advanced": true, "advanced": true,
@ -2395,6 +2500,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },

View file

@ -300,6 +300,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -316,7 +358,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n _background_color = self.background_color\n _text_color = self.text_color\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\"background_color\": _background_color, \"text_color\": _text_color, \"icon\": _icon},\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"files": { "files": {
"advanced": true, "advanced": true,
@ -453,6 +495,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },
@ -852,6 +915,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -868,7 +973,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Properties, Source\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, _id: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if _id:\n source_dict[\"id\"] = _id\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n def message_response(self) -> Message:\n _source, _icon, _display_name, _source_id = self.get_properties_from_source_component()\n _background_color = self.background_color\n _text_color = self.text_color\n if self.chat_icon:\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n flow_id=self.graph.flow_id,\n properties=Properties(\n source=self._build_source(_source_id, _display_name, _source),\n icon=_icon,\n background_color=_background_color,\n text_color=_text_color,\n ),\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"data_template": { "data_template": {
"advanced": true, "advanced": true,
@ -983,6 +1088,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },

View file

@ -937,6 +937,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -953,7 +995,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Properties, Source\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, _id: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if _id:\n source_dict[\"id\"] = _id\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n def message_response(self) -> Message:\n _source, _icon, _display_name, _source_id = self.get_properties_from_source_component()\n _background_color = self.background_color\n _text_color = self.text_color\n if self.chat_icon:\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n flow_id=self.graph.flow_id,\n properties=Properties(\n source=self._build_source(_source_id, _display_name, _source),\n icon=_icon,\n background_color=_background_color,\n text_color=_text_color,\n ),\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"data_template": { "data_template": {
"advanced": true, "advanced": true,
@ -1067,6 +1109,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },

View file

@ -418,6 +418,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -434,7 +476,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n _background_color = self.background_color\n _text_color = self.text_color\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\"background_color\": _background_color, \"text_color\": _text_color, \"icon\": _icon},\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"files": { "files": {
"_input_type": "FileInput", "_input_type": "FileInput",
@ -577,6 +619,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },
@ -641,6 +704,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -657,7 +762,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Properties, Source\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, _id: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if _id:\n source_dict[\"id\"] = _id\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n def message_response(self) -> Message:\n _source, _icon, _display_name, _source_id = self.get_properties_from_source_component()\n _background_color = self.background_color\n _text_color = self.text_color\n if self.chat_icon:\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n flow_id=self.graph.flow_id,\n properties=Properties(\n source=self._build_source(_source_id, _display_name, _source),\n icon=_icon,\n background_color=_background_color,\n text_color=_text_color,\n ),\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"data_template": { "data_template": {
"_input_type": "MessageTextInput", "_input_type": "MessageTextInput",
@ -778,6 +883,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },

View file

@ -310,6 +310,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -326,7 +368,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_USER, MESSAGE_SENDER_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n _background_color = self.background_color\n _text_color = self.text_color\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\"background_color\": _background_color, \"text_color\": _text_color, \"icon\": _icon},\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"files": { "files": {
"advanced": true, "advanced": true,
@ -462,6 +504,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },
@ -1325,6 +1388,48 @@
"pinned": false, "pinned": false,
"template": { "template": {
"_type": "Component", "_type": "Component",
"background_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Background Color",
"dynamic": false,
"info": "The background color of the icon.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "background_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"chat_icon": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Icon",
"dynamic": false,
"info": "The icon of the message.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "chat_icon",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"code": { "code": {
"advanced": true, "advanced": true,
"dynamic": true, "dynamic": true,
@ -1341,7 +1446,7 @@
"show": true, "show": true,
"title_case": false, "title_case": false,
"type": "code", "type": "code",
"value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.store_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Properties, Source\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, _id: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if _id:\n source_dict[\"id\"] = _id\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n def message_response(self) -> Message:\n _source, _icon, _display_name, _source_id = self.get_properties_from_source_component()\n _background_color = self.background_color\n _text_color = self.text_color\n if self.chat_icon:\n _icon = self.chat_icon\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n flow_id=self.graph.flow_id,\n properties=Properties(\n source=self._build_source(_source_id, _display_name, _source),\n icon=_icon,\n background_color=_background_color,\n text_color=_text_color,\n ),\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
}, },
"data_template": { "data_template": {
"advanced": true, "advanced": true,
@ -1455,6 +1560,27 @@
"trace_as_metadata": true, "trace_as_metadata": true,
"type": "bool", "type": "bool",
"value": true "value": true
},
"text_color": {
"_input_type": "MessageTextInput",
"advanced": true,
"display_name": "Text Color",
"dynamic": false,
"info": "The text color of the name",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "text_color",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
} }
} }
}, },

View file

@ -42,6 +42,7 @@ def instantiate_class(
_parameters=custom_params, _parameters=custom_params,
_vertex=vertex, _vertex=vertex,
_tracing_service=get_tracing_service(), _tracing_service=get_tracing_service(),
_id=vertex.id,
) )
if hasattr(custom_component, "set_event_manager"): if hasattr(custom_component, "set_event_manager"):
custom_component.set_event_manager(event_manager) custom_component.set_event_manager(event_manager)

View file

@ -1,12 +1,13 @@
import json
from collections.abc import Sequence from collections.abc import Sequence
from uuid import UUID from uuid import UUID
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.messages import BaseMessage from langchain_core.messages import BaseMessage
from loguru import logger from loguru import logger
from sqlalchemy import delete from sqlalchemy import delete
from sqlmodel import Session, col, select from sqlmodel import Session, col, select
from langflow.field_typing import BaseChatMessageHistory
from langflow.schema.message import Message from langflow.schema.message import Message
from langflow.services.database.models.message.model import MessageRead, MessageTable from langflow.services.database.models.message.model import MessageRead, MessageTable
from langflow.services.deps import session_scope from langflow.services.deps import session_scope
@ -74,6 +75,25 @@ def add_messages(messages: Message | list[Message], flow_id: str | None = None):
raise raise
def update_messages(messages: Message | list[Message]) -> list[Message]:
if not isinstance(messages, list):
messages = [messages]
with session_scope() as session:
updated_messages: list[MessageTable] = []
for message in messages:
msg = session.get(MessageTable, message.id)
if msg:
msg.sqlmodel_update(message.model_dump(exclude_unset=True, exclude_none=True))
session.add(msg)
session.commit()
session.refresh(msg)
updated_messages.append(msg)
else:
logger.warning(f"Message with id {message.id} not found")
return [MessageRead.model_validate(message, from_attributes=True) for message in updated_messages]
def add_messagetables(messages: list[MessageTable], session: Session): def add_messagetables(messages: list[MessageTable], session: Session):
for message in messages: for message in messages:
try: try:
@ -83,7 +103,15 @@ def add_messagetables(messages: list[MessageTable], session: Session):
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
raise raise
return [MessageRead.model_validate(message, from_attributes=True) for message in messages]
new_messages = []
for msg in messages:
msg.properties = json.loads(msg.properties) if isinstance(msg.properties, str) else msg.properties # type: ignore[arg-type]
msg.content_blocks = [json.loads(j) if isinstance(j, str) else j for j in msg.content_blocks] # type: ignore[arg-type]
msg.category = msg.category or ""
new_messages.append(msg)
return [MessageRead.model_validate(message, from_attributes=True) for message in new_messages]
def delete_messages(session_id: str) -> None: def delete_messages(session_id: str) -> None:
@ -100,6 +128,19 @@ def delete_messages(session_id: str) -> None:
) )
def delete_message(id_: str) -> None:
"""Delete a message from the monitor service based on the provided ID.
Args:
id_ (str): The ID of the message to delete.
"""
with session_scope() as session:
message = session.get(MessageTable, id_)
if message:
session.delete(message)
session.commit()
def store_message( def store_message(
message: Message, message: Message,
flow_id: str | None = None, flow_id: str | None = None,
@ -124,7 +165,8 @@ def store_message(
if not message.session_id or not message.sender or not message.sender_name: if not message.session_id or not message.sender or not message.sender_name:
msg = "All of session_id, sender, and sender_name must be provided." msg = "All of session_id, sender, and sender_name must be provided."
raise ValueError(msg) raise ValueError(msg)
if hasattr(message, "id") and message.id:
return update_messages([message])
return add_messages([message], flow_id=flow_id) return add_messages([message], flow_id=flow_id)

View file

@ -0,0 +1,34 @@
from typing import Annotated
from pydantic import BaseModel, Field
from typing_extensions import TypedDict
from .content_types import ContentTypes
# Create a union type of all content types
ContentType = Annotated[
ContentTypes,
Field(discriminator="type"),
]
class ContentBlock(BaseModel):
"""A block of content that can contain different types of content."""
title: str
content: ContentType
allow_markdown: bool = Field(default=True)
media_url: list[str] | None = None
def __init__(self, **data) -> None:
super().__init__(**data)
fields = self.__pydantic_core_schema__["schema"]["fields"]
fields_with_default = (f for f, d in fields.items() if "default" in d["schema"])
self.model_fields_set.update(fields_with_default)
class ContentBlockDict(TypedDict):
title: str
content: ContentType
allow_markdown: bool
media_url: list[str] | None

View file

@ -0,0 +1,94 @@
from typing import Any, Literal, TypeAlias
from pydantic import BaseModel, Field
class BaseContent(BaseModel):
"""Base class for all content types."""
type: str = Field(..., description="Type of the content")
def to_dict(self) -> dict[str, Any]:
return self.model_dump()
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "BaseContent":
return cls(**data)
class ErrorContent(BaseContent):
"""Content type for error messages."""
type: Literal["error"] = Field(default="error")
component: str | None = None
field: str | None = None
reason: str | None = None
solution: str | None = None
traceback: str | None = None
class TextContent(BaseContent):
"""Content type for simple text content."""
type: Literal["text"] = Field(default="text")
text: str
class MediaContent(BaseContent):
"""Content type for media content."""
type: Literal["media"] = Field(default="media")
urls: list[str]
caption: str | None = None
class JSONContent(BaseContent):
"""Content type for JSON content."""
type: Literal["json"] = Field(default="json")
data: dict[str, Any]
class CodeContent(BaseContent):
"""Content type for code snippets."""
type: Literal["code"] = Field(default="code")
code: str
language: str
title: str | None = None
class ToolStartContent(BaseContent):
"""Content type for tool start content."""
type: Literal["tool_start"] = Field(default="tool_start")
tool_name: str
tool_input: dict[str, Any]
class ToolEndContent(BaseContent):
"""Content type for tool end content."""
type: Literal["tool_end"] = Field(default="tool_end")
tool_name: str
tool_output: Any
class ToolErrorContent(BaseContent):
"""Content type for tool error content."""
type: Literal["tool_error"] = Field(default="tool_error")
tool_name: str
tool_error: str
ContentTypes: TypeAlias = (
ToolStartContent
| ToolEndContent
| ToolErrorContent
| ErrorContent
| TextContent
| MediaContent
| CodeContent
| JSONContent
)

View file

@ -1,6 +1,9 @@
import copy import copy
import json import json
from datetime import datetime
from decimal import Decimal
from typing import TYPE_CHECKING, cast from typing import TYPE_CHECKING, cast
from uuid import UUID
from langchain_core.documents import Document from langchain_core.documents import Document
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
@ -8,7 +11,6 @@ from langchain_core.prompts.image import ImagePromptTemplate
from loguru import logger from loguru import logger
from pydantic import BaseModel, model_serializer, model_validator from pydantic import BaseModel, model_serializer, model_validator
from langflow.schema.serialize import recursive_serialize_or_str
from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER
if TYPE_CHECKING: if TYPE_CHECKING:
@ -200,8 +202,7 @@ class Data(BaseModel):
# return a JSON string representation of the Data atributes # return a JSON string representation of the Data atributes
try: try:
data = {k: v.to_json() if hasattr(v, "to_json") else v for k, v in self.data.items()} data = {k: v.to_json() if hasattr(v, "to_json") else v for k, v in self.data.items()}
data = recursive_serialize_or_str(data) return serialize_data(data) # use the custom serializer
return json.dumps(data, indent=4)
except Exception: # noqa: BLE001 except Exception: # noqa: BLE001
logger.opt(exception=True).debug("Error converting Data to JSON") logger.opt(exception=True).debug("Error converting Data to JSON")
return str(self.data) return str(self.data)
@ -211,3 +212,21 @@ class Data(BaseModel):
def __eq__(self, other): def __eq__(self, other):
return isinstance(other, Data) and self.data == other.data return isinstance(other, Data) and self.data == other.data
def custom_serializer(obj):
if isinstance(obj, datetime):
return obj.astimezone().isoformat()
if isinstance(obj, Decimal):
return float(obj)
if isinstance(obj, UUID):
return str(obj)
if isinstance(obj, BaseModel):
return obj.model_dump()
# Add more custom serialization rules as needed
msg = f"Type {type(obj)} not serializable"
raise TypeError(msg)
def serialize_data(data):
return json.dumps(data, indent=4, default=custom_serializer)

View file

@ -1,10 +1,29 @@
from typing import TypeAlias from typing import Literal, TypeAlias
from pydantic import BaseModel from pydantic import BaseModel
from typing_extensions import Protocol from typing_extensions import Protocol
LoggableType: TypeAlias = str | dict | list | int | float | bool | None | BaseModel from langflow.schema.message import ContentBlock, Message
from langflow.schema.playground_events import PlaygroundEvent
LoggableType: TypeAlias = str | dict | list | int | float | bool | None | BaseModel | PlaygroundEvent
class LogFunctionType(Protocol): class LogFunctionType(Protocol):
def __call__(self, message: LoggableType | list[LoggableType], *, name: str | None = None) -> None: ... def __call__(self, message: LoggableType | list[LoggableType], *, name: str | None = None) -> None: ...
class SendMessageFunctionType(Protocol):
def __call__(
self,
message: Message | None = None,
text: str | None = None,
background_color: str | None = None,
text_color: str | None = None,
icon: str | None = None,
content_blocks: list[ContentBlock] | None = None,
format_type: Literal["default", "error", "warning", "info"] = "default",
id_: str | None = None,
*,
allow_markdown: bool = True,
) -> None: ...

View file

@ -1,9 +1,11 @@
from __future__ import annotations from __future__ import annotations
import json import json
import re
import traceback
from collections.abc import AsyncIterator, Iterator from collections.abc import AsyncIterator, Iterator
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import TYPE_CHECKING, Annotated, Any from typing import TYPE_CHECKING, Annotated, Any, Literal
from uuid import UUID from uuid import UUID
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
@ -12,11 +14,15 @@ from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, System
from langchain_core.prompts import BaseChatPromptTemplate, ChatPromptTemplate, PromptTemplate from langchain_core.prompts import BaseChatPromptTemplate, ChatPromptTemplate, PromptTemplate
from langchain_core.prompts.image import ImagePromptTemplate from langchain_core.prompts.image import ImagePromptTemplate
from loguru import logger from loguru import logger
from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, field_serializer, field_validator from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator
from langflow.base.prompts.utils import dict_values_to_string from langflow.base.prompts.utils import dict_values_to_string
from langflow.schema.content_block import ContentBlock
from langflow.schema.content_types import ErrorContent
from langflow.schema.data import Data from langflow.schema.data import Data
from langflow.schema.image import Image, get_file_paths, is_image_file from langflow.schema.image import Image, get_file_paths, is_image_file
from langflow.schema.properties import Properties, Source
from langflow.schema.utils import timestamp_to_str_validator # noqa: TCH001
from langflow.utils.constants import ( from langflow.utils.constants import (
MESSAGE_SENDER_AI, MESSAGE_SENDER_AI,
MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_NAME_AI,
@ -28,18 +34,6 @@ if TYPE_CHECKING:
from langchain_core.prompt_values import ImagePromptValue from langchain_core.prompt_values import ImagePromptValue
def _timestamp_to_str(timestamp: datetime | str) -> str:
if isinstance(timestamp, str):
# Just check if the string is a valid datetime
try:
datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") # noqa: DTZ007
except ValueError as e:
msg = f"Invalid timestamp: {timestamp}"
raise ValueError(msg) from e
return timestamp
return timestamp.strftime("%Y-%m-%d %H:%M:%S")
class Message(Data): class Message(Data):
model_config = ConfigDict(arbitrary_types_allowed=True) model_config = ConfigDict(arbitrary_types_allowed=True)
# Helper class to deal with image data # Helper class to deal with image data
@ -49,13 +43,17 @@ class Message(Data):
sender_name: str | None = None sender_name: str | None = None
files: list[str | Image] | None = Field(default=[]) files: list[str | Image] | None = Field(default=[])
session_id: str | None = Field(default="") session_id: str | None = Field(default="")
timestamp: Annotated[str, BeforeValidator(_timestamp_to_str)] = Field( timestamp: Annotated[str, timestamp_to_str_validator] = Field(
default_factory=lambda: datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") default_factory=lambda: datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z")
) )
flow_id: str | UUID | None = None flow_id: str | UUID | None = None
error: bool = Field(default=False) error: bool = Field(default=False)
edit: bool = Field(default=False) edit: bool = Field(default=False)
properties: Properties = Field(default_factory=Properties)
category: Literal["message", "error", "warning", "info"] | None = "message"
content_blocks: list[ContentBlock] = Field(default_factory=list)
@field_validator("flow_id", mode="before") @field_validator("flow_id", mode="before")
@classmethod @classmethod
def validate_flow_id(cls, value): def validate_flow_id(cls, value):
@ -71,7 +69,12 @@ class Message(Data):
@field_serializer("timestamp") @field_serializer("timestamp")
def serialize_timestamp(self, value): def serialize_timestamp(self, value):
return datetime.strptime(value, "%Y-%m-%d %H:%M:%S").astimezone(timezone.utc) try:
# Try parsing with timezone
return datetime.strptime(value.strip(), "%Y-%m-%d %H:%M:%S %Z").astimezone(timezone.utc)
except ValueError:
# Try parsing without timezone
return datetime.strptime(value.strip(), "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
@field_validator("files", mode="before") @field_validator("files", mode="before")
@classmethod @classmethod
@ -270,6 +273,11 @@ class MessageResponse(DefaultModel):
session_id: str session_id: str
text: str text: str
files: list[str] = [] files: list[str] = []
edit: bool
properties: Properties | None = None
category: str | None = None
content_blocks: list[ContentBlock] | None = None
@field_validator("files", mode="before") @field_validator("files", mode="before")
@classmethod @classmethod
@ -282,7 +290,7 @@ class MessageResponse(DefaultModel):
@classmethod @classmethod
def serialize_timestamp(cls, v): def serialize_timestamp(cls, v):
v = v.replace(microsecond=0) v = v.replace(microsecond=0)
return v.strftime("%Y-%m-%d %H:%M:%S") return v.strftime("%Y-%m-%d %H:%M:%S %Z")
@field_serializer("files") @field_serializer("files")
@classmethod @classmethod
@ -306,3 +314,60 @@ class MessageResponse(DefaultModel):
timestamp=message.timestamp, timestamp=message.timestamp,
flow_id=flow_id, flow_id=flow_id,
) )
class ErrorMessage(Message):
"""A message class specifically for error messages with predefined error-specific attributes."""
def __init__(
self,
exception: Exception,
session_id: str,
source: Source,
trace_name: str | None = None,
flow_id: str | None = None,
) -> None:
# Get the error reason
reason = exception.__class__.__name__
if hasattr(exception, "body") and "message" in exception.body:
reason = exception.body.get("message")
elif hasattr(exception, "code"):
reason = exception.code
# Get the sender ID
if trace_name:
match = re.search(r"\((.*?)\)", trace_name)
if match:
match.group(1)
super().__init__(
session_id=session_id,
sender=source.display_name,
sender_name=source.display_name,
text=reason,
properties=Properties(
text_color="red",
background_color="red",
edited=False,
source=source,
icon="error",
allow_markdown=False,
targets=[],
),
category="error",
error=True,
content_blocks=[
ContentBlock(
title="Error",
content=ErrorContent(
type="error",
component=source.display_name,
field=str(exception.field) if hasattr(exception, "field") else None,
reason=reason,
solution=str(exception.solution) if hasattr(exception, "solution") else None,
traceback=traceback.format_exc(),
),
)
],
flow_id=flow_id,
)

View file

@ -0,0 +1,180 @@
import inspect
from collections.abc import Callable
from datetime import datetime, timezone
from typing import Annotated, Literal
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator
from langflow.schema.content_block import ContentBlock
from langflow.schema.properties import Properties
from langflow.schema.utils import timestamp_to_str_validator
from langflow.utils.constants import MESSAGE_SENDER_USER
class PlaygroundEvent(BaseModel):
model_config = ConfigDict(extra="allow", populate_by_name=True)
properties: Properties | None = Field(default=None)
sender_name: str | None = Field(default=None)
content_blocks: list[ContentBlock] | None = Field(default=None)
format_type: Literal["default", "error", "warning", "info"] = Field(default="default")
files: list[str] | None = Field(default=None)
text: str | None = Field(default=None)
timestamp: Annotated[str, timestamp_to_str_validator] = Field(
default_factory=lambda: datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z")
)
id_: UUID | str | None = Field(alias="id")
@field_serializer("timestamp")
@classmethod
def serialize_timestamp(cls, v: str) -> str:
return v
@field_validator("id_")
@classmethod
def validate_id(cls, v: UUID | str | None) -> str | None:
if isinstance(v, UUID):
return str(v)
return v
class MessageEvent(PlaygroundEvent):
category: Literal["message", "error", "warning", "info"] = "message"
format_type: Literal["default", "error", "warning", "info"] = Field(default="default")
session_id: str | None = Field(default=None)
error: bool = Field(default=False)
edit: bool = Field(default=False)
flow_id: UUID | str | None = Field(default=None)
sender: str = Field(default=MESSAGE_SENDER_USER)
sender_name: str = Field(default="User")
@field_validator("flow_id")
@classmethod
def validate_flow_id(cls, v: UUID | str | None) -> str | None:
if isinstance(v, UUID):
return str(v)
return v
class ErrorEvent(MessageEvent):
background_color: str = Field(default="#FF0000")
text_color: str = Field(default="#FFFFFF")
format_type: Literal["default", "error", "warning", "info"] = Field(default="error")
allow_markdown: bool = Field(default=False)
category: Literal["error"] = "error"
class WarningEvent(PlaygroundEvent):
background_color: str = Field(default="#FFA500")
text_color: str = Field(default="#000000")
format_type: Literal["default", "error", "warning", "info"] = Field(default="warning")
class InfoEvent(PlaygroundEvent):
background_color: str = Field(default="#0000FF")
text_color: str = Field(default="#FFFFFF")
format_type: Literal["default", "error", "warning", "info"] = Field(default="info")
class TokenEvent(BaseModel):
chunk: str = Field(...)
id: UUID | str | None = Field(alias="id")
timestamp: Annotated[str, timestamp_to_str_validator] = Field(
default_factory=lambda: datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z")
)
# Factory functions first
def create_message(
text: str,
category: Literal["message", "error", "warning", "info"] = "message",
properties: dict | None = None,
content_blocks: list[ContentBlock] | None = None,
sender_name: str | None = None,
files: list[str] | None = None,
timestamp: str | None = None,
format_type: Literal["default", "error", "warning", "info"] = "default",
sender: str | None = None,
session_id: str | None = None,
id: UUID | str | None = None, # noqa: A002
flow_id: UUID | str | None = None,
*,
error: bool = False,
edit: bool = False,
) -> MessageEvent:
return MessageEvent(
text=text,
properties=properties,
category=category,
content_blocks=content_blocks,
sender_name=sender_name,
files=files,
timestamp=timestamp,
format_type=format_type,
sender=sender,
id=id,
session_id=session_id,
error=error,
edit=edit,
flow_id=flow_id,
)
def create_error(
text: str,
properties: dict | None = None,
traceback: str | None = None,
title: str = "Error",
timestamp: str | None = None,
id: UUID | str | None = None, # noqa: A002
flow_id: UUID | str | None = None,
session_id: str | None = None,
content_blocks: list[ContentBlock] | None = None,
) -> ErrorEvent:
if traceback:
content_blocks = content_blocks or []
content_blocks += [ContentBlock(title=title, content=traceback)]
return ErrorEvent(
text=text,
properties=properties,
content_blocks=content_blocks,
timestamp=timestamp,
id=id,
flow_id=flow_id,
session_id=session_id,
)
def create_warning(message: str) -> WarningEvent:
return WarningEvent(text=message)
def create_info(message: str) -> InfoEvent:
return InfoEvent(text=message)
def create_token(chunk: str, id: str) -> TokenEvent: # noqa: A002
return TokenEvent(
chunk=chunk,
id=id,
)
_EVENT_CREATORS: dict[str, tuple[Callable, inspect.Signature]] = {
"message": (create_message, inspect.signature(create_message)),
"error": (create_error, inspect.signature(create_error)),
"warning": (create_warning, inspect.signature(create_warning)),
"info": (create_info, inspect.signature(create_info)),
"token": (create_token, inspect.signature(create_token)),
}
def create_event_by_type(
event_type: Literal["message", "error", "warning", "info", "token"], **kwargs
) -> PlaygroundEvent | dict:
if event_type not in _EVENT_CREATORS:
return kwargs
creator_func, signature = _EVENT_CREATORS[event_type]
valid_params = {k: v for k, v in kwargs.items() if k in signature.parameters}
return creator_func(**valid_params)

View file

@ -0,0 +1,27 @@
from pydantic import BaseModel, Field, field_validator
class Source(BaseModel):
id: str = Field(default="", description="The id of the source component.")
display_name: str = Field(default="", description="The display name of the source component.")
source: str = Field(
default="",
description="The source of the message. Normally used to display the model name (e.g. 'gpt-4o')",
)
class Properties(BaseModel):
text_color: str | None = None
background_color: str | None = None
edited: bool = False
source: Source = Field(default_factory=Source)
icon: str | None = None
allow_markdown: bool = False
targets: list = []
@field_validator("source", mode="before")
@classmethod
def validate_source(cls, v):
if isinstance(v, str):
return Source(id=v, display_name=v, source=v)
return v

View file

@ -0,0 +1,20 @@
from datetime import datetime
from pydantic import BeforeValidator
def timestamp_to_str(timestamp: datetime | str) -> str:
if isinstance(timestamp, str):
# Just check if the string is a valid datetime
try:
datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S %Z") # noqa: DTZ007
result = timestamp
except ValueError as e:
msg = f"Invalid timestamp: {timestamp}"
raise ValueError(msg) from e
else:
result = timestamp.strftime("%Y-%m-%d %H:%M:%S %Z")
return result
timestamp_to_str_validator = BeforeValidator(timestamp_to_str)

View file

@ -2,10 +2,13 @@ from datetime import datetime, timezone
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from pydantic import field_validator from pydantic import field_serializer, field_validator
from sqlalchemy import Text from sqlalchemy import Text
from sqlmodel import JSON, Column, Field, Relationship, SQLModel from sqlmodel import JSON, Column, Field, Relationship, SQLModel
from langflow.schema.content_block import ContentBlock, ContentBlockDict
from langflow.schema.properties import Properties
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.schema.message import Message from langflow.schema.message import Message
from langflow.services.database.models.flow.model import Flow from langflow.services.database.models.flow.model import Flow
@ -21,6 +24,10 @@ class MessageBase(SQLModel):
error: bool = Field(default=False) error: bool = Field(default=False)
edit: bool = Field(default=False) edit: bool = Field(default=False)
properties: Properties = Field(default_factory=Properties)
category: str = Field(default="message")
content_blocks: list[ContentBlock] = Field(default_factory=list)
@field_validator("files", mode="before") @field_validator("files", mode="before")
@classmethod @classmethod
def validate_files(cls, value): def validate_files(cls, value):
@ -44,10 +51,12 @@ class MessageBase(SQLModel):
message.files = image_paths message.files = image_paths
if isinstance(message.timestamp, str): if isinstance(message.timestamp, str):
# The message.timestamp is created using strftime("%Y-%m-%dT%H:%M:%S"). # Convert timestamp string in format "YYYY-MM-DD HH:MM:SS UTC" to datetime
# This format is not fully ISO 8601 compliant because it lacks timezone information. try:
# Aadd timezone info (UTC) back to the timestamp here. timestamp = datetime.strptime(message.timestamp, "%Y-%m-%d %H:%M:%S %Z").replace(tzinfo=timezone.utc)
timestamp = datetime.fromisoformat(message.timestamp).replace(tzinfo=timezone.utc) except ValueError:
# Fallback for ISO format if the above fails
timestamp = datetime.fromisoformat(message.timestamp).replace(tzinfo=timezone.utc)
else: else:
timestamp = message.timestamp timestamp = message.timestamp
if not flow_id and message.flow_id: if not flow_id and message.flow_id:
@ -55,6 +64,17 @@ class MessageBase(SQLModel):
# If the text is not a string, it means it could be # If the text is not a string, it means it could be
# async iterator so we simply add it as an empty string # async iterator so we simply add it as an empty string
message_text = "" if not isinstance(message.text, str) else message.text message_text = "" if not isinstance(message.text, str) else message.text
properties = (
message.properties.model_dump_json()
if hasattr(message.properties, "model_dump_json")
else message.properties
)
content_blocks = []
for content_block in message.content_blocks or []:
content = content_block.model_dump_json() if hasattr(content_block, "model_dump_json") else content_block
content_blocks.append(content)
return cls( return cls(
sender=message.sender, sender=message.sender,
sender_name=message.sender_name, sender_name=message.sender_name,
@ -63,6 +83,9 @@ class MessageBase(SQLModel):
files=message.files or [], files=message.files or [],
timestamp=timestamp, timestamp=timestamp,
flow_id=flow_id, flow_id=flow_id,
properties=properties,
category=message.category,
content_blocks=content_blocks,
) )
@ -72,6 +95,9 @@ class MessageTable(MessageBase, table=True): # type: ignore[call-arg]
flow_id: UUID | None = Field(default=None, foreign_key="flow.id") flow_id: UUID | None = Field(default=None, foreign_key="flow.id")
flow: "Flow" = Relationship(back_populates="messages") flow: "Flow" = Relationship(back_populates="messages")
files: list[str] = Field(sa_column=Column(JSON)) files: list[str] = Field(sa_column=Column(JSON))
properties: Properties = Field(default_factory=lambda: Properties().model_dump(), sa_column=Column(JSON)) # type: ignore[assignment]
category: str = Field(sa_column=Column(Text))
content_blocks: list[ContentBlockDict] = Field(default_factory=list, sa_column=Column(JSON)) # type: ignore[assignment]
@field_validator("flow_id", mode="before") @field_validator("flow_id", mode="before")
@classmethod @classmethod
@ -82,6 +108,20 @@ class MessageTable(MessageBase, table=True): # type: ignore[call-arg]
value = UUID(value) value = UUID(value)
return value return value
@field_validator("properties")
@classmethod
def validate_properties(cls, value):
if hasattr(value, "model_dump"):
return value.model_dump()
return value
@field_serializer("properties")
@classmethod
def serialize_properties(cls, value):
if hasattr(value, "model_dump"):
return value.model_dump()
return value
# Needed for Column(JSON) # Needed for Column(JSON)
class Config: class Config:
arbitrary_types_allowed = True arbitrary_types_allowed = True

View file

@ -24,7 +24,7 @@ async def test_message():
async def test_do_not_store_message(): async def test_do_not_store_message():
session_id = "test-session-id" session_id = "test-session-id"
outputs = await run_single_component( outputs = await run_single_component(
ChatOutput, inputs={"input_value": "hello", "should_store_message": True}, session_id=session_id ChatOutput, inputs={"input_value": Message(text="hello"), "should_store_message": True}, session_id=session_id
) )
assert isinstance(outputs["message"], Message) assert isinstance(outputs["message"], Message)
assert outputs["message"].text == "hello" assert outputs["message"].text == "hello"
@ -33,7 +33,7 @@ async def test_do_not_store_message():
session_id = "test-session-id-another" session_id = "test-session-id-another"
outputs = await run_single_component( outputs = await run_single_component(
ChatOutput, inputs={"input_value": "hello", "should_store_message": False}, session_id=session_id ChatOutput, inputs={"input_value": Message(text="hello"), "should_store_message": False}, session_id=session_id
) )
assert isinstance(outputs["message"], Message) assert isinstance(outputs["message"], Message)
assert outputs["message"].text == "hello" assert outputs["message"].text == "hello"

View file

@ -72,6 +72,24 @@ def test_component_tool():
"title": "Files", "title": "Files",
"type": "array", "type": "array",
}, },
"background_color": {
"default": "",
"description": "The background color of the icon.",
"title": "Background Color",
"type": "string",
},
"chat_icon": {
"default": "",
"description": "The icon of the message.",
"title": "Chat Icon",
"type": "string",
},
"text_color": {
"default": "",
"description": "The text color of the name",
"title": "Text Color",
"type": "string",
},
} }
assert component_toolkit.component == chat_input assert component_toolkit.component == chat_input

View file

@ -198,7 +198,16 @@ class TestEventManager:
assert len(queue.data) == 1 assert len(queue.data) == 1
event_id, str_data, timestamp = queue.data[0] event_id, str_data, timestamp = queue.data[0]
assert isinstance(event_id, uuid.UUID) # event_id follows this pattern: f"{event_type}-{uuid.uuid4()}"
event_type_from_id = event_id.split("-")[0]
assert event_type_from_id == "test_type"
uuid_from_id = event_id.split(event_type_from_id)[1]
assert isinstance(uuid_from_id, str)
# assert that the uuid_from_id is a valid uuid
try:
uuid.UUID(uuid_from_id)
except ValueError:
pytest.fail(f"Invalid UUID: {uuid_from_id}")
assert isinstance(str_data, bytes) assert isinstance(str_data, bytes)
assert isinstance(timestamp, float) assert isinstance(timestamp, float)

View file

@ -10,7 +10,7 @@ from langflow.template import Output
class LogComponent(Component): class LogComponent(Component):
name = "LogComponent" display_name = "LogComponent"
inputs = [IntInput(name="times", value=1)] inputs = [IntInput(name="times", value=1)]
outputs = [Output(name="call_log", method="call_log_method")] outputs = [Output(name="call_log", method="call_log_method")]

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 128 128"><path d="M36.04 98.76c-10.66 0-19.33 8.67-19.33 19.33v5.9h10.47v-5.9c0-4.89 3.97-8.86 8.86-8.86s8.86 3.97 8.86 8.86v5.9h10.47v-5.9c0-10.65-8.67-19.33-19.33-19.33M36.04 38.84c-10.66 0-19.33 8.67-19.33 19.33v11.65c0 10.66 8.67 19.33 19.33 19.33s19.33-8.67 19.33-19.33V58.18c0-10.66-8.67-19.34-19.33-19.34m8.85 30.98c0 4.89-3.97 8.86-8.86 8.86s-8.86-3.97-8.86-8.86V58.18c0-4.89 3.97-8.86 8.86-8.86s8.86 3.97 8.86 8.86zM36.04 29.24c10.66 0 19.33-8.67 19.33-19.33V4H44.89v5.91c0 4.88-3.97 8.86-8.86 8.86s-8.86-3.97-8.86-8.86V4H16.7v5.91c0 10.65 8.68 19.33 19.34 19.33M91.96 68.8c-10.66 0-19.33 8.67-19.33 19.33v11.65c0 10.66 8.67 19.33 19.33 19.33s19.33-8.67 19.33-19.33V88.14c.01-10.66-8.67-19.34-19.33-19.34m8.86 30.98c0 4.88-3.97 8.86-8.86 8.86s-8.86-3.97-8.86-8.86V88.14c0-4.89 3.97-8.86 8.86-8.86 4.88 0 8.86 3.97 8.86 8.86zM91.96 8.88c-10.66 0-19.33 8.67-19.33 19.33v11.65c0 10.66 8.67 19.33 19.33 19.33s19.33-8.67 19.33-19.33V28.22c.01-10.66-8.67-19.34-19.33-19.34m8.86 30.98c0 4.89-3.97 8.86-8.86 8.86s-8.86-3.97-8.86-8.86V28.22c0-4.88 3.97-8.86 8.86-8.86 4.88 0 8.86 3.97 8.86 8.86z" style="fill:#84b0c1"/><path d="M36.02 89.16c-2.89 0-5.62-.66-8.09-1.8.22-3.78.92-7.54 1.74-11.25.01-.03.02-.07.02-.1a8.81 8.81 0 0 0 6.33 2.67c2.68 0 5.07-1.2 6.7-3.08l.09.45c.28 1.49.59 2.99 1.25 4.34.67 1.36 1.75 2.57 3.18 3.08.75.27 1.6.29 2.37.08-3.49 3.46-8.29 5.61-13.59 5.61M36.02 29.24c-2.89 0-5.62-.66-8.09-1.8.22-3.78.92-7.54 1.74-11.25.01-.03.02-.07.02-.1a8.81 8.81 0 0 0 6.33 2.67c2.68 0 5.07-1.2 6.7-3.08l.09.45c.28 1.49.59 2.99 1.25 4.34.67 1.36 1.75 2.57 3.18 3.08.75.27 1.6.29 2.37.08a19.2 19.2 0 0 1-13.59 5.61M48.15 43.14c-.93.2-1.83.71-2.47 1.42-1.28 1.42-1.74 3.38-1.97 5.27-.14 1.1-.22 2.21-.4 3.31a8.85 8.85 0 0 0-7.28-3.82 8.84 8.84 0 0 0-6.8 3.19c-.41-3.87-.65-7.76-.69-11.65v-.5c2.3-.97 4.83-1.51 7.49-1.51 4.58-.01 8.8 1.61 12.12 4.29M91.98 59.2c-2.89 0-5.62-.66-8.09-1.8.22-3.78.92-7.54 1.74-11.25.01-.03.02-.07.02-.1a8.81 8.81 0 0 0 6.33 2.67c2.68 0 5.07-1.2 6.7-3.08l.09.45c.28 1.49.59 2.99 1.25 4.34.67 1.36 1.75 2.57 3.18 3.08.75.27 1.6.29 2.37.08-3.49 3.46-8.29 5.61-13.59 5.61M104.11 13.18c-.93.2-1.83.71-2.47 1.42-1.28 1.42-1.74 3.38-1.97 5.27-.14 1.1-.22 2.21-.4 3.31a8.85 8.85 0 0 0-7.28-3.82 8.84 8.84 0 0 0-6.8 3.19c-.41-3.87-.65-7.76-.69-11.65v-.5c2.3-.97 4.83-1.51 7.49-1.51 4.58-.01 8.8 1.61 12.12 4.29M91.98 119.11c-2.89 0-5.62-.66-8.09-1.8.22-3.78.92-7.54 1.74-11.25.01-.03.02-.07.02-.1a8.81 8.81 0 0 0 6.33 2.67c2.68 0 5.07-1.2 6.7-3.08l.09.45c.28 1.49.59 2.99 1.25 4.34.67 1.36 1.75 2.57 3.18 3.08.75.27 1.6.29 2.37.08a19.2 19.2 0 0 1-13.59 5.61M104.11 73.1c-.93.2-1.83.71-2.47 1.42-1.28 1.42-1.74 3.38-1.97 5.27-.14 1.1-.22 2.21-.4 3.31a8.85 8.85 0 0 0-7.28-3.82 8.84 8.84 0 0 0-6.8 3.19c-.41-3.87-.65-7.76-.69-11.65v-.5c2.3-.97 4.83-1.51 7.49-1.51 4.58-.01 8.8 1.61 12.12 4.29" style="fill:#2f7889"/><path d="M91.96 41.25c-2.89 0-5.24 2.35-5.24 5.24v35.02c0 2.89 2.35 5.24 5.24 5.24s5.24-2.35 5.24-5.24V46.49c0-2.89-2.35-5.24-5.24-5.24M91.96 26.83c2.89 0 5.24-2.35 5.24-5.24V4H86.73v17.59c0 2.89 2.35 5.24 5.23 5.24M91.96 101.17c-2.89 0-5.24 2.35-5.24 5.24V124H97.2v-17.59c0-2.89-2.35-5.24-5.24-5.24M36.44 71.63c-2.89 0-5.24 2.35-5.24 5.24v35.02c0 2.89 2.35 5.24 5.24 5.24s5.24-2.35 5.24-5.24V76.87c0-2.89-2.35-5.24-5.24-5.24M36.44 11.71c-2.89 0-5.24 2.35-5.24 5.24v35.02c0 2.89 2.35 5.24 5.24 5.24s5.24-2.35 5.24-5.24V16.95c0-2.89-2.35-5.24-5.24-5.24" style="fill:#84b0c1"/><path d="M33.52 17.81c-.54 3.4-.69 6.87-.07 10.25.27 1.49 1.3 2.67 2.27.72 1.73-3.45 3.23-7.97 3.29-11.92.02-1.45-.65-3.01-2.32-2.99-2.02.04-2.87 2.07-3.17 3.94M33.52 77.16c-.54 3.4-.69 6.87-.07 10.25.27 1.49 1.3 2.67 2.27.72 1.73-3.45 3.23-7.97 3.29-11.92.02-1.45-.65-3.01-2.32-2.99-2.02.04-2.87 2.08-3.17 3.94M88.8 47.49c-.54 3.4-.69 6.87-.07 10.25.27 1.49 1.3 2.67 2.27.72 1.73-3.45 3.23-7.97 3.29-11.92.02-1.45-.65-3.01-2.32-2.99-2.02.04-2.87 2.07-3.17 3.94M88.8 106.84c-.54 3.4-.69 6.87-.07 10.25.27 1.49 1.3 2.67 2.27.72 1.73-3.45 3.23-7.97 3.29-11.92.02-1.45-.65-3.01-2.32-2.99-2.02.04-2.87 2.07-3.17 3.94" style="fill:#a8e3f0"/></svg>

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -40,7 +40,7 @@ export default function CollectionCardComponent({
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const getFlowById = useFlowsManagerStore((state) => state.getFlowById); const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
const [openPlayground, setOpenPlayground] = useState(false); // const [openPlayground, setOpenPlayground] = useState(false);
const [loadingPlayground, setLoadingPlayground] = useState(false); const [loadingPlayground, setLoadingPlayground] = useState(false);
const selectedFlowsComponentsCards = useFlowsManagerStore( const selectedFlowsComponentsCards = useFlowsManagerStore(
(state) => state.selectedFlowsComponentsCards, (state) => state.selectedFlowsComponentsCards,
@ -75,7 +75,7 @@ export default function CollectionCardComponent({
return; return;
} }
setCurrentFlow(data); setCurrentFlow(data);
setOpenPlayground(true); // setOpenPlayground(true);
setLoadingPlayground(false); setLoadingPlayground(false);
} else { } else {
setErrorData({ setErrorData({
@ -156,7 +156,7 @@ export default function CollectionCardComponent({
<CardFooter> <CardFooter>
<div className="z-50 flex w-full items-center justify-between gap-2"> <div className="z-50 flex w-full items-center justify-between gap-2">
<div className="flex w-full flex-wrap items-end justify-end gap-2"> <div className="flex w-full flex-wrap items-end justify-end gap-2">
{playground && ( {/* {playground && (
<Button <Button
disabled={loadingPlayground || !hasPlayground(data)} disabled={loadingPlayground || !hasPlayground(data)}
key={data.id} key={data.id}
@ -177,12 +177,12 @@ export default function CollectionCardComponent({
)} )}
Playground Playground
</Button> </Button>
)} )} */}
</div> </div>
</div> </div>
</CardFooter> </CardFooter>
</Card> </Card>
{openPlayground && ( {/* {openPlayground && (
<IOModal <IOModal
key={data.id} key={data.id}
cleanOnClose={true} cleanOnClose={true}
@ -191,7 +191,7 @@ export default function CollectionCardComponent({
> >
<></> <></>
</IOModal> </IOModal>
)} )} */}
</> </>
); );
} }

View file

@ -1,3 +1,4 @@
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
@ -12,6 +13,7 @@ export default function CardsWrapComponent({
dragMessage?: string; dragMessage?: string;
}) { }) {
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const isIOModalOpen = useFlowsManagerStore((state) => state.IOModalOpen);
useEffect(() => { useEffect(() => {
// Function to handle visibility change // Function to handle visibility change
const handleVisibilityChange = () => { const handleVisibilityChange = () => {
@ -32,13 +34,21 @@ export default function CardsWrapComponent({
const dragOver = (e) => { const dragOver = (e) => {
e.preventDefault(); e.preventDefault();
if (e.dataTransfer.types.some((types) => types === "Files") && onFileDrop) { if (
e.dataTransfer.types.some((types) => types === "Files") &&
onFileDrop &&
!isIOModalOpen
) {
setIsDragging(true); setIsDragging(true);
} }
}; };
const dragEnter = (e) => { const dragEnter = (e) => {
if (e.dataTransfer.types.some((types) => types === "Files") && onFileDrop) { if (
e.dataTransfer.types.some((types) => types === "Files") &&
onFileDrop &&
!isIOModalOpen
) {
setIsDragging(true); setIsDragging(true);
} }
e.preventDefault(); e.preventDefault();
@ -46,12 +56,12 @@ export default function CardsWrapComponent({
const dragLeave = (e) => { const dragLeave = (e) => {
e.preventDefault(); e.preventDefault();
if (onFileDrop) setIsDragging(false); if (onFileDrop && !isIOModalOpen) setIsDragging(false);
}; };
const onDrop = (e) => { const onDrop = (e) => {
e.preventDefault(); e.preventDefault();
if (onFileDrop) onFileDrop(e); if (onFileDrop && !isIOModalOpen) onFileDrop(e);
setIsDragging(false); setIsDragging(false);
}; };

View file

@ -31,13 +31,14 @@ export default function SimplifiedCodeTabComponent({
}; };
return ( return (
<div className="flex w-full flex-col overflow-hidden rounded-md text-left"> <div className="flex w-full flex-col overflow-hidden rounded-md text-left dark">
<div className="flex w-full items-center justify-between bg-zinc-700 px-4 py-2"> <div className="flex w-full items-center justify-between rounded-t-md border border-border bg-accent px-4 py-2">
<span className="text-sm font-semibold">{language}</span> <span className="dar text-sm font-semibold text-white">{language}</span>
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="text-gray-400 hover:bg-[#3a3a3a]" className="bg-card text-muted-foreground"
data-testid="copy-code-button"
onClick={copyToClipboard} onClick={copyToClipboard}
> >
{isCopied ? ( {isCopied ? (
@ -50,7 +51,7 @@ export default function SimplifiedCodeTabComponent({
<SyntaxHighlighter <SyntaxHighlighter
language={language.toLowerCase()} language={language.toLowerCase()}
style={oneDark} style={oneDark}
className="!mt-0 h-full w-full overflow-scroll !rounded-b-md !rounded-t-none text-left !custom-scroll" className="!mt-0 h-full w-full overflow-scroll !rounded-b-md !rounded-t-none border border-border text-left !custom-scroll"
> >
{code} {code}
</SyntaxHighlighter> </SyntaxHighlighter>

View file

@ -126,7 +126,12 @@ export default function FlowToolbar(): JSX.Element {
<div className="flex gap-1.5"> <div className="flex gap-1.5">
<div className="flex h-full w-full gap-1.5 rounded-sm transition-all"> <div className="flex h-full w-full gap-1.5 rounded-sm transition-all">
{hasIO ? ( {hasIO ? (
<IOModal open={open} setOpen={setOpen} disable={!hasIO}> <IOModal
open={open}
setOpen={setOpen}
disable={!hasIO}
canvasOpen
>
<div <div
data-testid="playground-btn-flow-io" data-testid="playground-btn-flow-io"
className="relative inline-flex w-full items-center justify-center gap-1.5 rounded px-3 py-1.5 text-sm font-semibold transition-all duration-500 ease-in-out hover:bg-accent" className="relative inline-flex w-full items-center justify-center gap-1.5 rounded px-3 py-1.5 text-sm font-semibold transition-all duration-500 ease-in-out hover:bg-accent"
@ -139,16 +144,18 @@ export default function FlowToolbar(): JSX.Element {
</div> </div>
</IOModal> </IOModal>
) : ( ) : (
<div <ShadTooltip content="Add a Chat Input or Chat Output to use the playground">
className={`relative inline-flex w-full cursor-not-allowed items-center justify-center gap-1.5 rounded px-3 py-1.5 text-sm font-semibold text-muted-foreground transition-all duration-150 ease-in-out`} <div
data-testid="playground-btn-flow" className={`relative inline-flex w-full cursor-not-allowed items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-muted-foreground transition-all duration-150 ease-in-out`}
> data-testid="playground-btn-flow"
<ForwardedIconComponent >
name="Play" <ForwardedIconComponent
className={"h-4 w-4 transition-all"} name="BotMessageSquareIcon"
/> className={"h-5 w-5 transition-all"}
Playground />
</div> Playground
</div>
</ShadTooltip>
)} )}
</div> </div>
{ENABLE_API && ( {ENABLE_API && (

View file

@ -48,7 +48,7 @@ export default function StoreCardComponent({
data?.downloads_count ?? 0, data?.downloads_count ?? 0,
); );
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const [openPlayground, setOpenPlayground] = useState(false); // const [openPlayground, setOpenPlayground] = useState(false);
const [loadingPlayground, setLoadingPlayground] = useState(false); const [loadingPlayground, setLoadingPlayground] = useState(false);
const playground = const playground =
data.last_tested_version?.includes("1.0.0") && !data.is_component; data.last_tested_version?.includes("1.0.0") && !data.is_component;
@ -207,7 +207,7 @@ export default function StoreCardComponent({
<CardFooter> <CardFooter>
<div className="z-50 flex w-full items-center justify-between gap-2"> <div className="z-50 flex w-full items-center justify-between gap-2">
<div className="flex w-full flex-wrap items-end justify-end gap-2"> <div className="flex w-full flex-wrap items-end justify-end gap-2">
{playground && ( {/* {playground && (
<Button <Button
disabled={loadingPlayground || !authorized} disabled={loadingPlayground || !authorized}
key={data.id} key={data.id}
@ -245,7 +245,7 @@ export default function StoreCardComponent({
)} )}
Playground Playground
</Button> </Button>
)} )} */}
<div className="flex gap-0.5"> <div className="flex gap-0.5">
<ShadTooltip <ShadTooltip
content={authorized ? "Like" : "Please review your API key."} content={authorized ? "Like" : "Please review your API key."}
@ -316,7 +316,7 @@ export default function StoreCardComponent({
</div> </div>
</CardFooter> </CardFooter>
</Card> </Card>
{openPlayground && ( {/* {openPlayground && (
<IOModal <IOModal
key={data.id} key={data.id}
cleanOnClose={true} cleanOnClose={true}
@ -325,7 +325,7 @@ export default function StoreCardComponent({
> >
<></> <></>
</IOModal> </IOModal>
)} )} */}
</> </>
); );
} }

View file

@ -0,0 +1,53 @@
"use client";
import { cn } from "@/utils/utils";
import { motion } from "framer-motion";
import React, { useMemo, type JSX } from "react";
interface TextShimmerProps {
children: string;
as?: React.ElementType;
className?: string;
duration?: number;
spread?: number;
}
export function TextShimmer({
children,
as: Component = "p",
className,
duration = 2,
spread = 2,
}: TextShimmerProps) {
const MotionComponent = motion(Component as keyof JSX.IntrinsicElements);
const dynamicSpread = useMemo(() => {
return children.length * spread;
}, [children, spread]);
return (
<MotionComponent
className={cn(
"relative inline-block bg-[length:250%_100%,auto] bg-clip-text",
"text-transparent [--base-color:#a1a1aa] [--base-gradient-color:#000]",
"[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--base-gradient-color),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]",
"dark:[--base-color:#71717a] dark:[--base-gradient-color:#ffffff] dark:[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--base-gradient-color),#0000_calc(50%+var(--spread)))]",
className,
)}
initial={{ backgroundPosition: "100% center" }}
animate={{ backgroundPosition: "0% center" }}
transition={{
repeat: Infinity,
duration,
ease: "linear",
}}
style={
{
"--spread": `${dynamicSpread}px`,
backgroundImage: `var(--bg), linear-gradient(var(--base-color), var(--base-color))`,
} as React.CSSProperties
}
>
{children}
</MotionComponent>
);
}

View file

@ -20,7 +20,7 @@ const buttonVariants = cva(
"border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5", "border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5",
ghost: "text-foreground hover:bg-accent hover:text-accent-foreground", ghost: "text-foreground hover:bg-accent hover:text-accent-foreground",
ghostActive: ghostActive:
"bg-muted text-foreground hover:bg-accent hover:text-accent-foreground", "bg-muted text-foreground hover:bg-secondary-hover hover:text-accent-foreground",
menu: "hover:bg-muted hover:text-accent-foreground focus:!ring-0 focus-visible:!ring-0", menu: "hover:bg-muted hover:text-accent-foreground focus:!ring-0 focus-visible:!ring-0",
"menu-active": "menu-active":
"font-semibold hover:bg-muted hover:text-accent-foreground focus-visible:!ring-offset-0", "font-semibold hover:bg-muted hover:text-accent-foreground focus-visible:!ring-offset-0",

View file

@ -2,6 +2,7 @@ import * as DialogPrimitive from "@radix-ui/react-dialog";
import { Cross2Icon } from "@radix-ui/react-icons"; import { Cross2Icon } from "@radix-ui/react-icons";
import * as React from "react"; import * as React from "react";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
import ShadTooltip from "../shadTooltipComponent";
const Dialog = DialogPrimitive.Root; const Dialog = DialogPrimitive.Root;
@ -49,10 +50,12 @@ const DialogContent = React.forwardRef<
{...props} {...props}
> >
{children} {children}
<DialogPrimitive.Close className="absolute right-2.5 top-2.5 rounded-sm p-1.5 opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> <ShadTooltip styleClasses="z-50" side="left" content="Close">
<Cross2Icon className="h-4 w-4" /> <DialogPrimitive.Close className="absolute right-2 top-2 flex h-8 w-8 items-center justify-center rounded-sm ring-offset-background transition-opacity hover:bg-secondary-hover hover:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<span className="sr-only">Close</span> <Cross2Icon className="h-[18px] w-[18px]" />
</DialogPrimitive.Close> <span className="sr-only">Close</span>
</DialogPrimitive.Close>
</ShadTooltip>
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal> </DialogPortal>
)); ));

View file

@ -0,0 +1,231 @@
"use client";
import { cn } from "@/utils/utils";
import {
AnimatePresence,
motion,
TargetAndTransition,
Variants,
} from "framer-motion";
import React, { ReactElement } from "react";
type PresetType = "blur" | "shake" | "scale" | "fade" | "slide";
type TextEffectProps = {
children: string;
per?: "word" | "char" | "line";
as?: keyof React.JSX.IntrinsicElements;
variants?: {
container?: Variants;
item?: Variants;
};
className?: string;
preset?: PresetType;
delay?: number;
trigger?: boolean;
onAnimationComplete?: () => void;
segmentWrapperClassName?: string;
};
const defaultStaggerTimes: Record<"char" | "word" | "line", number> = {
char: 0.03,
word: 0.05,
line: 0.1,
};
const defaultContainerVariants: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.05,
},
},
exit: {
transition: { staggerChildren: 0.05, staggerDirection: -1 },
},
};
const defaultItemVariants: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
},
exit: { opacity: 0 },
};
const presetVariants: Record<
PresetType,
{ container: Variants; item: Variants }
> = {
blur: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0, filter: "blur(12px)" },
visible: { opacity: 1, filter: "blur(0px)" },
exit: { opacity: 0, filter: "blur(12px)" },
},
},
shake: {
container: defaultContainerVariants,
item: {
hidden: { x: 0 },
visible: { x: [-5, 5, -5, 5, 0], transition: { duration: 0.5 } },
exit: { x: 0 },
},
},
scale: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0, scale: 0 },
visible: { opacity: 1, scale: 1 },
exit: { opacity: 0, scale: 0 },
},
},
fade: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0 },
visible: { opacity: 1 },
exit: { opacity: 0 },
},
},
slide: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
exit: { opacity: 0, y: 20 },
},
},
};
const AnimationComponent: React.FC<{
segment: string;
variants: Variants;
per: "line" | "word" | "char";
segmentWrapperClassName?: string;
}> = React.memo(({ segment, variants, per, segmentWrapperClassName }) => {
const content =
per === "line" ? (
<motion.span variants={variants} className="block">
{segment}
</motion.span>
) : per === "word" ? (
<motion.span
aria-hidden="true"
variants={variants}
className="inline-block whitespace-pre"
>
{segment}
</motion.span>
) : (
<motion.span className="inline-block whitespace-pre">
{segment.split("").map((char, charIndex) => (
<motion.span
key={`char-${charIndex}`}
aria-hidden="true"
variants={variants}
className="inline-block whitespace-pre"
>
{char}
</motion.span>
))}
</motion.span>
);
if (!segmentWrapperClassName) {
return content;
}
const defaultWrapperClassName = per === "line" ? "block" : "inline-block";
return (
<span className={cn(defaultWrapperClassName, segmentWrapperClassName)}>
{content}
</span>
);
});
AnimationComponent.displayName = "AnimationComponent";
export function TextEffect({
children,
per = "word",
as = "p",
variants,
className,
preset,
delay = 0,
trigger = true,
onAnimationComplete,
segmentWrapperClassName,
}: TextEffectProps) {
let segments: string[];
if (per === "line") {
segments = children.split("\n");
} else if (per === "word") {
segments = children.split(/(\s+)/);
} else {
segments = children.split("");
}
const MotionTag = motion[as as keyof typeof motion] as typeof motion.div;
const selectedVariants = preset
? presetVariants[preset]
: { container: defaultContainerVariants, item: defaultItemVariants };
const containerVariants = variants?.container || selectedVariants.container;
const itemVariants = variants?.item || selectedVariants.item;
const ariaLabel = per === "line" ? undefined : children;
const stagger = defaultStaggerTimes[per];
const delayedContainerVariants: Variants = {
hidden: containerVariants.hidden,
visible: {
...containerVariants.visible,
transition: {
...(containerVariants.visible as TargetAndTransition)?.transition,
staggerChildren:
(containerVariants.visible as TargetAndTransition)?.transition
?.staggerChildren || stagger,
delayChildren: delay,
},
},
exit: containerVariants.exit,
};
return (
<AnimatePresence mode="popLayout">
{trigger && (
<MotionTag
initial="hidden"
animate="visible"
exit="exit"
aria-label={ariaLabel}
variants={delayedContainerVariants}
className={cn("whitespace-pre-wrap", className)}
onAnimationComplete={onAnimationComplete}
>
{segments.map((segment, index) => (
<AnimationComponent
key={`${per}-${index}-${segment}`}
segment={segment}
variants={itemVariants}
per={per}
segmentWrapperClassName={segmentWrapperClassName}
/>
))}
</MotionTag>
)}
</AnimatePresence>
);
}
export function TextEffectPerChar({ children }: { children: string }) {
return (
<TextEffect per="char" preset="fade">
{children}
</TextEffect>
);
}

View file

@ -660,22 +660,22 @@ export const priorityFields = new Set(["code", "template"]);
export const INPUT_TYPES = new Set([ export const INPUT_TYPES = new Set([
"ChatInput", "ChatInput",
"TextInput", // "TextInput",
"KeyPairInput", // "KeyPairInput",
"JsonInput", // "JsonInput",
"StringListInput", // "StringListInput",
]); ]);
export const OUTPUT_TYPES = new Set([ export const OUTPUT_TYPES = new Set([
"ChatOutput", "ChatOutput",
"TextOutput", // "TextOutput",
"PDFOutput", // "PDFOutput",
"ImageOutput", // "ImageOutput",
"CSVOutput", // "CSVOutput",
"JsonOutput", // "JsonOutput",
"KeyPairOutput", // "KeyPairOutput",
"StringListOutput", // "StringListOutput",
"DataOutput", // "DataOutput",
"TableOutput", // "TableOutput",
]); ]);
export const CHAT_FIRST_INITIAL_TEXT = export const CHAT_FIRST_INITIAL_TEXT =

View file

@ -7,7 +7,7 @@ export const ENABLE_BRANDING = true;
export const ENABLE_MVPS = false; export const ENABLE_MVPS = false;
export const ENABLE_CUSTOM_PARAM = false; export const ENABLE_CUSTOM_PARAM = false;
export const ENABLE_INTEGRATIONS = false; export const ENABLE_INTEGRATIONS = false;
export const ENABLE_NEW_IO_MODAL = false; export const ENABLE_NEW_IO_MODAL = true;
export const ENABLE_NEW_LOGO = false; export const ENABLE_NEW_LOGO = false;
export const ENABLE_DATASTAX_LANGFLOW = false; export const ENABLE_DATASTAX_LANGFLOW = false;
export const ENABLE_HOMEPAGE = true; export const ENABLE_HOMEPAGE = true;

View file

@ -117,12 +117,12 @@ export default function SessionSelector({
else toggleVisibility(); else toggleVisibility();
}} }}
className={cn( className={cn(
"file-component-accordion-div group cursor-pointer rounded-md hover:bg-muted-foreground/30", "file-component-accordion-div group cursor-pointer rounded-md text-left text-[13px] hover:bg-secondary-hover",
isVisible ? "bg-muted-foreground/15" : "", isVisible ? "bg-secondary-hover font-semibold" : "font-normal",
)} )}
> >
<div className="flex w-full items-center justify-between gap-2 overflow-hidden px-2 py-1 align-middle"> <div className="flex w-full items-center justify-between overflow-hidden px-2 py-1 align-middle">
<div className="flex min-w-0 items-center gap-2"> <div className="flex w-full min-w-0 items-center">
{isEditing ? ( {isEditing ? (
<div className="flex items-center"> <div className="flex items-center">
<Input <Input
@ -150,28 +150,37 @@ export default function SessionSelector({
</div> </div>
) : ( ) : (
<ShadTooltip styleClasses="z-50" content={session}> <ShadTooltip styleClasses="z-50" content={session}>
<div> <div
className={cn(
"h-4 w-full group-hover:truncate-secondary-hover",
isVisible
? "truncate-secondary-hover"
: "truncate-muted dark:truncate-background",
)}
>
{session === currentFlowId ? "Default Session" : session} {session === currentFlowId ? "Default Session" : session}
</div> </div>
</ShadTooltip> </ShadTooltip>
)} )}
</div> </div>
<Select value={""} onValueChange={handleSelectChange}> <Select value={""} onValueChange={handleSelectChange}>
<SelectTrigger <ShadTooltip styleClasses="z-50" side="right" content="Options">
onClick={(e) => { <SelectTrigger
e.stopPropagation(); onClick={(e) => {
}} e.stopPropagation();
onFocusCapture={() => { }}
inputRef.current?.focus(); onFocusCapture={() => {
}} inputRef.current?.focus();
data-confirm="true" }}
className={cn( data-confirm="true"
"h-8 w-fit border-none bg-transparent p-2 focus:ring-0", className={cn(
isVisible ? "visible" : "invisible group-hover:visible", "h-8 w-fit border-none bg-transparent p-2 focus:ring-0",
)} isVisible ? "visible" : "invisible group-hover:visible",
> )}
<IconComponent name="MoreHorizontal" className="h-4 w-4" /> >
</SelectTrigger> <IconComponent name="MoreHorizontal" className="h-4 w-4" />
</SelectTrigger>
</ShadTooltip>
<SelectContent side="right" align="start" className="p-0"> <SelectContent side="right" align="start" className="p-0">
<SelectItem <SelectItem
value="rename" value="rename"
@ -191,10 +200,6 @@ export default function SessionSelector({
<IconComponent name="Scroll" className="mr-2 h-4 w-4" /> <IconComponent name="Scroll" className="mr-2 h-4 w-4" />
Message logs Message logs
</div> </div>
<IconComponent
name="ArrowUpRight"
className="absolute right-2 h-4 w-4"
/>
</div> </div>
</SelectItem> </SelectItem>
<SelectItem <SelectItem

View file

@ -1,6 +1,5 @@
import Loading from "@/components/ui/loading"; import Loading from "@/components/ui/loading";
import useFlowStore from "@/stores/flowStore"; import useFlowStore from "@/stores/flowStore";
import IconComponent from "../../../../../../../components/genericIconComponent";
import { Button } from "../../../../../../../components/ui/button"; import { Button } from "../../../../../../../components/ui/button";
import { Case } from "../../../../../../../shared/components/caseComponent"; import { Case } from "../../../../../../../shared/components/caseComponent";
import { FilePreviewType } from "../../../../../../../types/components"; import { FilePreviewType } from "../../../../../../../types/components";
@ -9,8 +8,10 @@ import { classNames } from "../../../../../../../utils/utils";
const BUTTON_STATES = { const BUTTON_STATES = {
NO_INPUT: "bg-high-indigo text-background", NO_INPUT: "bg-high-indigo text-background",
HAS_CHAT_VALUE: "text-primary", HAS_CHAT_VALUE: "text-primary",
SHOW_STOP: "bg-zinc-400 text-white cursor-pointer", SHOW_STOP:
DEFAULT: "bg-chat-send text-background", "bg-muted hover:bg-secondary-hover dark:hover:bg-input text-foreground cursor-pointer",
DEFAULT:
"bg-primary text-primary-foreground hover:bg-primary-hover hover:text-secondary",
}; };
type ButtonSendWrapperProps = { type ButtonSendWrapperProps = {
@ -60,24 +61,27 @@ const ButtonSendWrapper = ({
disabled={lockChat && !isBuilding} disabled={lockChat && !isBuilding}
onClick={handleClick} onClick={handleClick}
unstyled unstyled
data-testid={showStopButton ? "button-stop" : "button-send"}
> >
<Case condition={showStopButton}> <Case condition={showStopButton}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 rounded-md text-[14px] font-medium">
Stop Stop
<Loading className="text-black" /> <Loading className="h-[16px] w-[16px]" />
</div> </div>
</Case> </Case>
<Case condition={showPlayButton}> {/* <Case condition={showPlayButton}>
<IconComponent <IconComponent
name="Zap" name="Zap"
className="form-modal-play-icon" className="form-modal-play-icon"
aria-hidden="true" aria-hidden="true"
/> />
</Case> </Case> */}
<Case condition={showSendButton}> <Case condition={showSendButton}>
<div className="flex items-center gap-2">Send</div> <div className="flex h-fit w-fit items-center gap-2 text-[14px] font-medium">
Send
</div>
</Case> </Case>
</Button> </Button>
); );

View file

@ -29,14 +29,10 @@ const TextAreaWrapper = ({
} }
}; };
const lockClass = noInput
? "form-modal-no-input bg-input"
: "form-modal-lock-false bg-background";
const fileClass = files.length > 0 ? "!rounded-t-none border-t-0" : ""; const fileClass = files.length > 0 ? "!rounded-t-none border-t-0" : "";
const additionalClassNames = const additionalClassNames =
"form-input block w-full border-0 custom-scroll focus:border-ring focus:ring-0 p-0 sm:text-sm"; "form-input block w-full border-0 custom-scroll focus:border-ring rounded-none shadow-none focus:ring-0 p-0 sm:text-sm !bg-transparent";
useEffect(() => { useEffect(() => {
if (!lockChat && !noInput) { if (!lockChat && !noInput) {
@ -73,7 +69,7 @@ const TextAreaWrapper = ({
onChange={(event): void => { onChange={(event): void => {
setChatValue(event.target.value); setChatValue(event.target.value);
}} }}
className={classNames(lockClass, fileClass, additionalClassNames)} className={classNames(fileClass, additionalClassNames)}
placeholder={getPlaceholderText(isDragging, noInput)} placeholder={getPlaceholderText(isDragging, noInput)}
/> />
); );

View file

@ -1,3 +1,4 @@
import ShadTooltip from "@/components/shadTooltipComponent";
import ForwardedIconComponent from "../../../../../../../components/genericIconComponent"; import ForwardedIconComponent from "../../../../../../../components/genericIconComponent";
import { Button } from "../../../../../../../components/ui/button"; import { Button } from "../../../../../../../components/ui/button";
@ -8,25 +9,33 @@ const UploadFileButton = ({
lockChat, lockChat,
}) => { }) => {
return ( return (
<div> <ShadTooltip
<input styleClasses="z-50"
disabled={lockChat} side="right"
type="file" content="Attach image (png, jpg, jpeg)"
ref={fileInputRef} >
style={{ display: "none" }} <div>
onChange={handleFileChange} <input
/> disabled={lockChat}
<Button type="file"
disabled={lockChat} ref={fileInputRef}
className={`rounded-md bg-zinc-500 p-1 font-bold transition-all ${ style={{ display: "none" }}
lockChat ? "cursor-not-allowed" : "hover:text-muted-foreground" onChange={handleFileChange}
}`} />
onClick={handleButtonClick} <Button
unstyled disabled={lockChat}
> className={`flex h-[32px] w-[32px] items-center justify-center rounded-md bg-muted font-bold transition-all ${
<ForwardedIconComponent name="Image" /> lockChat
</Button> ? "cursor-not-allowed"
</div> : "text-muted-foreground hover:text-primary"
}`}
onClick={handleButtonClick}
unstyled
>
<ForwardedIconComponent className="h-[18px] w-[18px]" name="Image" />
</Button>
</div>
</ShadTooltip>
); );
}; };

View file

@ -1,7 +1,9 @@
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import Loading from "@/components/ui/loading";
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file"; import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
import useFileSizeValidator from "@/shared/hooks/use-file-size-validator"; import useFileSizeValidator from "@/shared/hooks/use-file-size-validator";
import useAlertStore from "@/stores/alertStore"; import useAlertStore from "@/stores/alertStore";
import useFlowStore from "@/stores/flowStore";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import { import {
@ -38,6 +40,7 @@ export default function ChatInput({
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const { validateFileSize } = useFileSizeValidator(setErrorData); const { validateFileSize } = useFileSizeValidator(setErrorData);
const stopBuilding = useFlowStore((state) => state.stopBuilding);
useFocusOnUnlock(lockChat, inputRef); useFocusOnUnlock(lockChat, inputRef);
useAutoResizeTextArea(chatValue, inputRef); useAutoResizeTextArea(chatValue, inputRef);
@ -150,7 +153,7 @@ export default function ChatInput({
); );
}; };
const classNameFilePreview = `flex w-full items-center gap-2 bg-background py-2 overflow-auto custom-scroll`; const classNameFilePreview = `flex w-full items-center gap-2 py-2 overflow-auto custom-scroll`;
const handleButtonClick = () => { const handleButtonClick = () => {
fileInputRef.current!.click(); fileInputRef.current!.click();
@ -164,17 +167,33 @@ export default function ChatInput({
if (noInput) { if (noInput) {
return ( return (
<div className="flex h-full w-full flex-col items-center justify-center"> <div className="flex h-full w-full flex-col items-center justify-center">
<div className="flex flex-col items-center justify-center gap-3 bg-background p-2"> <div className="flex w-full flex-col items-center justify-center gap-3 rounded-md border border-input bg-muted p-2 py-4">
<Button {!lockChat ? (
className="font-semibold" <Button
onClick={() => { data-testid="button-send"
sendMessage({ className="font-semibold"
repeat: 1, onClick={() => {
}); sendMessage({
}} repeat: 1,
> });
Run Flow }}
</Button> >
Run Flow
</Button>
) : (
<Button
onClick={stopBuilding}
data-testid="button-stop"
unstyled
className="form-modal-send-button cursor-pointer bg-muted text-foreground hover:bg-secondary-hover dark:hover:bg-input"
>
<div className="flex items-center gap-2 rounded-md text-[14px] font-medium">
Stop
<Loading className="h-[16px] w-[16px]" />
</div>
</Button>
)}
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Add a{" "} Add a{" "}
<a <a
@ -193,7 +212,7 @@ export default function ChatInput({
return ( return (
<div className="flex w-full flex-col-reverse"> <div className="flex w-full flex-col-reverse">
<div className="flex w-full flex-col rounded-md border border-border p-4"> <div className="flex w-full flex-col rounded-md border border-input p-4 hover:border-muted-foreground focus:border-[1.75px] has-[:focus]:border-primary">
<TextAreaWrapper <TextAreaWrapper
checkSendingOk={checkSendingOk} checkSendingOk={checkSendingOk}
send={send} send={send}

View file

@ -0,0 +1,15 @@
import logo from "/src/assets/logo.svg";
export default function LogoIcon() {
return (
<div className="relative flex h-8 w-8 items-center justify-center rounded-md bg-muted">
<div className="flex h-8 w-8 items-center justify-center">
<img
src={logo}
alt="Chain logo"
className="absolute h-[18px] w-[18px]"
/>
</div>
</div>
);
}

View file

@ -31,14 +31,14 @@ export function EditMessageButton({
return ( return (
<div className="flex items-center rounded-md border border-border bg-background"> <div className="flex items-center rounded-md border border-border bg-background">
<ShadTooltip styleClasses="z-50" content="Edit message" side="top"> <ShadTooltip styleClasses="z-50" content="Edit message" side="top">
<div> <div className="p-1">
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
onClick={onEdit} onClick={onEdit}
className="h-8 w-8 rounded-none p-0" className="h-8 w-8"
> >
<IconComponent name="Pencil" className="h-4 w-4" /> <IconComponent name="Pen" className="h-4 w-4" />
</Button> </Button>
</div> </div>
</ShadTooltip> </ShadTooltip>
@ -48,12 +48,12 @@ export function EditMessageButton({
content={isCopied ? "Copied!" : "Copy message"} content={isCopied ? "Copied!" : "Copy message"}
side="top" side="top"
> >
<div> <div className="p-1">
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
onClick={handleCopy} onClick={handleCopy}
className="h-8 w-8 rounded-none p-0" className="h-8 w-8"
> >
<IconComponent <IconComponent
name={isCopied ? "Check" : "Copy"} name={isCopied ? "Check" : "Copy"}

View file

@ -13,6 +13,7 @@ export default function EditMessageField({
}) { }) {
const [message, setMessage] = useState(initialMessage); const [message, setMessage] = useState(initialMessage);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
// used before to onBlur function, leave it here because in the future we may want this functionality again
const [isButtonClicked, setIsButtonClicked] = useState(false); const [isButtonClicked, setIsButtonClicked] = useState(false);
const adjustTextareaHeight = () => { const adjustTextareaHeight = () => {
if (textareaRef.current) { if (textareaRef.current) {
@ -25,52 +26,52 @@ export default function EditMessageField({
}, []); }, []);
return ( return (
<div className="flex h-fit w-full flex-col bg-zinc-800"> <div className="flex h-fit w-full flex-col rounded-md bg-muted px-4 py-2">
<Textarea <Textarea
ref={textareaRef} ref={textareaRef}
className="h-mx-full w-full resize-none border-0 bg-zinc-800 focus:ring-0" className="max-h-[400px] w-full resize-none rounded-none border-0 bg-muted shadow-none focus:ring-0"
onBlur={() => { // onBlur={() => {
if (!isButtonClicked) { // if (!isButtonClicked) {
onCancel(); // onCancel();
} // }
}} // }}
value={message} value={message}
autoFocus={true} autoFocus={true}
onChange={(e) => setMessage(e.target.value)} onChange={(e) => setMessage(e.target.value)}
/> />
<div className="flex w-full flex-row-reverse justify-between"> <div className="flex w-full flex-row-reverse justify-between">
<div className="flex flex-row-reverse gap-2"> <div className="flex w-full flex-row-reverse items-center justify-between">
<Button <div className="flex min-w-fit flex-row-reverse gap-2">
data-testid="save-button" <Button
variant={"primary"} data-testid="save-button"
onMouseDown={() => setIsButtonClicked(true)} onMouseDown={() => setIsButtonClicked(true)}
onClick={() => { onClick={() => {
onEdit(message); onEdit(message);
setIsButtonClicked(false); setIsButtonClicked(false);
}} }}
className="mt-2 hover:!bg-zinc-950" className="mt-2 bg-primary text-background hover:bg-primary-hover hover:text-secondary"
> >
Save Save
</Button> </Button>
<Button <Button
variant={"secondary"} variant={"secondary"}
data-testid="cancel-button" data-testid="cancel-button"
onMouseDown={() => setIsButtonClicked(true)} onMouseDown={() => setIsButtonClicked(true)}
onClick={() => { onClick={() => {
onCancel(); onCancel();
setIsButtonClicked(false); setIsButtonClicked(false);
}} }}
className="mt-2 bg-white !text-black hover:bg-white" className="mt-2 !bg-transparent text-foreground hover:!bg-secondary-hover"
> >
Cancel Cancel
</Button> </Button>
</div> </div>
<div> <div className="text-[13px] font-medium text-muted-foreground word-break-break-word">
<span className="mr-4 text-sm text-muted-foreground">
Editing messages will update the memory but won't restart the Editing messages will update the memory but won't restart the
conversation. conversation.
</span> </div>
</div> </div>
<div></div>
</div> </div>
</div> </div>
); );

View file

@ -203,6 +203,10 @@ export default function ChatMessage({
"form-modal-chat-position group hover:bg-background", "form-modal-chat-position group hover:bg-background",
chat.isSend ? "" : " ", chat.isSend ? "" : " ",
)} )}
style={{
backgroundColor: chat.background_color || "#FF00FF", // Loud magenta as default
color: chat.text_color || "#00FFFF", // Loud cyan as default
}}
> >
<div <div
className={ className={
@ -216,11 +220,19 @@ export default function ChatMessage({
!chat.isSend ? "bg-chat-bot-icon" : "bg-chat-user-icon", !chat.isSend ? "bg-chat-bot-icon" : "bg-chat-user-icon",
)} )}
> >
<img {chat.icon ? (
src={!chat.isSend ? Robot : MaleTechnology} <img
className="absolute scale-[60%]" src={chat.icon}
alt={!chat.isSend ? "robot_image" : "male_technology"} className="absolute scale-[60%]"
/> alt="icon"
/>
) : (
<img
src={!chat.isSend ? Robot : MaleTechnology}
className="absolute scale-[60%]"
alt={!chat.isSend ? "robot_image" : "male_technology"}
/>
)}
</div> </div>
<span <span
className="max-w-24 truncate text-xs" className="max-w-24 truncate text-xs"

View file

@ -1,17 +1,21 @@
import { ProfileIcon } from "@/components/appHeaderComponent/components/ProfileIcon"; import { ProfileIcon } from "@/components/appHeaderComponent/components/ProfileIcon";
import ShadTooltip from "@/components/shadTooltipComponent"; import { TextShimmer } from "@/components/ui/TextShimmer";
import { useUpdateMessage } from "@/controllers/API/queries/messages"; import { useUpdateMessage } from "@/controllers/API/queries/messages";
import useFlowsManagerStore from "@/stores/flowsManagerStore"; import useFlowsManagerStore from "@/stores/flowsManagerStore";
import useFlowStore from "@/stores/flowStore";
import { useUtilityStore } from "@/stores/utilityStore"; import { useUtilityStore } from "@/stores/utilityStore";
import { ContentBlock, ErrorContent } from "@/types/chat";
import Convert from "ansi-to-html"; import Convert from "ansi-to-html";
import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import Markdown from "react-markdown"; import Markdown from "react-markdown";
import rehypeMathjax from "rehype-mathjax"; import rehypeMathjax from "rehype-mathjax";
import remarkGfm from "remark-gfm"; import remarkGfm from "remark-gfm";
import MaleTechnology from "../../../../../assets/male-technologist.png";
import Robot from "../../../../../assets/robot.png"; import Robot from "../../../../../assets/robot.png";
import CodeTabsComponent from "../../../../../components/codeTabsComponent/ChatCodeTabComponent"; import CodeTabsComponent from "../../../../../components/codeTabsComponent/ChatCodeTabComponent";
import IconComponent from "../../../../../components/genericIconComponent"; import IconComponent, {
ForwardedIconComponent,
} from "../../../../../components/genericIconComponent";
import SanitizedHTMLWrapper from "../../../../../components/sanitizedHTMLWrapper"; import SanitizedHTMLWrapper from "../../../../../components/sanitizedHTMLWrapper";
import { import {
EMPTY_INPUT_SEND_MESSAGE, EMPTY_INPUT_SEND_MESSAGE,
@ -20,6 +24,7 @@ import {
import useAlertStore from "../../../../../stores/alertStore"; import useAlertStore from "../../../../../stores/alertStore";
import { chatMessagePropsType } from "../../../../../types/components"; import { chatMessagePropsType } from "../../../../../types/components";
import { cn } from "../../../../../utils/utils"; import { cn } from "../../../../../utils/utils";
import LogoIcon from "./components/chatLogoIcon";
import { EditMessageButton } from "./components/editMessageButton/newMessageOptions"; import { EditMessageButton } from "./components/editMessageButton/newMessageOptions";
import EditMessageField from "./components/editMessageField/newEditMessageField"; import EditMessageField from "./components/editMessageField/newEditMessageField";
import FileCardWrapper from "./components/fileCardWrapper"; import FileCardWrapper from "./components/fileCardWrapper";
@ -30,6 +35,7 @@ export default function ChatMessage({
lastMessage, lastMessage,
updateChat, updateChat,
setLockChat, setLockChat,
closeChat,
}: chatMessagePropsType): JSX.Element { }: chatMessagePropsType): JSX.Element {
const convert = new Convert({ newline: true }); const convert = new Convert({ newline: true });
const [hidden, setHidden] = useState(true); const [hidden, setHidden] = useState(true);
@ -37,6 +43,7 @@ export default function ChatMessage({
const [promptOpen, setPromptOpen] = useState(false); const [promptOpen, setPromptOpen] = useState(false);
const [streamUrl, setStreamUrl] = useState(chat.stream_url); const [streamUrl, setStreamUrl] = useState(chat.stream_url);
const flow_id = useFlowsManagerStore((state) => state.currentFlowId); const flow_id = useFlowsManagerStore((state) => state.currentFlowId);
const fitViewNode = useFlowStore((state) => state.fitViewNode);
// We need to check if message is not undefined because // We need to check if message is not undefined because
// we need to run .toString() on it // we need to run .toString() on it
const [chatMessage, setChatMessage] = useState( const [chatMessage, setChatMessage] = useState(
@ -47,6 +54,7 @@ export default function ChatMessage({
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const chatMessageRef = useRef(chatMessage); const chatMessageRef = useRef(chatMessage);
const [editMessage, setEditMessage] = useState(false); const [editMessage, setEditMessage] = useState(false);
const [showError, setShowError] = useState(false);
useEffect(() => { useEffect(() => {
const chatMessageString = chat.message ? chat.message.toString() : ""; const chatMessageString = chat.message ? chat.message.toString() : "";
@ -58,7 +66,6 @@ export default function ChatMessage({
const setPlaygroundScrollBehaves = useUtilityStore( const setPlaygroundScrollBehaves = useUtilityStore(
(state) => state.setPlaygroundScrollBehaves, (state) => state.setPlaygroundScrollBehaves,
); );
// Sync ref with state // Sync ref with state
useEffect(() => { useEffect(() => {
chatMessageRef.current = chatMessage; chatMessageRef.current = chatMessage;
@ -136,6 +143,16 @@ export default function ChatMessage({
} }
}, [lastMessage, chat]); }, [lastMessage, chat]);
useEffect(() => {
if (chat.category === "error") {
// Short delay before showing error to allow for loading animation
const timer = setTimeout(() => {
setShowError(true);
}, 50);
return () => clearTimeout(timer);
}
}, [chat.category]);
let decodedMessage = chatMessage ?? ""; let decodedMessage = chatMessage ?? "";
try { try {
decodedMessage = decodeURIComponent(chatMessage); decodedMessage = decodeURIComponent(chatMessage);
@ -194,304 +211,434 @@ export default function ChatMessage({
); );
}; };
const editedFlag = chat.edit ? ( const editedFlag = chat.edit ? (
<span className="text-sm text-chat-trigger-disabled">(Edited)</span> <div className="text-sm text-muted-foreground">(Edited)</div>
) : null; ) : null;
if (chat.category === "error") {
const block = (chat.content_blocks?.[0] ?? {}) as ContentBlock;
const errorContent = (block.content as ErrorContent) ?? {};
return (
<div className="w-5/6 max-w-[768px] py-4 word-break-break-word">
<AnimatePresence mode="wait">
{!showError && lastMessage ? (
<motion.div
key="loading"
initial={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="flex w-full gap-4 rounded-md p-2"
>
<LogoIcon />
<div className="flex items-center">
<TextShimmer className="" duration={1}>
Flow running...
</TextShimmer>
</div>
</motion.div>
) : (
<motion.div
key="error"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
className="flex w-full gap-4 rounded-md p-2"
>
<LogoIcon />
<div className="w-full rounded-md border border-error-red-border bg-error-red p-4 text-[14px] text-foreground">
<div className="mb-2 flex items-center gap-2">
<ForwardedIconComponent
className="h-[18px] w-[18px] text-destructive"
name="OctagonAlert"
/>
<span className="">An error stopped your flow.</span>
</div>
<div className="mb-4">
<h3 className="pb-3 font-semibold">Error details:</h3>
<p className="pb-1">
Component:{" "}
<span
className={cn(
closeChat ? "cursor-pointer underline" : "",
)}
onClick={() => {
fitViewNode(chat.properties?.source?.id ?? "");
closeChat?.();
}}
>
{errorContent.component}
</span>
</p>
{errorContent.field && (
<p className="pb-1">Field: {errorContent.field}</p>
)}
{errorContent.reason && (
<span className="">
Reason:{" "}
<Markdown
linkTarget="_blank"
remarkPlugins={[remarkGfm]}
components={{
a: ({ node, ...props }) => {
return (
<a
href={props.href}
target="_blank"
className="underline"
rel="noopener noreferrer"
>
{props.children}
</a>
);
},
}}
>
{errorContent.reason}
</Markdown>
</span>
)}
</div>
{errorContent.solution && (
<div>
<h3 className="pb-3 font-semibold">Steps to fix:</h3>
<ol className="list-decimal pl-5">
<li>Check the component settings</li>
<li>Ensure all required fields are filled</li>
<li>Re-run your flow</li>
</ol>
</div>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
return ( return (
<> <>
<div className="flex-max-width px-2 py-6 pl-32 pr-9"> <div className="w-5/6 max-w-[768px] py-4 word-break-break-word">
<div className={"mr-3 mt-1 flex w-11/12 pb-3"}> <div
className={cn(
"group relative flex w-full gap-4 rounded-md p-2",
editMessage ? "" : "hover:bg-muted",
)}
>
<div <div
className={cn( className={cn(
"group relative flex w-full gap-4 rounded-md p-2 hover:bg-zinc-800", "relative flex h-[32px] w-[32px] items-center justify-center overflow-hidden rounded-md text-2xl",
editMessage ? "bg-zinc-800" : "", !chat.isSend
? "bg-muted"
: "border border-border hover:border-input",
)} )}
style={
chat.properties?.background_color
? { backgroundColor: chat.properties.background_color }
: {}
}
> >
<div {!chat.isSend ? (
className={cn( <div className="flex h-[18px] w-[18px] items-center justify-center">
"relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md p-5 text-2xl", {chat.properties?.icon ? (
!chat.isSend ? "bg-chat-bot-icon" : "bg-zinc-400", chat.properties.icon.match(
)} /[\u2600-\u27BF\uD83C-\uDBFF\uDC00-\uDFFF]/,
> ) ? (
{!chat.isSend ? ( <span className="">{chat.properties.icon}</span>
<img ) : (
src={Robot} <ForwardedIconComponent name={chat.properties.icon} />
className="absolute scale-[60%]" )
alt={"robot_image"} ) : (
/> <img
) : ( src={Robot}
<div className="absolute scale-[80%]"> className="absolute bottom-0 left-0 scale-[60%]"
<ProfileIcon /> alt={"robot_image"}
</div> />
)} )}
</div>
<div className="flex w-[94%] flex-col">
<div>
<div
className="max-w-full truncate pb-2 font-semibold"
data-testid={
"sender_name_" + chat.sender_name?.toLocaleLowerCase()
}
>
{chat.sender_name}
</div>
{/* TODO: ADD MODEL RELATED NAME */}
</div> </div>
{!chat.isSend ? ( ) : (
<div className="form-modal-chat-text-position min-w-96 flex-grow"> <div className="flex h-[18px] w-[18px] items-center justify-center">
<div className="form-modal-chat-text"> {chat.properties?.icon ? (
{hidden && chat.thought && chat.thought !== "" && ( chat.properties.icon.match(
<div /[\u2600-\u27BF\uD83C-\uDBFF\uDC00-\uDFFF]/,
onClick={(): void => setHidden((prev) => !prev)} ) ? (
className="form-modal-chat-icon-div" <div className="">{chat.properties.icon}</div>
> ) : (
<IconComponent <ForwardedIconComponent name={chat.properties.icon} />
name="MessageSquare" )
className="form-modal-chat-icon" ) : (
/> <ProfileIcon />
</div> )}
)} </div>
{chat.thought && chat.thought !== "" && !hidden && ( )}
<SanitizedHTMLWrapper </div>
className="form-modal-chat-thought" <div className="flex w-[94%] flex-col">
content={convert.toHtml(chat.thought)} <div>
onClick={() => setHidden((prev) => !prev)} <div
className={cn(
"flex max-w-full items-baseline gap-3 truncate pb-2 text-[14px] font-semibold",
)}
style={
chat.properties?.text_color
? { color: chat.properties.text_color }
: {}
}
data-testid={
"sender_name_" + chat.sender_name?.toLocaleLowerCase()
}
>
{chat.sender_name}
{chat.properties?.source && (
<div className="text-[13px] font-normal text-muted-foreground">
{chat.properties?.source.source}
</div>
)}
</div>
</div>
{!chat.isSend ? (
<div className="form-modal-chat-text-position flex-grow">
<div className="form-modal-chat-text">
{hidden && chat.thought && chat.thought !== "" && (
<div
onClick={(): void => setHidden((prev) => !prev)}
className="form-modal-chat-icon-div"
>
<IconComponent
name="MessageSquare"
className="form-modal-chat-icon"
/> />
)} </div>
{chat.thought && chat.thought !== "" && !hidden && ( )}
<br></br> {chat.thought && chat.thought !== "" && !hidden && (
)} <SanitizedHTMLWrapper
<div className="flex w-full flex-col"> className="form-modal-chat-thought"
content={convert.toHtml(chat.thought ?? "")}
onClick={() => setHidden((prev) => !prev)}
/>
)}
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="flex w-full flex-col">
<div
className="flex w-full flex-col dark:text-white"
data-testid="div-chat-message"
>
<div <div
className="flex w-full flex-col dark:text-white" data-testid={
data-testid="div-chat-message" "chat-message-" + chat.sender_name + "-" + chatMessage
}
className="flex w-full flex-col"
> >
<div {chatMessage === "" && lockChat ? (
data-testid={ <IconComponent
"chat-message-" + name="MoreHorizontal"
chat.sender_name + className="h-8 w-8 animate-pulse"
"-" + />
chatMessage ) : (
} <div className="w-full">
className="flex w-full flex-col" {editMessage ? (
> <EditMessageField
{chatMessage === "" && lockChat ? ( key={`edit-message-${chat.id}`}
<IconComponent message={decodedMessage}
name="MoreHorizontal" onEdit={(message) => {
className="h-8 w-8 animate-pulse" handleEditMessage(message);
/> }}
) : ( onCancel={() => setEditMessage(false)}
<div className="w-full"> />
{editMessage ? ( ) : (
<EditMessageField <>
key={`edit-message-${chat.id}`} <div className="w-full items-baseline gap-2">
message={decodedMessage} <Markdown
onEdit={(message) => { remarkPlugins={[remarkGfm]}
handleEditMessage(message); linkTarget="_blank"
}} rehypePlugins={[rehypeMathjax]}
onCancel={() => setEditMessage(false)} className={cn(
/> "markdown prose flex w-fit max-w-full flex-col items-baseline text-[14px] font-normal word-break-break-word dark:prose-invert",
) : ( isEmpty
<> ? "text-muted-foreground"
<div className="flex w-full gap-2"> : "text-primary",
<Markdown )}
remarkPlugins={[remarkGfm]} components={{
linkTarget="_blank" p({ node, ...props }) {
rehypePlugins={[rehypeMathjax]} return (
className={cn( <span className="inline-block w-fit max-w-full">
"markdown prose flex w-full max-w-full flex-col word-break-break-word dark:prose-invert", {props.children}
isEmpty </span>
? "text-chat-trigger-disabled" );
: "text-primary", },
)} pre({ node, ...props }) {
components={{ return <>{props.children}</>;
pre({ node, ...props }) { },
return <>{props.children}</>; code: ({
}, node,
code: ({ inline,
node, className,
inline, children,
className, ...props
children, }) => {
...props let content = children as string;
}) => { if (
let content = children as string; Array.isArray(children) &&
if ( children.length === 1 &&
Array.isArray(children) && typeof children[0] === "string"
children.length === 1 && ) {
typeof children[0] === "string" content = children[0] as string;
) { }
content = children[0] as string; if (typeof content === "string") {
} if (content.length) {
if (typeof content === "string") { if (content[0] === "▍") {
if (content.length) { return (
if (content[0] === "▍") { <span className="form-modal-markdown-span"></span>
return ( );
<span className="form-modal-markdown-span">
▍
</span>
);
}
} }
const match = /language-(\w+)/.exec(
className || "",
);
return !inline ? (
<CodeTabsComponent
language={
(match && match[1]) || ""
}
code={String(content).replace(
/\n$/,
"",
)}
/>
) : (
<code
className={className}
{...props}
>
{content}
</code>
);
} }
},
}} const match = /language-(\w+)/.exec(
> className || "",
{isEmpty && !chat.stream_url );
? EMPTY_OUTPUT_SEND_MESSAGE
: chatMessage} return !inline ? (
</Markdown> <CodeTabsComponent
</div> language={
(match && match[1]) || ""
}
code={String(content).replace(
/\n$/,
"",
)}
/>
) : (
<code
className={className}
{...props}
>
{content}
</code>
);
}
},
}}
>
{isEmpty && !chat.stream_url
? EMPTY_OUTPUT_SEND_MESSAGE
: chatMessage}
</Markdown>
{editedFlag} {editedFlag}
</> </div>
)} </>
</div> )}
)} </div>
</div> )}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
) : ( </div>
<div className="form-modal-chat-text-position min-w-96 flex-grow"> ) : (
{template ? ( <div className="form-modal-chat-text-position flex-grow">
<> {template ? (
<button <>
className="form-modal-initial-prompt-btn" <button
onClick={() => { className="form-modal-initial-prompt-btn"
setPromptOpen((old) => !old); onClick={() => {
}} setPromptOpen((old) => !old);
> }}
Display Prompt >
<IconComponent Display Prompt
name="ChevronDown" <IconComponent
className={`h-3 w-3 transition-all ${promptOpen ? "rotate-180" : ""}`} name="ChevronDown"
/> className={`h-3 w-3 transition-all ${promptOpen ? "rotate-180" : ""}`}
</button> />
<span </button>
className={cn( <span
"prose word-break-break-word dark:prose-invert", className={cn(
!isEmpty "prose text-[14px] font-normal word-break-break-word dark:prose-invert",
? "text-primary" !isEmpty ? "text-primary" : "text-muted-foreground",
: "text-chat-trigger-disabled", )}
)} >
> {promptOpen
{promptOpen ? template?.split("\n")?.map((line, index) => {
? template?.split("\n")?.map((line, index) => { const regex = /{([^}]+)}/g;
const regex = /{([^}]+)}/g; let match;
let match; let parts: Array<JSX.Element | string> = [];
let parts: Array<JSX.Element | string> = []; let lastIndex = 0;
let lastIndex = 0; while ((match = regex.exec(line)) !== null) {
while ((match = regex.exec(line)) !== null) { // Push text up to the match
// Push text up to the match if (match.index !== lastIndex) {
if (match.index !== lastIndex) { parts.push(
parts.push( line.substring(lastIndex, match.index),
line.substring(lastIndex, match.index), );
); }
} // Push div with matched text
// Push div with matched text if (chat.message[match[1]]) {
if (chat.message[match[1]]) { parts.push(
parts.push( <span className="chat-message-highlight">
<span className="chat-message-highlight"> {chat.message[match[1]]}
{chat.message[match[1]]} </span>,
</span>, );
); }
}
// Update last index // Update last index
lastIndex = regex.lastIndex; lastIndex = regex.lastIndex;
} }
// Push text after the last match // Push text after the last match
if (lastIndex !== line.length) { if (lastIndex !== line.length) {
parts.push(line.substring(lastIndex)); parts.push(line.substring(lastIndex));
} }
return <p>{parts}</p>; return <p>{parts}</p>;
}) })
: isEmpty : isEmpty
? EMPTY_INPUT_SEND_MESSAGE ? EMPTY_INPUT_SEND_MESSAGE
: chatMessage} : chatMessage}
</span> </span>
</> </>
) : ( ) : (
<div className="flex w-full flex-col"> <div className="flex w-full flex-col">
{editMessage ? ( {editMessage ? (
<EditMessageField <EditMessageField
key={`edit-message-${chat.id}`} key={`edit-message-${chat.id}`}
message={decodedMessage} message={decodedMessage}
onEdit={(message) => { onEdit={(message) => {
handleEditMessage(message); handleEditMessage(message);
}} }}
onCancel={() => setEditMessage(false)} onCancel={() => setEditMessage(false)}
/> />
) : ( ) : (
<> <>
<div <div
className={`flex w-full gap-2 whitespace-pre-wrap break-words ${ className={`w-full items-baseline whitespace-pre-wrap break-words text-[14px] font-normal ${
isEmpty isEmpty ? "text-muted-foreground" : "text-primary"
? "text-chat-trigger-disabled" }`}
: "text-primary" data-testid={`chat-message-${chat.sender_name}-${chatMessage}`}
}`} >
data-testid={`chat-message-${chat.sender_name}-${chatMessage}`} {isEmpty ? EMPTY_INPUT_SEND_MESSAGE : decodedMessage}
>
{isEmpty
? EMPTY_INPUT_SEND_MESSAGE
: decodedMessage}
</div>
{editedFlag} {editedFlag}
</>
)}
{chat.files && (
<div className="my-2 flex flex-col gap-5">
{chat.files.map((file, index) => {
return (
<FileCardWrapper index={index} path={file} />
);
})}
</div> </div>
)} </>
</div> )}
)} {chat.files && (
</div> <div className="my-2 flex flex-col gap-5">
)} {chat.files?.map((file, index) => {
</div> return <FileCardWrapper index={index} path={file} />;
{!editMessage && ( })}
<div className="invisible absolute -top-4 right-0 group-hover:visible"> </div>
<div> )}
<EditMessageButton </div>
onCopy={() => { )}
navigator.clipboard.writeText(chatMessage);
}}
onDelete={() => {}}
onEdit={() => setEditMessage(true)}
className="h-fit group-hover:visible"
/>
</div>
</div> </div>
)} )}
</div> </div>
{!editMessage && (
<div className="invisible absolute -top-4 right-0 group-hover:visible">
<div>
<EditMessageButton
onCopy={() => {
navigator.clipboard.writeText(chatMessage);
}}
onDelete={() => {}}
onEdit={() => setEditMessage(true)}
className="h-fit group-hover:visible"
/>
</div>
</div>
)}
</div> </div>
</div> </div>
<div id={lastMessage ? "last-chat-message" : undefined} /> <div id={lastMessage ? "last-chat-message" : undefined} />

View file

@ -85,10 +85,13 @@ export default function FilePreview({
className={`absolute -right-2 -top-2 flex h-5 w-5 items-center justify-center opacity-100 transition-opacity`} className={`absolute -right-2 -top-2 flex h-5 w-5 items-center justify-center opacity-100 transition-opacity`}
> >
<div <div
className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-full bg-zinc-800 p-2 transition-all hover:bg-zinc-700" className="group flex h-7 w-7 cursor-pointer items-center justify-center rounded-full bg-muted p-2 transition-all hover:bg-input"
onClick={onDelete} onClick={onDelete}
> >
<IconComponent name="X" className="stroke-zinc-100 stroke-2" /> <IconComponent
name="X"
className="h-4 w-4 stroke-muted-foreground stroke-2 group-hover:stroke-primary"
/>
</div> </div>
</div> </div>
</div> </div>

View file

@ -17,7 +17,7 @@ import {
import useAlertStore from "../../../../stores/alertStore"; import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore"; import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { ChatMessageType } from "../../../../types/chat"; import { ChatMessageType, PlaygroundEvent } from "../../../../types/chat";
import { FilePreviewType, chatViewProps } from "../../../../types/components"; import { FilePreviewType, chatViewProps } from "../../../../types/components";
import ChatInput from "./chatInput"; import ChatInput from "./chatInput";
import useDragAndDrop from "./chatInput/hooks/use-drag-and-drop"; import useDragAndDrop from "./chatInput/hooks/use-drag-and-drop";
@ -51,13 +51,22 @@ export default function ChatView({
.filter( .filter(
(message) => (message) =>
message.flow_id === currentFlowId && message.flow_id === currentFlowId &&
(visibleSession === message.session_id ?? true), (visibleSession === message.session_id || visibleSession === null),
) )
.map((message) => { .map((message) => {
let files = message.files; let files = message.files;
//HANDLE THE "[]" case // Handle the "[]" case, empty string, or already parsed array
if (typeof files === "string") { if (Array.isArray(files)) {
files = JSON.parse(files); // files is already an array, no need to parse
} else if (files === "[]" || files === "") {
files = [];
} else if (typeof files === "string") {
try {
files = JSON.parse(files);
} catch (error) {
console.error("Error parsing files:", error);
files = [];
}
} }
return { return {
isSend: message.sender === "User", isSend: message.sender === "User",
@ -68,6 +77,8 @@ export default function ChatView({
timestamp: message.timestamp, timestamp: message.timestamp,
session: message.session_id, session: message.session_id,
edit: message.edit, edit: message.edit,
background_color: message.background_color || "",
text_color: message.text_color || "",
}; };
}); });
const finalChatHistory = [...messagesFromMessagesStore].sort((a, b) => { const finalChatHistory = [...messagesFromMessagesStore].sort((a, b) => {
@ -210,6 +221,57 @@ export default function ChatView({
} }
}; };
const handlePlaygroundEvent = (event: PlaygroundEvent) => {
switch (event.event_type) {
case "message":
setChatHistory((prev) => [
...prev,
{
isSend: event.sender_name === "User",
message: event.text || "",
sender_name: event.sender_name,
files: event.files,
id: event.id || "",
timestamp: event.timestamp || "",
content_blocks: event.content_blocks || undefined,
background_color: event.background_color || "",
text_color: event.text_color || "",
},
]);
break;
case "error":
// Handle error event (e.g., display error message)
setErrorData({
title: "Error",
list: event.text ? [event.text] : [],
});
break;
case "warning":
// Handle warning event
break;
case "info":
// Handle info event
break;
case "token":
// Update the last message with the new token
setChatHistory((prev) => {
const newHistory = [...prev];
const lastMessage = newHistory[newHistory.length - 1];
if (lastMessage && event.token) {
lastMessage.message += event.token;
}
return newHistory;
});
break;
}
};
// Use this function in your streaming logic
const handleStreamedEvent = (event: any) => {
const playgroundEvent = event.data as PlaygroundEvent;
handlePlaygroundEvent(playgroundEvent);
};
return ( return (
<div <div
className="eraser-column-arrangement" className="eraser-column-arrangement"

View file

@ -1,15 +1,16 @@
import { useDeleteBuilds } from "@/controllers/API/queries/_builds"; import { TextEffectPerChar } from "@/components/ui/textAnimation";
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file"; import { TextShimmer } from "@/components/ui/TextShimmer";
import { track } from "@/customization/utils/analytics"; import { track } from "@/customization/utils/analytics";
import { useMessagesStore } from "@/stores/messagesStore"; import { useMessagesStore } from "@/stores/messagesStore";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import useFlowStore from "../../../../stores/flowStore";
import { ChatMessageType } from "../../../../types/chat"; import { ChatMessageType } from "../../../../types/chat";
import { chatViewProps } from "../../../../types/components"; import { chatViewProps } from "../../../../types/components";
import useDragAndDrop from "./chatInput/hooks/use-drag-and-drop"; import useDragAndDrop from "./chatInput/hooks/use-drag-and-drop";
import { useFileHandler } from "./chatInput/hooks/use-file-handler"; import { useFileHandler } from "./chatInput/hooks/use-file-handler";
import ChatInput from "./chatInput/newChatInput"; import ChatInput from "./chatInput/newChatInput";
import LogoIcon from "./chatMessage/components/chatLogoIcon";
import ChatMessage from "./chatMessage/newChatMessage"; import ChatMessage from "./chatMessage/newChatMessage";
export default function ChatView({ export default function ChatView({
@ -20,6 +21,7 @@ export default function ChatView({
setLockChat, setLockChat,
visibleSession, visibleSession,
focusChat, focusChat,
closeChat,
}: chatViewProps): JSX.Element { }: chatViewProps): JSX.Element {
const { flowPool, inputs, CleanFlowPool } = useFlowStore(); const { flowPool, inputs, CleanFlowPool } = useFlowStore();
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
@ -29,7 +31,6 @@ export default function ChatView({
const inputTypes = inputs.map((obj) => obj.type); const inputTypes = inputs.map((obj) => obj.type);
const updateFlowPool = useFlowStore((state) => state.updateFlowPool); const updateFlowPool = useFlowStore((state) => state.updateFlowPool);
const { mutate: mutateDeleteFlowPool } = useDeleteBuilds();
//build chat history //build chat history
useEffect(() => { useEffect(() => {
@ -37,13 +38,22 @@ export default function ChatView({
.filter( .filter(
(message) => (message) =>
message.flow_id === currentFlowId && message.flow_id === currentFlowId &&
(visibleSession === message.session_id ?? true), (visibleSession === message.session_id || visibleSession === null),
) )
.map((message) => { .map((message) => {
let files = message.files; let files = message.files;
//HANDLE THE "[]" case // Handle the "[]" case, empty string, or already parsed array
if (typeof files === "string") { if (Array.isArray(files)) {
files = JSON.parse(files); // files is already an array, no need to parse
} else if (files === "[]" || files === "") {
files = [];
} else if (typeof files === "string") {
try {
files = JSON.parse(files);
} catch (error) {
console.error("Error parsing files:", error);
files = [];
}
} }
return { return {
isSend: message.sender === "User", isSend: message.sender === "User",
@ -54,6 +64,11 @@ export default function ChatView({
timestamp: message.timestamp, timestamp: message.timestamp,
session: message.session_id, session: message.session_id,
edit: message.edit, edit: message.edit,
background_color: message.background_color || "",
text_color: message.text_color || "",
content_blocks: message.content_blocks || [],
category: message.category || "",
properties: message.properties || {},
}; };
}); });
const finalChatHistory = [...messagesFromMessagesStore].sort((a, b) => { const finalChatHistory = [...messagesFromMessagesStore].sort((a, b) => {
@ -105,11 +120,9 @@ export default function ChatView({
setIsDragging(false); setIsDragging(false);
}; };
const { mutate } = usePostUploadFile();
return ( return (
<div <div
className="background flex h-full w-full flex-col rounded-md" className="flex h-full w-full flex-col rounded-md"
onDragOver={dragOver} onDragOver={dragOver}
onDragEnter={dragEnter} onDragEnter={dragEnter}
onDragLeave={dragLeave} onDragLeave={dragLeave}
@ -125,47 +138,53 @@ export default function ChatView({
lastMessage={chatHistory.length - 1 === index ? true : false} lastMessage={chatHistory.length - 1 === index ? true : false}
key={`${chat.id}-${index}`} key={`${chat.id}-${index}`}
updateChat={updateChat} updateChat={updateChat}
closeChat={closeChat}
/> />
)) ))
) : ( ) : (
<div className="flex h-full w-full flex-col items-center justify-center"> <div className="flex h-full w-full flex-col items-center justify-center">
<div className="flex flex-col items-center justify-center bg-background p-8"> <div className="flex flex-col items-center justify-center gap-4 p-8">
<span className="pb-5 text-4xl">⛓️</span> <img
<h3 className="mt-2 pb-2 text-2xl font-semibold text-primary"> src="/src/assets/logo.svg"
New chat alt="Chain logo"
</h3> className="h-[40px] w-[40px] scale-[1.5]"
<p className="text-lg text-muted-foreground"> />
Test your flow with a chat prompt <div className="flex flex-col items-center justify-center">
</p> <h3 className="mt-2 pb-2 text-2xl font-semibold text-primary">
New chat
</h3>
<p className="text-lg text-muted-foreground">
<TextEffectPerChar>
Test your flow with a chat prompt
</TextEffectPerChar>
</p>
</div>
</div> </div>
</div> </div>
)} )}
<div <div
className={lockChat ? "flex-max-width px-2 py-6 pl-32 pr-9" : ""} className={
lockChat ? "w-5/6 max-w-[768px] py-4 word-break-break-word" : ""
}
ref={ref} ref={ref}
> >
{lockChat && ( {lockChat &&
<div className={"mr-3 mt-1 flex w-full overflow-hidden pb-3"}> chatHistory.length > 0 &&
<div className="flex w-full gap-4"> !(chatHistory[chatHistory.length - 1]?.category === "error") && (
<div className="relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md bg-zinc-800 p-5"> <div className="flex w-full gap-4 rounded-md p-2">
<span> <LogoIcon />
<div className="text-3xl">⛓️</div>
</span>
</div>
<div className="flex items-center"> <div className="flex items-center">
<div> <div>
<span className="animate-pulse text-muted-foreground"> <TextShimmer className="" duration={1}>
Flow running... Flow running...
</span> </TextShimmer>
{/* TODO: ADD MODEL RELATED NAME */}
</div> </div>
</div> </div>
</div> </div>
</div> )}
)}
</div> </div>
</div> </div>
<div className="m-auto w-5/6"> <div className="m-auto w-5/6 max-w-[768px]">
<ChatInput <ChatInput
chatValue={chatValue} chatValue={chatValue}
noInput={!inputTypes.includes("ChatInput")} noInput={!inputTypes.includes("ChatInput")}

View file

@ -9,12 +9,6 @@ import IconComponent from "../../components/genericIconComponent";
import ShadTooltip from "../../components/shadTooltipComponent"; import ShadTooltip from "../../components/shadTooltipComponent";
import { Badge } from "../../components/ui/badge"; import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "../../components/ui/tabs";
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants"; import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
import { InputOutput } from "../../constants/enums"; import { InputOutput } from "../../constants/enums";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
@ -36,8 +30,10 @@ export default function IOModal({
setOpen, setOpen,
disable, disable,
isPlayground, isPlayground,
canvasOpen,
}: IOModalPropsType): JSX.Element { }: IOModalPropsType): JSX.Element {
const allNodes = useFlowStore((state) => state.nodes); const allNodes = useFlowStore((state) => state.nodes);
const setIOModalOpen = useFlowsManagerStore((state) => state.setIOModalOpen);
const inputs = useFlowStore((state) => state.inputs).filter( const inputs = useFlowStore((state) => state.inputs).filter(
(input) => input.type !== "ChatInput", (input) => input.type !== "ChatInput",
); );
@ -60,7 +56,6 @@ export default function IOModal({
inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0, inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0,
); );
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const setNoticeData = useAlertStore((state) => state.setNoticeData);
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const deleteSession = useMessagesStore((state) => state.deleteSession); const deleteSession = useMessagesStore((state) => state.deleteSession);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
@ -71,6 +66,13 @@ export default function IOModal({
currentFlowId, currentFlowId,
); );
useEffect(() => {
setIOModalOpen(open);
return () => {
setIOModalOpen(false);
};
}, [open]);
function handleDeleteSession(session_id: string) { function handleDeleteSession(session_id: string) {
deleteSessionFunction( deleteSessionFunction(
{ {
@ -169,8 +171,9 @@ export default function IOModal({
if (chatInput) { if (chatInput) {
setNode(chatInput.id, (node: NodeType) => { setNode(chatInput.id, (node: NodeType) => {
const newNode = { ...node }; const newNode = { ...node };
if (newNode.data.node?.template) {
newNode.data.node!.template["input_value"].value = chatValue; newNode.data.node!.template["input_value"].value = chatValue;
}
return newNode; return newNode;
}); });
} }
@ -224,6 +227,28 @@ export default function IOModal({
} }
}, [open]); }, [open]);
useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 1024) {
// 1024px is Tailwind's 'lg' breakpoint
setSidebarOpen(false);
} else {
setSidebarOpen(true);
}
};
// Initial check
handleResize();
// Add event listener
window.addEventListener("resize", handleResize);
// Cleanup
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return ( return (
<BaseModal <BaseModal
open={open} open={open}
@ -232,7 +257,7 @@ export default function IOModal({
type={isPlayground ? "modal" : undefined} type={isPlayground ? "modal" : undefined}
onSubmit={() => sendMessage({ repeat: 1 })} onSubmit={() => sendMessage({ repeat: 1 })}
size="x-large" size="x-large"
className="p-0" className="!rounded-[12px] p-0"
> >
<BaseModal.Trigger>{children}</BaseModal.Trigger> <BaseModal.Trigger>{children}</BaseModal.Trigger>
{/* TODO ADAPT TO ALL TYPES OF INPUTS AND OUTPUTS */} {/* TODO ADAPT TO ALL TYPES OF INPUTS AND OUTPUTS */}
@ -241,21 +266,29 @@ export default function IOModal({
<div <div
className={cn( className={cn(
"flex h-full flex-shrink-0 flex-col justify-start transition-all duration-300", "flex h-full flex-shrink-0 flex-col justify-start transition-all duration-300",
sidebarOpen ? "w-1/5" : "w-16", sidebarOpen
? "absolute z-50 lg:relative lg:w-1/5 lg:max-w-[280px]"
: "w-0",
)} )}
> >
<div className="flex h-full flex-col overflow-y-auto border-r border-border bg-zinc-950 p-6 text-center custom-scroll"> <div className="flex h-full flex-col overflow-y-auto border-r border-border bg-muted p-6 text-center custom-scroll dark:bg-background">
<div className="flex items-center gap-2 pb-8"> <div className="flex items-center gap-2 pb-8">
<Button <ShadTooltip
variant="ghost" styleClasses="z-50"
size="icon" side="right"
onClick={() => setSidebarOpen(!sidebarOpen)} content="Hide sidebar"
> >
<IconComponent <Button
name={sidebarOpen ? "PanelLeftClose" : "PanelLeftOpen"} variant="ghost"
className="h-6 w-6 text-ring" className="flex h-8 w-8 items-center justify-center !p-0"
/> onClick={() => setSidebarOpen(!sidebarOpen)}
</Button> >
<IconComponent
name={sidebarOpen ? "PanelLeftClose" : "PanelLeftOpen"}
className="h-[18px] w-[18px] text-ring"
/>
</Button>
</ShadTooltip>
{sidebarOpen && <div className="font-semibold">Playground</div>} {sidebarOpen && <div className="font-semibold">Playground</div>}
</div> </div>
{sidebarOpen && ( {sidebarOpen && (
@ -265,23 +298,28 @@ export default function IOModal({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<IconComponent <IconComponent
name="MessagesSquare" name="MessagesSquare"
className="h-6 w-6 text-ring" className="h-[18px] w-[18px] text-ring"
/> />
<div className="font-semibold">Chat</div> <div className="text-[13px] font-normal">Chat</div>
</div> </div>
<Button <ShadTooltip styleClasses="z-50" content="New Chat">
variant="ghost" <div>
size="icon" <Button
onClick={(_) => { data-testid="new-chat"
setvisibleSession(undefined); variant="ghost"
setSelectedViewField(undefined); className="flex h-8 w-8 items-center justify-center !p-0"
}} onClick={(_) => {
> setvisibleSession(undefined);
<IconComponent setSelectedViewField(undefined);
name="Plus" }}
className="h-6 w-6 text-ring" >
/> <IconComponent
</Button> name="Plus"
className="h-[18px] w-[18px] text-ring"
/>
</Button>
</div>
</ShadTooltip>
</div> </div>
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
@ -317,11 +355,11 @@ export default function IOModal({
)} )}
</div> </div>
</div> </div>
<div className="flex h-full min-w-96 flex-grow"> <div className="flex h-full min-w-96 flex-grow bg-background dark:bg-accent">
{selectedViewField && ( {selectedViewField && (
<div <div
className={cn( className={cn(
"flex h-full w-full flex-col items-start gap-4 pt-4", "flex h-full w-full flex-col items-start gap-4 p-4",
!selectedViewField ? "hidden" : "", !selectedViewField ? "hidden" : "",
)} )}
> >
@ -373,19 +411,66 @@ export default function IOModal({
)} )}
<div <div
className={cn( className={cn(
"flex h-full w-full flex-col p-6", "flex h-full w-full flex-col justify-between p-6",
selectedViewField ? "hidden" : "", selectedViewField ? "hidden" : "",
)} )}
> >
{visibleSession && ( <div className="mb-4 h-[5%] text-[16px] font-semibold">
<div className="mb-4 h-[5%] text-xl font-semibold"> {visibleSession && sessions.length > 0 && sidebarOpen && (
{visibleSession === currentFlowId <div className="hidden lg:block">
? "Default Session" {visibleSession === currentFlowId
: `${visibleSession}`} ? "Default Session"
: `${visibleSession}`}
</div>
)}
<div className={cn(sidebarOpen ? "lg:hidden" : "")}>
<div className="-ml-4 -mt-4 flex items-center gap-2">
<Button
variant="ghost"
size="icon"
onClick={() => setSidebarOpen(true)}
className="h-8 w-8"
>
<IconComponent
name={"PanelLeftOpen"}
className="h-[18px] w-[18px] text-ring"
/>
</Button>
<div className="font-semibold">Playground</div>
</div>
</div> </div>
)} <div
className={cn(
sidebarOpen ? "pointer-events-none opacity-0" : "",
"absolute right-10 top-2 flex h-8 w-8 items-center justify-center rounded-sm ring-offset-background transition-opacity focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
)}
>
<ShadTooltip styleClasses="z-50" content="New Chat">
<Button
variant="ghost"
size="icon"
onClick={(_) => {
setvisibleSession(undefined);
setSelectedViewField(undefined);
}}
>
<IconComponent
name="Plus"
className="h-[18px] w-[18px] text-ring"
/>
</Button>
</ShadTooltip>
</div>
</div>
{haveChat ? ( {haveChat ? (
<div className={visibleSession ? "h-[95%]" : "h-full"}> <div
className={cn(
visibleSession ? "h-[95%]" : "h-full",
sidebarOpen
? "pointer-events-none blur-sm lg:pointer-events-auto lg:blur-0"
: "",
)}
>
<ChatView <ChatView
focusChat={sessionId} focusChat={sessionId}
sendMessage={sendMessage} sendMessage={sendMessage}
@ -394,6 +479,13 @@ export default function IOModal({
lockChat={lockChat} lockChat={lockChat}
setLockChat={setLockChat} setLockChat={setLockChat}
visibleSession={visibleSession} visibleSession={visibleSession}
closeChat={
!canvasOpen
? undefined
: () => {
setOpen(false);
}
}
/> />
</div> </div>
) : ( ) : (

View file

@ -53,7 +53,7 @@ const DropdownComponent = ({
/> />
Edit details Edit details
</DropdownMenuItem> */} </DropdownMenuItem> */}
{handlePlaygroundClick && ( {/* {handlePlaygroundClick && (
<DropdownMenuItem <DropdownMenuItem
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@ -68,7 +68,7 @@ const DropdownComponent = ({
/> />
Playground Playground
</DropdownMenuItem> </DropdownMenuItem>
)} )} */}
<DropdownMenuItem <DropdownMenuItem
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();

View file

@ -23,7 +23,7 @@ import DropdownComponent from "../dropdown";
const GridComponent = ({ flowData }: { flowData: FlowType }) => { const GridComponent = ({ flowData }: { flowData: FlowType }) => {
const navigate = useCustomNavigate(); const navigate = useCustomNavigate();
const [openPlayground, setOpenPlayground] = useState(false); // const [openPlayground, setOpenPlayground] = useState(false);
const [loadingPlayground, setLoadingPlayground] = useState(false); const [loadingPlayground, setLoadingPlayground] = useState(false);
const [openDelete, setOpenDelete] = useState(false); const [openDelete, setOpenDelete] = useState(false);
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
@ -63,7 +63,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
return; return;
} }
setCurrentFlow(flowData); setCurrentFlow(flowData);
setOpenPlayground(true); // setOpenPlayground(true);
setLoadingPlayground(false); setLoadingPlayground(false);
} else { } else {
setErrorData({ setErrorData({
@ -158,7 +158,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
{flowData.description} {flowData.description}
</div> </div>
<div className="flex justify-end pt-[24px]"> {/* <div className="flex justify-end pt-[24px]">
{flowData.is_component ? ( {flowData.is_component ? (
<></> <></>
) : ( ) : (
@ -174,9 +174,9 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
Playground Playground
</Button> </Button>
)} )}
</div> </div> */}
</div> </div>
{openPlayground && ( {/* {openPlayground && (
<IOModal <IOModal
key={flowData.id} key={flowData.id}
cleanOnClose={true} cleanOnClose={true}
@ -185,7 +185,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
> >
<></> <></>
</IOModal> </IOModal>
)} )} */}
{openDelete && ( {openDelete && (
<DeleteConfirmationModal <DeleteConfirmationModal
open={openDelete} open={openDelete}

View file

@ -24,8 +24,8 @@ import DropdownComponent from "../dropdown";
const ListComponent = ({ flowData }: { flowData: FlowType }) => { const ListComponent = ({ flowData }: { flowData: FlowType }) => {
const navigate = useCustomNavigate(); const navigate = useCustomNavigate();
const [openPlayground, setOpenPlayground] = useState(false); // const [openPlayground, setOpenPlayground] = useState(false);
const [loadingPlayground, setLoadingPlayground] = useState(false); // const [loadingPlayground, setLoadingPlayground] = useState(false);
const [openDelete, setOpenDelete] = useState(false); const [openDelete, setOpenDelete] = useState(false);
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const { deleteFlow } = useDeleteFlow(); const { deleteFlow } = useDeleteFlow();
@ -48,29 +48,29 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
return inputs.length > 0 || outputs.length > 0; return inputs.length > 0 || outputs.length > 0;
} }
const handlePlaygroundClick = () => { // const handlePlaygroundClick = () => {
track("Playground Button Clicked", { flowId: flowData.id }); // track("Playground Button Clicked", { flowId: flowData.id });
setLoadingPlayground(true); // setLoadingPlayground(true);
if (flowData) { // if (flowData) {
if (!hasPlayground(flowData)) { // if (!hasPlayground(flowData)) {
setErrorData({ // setErrorData({
title: "Error", // title: "Error",
list: ["This flow doesn't have a playground."], // list: ["This flow doesn't have a playground."],
}); // });
setLoadingPlayground(false); // setLoadingPlayground(false);
return; // return;
} // }
setCurrentFlow(flowData); // setCurrentFlow(flowData);
setOpenPlayground(true); // setOpenPlayground(true);
setLoadingPlayground(false); // setLoadingPlayground(false);
} else { // } else {
setErrorData({ // setErrorData({
title: "Error", // title: "Error",
list: ["Error getting flow data."], // list: ["Error getting flow data."],
}); // });
} // }
}; // };
const handleClick = async () => { const handleClick = async () => {
if (!isComponent) { if (!isComponent) {
@ -139,7 +139,7 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
{/* right side */} {/* right side */}
<div className="ml-5 flex items-center gap-2"> <div className="ml-5 flex items-center gap-2">
{flowData.is_component ? ( {/* {flowData.is_component ? (
<></> <></>
) : ( ) : (
<Button <Button
@ -154,7 +154,7 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
> >
Playground Playground
</Button> </Button>
)} )} */}
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
@ -179,14 +179,14 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
flowData={flowData} flowData={flowData}
setOpenDelete={setOpenDelete} setOpenDelete={setOpenDelete}
handlePlaygroundClick={() => { handlePlaygroundClick={() => {
handlePlaygroundClick(); // handlePlaygroundClick();
}} }}
/> />
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </div>
</div> </div>
{openPlayground && ( {/* {openPlayground && (
<IOModal <IOModal
key={flowData.id} key={flowData.id}
cleanOnClose={true} cleanOnClose={true}
@ -195,7 +195,7 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
> >
<></> <></>
</IOModal> </IOModal>
)} )} */}
{openDelete && ( {openDelete && (
<DeleteConfirmationModal <DeleteConfirmationModal
open={openDelete} open={openDelete}

View file

@ -1,9 +1,8 @@
import { useGetRefreshFlows } from "@/controllers/API/queries/flows/use-get-refresh-flows"; import { useGetFlow } from "@/controllers/API/queries/flows/use-get-flow";
import { ENABLE_NEW_IO_MODAL } from "@/customization/feature-flags"; import { ENABLE_NEW_IO_MODAL } from "@/customization/feature-flags";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate"; import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { track } from "@/customization/utils/analytics"; import { track } from "@/customization/utils/analytics";
import { useStoreStore } from "@/stores/storeStore"; import { useStoreStore } from "@/stores/storeStore";
import { useTypesStore } from "@/stores/typesStore";
import { useEffect } from "react"; import { useEffect } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { getComponent } from "../../controllers/API"; import { getComponent } from "../../controllers/API";
@ -14,51 +13,54 @@ import cloneFLowWithParent from "../../utils/storeUtils";
const IOModal = ENABLE_NEW_IO_MODAL ? IOModalNew : IOModalOld; const IOModal = ENABLE_NEW_IO_MODAL ? IOModalNew : IOModalOld;
export default function PlaygroundPage() { export default function PlaygroundPage() {
const flows = useFlowsManagerStore((state) => state.flows);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow); const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
const validApiKey = useStoreStore((state) => state.validApiKey); const validApiKey = useStoreStore((state) => state.validApiKey);
const { id } = useParams(); const { id } = useParams();
async function getFlowData() { const { mutateAsync: getFlow } = useGetFlow();
const res = await getComponent(id!);
const newFlow = cloneFLowWithParent(res, res.id, false, true);
return newFlow;
}
const navigate = useCustomNavigate(); const navigate = useCustomNavigate();
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const { mutateAsync: refreshFlows } = useGetRefreshFlows();
const setIsLoading = useFlowsManagerStore((state) => state.setIsLoading); const setIsLoading = useFlowsManagerStore((state) => state.setIsLoading);
const getTypes = useTypesStore((state) => state.getTypes);
const types = useTypesStore((state) => state.types);
// Set flow tab id async function getFlowData() {
useEffect(() => { try {
const awaitgetTypes = async () => { const flow = await getFlow({ id: id! });
if (flows && currentFlowId === "") { return flow;
const isAnExistingFlow = flows.find((flow) => flow.id === id); } catch (error: any) {
if (error?.response?.status === 404) {
if (!isAnExistingFlow) { if (!validApiKey) {
if (validApiKey) { return null;
getFlowData().then((flow) => { }
setCurrentFlow(flow); try {
}); const res = await getComponent(id!);
} else { const newFlow = cloneFLowWithParent(res, res.id, false, true);
navigate("/"); return newFlow;
} } catch (componentError) {
return null;
}
}
return null;
}
}
useEffect(() => {
const initializeFlow = async () => {
setIsLoading(true);
if (currentFlowId === "") {
const flow = await getFlowData();
if (flow) {
setCurrentFlow(flow);
} else {
navigate("/");
} }
setCurrentFlow(isAnExistingFlow);
} else if (!flows) {
setIsLoading(true);
await refreshFlows({ get_all: true, header_flows: true });
if (!types || Object.keys(types).length === 0) await getTypes();
setIsLoading(false);
} }
}; };
awaitgetTypes();
}, [id, flows, validApiKey]); initializeFlow();
setIsLoading(false);
}, [id, validApiKey]);
useEffect(() => { useEffect(() => {
if (id) track("Playground Page Loaded", { flowId: id }); if (id) track("Playground Page Loaded", { flowId: id });

View file

@ -201,9 +201,9 @@ const router = createBrowserRouter(
</Route> </Route>
<Route path="view" element={<ViewPage />} /> <Route path="view" element={<ViewPage />} />
</Route> </Route>
<Route path="playground/:id/"> {/* <Route path="playground/:id/">
<Route path="" element={<PlaygroundPage />} /> <Route path="" element={<PlaygroundPage />} />
</Route> </Route> */}
</Route> </Route>
</Route> </Route>
<Route <Route

View file

@ -51,6 +51,11 @@ import { useTypesStore } from "./typesStore";
// this is our useStore hook that we can use in our components to get parts of the store and call actions // this is our useStore hook that we can use in our components to get parts of the store and call actions
const useFlowStore = create<FlowStoreType>((set, get) => ({ const useFlowStore = create<FlowStoreType>((set, get) => ({
fitViewNode: (nodeId) => {
if (get().reactFlowInstance && get().nodes.find((n) => n.id === nodeId)) {
get().reactFlowInstance?.fitView({ nodes: [{ id: nodeId }] });
}
},
autoSaveFlow: undefined, autoSaveFlow: undefined,
componentsToUpdate: false, componentsToUpdate: false,
updateComponentsToUpdate: (nodes) => { updateComponentsToUpdate: (nodes) => {

View file

@ -17,6 +17,10 @@ const past = {};
const future = {}; const future = {};
const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
IOModalOpen: false,
setIOModalOpen: (IOModalOpen: boolean) => {
set({ IOModalOpen });
},
healthCheckMaxRetries: 5, healthCheckMaxRetries: 5,
setHealthCheckMaxRetries: (healthCheckMaxRetries: number) => setHealthCheckMaxRetries: (healthCheckMaxRetries: number) =>
set({ healthCheckMaxRetries }), set({ healthCheckMaxRetries }),

View file

@ -43,6 +43,21 @@ export const useMessagesStore = create<MessagesStoreType>((set, get) => ({
return { messages: updatedMessages }; return { messages: updatedMessages };
}); });
}, },
updateMessageText: (id, chunk) => {
set((state) => {
const updatedMessages = [...state.messages];
for (let i = state.messages.length - 1; i >= 0; i--) {
if (state.messages[i].id === id) {
updatedMessages[i] = {
...updatedMessages[i],
text: updatedMessages[i].text + chunk,
};
break;
}
}
return { messages: updatedMessages };
});
},
clearMessages: () => { clearMessages: () => {
set(() => ({ messages: [] })); set(() => ({ messages: [] }));
}, },

View file

@ -965,7 +965,7 @@
@apply absolute bottom-2 right-4; @apply absolute bottom-2 right-4;
} }
.form-modal-send-button { .form-modal-send-button {
@apply rounded-md p-1.5 px-2.5 transition-all duration-300; @apply rounded-md p-1.5 px-2.5;
} }
.form-modal-lock-icon { .form-modal-lock-icon {
@apply ml-1 mr-1 h-5 w-5 animate-pulse; @apply ml-1 mr-1 h-5 w-5 animate-pulse;

View file

@ -253,17 +253,17 @@ span.token {
max-width: 100%; max-width: 100%;
} }
code { pre code {
max-width: 100%; max-width: 100%;
display: inline-block; display: inline-block;
width: 100%; width: 100%;
/* bg ignored now */ /* Background color */
background-color: var(--canvas); background-color: hsl(var(--code-background)) !important;
} }
pre { pre {
/* bg ignored now */ /* Background color */
background-color: var(--canvas); background-color: hsl(var(--code-background)) !important;
} }
[type="search"]::-webkit-search-cancel-button { [type="search"]::-webkit-search-cancel-button {

View file

@ -91,13 +91,14 @@
--note-indigo: #e0e7ff; --note-indigo: #e0e7ff;
--note-emerald: #d1fae5; --note-emerald: #d1fae5;
--note-red: #fee2e2; --note-red: #fee2e2;
--error-red:0,86%,97%; /*hsla(0, 86%, 97%)*/
--error-red-border: 0,96%,89%; /*hsla(0,96%,89%)*/
--note-default-opacity: #f1f5f980; --note-default-opacity: #f1f5f980;
--note-indigo-opacity: #312e8180; --note-indigo-opacity: #312e8180;
--note-emerald-opacity: #064e3b80; --note-emerald-opacity: #064e3b80;
--note-amber-opacity: #78350f80; --note-amber-opacity: #78350f80;
--note-red-opacity: #7f1d1d80; --note-red-opacity: #7f1d1d80;
--code-background: var(--canvas);
--emerald-success: 160.1 84.1% 39.4%; --emerald-success: 160.1 84.1% 39.4%;
--accent-emerald-foreground: 161.4 93.5% 30.4%; --accent-emerald-foreground: 161.4 93.5% 30.4%;
--placeholder: 240 5% 64.9%; --placeholder: 240 5% 64.9%;
@ -158,7 +159,7 @@
--background: 240 6% 10%; /* hsl(240, 6%, 10%) */ --background: 240 6% 10%; /* hsl(240, 6%, 10%) */
--muted: 240 4% 16%; /* hsl(240, 4%, 16%) */ --muted: 240 4% 16%; /* hsl(240, 4%, 16%) */
--muted-foreground: 240 5% 65%; /* hsl(240, 5%, 65%) */ --muted-foreground: 240 5% 65%; /* hsl(240, 5%, 65%) */
--card: 0 0% 0%; /* hsl(0, 0%, 0%) */ --card: 0 0% 0%; /* hsl(0, 0, 0%) */
--card-foreground: 0 0% 100%; /* hsl(0, 0%, 100%) */ --card-foreground: 0 0% 100%; /* hsl(0, 0%, 100%) */
--popover: 0 0% 0%; /* hsl(0, 0%, 0%) */ --popover: 0 0% 0%; /* hsl(0, 0%, 0%) */
--popover-foreground: 0 0% 100%; /* hsl(0, 0%, 100%) */ --popover-foreground: 0 0% 100%; /* hsl(0, 0%, 100%) */
@ -185,7 +186,10 @@
--accent-pink-foreground: 329 86% 70%; /* hsl(329, 86%, 70%) */ --accent-pink-foreground: 329 86% 70%; /* hsl(329, 86%, 70%) */
--note-amber: 46 97% 65%; /* hsl(46, 97%, 65%) */ --note-amber: 46 97% 65%; /* hsl(46, 97%, 65%) */
--tooltip: 0 0% 100%; /* hsl(0, 0%, 100%) */ --tooltip: 0 0% 100%; /* hsl(0, 0%, 100%) */
--tooltip-foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */ --tooltip-foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */
--error-red:0,75%,15%; /*hsla(0, 75%, 15%)*/
--error-red-border: 0,70%,35%; /*hsla(0,70%,35%)*/
--note-default: #0f172a; --note-default: #0f172a;
--note-indigo: #312e81; --note-indigo: #312e81;
@ -239,6 +243,7 @@
--status-green: #4ade80; --status-green: #4ade80;
--status-blue: #2563eb; --status-blue: #2563eb;
--connection: #6d6c6c; --connection: #6d6c6c;
--code-background: var(--background);
--beta-background: rgb(37 99 235); --beta-background: rgb(37 99 235);
--beta-foreground: rgb(219 234 254); --beta-foreground: rgb(219 234 254);

View file

@ -16,6 +16,23 @@ export type ChatMessageType = {
sender_name?: string; sender_name?: string;
session?: string; session?: string;
edit?: boolean; edit?: boolean;
icon?: string;
category?: string;
properties?: PropertiesType;
content_blocks?: ContentBlock[];
};
export type SourceType = {
id: string;
display_name: string;
source: string;
};
export type PropertiesType = {
source: SourceType;
icon?: string;
background_color?: string;
text_color?: string;
}; };
export type ChatOutputType = { export type ChatOutputType = {
@ -42,3 +59,98 @@ export type FlowPoolObjectType = {
data: { artifacts: any; results: any | ChatOutputType | ChatInputType }; data: { artifacts: any; results: any | ChatOutputType | ChatInputType };
id: string; id: string;
}; };
// Base content type
export interface BaseContent {
type: string;
}
// Individual content types
export interface ErrorContent extends BaseContent {
type: "error";
component?: string;
field?: string;
reason?: string;
solution?: string;
traceback?: string;
}
export interface TextContent extends BaseContent {
type: "text";
text: string;
}
export interface MediaContent extends BaseContent {
type: "media";
urls: string[];
caption?: string;
}
export interface JSONContent extends BaseContent {
type: "json";
data: Record<string, any>;
}
export interface CodeContent extends BaseContent {
type: "code";
code: string;
language: string;
title?: string;
}
export interface ToolStartContent extends BaseContent {
type: "tool_start";
tool_name: string;
tool_input: Record<string, any>;
}
export interface ToolEndContent extends BaseContent {
type: "tool_end";
tool_name: string;
tool_output: any;
}
export interface ToolErrorContent extends BaseContent {
type: "tool_error";
tool_name: string;
tool_error: string;
}
// Union type for all content types
export type ContentType =
| ErrorContent
| TextContent
| MediaContent
| JSONContent
| CodeContent
| ToolStartContent
| ToolEndContent
| ToolErrorContent;
// Updated ContentBlock interface
export interface ContentBlock {
title: string;
content: ContentType;
allow_markdown: boolean;
media_url?: string[];
component: string;
}
export interface PlaygroundEvent {
event_type: "message" | "error" | "warning" | "info" | "token";
background_color?: string;
text_color?: string;
allow_markdown?: boolean;
icon?: string | null;
sender_name: string;
content_blocks?: ContentBlock[] | null;
files?: string[];
text?: string;
timestamp?: string;
token?: string;
id?: string;
flow_id?: string;
sender?: string;
session_id?: string;
edit?: boolean;
}

View file

@ -589,6 +589,7 @@ export type chatMessagePropsType = {
message: string, message: string,
stream_url?: string, stream_url?: string,
) => void; ) => void;
closeChat?: () => void;
}; };
export type genericModalPropsType = { export type genericModalPropsType = {
@ -644,6 +645,7 @@ export type IOModalPropsType = {
disable?: boolean; disable?: boolean;
isPlayground?: boolean; isPlayground?: boolean;
cleanOnClose?: boolean; cleanOnClose?: boolean;
canvasOpen?: boolean;
}; };
export type buttonBoxPropsType = { export type buttonBoxPropsType = {
@ -762,6 +764,7 @@ export type chatViewProps = {
setLockChat: (lock: boolean) => void; setLockChat: (lock: boolean) => void;
visibleSession?: string; visibleSession?: string;
focusChat?: string; focusChat?: string;
closeChat?: () => void;
}; };
export type IOFileInputProps = { export type IOFileInputProps = {

View file

@ -1,3 +1,5 @@
import { ContentBlock } from "../chat";
type Message = { type Message = {
flow_id: string; flow_id: string;
text: string; text: string;
@ -8,6 +10,11 @@ type Message = {
files: Array<string>; files: Array<string>;
id: string; id: string;
edit: boolean; edit: boolean;
background_color: string;
text_color: string;
category?: string;
properties?: any;
content_blocks?: ContentBlock[];
}; };
export type { Message }; export type { Message };

View file

@ -54,6 +54,7 @@ export type FlowPoolType = {
}; };
export type FlowStoreType = { export type FlowStoreType = {
fitViewNode: (nodeId: string) => void;
autoSaveFlow: (() => void) | undefined; autoSaveFlow: (() => void) | undefined;
componentsToUpdate: boolean; componentsToUpdate: boolean;
updateComponentsToUpdate: (nodes: Node[]) => void; updateComponentsToUpdate: (nodes: Node[]) => void;

View file

@ -28,6 +28,8 @@ export type FlowsManagerStoreType = {
setHealthCheckMaxRetries: (healthCheckMaxRetries: number) => void; setHealthCheckMaxRetries: (healthCheckMaxRetries: number) => void;
flowToCanvas: FlowType | null; flowToCanvas: FlowType | null;
setFlowToCanvas: (flowToCanvas: FlowType | null) => Promise<void>; setFlowToCanvas: (flowToCanvas: FlowType | null) => Promise<void>;
IOModalOpen: boolean;
setIOModalOpen: (IOModalOpen: boolean) => void;
}; };
export type UseUndoRedoOptions = { export type UseUndoRedoOptions = {

View file

@ -8,6 +8,7 @@ export type MessagesStoreType = {
removeMessage: (message: Message) => void; removeMessage: (message: Message) => void;
updateMessage: (message: Message) => void; updateMessage: (message: Message) => void;
updateMessagePartial: (message: Partial<Message>) => void; updateMessagePartial: (message: Partial<Message>) => void;
updateMessageText: (id: string, chunk: string) => void;
clearMessages: () => void; clearMessages: () => void;
removeMessages: (ids: string[]) => void; removeMessages: (ids: string[]) => void;
deleteSession: (id: string) => void; deleteSession: (id: string) => void;

View file

@ -302,7 +302,7 @@ export async function buildFlowVertices({
// flushSync and timeout is needed to avoid react batched updates // flushSync and timeout is needed to avoid react batched updates
setTimeout(() => { setTimeout(() => {
flushSync(() => { flushSync(() => {
useMessagesStore.getState().updateMessagePartial(data); useMessagesStore.getState().updateMessageText(data.id, data.chunk);
}); });
}, 10); }, 10);
return true; return true;
@ -314,10 +314,11 @@ export async function buildFlowVertices({
return true; return true;
} }
case "error": { case "error": {
const errorMessage = data.error;
onBuildError!("Error Running Flow", [errorMessage], []);
buildResults.push(false);
useFlowStore.getState().setIsBuilding(false); useFlowStore.getState().setIsBuilding(false);
if (data.category === "error") {
useMessagesStore.getState().addMessage(data);
}
buildResults.push(false);
return true; return true;
} }
default: default:

View file

@ -118,6 +118,7 @@ import {
MoonIcon, MoonIcon,
MoreHorizontal, MoreHorizontal,
Network, Network,
OctagonAlert,
OptionIcon, OptionIcon,
Package2, Package2,
Palette, Palette,
@ -125,6 +126,7 @@ import {
PanelLeftOpen, PanelLeftOpen,
Paperclip, Paperclip,
PaperclipIcon, PaperclipIcon,
Pen,
Pencil, Pencil,
PencilLine, PencilLine,
Pin, Pin,
@ -778,6 +780,7 @@ export const nodeIconsLucide: iconsType = {
Sliders, Sliders,
ScreenShare, ScreenShare,
Code, Code,
OctagonAlert,
Ellipsis, Ellipsis,
Braces, Braces,
FlaskConical, FlaskConical,
@ -800,6 +803,7 @@ export const nodeIconsLucide: iconsType = {
ArrowUpRight, ArrowUpRight,
Scroll, Scroll,
Image, Image,
Pen,
CornerDownLeft, CornerDownLeft,
ChevronsDownUp, ChevronsDownUp,
OptionIcon, OptionIcon,

6
src/frontend/src/vite-env.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
/// <reference types="vite/client" />
declare module "*.svg" {
const content: string;
export default content;
}

View file

@ -117,6 +117,7 @@ const config = {
}, },
"chat-bot-icon": "var(--chat-bot-icon)", "chat-bot-icon": "var(--chat-bot-icon)",
"chat-user-icon": "var(--chat-user-icon)", "chat-user-icon": "var(--chat-user-icon)",
"code-background": "hsl(var(--code-background))",
canvas: "hsl(var(--canvas))", canvas: "hsl(var(--canvas))",
ice: "var(--ice)", ice: "var(--ice)",
selected: "var(--selected)", selected: "var(--selected)",
@ -124,6 +125,8 @@ const config = {
border: "hsl(var(--border))", border: "hsl(var(--border))",
input: "hsl(var(--input))", input: "hsl(var(--input))",
ring: "hsl(var(--ring))", ring: "hsl(var(--ring))",
"error-red":"hsl(var(--error-red))",
"error-red-border":"hsl(var(--error-red-border))",
"node-selected": "hsl(var(--node-selected))", "node-selected": "hsl(var(--node-selected))",
background: "hsl(var(--background))", background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))", foreground: "hsl(var(--foreground))",
@ -366,6 +369,51 @@ const config = {
}), }),
tailwindcssTypography, tailwindcssTypography,
tailwindcssDottedBackground, tailwindcssDottedBackground,
plugin(function ({ addUtilities, theme, e }) {
const colors = theme('colors');
const generateUtilities = (colors, prefix = '') => {
return Object.keys(colors).reduce((acc, colorName) => {
const colorValue = colors[colorName];
const className = prefix ? `${prefix}-${e(colorName)}` : e(colorName);
if (typeof colorValue === 'string') {
acc[`.truncate-${className}`] = {
position: 'relative',
overflow: 'hidden',
'&::after': {
content: '""',
position: 'absolute',
inset: '0 0 0 0',
background: `linear-gradient(to right, transparent, 75%, ${colorValue})`,
},
};
} else if (typeof colorValue === 'object') {
// Use the DEFAULT value for the base class if it exists
if (colorValue.DEFAULT) {
acc[`.truncate-${className}`] = {
position: 'relative',
overflow: 'hidden',
'&::after': {
content: '""',
position: 'absolute',
inset: '0 0 0 0',
background: `linear-gradient(to right, transparent, ${colorValue.DEFAULT})`,
},
};
}
// Recursively generate utilities for nested color objects
Object.assign(acc, generateUtilities(colorValue, className));
}
return acc;
}, {});
};
const newUtilities = generateUtilities(colors);
addUtilities(newUtilities, ['responsive', 'hover']);
}),
plugin(({ addVariant }) => { plugin(({ addVariant }) => {
addVariant("group-increment-hover", ":merge(.group-increment):hover &"); addVariant("group-increment-hover", ":merge(.group-increment):hover &");
addVariant("group-decrement-hover", ":merge(.group-decrement):hover &"); addVariant("group-decrement-hover", ":merge(.group-decrement):hover &");

View file

@ -109,11 +109,11 @@ test("user must be able to send an image on chat", async ({ page }) => {
// Dispatch the drop event on the target element // Dispatch the drop event on the target element
await element.dispatchEvent("drop", { dataTransfer }); await element.dispatchEvent("drop", { dataTransfer });
await page.waitForSelector('[data-testid="icon-LucideSend"]', { await page.waitForSelector('[data-testid="button-send"]', {
timeout: 100000, timeout: 100000,
}); });
await page.getByTestId("icon-LucideSend").click(); await page.getByTestId("button-send").click();
await page.waitForSelector("text=chain.png", { timeout: 30000 }); await page.waitForSelector("text=chain.png", { timeout: 30000 });

View file

@ -1,6 +1,6 @@
import { expect, test } from "@playwright/test"; import { expect, test } from "@playwright/test";
test.skip("user must be able to freeze a component", async ({ page }) => { test("user must be able to freeze a component", async ({ page }) => {
await page.goto("/"); await page.goto("/");
await page.waitForSelector('[data-testid="mainpage_title"]', { await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000, timeout: 30000,

View file

@ -2,7 +2,7 @@ import { expect, test } from "@playwright/test";
import * as dotenv from "dotenv"; import * as dotenv from "dotenv";
import path from "path"; import path from "path";
test.skip("fresh start playground", async ({ page }) => { test("fresh start playground", async ({ page }) => {
if (!process.env.CI) { if (!process.env.CI) {
dotenv.config({ path: path.resolve(__dirname, "../../.env") }); dotenv.config({ path: path.resolve(__dirname, "../../.env") });
} }
@ -124,15 +124,14 @@ test.skip("fresh start playground", async ({ page }) => {
.getByText("message") .getByText("message")
.click(); .click();
//check session //check session
await page.getByText("Default Session").click(); await page.getByText("Default Session").first().click();
await page.getByTestId("chat-message-User-message 1").click(); await page.getByTestId("chat-message-User-message 1").click();
//check edit message //check edit message
await page.getByTestId("chat-message-User-message 1").hover(); await page.getByTestId("chat-message-User-message 1").hover();
await page await page
.locator("div") .locator("div")
.filter({ hasText: /^Usermessage 1$/ }) .filter({ hasText: /^Usermessage 1$/ })
.getByTestId("icon-pencil") .getByTestId("icon-Pen")
.click(); .click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
@ -141,46 +140,21 @@ test.skip("fresh start playground", async ({ page }) => {
await page.getByTestId("chat-message-User-edit_1").click(); await page.getByTestId("chat-message-User-edit_1").click();
await page.getByTestId("chat-message-User-edit_1").hover(); await page.getByTestId("chat-message-User-edit_1").hover();
// check cancel edit // check cancel edit
await page await page.getByTestId("sender_name_user").hover();
.locator("div") await page.getByTestId("icon-Pen").first().click();
.filter({ hasText: /^Useredit_1$/ })
.getByTestId("icon-pencil")
.click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page.getByTestId("textarea").fill("cancel_edit"); await page.getByTestId("textarea").fill("cancel_edit");
await page.getByTestId("cancel-button").click(); await page.getByTestId("cancel-button").click();
await page.getByTestId("chat-message-User-edit_1").click(); await page.getByTestId("chat-message-User-edit_1").click();
await page.getByTestId("chat-message-User-edit_1").hover(); await page.getByTestId("chat-message-User-edit_1").hover();
// check cancel edit blur
await page
.locator("div")
.filter({ hasText: /^Useredit_1$/ })
.getByTestId("icon-pencil")
.click();
await page.waitForTimeout(500);
await page.getByTestId("textarea").fill("cancel_edit_blur");
await page
.getByLabel("Playground")
.locator("div")
.filter({ hasText: "ChatDefault" })
.nth(2)
.click();
await page.waitForTimeout(500);
await page.getByTestId("chat-message-User-edit_1").click();
//check edit bot message //check edit bot message
await page await page
.getByTestId("chat-message-AI-message 1") .getByTestId("chat-message-AI-message 1")
.getByText("message") .getByText("message")
.click(); .click();
await page.getByTestId("chat-message-AI-message 1").hover(); await page.getByTestId("chat-message-AI-message 1").hover();
await page await page.getByTestId("icon-Pen").last().click();
.locator("div")
.filter({ hasText: /^AImessage 1$/ })
.getByTestId("icon-pencil")
.click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page.getByTestId("textarea").fill("edit_bot_1"); await page.getByTestId("textarea").fill("edit_bot_1");
@ -188,35 +162,13 @@ test.skip("fresh start playground", async ({ page }) => {
await page.getByText("edit_bot_1").click(); await page.getByText("edit_bot_1").click();
// check cancel edit bot // check cancel edit bot
await page.getByTestId("chat-message-AI-edit_bot_1").hover(); await page.getByTestId("chat-message-AI-edit_bot_1").hover();
await page await page.getByTestId("icon-Pen").last().click();
.locator("div")
.filter({ hasText: /^AIedit_bot_1$/ })
.getByTestId("icon-pencil")
.click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page.getByTestId("textarea").fill("edit_bot_cancel"); await page.getByTestId("textarea").fill("edit_bot_cancel");
await page.getByTestId("cancel-button").click(); await page.getByTestId("cancel-button").click();
await page.getByText("edit_bot_1").click(); await page.getByText("edit_bot_1").click();
await page.getByTestId("chat-message-AI-edit_bot_1").hover(); await page.getByTestId("chat-message-AI-edit_bot_1").hover();
// check cancel edit bot blur
await page
.locator("div")
.filter({ hasText: /^AIedit_bot_1$/ })
.getByTestId("icon-pencil")
.click();
await page.waitForTimeout(500);
await page.getByTestId("textarea").fill("edit_bot_blur_cancel");
await page
.getByLabel("Playground")
.locator("div")
.filter({ hasText: "ChatDefault" })
.nth(2)
.click();
await page.waitForTimeout(500);
await page.getByText("edit_bot_1").click();
// check table messages view // check table messages view
await page.getByRole("combobox").click(); await page.getByRole("combobox").click();
await page.getByLabel("Message logs").click(); await page.getByLabel("Message logs").click();
@ -225,29 +177,26 @@ test.skip("fresh start playground", async ({ page }) => {
await page.getByRole("combobox").click(); await page.getByRole("combobox").click();
await page.getByLabel("Rename").getByText("Rename").click(); await page.getByLabel("Rename").getByText("Rename").click();
await page.getByRole("textbox").fill("new name"); await page.getByRole("textbox").fill("new name");
await page await page.getByTestId("icon-Check").click();
.getByLabel("Chat", { exact: true })
.getByTestId("icon-Check")
.click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page.getByLabel("Chat", { exact: true }).getByText("new name").click(); await page.getByTestId("session-selector").getByText("new name").click();
// check cancel rename // check cancel rename
await page.getByRole("combobox").click(); await page.getByRole("combobox").click();
await page.getByLabel("Rename").getByText("Rename").click(); await page.getByLabel("Rename").getByText("Rename").click();
await page.getByRole("textbox").fill("cancel name"); await page.getByRole("textbox").fill("cancel name");
await page.getByLabel("Chat", { exact: true }).getByTestId("icon-X").click(); await page.getByTestId("session-selector").getByTestId("icon-X").click();
await page.getByLabel("Chat", { exact: true }).getByText("new name").click(); await page.getByTestId("session-selector").getByText("new name").click();
// check cancel rename blur // check cancel rename blur
await page.getByRole("combobox").click(); await page.getByRole("combobox").click();
await page.getByLabel("Rename").getByText("Rename").click(); await page.getByLabel("Rename").getByText("Rename").click();
await page.getByRole("textbox").fill("cancel_blur"); await page.getByRole("textbox").fill("cancel_blur");
await page.getByRole("tab", { name: "Chat" }).click(); await page.getByText("PlaygroundChat").click();
await page.getByLabel("Chat", { exact: true }).getByText("new name").click(); await page.getByTestId("session-selector").getByText("new name").click();
// check delete session // check delete session
await page.getByRole("combobox").click(); await page.getByRole("combobox").click();
await page.getByLabel("Delete").click(); await page.getByLabel("Delete").click();
await page.getByText("No memories available.").click(); await page.getByRole("heading", { name: "New chat" }).click();
// check new session // check new session
await page.getByTestId("input-chat-playground").click(); await page.getByTestId("input-chat-playground").click();
await page.getByTestId("input-chat-playground").fill("session_after_delete"); await page.getByTestId("input-chat-playground").fill("session_after_delete");
@ -256,9 +205,9 @@ test.skip("fresh start playground", async ({ page }) => {
await expect(page.getByTestId("session-selector")).toBeVisible(); await expect(page.getByTestId("session-selector")).toBeVisible();
// check new chat // check new chat
await page.getByRole("button", { name: "New Chat" }).click(); await page.getByTestId("new-chat").click();
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page.getByText("👋 Langflow Chat").click(); await page.getByText("New chat").click();
await page.getByTestId("input-chat-playground").click(); await page.getByTestId("input-chat-playground").click();
await page.getByTestId("input-chat-playground").fill("second session"); await page.getByTestId("input-chat-playground").fill("second session");
await page.keyboard.press("Enter"); await page.keyboard.press("Enter");
@ -271,9 +220,6 @@ test.skip("fresh start playground", async ({ page }) => {
.click(); .click();
expect(await page.getByTestId("session-selector").count()).toBe(2); expect(await page.getByTestId("session-selector").count()).toBe(2);
const sessionElements = await page const sessionElements = await page.getByTestId("session-selector").all();
.getByLabel("Playground")
.getByText(/^Session .+/)
.all();
expect(sessionElements.length).toBe(2); expect(sessionElements.length).toBe(2);
}); });

View file

@ -98,11 +98,11 @@ test("Basic Prompting (Hello, World)", async ({ page }) => {
.last() .last()
.fill("Say hello as a pirate"); .fill("Say hello as a pirate");
await page.waitForSelector('[data-testid="icon-LucideSend"]', { await page.waitForSelector('[data-testid="button-send"]', {
timeout: 100000, timeout: 100000,
}); });
await page.getByTestId("icon-LucideSend").last().click(); await page.getByTestId("button-send").last().click();
await page.waitForSelector("text=matey", { await page.waitForSelector("text=matey", {
timeout: 100000, timeout: 100000,

View file

@ -113,26 +113,28 @@ test("Blog Writer", async ({ page }) => {
await page.getByText("sea").last().isVisible(); await page.getByText("sea").last().isVisible();
await page.getByText("survival").last().isVisible(); await page.getByText("survival").last().isVisible();
await page.getByText("Instructions").last().click(); //commented out for now because text input is not available in the playground
const value = await page // await page.getByText("Instructions").last().click();
.getByPlaceholder("Enter text...")
.last()
.inputValue();
expect(value).toBe( // const value = await page
"Use the references above for style to write a new blog/tutorial about turtles. Suggest non-covered topics.", // .getByPlaceholder("Enter text...")
); // .last()
// .inputValue();
await page.getByTestId("icon-ExternalLink").last().click(); // expect(value).toBe(
// "Use the references above for style to write a new blog/tutorial about turtles. Suggest non-covered topics.",
// );
const count = await page // await page.getByTestId("icon-ExternalLink").last().click();
.getByText(
"Use the references above for style to write a new blog/tutorial about turtles. Suggest non-covered topics.",
)
.count();
if (count <= 1) { // const count = await page
expect(false).toBe(true); // .getByText(
} // "Use the references above for style to write a new blog/tutorial about turtles. Suggest non-covered topics.",
// )
// .count();
// if (count <= 1) {
// expect(false).toBe(true);
// }
}); });

View file

@ -102,7 +102,7 @@ test("Document QA", async ({ page }) => {
.getByTestId("input-chat-playground") .getByTestId("input-chat-playground")
.last() .last()
.fill("whats the text in the file?"); .fill("whats the text in the file?");
await page.getByTestId("icon-LucideSend").last().click(); await page.getByTestId("button-send").last().click();
await page.waitForTimeout(3000); await page.waitForTimeout(3000);

View file

@ -97,7 +97,7 @@ test("Memory Chatbot", async ({ page }) => {
.getByTestId("input-chat-playground") .getByTestId("input-chat-playground")
.last() .last()
.fill("Remember that I'm a lion"); .fill("Remember that I'm a lion");
await page.getByTestId("icon-LucideSend").last().click(); await page.getByTestId("button-send").last().click();
await page.waitForSelector('[data-testid="input-chat-playground"]', { await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000, timeout: 100000,
@ -108,11 +108,11 @@ test("Memory Chatbot", async ({ page }) => {
.last() .last()
.fill("try reproduce the sound I made in words"); .fill("try reproduce the sound I made in words");
await page.waitForSelector('[data-testid="icon-LucideSend"]', { await page.waitForSelector('[data-testid="button-send"]', {
timeout: 100000, timeout: 100000,
}); });
await page.getByTestId("icon-LucideSend").last().click(); await page.getByTestId("button-send").last().click();
await page.waitForSelector("text=roar", { timeout: 30000 }); await page.waitForSelector("text=roar", { timeout: 30000 });
await page.getByText("roar").last().isVisible(); await page.getByText("roar").last().isVisible();

View file

@ -3,7 +3,7 @@ import * as dotenv from "dotenv";
import path from "path"; import path from "path";
import uaParser from "ua-parser-js"; import uaParser from "ua-parser-js";
test("Simple Agent", async ({ page }) => { test.skip("Simple Agent", async ({ page }) => {
test.skip( test.skip(
!process?.env?.OPENAI_API_KEY, !process?.env?.OPENAI_API_KEY,
"OPENAI_API_KEY required to run this test", "OPENAI_API_KEY required to run this test",
@ -116,36 +116,25 @@ test("Simple Agent", async ({ page }) => {
await page await page
.getByPlaceholder("Send a message...") .getByPlaceholder("Send a message...")
.fill("write short python scsript to say hello world"); .fill("write short python script to say hello world");
await page.getByTestId("icon-LucideSend").last().click(); await page.getByTestId("button-send").last().click();
await page.waitForSelector( await page.waitForSelector(
"text=write short python scsript to say hello world", "text=write short python script to say hello world",
{ {
timeout: 30000, timeout: 30000,
}, },
); );
await page.waitForSelector(".api-modal-tablist-div", { await page.waitForSelector('[data-testid="copy-code-button"]', {
timeout: 100000,
state: "visible",
});
await page.waitForSelector("role=tab", {
timeout: 100000, timeout: 100000,
state: "visible", state: "visible",
}); });
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page.waitForSelector('[data-testid="btn-copy-code"]', { await page.getByTestId("copy-code-button").last().click();
state: "visible",
timeout: 30000,
});
await page.waitForTimeout(1000);
await page.getByTestId("btn-copy-code").last().click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
@ -157,7 +146,7 @@ test("Simple Agent", async ({ page }) => {
await page.waitForTimeout(500); await page.waitForTimeout(500);
pythonWords = await page.getByText("Hello, World!").count(); pythonWords = await page.getByText("print(").count();
expect(pythonWords).toBe(3); expect(pythonWords).toBe(1);
}); });

View file

@ -339,14 +339,13 @@ test("user must be able to check similarity between embedding texts", async ({
await page.waitForSelector("text=built successfully", { timeout: 30000 }); await page.waitForSelector("text=built successfully", { timeout: 30000 });
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page.getByText("Playground", { exact: true }).last().click();
await page.waitForTimeout(1000);
await page await page
.getByPlaceholder("Empty") .getByTestId(/rf__node-TextOutput-[a-zA-Z0-9]{5}/)
.waitFor({ state: "visible", timeout: 30000 }); .getByTestId("output-inspection-text")
.first()
.click();
const valueSimilarity = await page.getByTestId("textarea").textContent();
const valueSimilarity = await page.getByPlaceholder("Empty").textContent();
expect(valueSimilarity).toContain("cosine_similarity"); expect(valueSimilarity).toContain("cosine_similarity");
const valueLength = valueSimilarity!.length; const valueLength = valueSimilarity!.length;
expect(valueLength).toBeGreaterThan(20); expect(valueLength).toBeGreaterThan(20);

View file

@ -3,237 +3,190 @@ import * as dotenv from "dotenv";
import path from "path"; import path from "path";
test("TextInputOutputComponent", async ({ page }) => { test("TextInputOutputComponent", async ({ page }) => {
test.skip( // commented out because new playground does not support text io yet
!process?.env?.OPENAI_API_KEY, // test.skip(
"OPENAI_API_KEY required to run this test", // !process?.env?.OPENAI_API_KEY,
); // "OPENAI_API_KEY required to run this test",
// );
if (!process.env.CI) { // if (!process.env.CI) {
dotenv.config({ path: path.resolve(__dirname, "../../.env") }); // dotenv.config({ path: path.resolve(__dirname, "../../.env") });
} // }
// await page.goto("/");
await page.goto("/"); // await page.waitForSelector('[data-testid="mainpage_title"]', {
await page.waitForSelector('[data-testid="mainpage_title"]', { // timeout: 30000,
timeout: 30000, // });
}); // await page.waitForSelector('[id="new-project-btn"]', {
// timeout: 30000,
await page.waitForSelector('[id="new-project-btn"]', { // });
timeout: 30000, // let modalCount = 0;
}); // try {
// const modalTitleElement = await page?.getByTestId("modal-title");
let modalCount = 0; // if (modalTitleElement) {
try { // modalCount = await modalTitleElement.count();
const modalTitleElement = await page?.getByTestId("modal-title"); // }
if (modalTitleElement) { // } catch (error) {
modalCount = await modalTitleElement.count(); // modalCount = 0;
} // }
} catch (error) { // while (modalCount === 0) {
modalCount = 0; // await page.getByText("New Flow", { exact: true }).click();
} // await page.waitForTimeout(3000);
// modalCount = await page.getByTestId("modal-title")?.count();
while (modalCount === 0) { // }
await page.getByText("New Flow", { exact: true }).click(); // await page.waitForSelector('[data-testid="blank-flow"]', {
await page.waitForTimeout(3000); // timeout: 30000,
modalCount = await page.getByTestId("modal-title")?.count(); // });
} // await page.getByTestId("blank-flow").click();
// await page.getByTestId("sidebar-search-input").click();
await page.waitForSelector('[data-testid="blank-flow"]', { // await page.getByTestId("sidebar-search-input").fill("text input");
timeout: 30000, // await page.waitForTimeout(1000);
}); // await page
await page.getByTestId("blank-flow").click(); // .getByTestId("inputsText Input")
// .dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.getByTestId("sidebar-search-input").click(); // await page.mouse.up();
await page.getByTestId("sidebar-search-input").fill("text input"); // await page.mouse.down();
await page.waitForTimeout(1000); // await page.getByTestId("sidebar-search-input").click();
// await page.getByTestId("sidebar-search-input").fill("openai");
await page // await page.waitForTimeout(1000);
.getByTestId("inputsText Input") // await page
.dragTo(page.locator('//*[@id="react-flow-id"]')); // .getByTestId("modelsOpenAI")
await page.mouse.up(); // .dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.down(); // await page.mouse.up();
// await page.mouse.down();
await page.getByTestId("sidebar-search-input").click(); // await page.waitForSelector('[data-testid="fit_view"]', {
await page.getByTestId("sidebar-search-input").fill("openai"); // timeout: 100000,
await page.waitForTimeout(1000); // });
// await page.getByTestId("fit_view").click();
await page // await page.getByTestId("zoom_out").click();
.getByTestId("modelsOpenAI") // await page.getByTestId("zoom_out").click();
.dragTo(page.locator('//*[@id="react-flow-id"]')); // await page.getByTestId("zoom_out").click();
await page.mouse.up(); // let visibleElementHandle;
await page.mouse.down(); // const elementsTextInputOutput = await page
// .getByTestId("handle-textinput-shownode-text-right")
await page.waitForSelector('[data-testid="fit_view"]', { // .all();
timeout: 100000, // for (const element of elementsTextInputOutput) {
}); // if (await element.isVisible()) {
// visibleElementHandle = element;
await page.getByTestId("fit_view").click(); // break;
await page.getByTestId("zoom_out").click(); // }
await page.getByTestId("zoom_out").click(); // }
await page.getByTestId("zoom_out").click(); // await visibleElementHandle.waitFor({
// state: "visible",
let visibleElementHandle; // timeout: 30000,
// });
const elementsTextInputOutput = await page // await visibleElementHandle.hover();
.getByTestId("handle-textinput-shownode-text-right") // await page.mouse.down();
.all(); // for (const element of elementsTextInputOutput) {
// if (await element.isVisible()) {
for (const element of elementsTextInputOutput) { // visibleElementHandle = element;
if (await element.isVisible()) { // break;
visibleElementHandle = element; // }
break; // }
} // await visibleElementHandle.waitFor({
} // state: "visible",
// timeout: 30000,
await visibleElementHandle.waitFor({ // });
state: "visible", // // Move to the second element
timeout: 30000, // await visibleElementHandle.hover();
}); // // Release the mouse
// await page.mouse.up();
await visibleElementHandle.hover(); // await page.getByTestId("sidebar-search-input").click();
await page.mouse.down(); // await page.getByTestId("sidebar-search-input").fill("text output");
// await page
for (const element of elementsTextInputOutput) { // .getByTestId("outputsText Output")
if (await element.isVisible()) { // .dragTo(page.locator('//*[@id="react-flow-id"]'));
visibleElementHandle = element; // await page.mouse.up();
break; // await page.mouse.down();
} // await page.waitForSelector('[data-testid="fit_view"]', {
} // timeout: 100000,
// });
await visibleElementHandle.waitFor({ // await page.getByTestId("zoom_out").click();
state: "visible", // await page.getByTestId("zoom_out").click();
timeout: 30000, // await page.getByTestId("zoom_out").click();
}); // await page.getByTestId("zoom_out").click();
// await page.getByTestId("zoom_out").click();
// Move to the second element // await page.getByTestId("zoom_out").click();
await visibleElementHandle.hover(); // const elementsOpenAiOutput = await page
// .getByTestId("handle-openaimodel-shownode-text-right")
// Release the mouse // .all();
await page.mouse.up(); // for (const element of elementsOpenAiOutput) {
// if (await element.isVisible()) {
await page.getByTestId("sidebar-search-input").click(); // visibleElementHandle = element;
await page.getByTestId("sidebar-search-input").fill("text output"); // break;
// }
await page // }
.getByTestId("outputsText Output") // await visibleElementHandle.waitFor({
.dragTo(page.locator('//*[@id="react-flow-id"]')); // state: "visible",
await page.mouse.up(); // timeout: 30000,
await page.mouse.down(); // });
// // Click and hold on the first element
await page.waitForSelector('[data-testid="fit_view"]', { // await visibleElementHandle.hover();
timeout: 100000, // await page.mouse.down();
}); // const elementTextOutputInput = await page
// .getByTestId("handle-textoutput-shownode-text-left")
await page.getByTestId("zoom_out").click(); // .all();
await page.getByTestId("zoom_out").click(); // for (const element of elementTextOutputInput) {
await page.getByTestId("zoom_out").click(); // if (await element.isVisible()) {
await page.getByTestId("zoom_out").click(); // visibleElementHandle = element;
await page.getByTestId("zoom_out").click(); // break;
await page.getByTestId("zoom_out").click(); // }
// }
const elementsOpenAiOutput = await page // await visibleElementHandle.waitFor({
.getByTestId("handle-openaimodel-shownode-text-right") // state: "visible",
.all(); // timeout: 30000,
// });
for (const element of elementsOpenAiOutput) { // // Move to the second element
if (await element.isVisible()) { // await visibleElementHandle.hover();
visibleElementHandle = element; // // Release the mouse
break; // await page.mouse.up();
} // await page
} // .getByTestId(/^rf__node-TextInput-[a-zA-Z0-9]+$/)
// .getByTestId("textarea_str_input_value")
await visibleElementHandle.waitFor({ // .fill("This is a test!");
state: "visible", // let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count();
timeout: 30000, // while (outdatedComponents > 0) {
}); // await page.getByTestId("icon-AlertTriangle").first().click();
// await page.waitForTimeout(1000);
// Click and hold on the first element // outdatedComponents = await page.getByTestId("icon-AlertTriangle").count();
await visibleElementHandle.hover(); // }
await page.mouse.down(); // let filledApiKey = await page.getByTestId("remove-icon-badge").count();
// while (filledApiKey > 0) {
const elementTextOutputInput = await page // await page.getByTestId("remove-icon-badge").first().click();
.getByTestId("handle-textoutput-shownode-text-left") // await page.waitForTimeout(1000);
.all(); // filledApiKey = await page.getByTestId("remove-icon-badge").count();
// }
for (const element of elementTextOutputInput) { // const apiKeyInput = page.getByTestId("popover-anchor-input-api_key");
if (await element.isVisible()) { // const isApiKeyInputVisible = await apiKeyInput.isVisible();
visibleElementHandle = element; // if (isApiKeyInputVisible) {
break; // await apiKeyInput.fill(process.env.OPENAI_API_KEY ?? "");
} // }
} // await page.getByTestId("dropdown_str_model_name").click();
// await page.getByTestId("gpt-4o-1-option").click();
await visibleElementHandle.waitFor({ // await page.waitForTimeout(1000);
state: "visible", // await page.getByText("Playground", { exact: true }).last().click();
timeout: 30000, // await page.getByTestId("button_run_text_output").click();
}); // await page.getByTestId(/^rf__node-TextOutput-[a-zA-Z0-9]+$/).getByTestId("output-inspection-text").click();
// await page.getByText("Run Flow", { exact: true }).click();
// Move to the second element // await page.waitForTimeout(5000);
await visibleElementHandle.hover(); // let textInputContent = await page
// .getByPlaceholder("Enter text...")
// Release the mouse // .textContent();
await page.mouse.up(); // expect(textInputContent).toBe("This is a test!");
// await page.getByText("Outputs", { exact: true }).nth(1).click();
await page // await page.getByText("Text Output", { exact: true }).nth(2).click();
.getByTestId(/^rf__node-TextInput-[a-zA-Z0-9]+$/) // let contentOutput = await page.getByPlaceholder("Enter text...").inputValue();
.getByTestId("textarea_str_input_value") // expect(contentOutput).not.toBe(null);
.fill("This is a test!"); // await page.keyboard.press("Escape");
// await page
let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); // .getByTestId(/^rf__node-TextInput-[a-zA-Z0-9]+$/)
// .getByTestId("textarea_str_input_value")
while (outdatedComponents > 0) { // .fill("This is a test, again just to be sure!");
await page.getByTestId("icon-AlertTriangle").first().click(); // await page.getByText("Playground", { exact: true }).last().click();
await page.waitForTimeout(1000); // await page.getByText("Run Flow", { exact: true }).click();
outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); // await page.waitForTimeout(5000);
} // textInputContent = await page.getByPlaceholder("Enter text...").textContent();
// expect(textInputContent).toBe("This is a test, again just to be sure!");
let filledApiKey = await page.getByTestId("remove-icon-badge").count(); // await page.getByText("Outputs", { exact: true }).nth(1).click();
while (filledApiKey > 0) { // await page.getByText("Text Output", { exact: true }).nth(2).click();
await page.getByTestId("remove-icon-badge").first().click(); // contentOutput = await page.getByPlaceholder("Enter text...").inputValue();
await page.waitForTimeout(1000); // expect(contentOutput).not.toBe(null);
filledApiKey = await page.getByTestId("remove-icon-badge").count();
}
const apiKeyInput = page.getByTestId("popover-anchor-input-api_key");
const isApiKeyInputVisible = await apiKeyInput.isVisible();
if (isApiKeyInputVisible) {
await apiKeyInput.fill(process.env.OPENAI_API_KEY ?? "");
}
await page.getByTestId("dropdown_str_model_name").click();
await page.getByTestId("gpt-4o-1-option").click();
await page.waitForTimeout(1000);
await page.getByText("Playground", { exact: true }).last().click();
await page.getByText("Run Flow", { exact: true }).click();
await page.waitForTimeout(5000);
let textInputContent = await page
.getByPlaceholder("Enter text...")
.textContent();
expect(textInputContent).toBe("This is a test!");
await page.getByText("Outputs", { exact: true }).nth(1).click();
await page.getByText("Text Output", { exact: true }).nth(2).click();
let contentOutput = await page.getByPlaceholder("Enter text...").inputValue();
expect(contentOutput).not.toBe(null);
await page.keyboard.press("Escape");
await page
.getByTestId(/^rf__node-TextInput-[a-zA-Z0-9]+$/)
.getByTestId("textarea_str_input_value")
.fill("This is a test, again just to be sure!");
await page.getByText("Playground", { exact: true }).last().click();
await page.getByText("Run Flow", { exact: true }).click();
await page.waitForTimeout(5000);
textInputContent = await page.getByPlaceholder("Enter text...").textContent();
expect(textInputContent).toBe("This is a test, again just to be sure!");
await page.getByText("Outputs", { exact: true }).nth(1).click();
await page.getByText("Text Output", { exact: true }).nth(2).click();
contentOutput = await page.getByPlaceholder("Enter text...").inputValue();
expect(contentOutput).not.toBe(null);
}); });

View file

@ -143,7 +143,7 @@ AI:
await page.getByText("Playground", { exact: true }).last().click(); await page.getByText("Playground", { exact: true }).last().click();
await page.waitForSelector('[data-testid="icon-LucideSend"]', { await page.waitForSelector('[data-testid="button-send"]', {
timeout: 100000, timeout: 100000,
}); });
@ -151,7 +151,7 @@ AI:
.getByPlaceholder("Send a message...") .getByPlaceholder("Send a message...")
.fill("hi, my car is blue and I like to eat pizza"); .fill("hi, my car is blue and I like to eat pizza");
await page.getByTestId("icon-LucideSend").click(); await page.getByTestId("button-send").click();
await page.waitForSelector("text=AI", { timeout: 30000 }); await page.waitForSelector("text=AI", { timeout: 30000 });
@ -159,7 +159,7 @@ AI:
.getByPlaceholder("Send a message...") .getByPlaceholder("Send a message...")
.fill("what color is my car and what do I like to eat?"); .fill("what color is my car and what do I like to eat?");
await page.getByTestId("icon-LucideSend").click(); await page.getByTestId("button-send").click();
await page.waitForTimeout(400); await page.waitForTimeout(400);

View file

@ -107,7 +107,7 @@ test("chat_io_teste", async ({ page }) => {
}); });
await page.getByTestId("input-chat-playground").click(); await page.getByTestId("input-chat-playground").click();
await page.getByTestId("input-chat-playground").fill("teste"); await page.getByTestId("input-chat-playground").fill("teste");
await page.getByTestId("icon-LucideSend").first().click(); await page.getByTestId("button-send").first().click();
const chat_output = page.getByTestId("chat-message-AI-teste"); const chat_output = page.getByTestId("chat-message-AI-teste");
const chat_input = page.getByTestId("chat-message-User-teste"); const chat_input = page.getByTestId("chat-message-User-teste");
await expect(chat_output).toHaveText("teste"); await expect(chat_output).toHaveText("teste");

View file

@ -1,7 +1,7 @@
import { expect, test } from "@playwright/test"; import { expect, test } from "@playwright/test";
import path from "path"; import path from "path";
test("should be able to upload a file", async ({ page }) => { test.skip("should be able to upload a file", async ({ page }) => {
await page.goto("/"); await page.goto("/");
await page.waitForSelector('[data-testid="mainpage_title"]', { await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000, timeout: 30000,
@ -55,10 +55,10 @@ test("should be able to upload a file", async ({ page }) => {
await page.getByText("test_file.txt").isVisible(); await page.getByText("test_file.txt").isVisible();
await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text output"); await page.getByTestId("sidebar-search-input").fill("chat output");
await page await page
.getByTestId("outputsText Output") .getByTestId("outputsChat Output")
.first() .first()
.dragTo(page.locator('//*[@id="react-flow-id"]')); .dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up(); await page.mouse.up();
@ -136,11 +136,11 @@ test("should be able to upload a file", async ({ page }) => {
await page.mouse.down(); await page.mouse.down();
// Move to the second element // Move to the second element
const textOutputElement = await page const chatOutputElement = await page
.getByTestId("handle-textoutput-shownode-text-left") .getByTestId("handle-chatoutput-shownode-text-left")
.all(); .all();
for (const element of textOutputElement) { for (const element of chatOutputElement) {
if (await element.isVisible()) { if (await element.isVisible()) {
visibleElementHandle = element; visibleElementHandle = element;
break; break;
@ -161,8 +161,5 @@ test("should be able to upload a file", async ({ page }) => {
await page.getByText("Run Flow", { exact: true }).click(); await page.getByText("Run Flow", { exact: true }).click();
await page.waitForTimeout(3000); await page.waitForTimeout(3000);
expect(await page.getByText("this is a test file").isVisible()).toBe(true);
const textOutput = await page.getByPlaceholder("Empty").first().inputValue();
expect(textOutput).toContain("this is a test file");
}); });

View file

@ -2,10 +2,7 @@ import { expect, Page, test } from "@playwright/test";
import uaParser from "ua-parser-js"; import uaParser from "ua-parser-js";
// TODO: This test might not be needed anymore // TODO: This test might not be needed anymore
test.skip("user should interact with link component", async ({ test("user should interact with link component", async ({ context, page }) => {
context,
page,
}) => {
await page.goto("/"); await page.goto("/");
await page.waitForSelector('[data-testid="mainpage_title"]', { await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000, timeout: 30000,

View file

@ -2,7 +2,7 @@ import { expect, test } from "@playwright/test";
import uaParser from "ua-parser-js"; import uaParser from "ua-parser-js";
// TODO: This component doesn't have table input needs updating // TODO: This component doesn't have table input needs updating
test.skip("user must be able to interact with table input component", async ({ test("user must be able to interact with table input component", async ({
page, page,
}) => { }) => {
await page.goto("/"); await page.goto("/");

View file

@ -1,7 +1,7 @@
import { expect, test } from "@playwright/test"; import { expect, test } from "@playwright/test";
import uaParser from "ua-parser-js"; import uaParser from "ua-parser-js";
test.skip("User must be able to stop building from inside Playground", async ({ test("User must be able to stop building from inside Playground", async ({
page, page,
}) => { }) => {
await page.goto("/"); await page.goto("/");
@ -129,20 +129,20 @@ class CustomComponent(Component):
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page.waitForSelector('[data-testid="icon-Square"]', { await page.waitForSelector('[data-testid="button-stop"]', {
timeout: 30000, timeout: 30000,
}); });
const elements = await page.$$('[data-testid="icon-Square"]'); const elements = await page.$$('[data-testid="button-stop"]');
if (elements.length > 0) { if (elements.length > 0) {
const lastElement = elements[elements.length - 1]; const lastElement = elements[elements.length - 1];
await lastElement.waitForElementState("visible"); await lastElement.waitForElementState("visible");
} }
expect(await page.getByTestId("icon-Square").last()).toBeVisible(); expect(await page.getByTestId("button-stop").last()).toBeVisible();
await page.getByTestId("icon-Square").last().click(); await page.getByTestId("button-stop").last().click();
await page.waitForSelector("text=build stopped", { timeout: 30000 }); await page.waitForSelector("text=build stopped", { timeout: 30000 });
expect(await page.getByText("build stopped").isVisible()).toBeTruthy(); expect(await page.getByText("build stopped").isVisible()).toBeTruthy();

Some files were not shown because too many files have changed in this diff Show more