diff --git a/panel/tests/widgets/test_chatbox.py b/panel/tests/widgets/test_chatbox.py index 71d5c3bad3..cddd241a98 100644 --- a/panel/tests/widgets/test_chatbox.py +++ b/panel/tests/widgets/test_chatbox.py @@ -156,6 +156,52 @@ def test_chat_box_extend(document, comm): assert len(chat_box) == 2 +def test_insert(document, comm): + chat_box = ChatBox() + message = {"user": "Hello"} + chat_box.insert(0, message) + assert chat_box.value == [message] + assert len(chat_box) == 1 + + # Inserting at an index greater than the current length + chat_box.insert(2, {"user": "Hi"}) + assert chat_box.value == [message, {"user": "Hi"}] + assert len(chat_box) == 2 + + +def test_pop(document, comm): + chat_box = ChatBox() + message1 = {"user": "Hello"} + message2 = {"user": "Hi"} + message3 = {"user": "Hey"} + chat_box.extend([message1, message2, message3]) + + # Pop the last message + popped_message = chat_box.pop() + assert popped_message == message3 + assert chat_box.value == [message1, message2] + assert len(chat_box) == 2 + + # Pop a specific index + popped_message = chat_box.pop(0) + assert popped_message == message1 + assert chat_box.value == [message2] + assert len(chat_box) == 1 + + +def test_replace(document, comm): + chat_box = ChatBox() + message1 = {"user": "Hello"} + message2 = {"user": "Hi"} + chat_box.extend([message1, message2]) + + # Replace a message at a specific index + new_message = {"user": "Hey"} + chat_box.replace(1, new_message) + assert chat_box.value == [message1, new_message] + assert len(chat_box) == 2 + + def test_chat_box_allow_input(document, comm): chat_box = ChatBox(allow_input=True) assert chat_box.allow_input == True diff --git a/panel/widgets/chatbox.py b/panel/widgets/chatbox.py index f4c8d7acd9..1a936b2701 100644 --- a/panel/widgets/chatbox.py +++ b/panel/widgets/chatbox.py @@ -1,7 +1,7 @@ from __future__ import annotations from typing import ( - Any, Callable, ClassVar, Dict, List, Optional, Tuple, Type, Union, + Any, ClassVar, Dict, List, Optional, Tuple, Type, Union, ) import param @@ -65,7 +65,6 @@ class ChatRow(CompositeWidget): def __init__( self, value: List[Any], - default_message_callable: Viewable = None, icon: str = None, show_name: bool = True, show_like: bool = True, @@ -80,7 +79,6 @@ def __init__( } if styles: bubble_styles.update(styles) - text_style = {"color": bubble_styles.get("color")} icon_styles = dict((styles or {})) icon_styles.pop("background", None) icon_styles.pop("color", None) @@ -94,6 +92,7 @@ def __init__( ) if "background" not in bubble_styles: bubble_styles["background"] = "black" + self._bubble_styles = bubble_styles super().__init__(value=value, icon=icon, **params) # create the chat icon @@ -104,15 +103,11 @@ def __init__( self._icon = None # create the chat bubble - bubble_objects = [ - self._serialize_obj(obj, default_message_callable, text_style) - for obj in value - ] self._bubble = Column( - *bubble_objects, + *[self._serialize_obj(obj) for obj in self.value], align="center", margin=8, - styles=bubble_styles, + styles=bubble_styles ) # create heart icon next to chat @@ -163,7 +158,7 @@ def __init__( self._composite[:] = [row] def _serialize_obj( - self, obj: Any, default_message_callable: Callable, text_styles: Dict + self, obj: Any ) -> Viewable: """ Convert an object to a Panel object. @@ -172,15 +167,16 @@ def _serialize_obj( return obj stylesheets = ["p { margin-block-start: 0.2em; margin-block-end: 0.2em;}"] + text_styles = {"color": self._bubble_styles.get("color")} try: - if default_message_callable is None or issubclass( - default_message_callable, PaneBase + if self.default_message_callable is None or issubclass( + self.default_message_callable, PaneBase ): - panel_obj = (default_message_callable or _panel)( + panel_obj = (self.default_message_callable or _panel)( obj, stylesheets=stylesheets, styles=text_styles ) else: - panel_obj = default_message_callable(value=obj) + panel_obj = self.default_message_callable(value=obj) except ValueError: panel_obj = _panel(obj, stylesheets=stylesheets, styles=text_styles) @@ -543,7 +539,8 @@ def append(self, user_message: Dict[str, Union[List[Any], Any]]) -> None: """ if not isinstance(user_message, dict): raise ValueError(f"Expected a dictionary, but got {user_message}") - self.value = self.value + [user_message] + self.value.append(user_message) + self.param.trigger("value") def extend(self, user_messages: List[Dict[str, Union[List[Any], Any]]]) -> None: """ @@ -553,8 +550,48 @@ def extend(self, user_messages: List[Dict[str, Union[List[Any], Any]]]) -> None: --------- user_messages (list): List of user messages to add. """ - for user_message in user_messages: - self.append(user_message) + self.value.extend(user_messages) + self.param.trigger("value") + + def insert(self, index: int, user_message: Dict[str, Union[List[Any], Any]]) -> None: + """ + Inserts a message into the chat log at the given index. + + Arguments + --------- + index (int): Index to insert the message at. + user_message (dict): Dictionary mapping user to message. + """ + self.value.insert(index, user_message) + self.param.trigger("value") + + def pop(self, index: int = -1) -> Dict[str, Union[List[Any], Any]]: + """ + Pops the last message from the chat log. + + Arguments + --------- + index (int): Index of the message to pop; defaults to the last message. + + Returns + ------- + user_message (dict): Dictionary mapping user to message. + """ + value = self.value.pop(index) + self.param.trigger("value") + return value + + def replace(self, index: int, user_message: Dict[str, Union[List[Any], Any]]): + """ + Replaces a message in the chat log at the given index. + + Arguments + --------- + index (int): Index to replace the message at. + user_message (dict): Dictionary mapping user to message. + """ + self.value[index] = user_message + self.param.trigger("value") def clear(self) -> None: """