diff --git a/examples/reference/chat/ChatFeed.ipynb b/examples/reference/chat/ChatFeed.ipynb index fae2ededbf..32bf26dfb7 100644 --- a/examples/reference/chat/ChatFeed.ipynb +++ b/examples/reference/chat/ChatFeed.ipynb @@ -56,6 +56,7 @@ "* **`placeholder_threshold`** (float): Min duration in seconds of buffering before displaying the placeholder. If 0, the placeholder will be disabled. Defaults to 0.2.\n", "* **`auto_scroll_limit`** (int): Max pixel distance from the latest object in the Column to activate automatic scrolling upon update. Setting to 0 disables auto-scrolling.\n", "* **`scroll_button_threshold`** (int): Min pixel distance from the latest object in the Column to display the scroll button. Setting to 0 disables the scroll button.\n", + "* **`load_buffer`** (int): The number of objects loaded on each side of the visible objects. When scrolled halfway into the buffer, the feed will automatically load additional objects while unloading objects on the opposite side.\n", "* **`show_activity_dot`** (bool): Whether to show an activity dot on the ChatMessage while streaming the callback response.\n", "* **`view_latest`** (bool): Whether to scroll to the latest object on init. If not enabled the view will be on the first object. Defaults to True.\n", "\n", diff --git a/panel/chat/feed.py b/panel/chat/feed.py index 3c5e5976f3..c2eb2c2971 100644 --- a/panel/chat/feed.py +++ b/panel/chat/feed.py @@ -22,7 +22,7 @@ from .._param import Margin from ..io.resources import CDN_DIST -from ..layout import Column, ListPanel +from ..layout import Feed, ListPanel from ..layout.card import Card from ..layout.spacer import VSpacer from ..pane.image import SVG @@ -156,6 +156,11 @@ class ChatFeed(ListPanel): exception. If None, will attempt to infer the renderer from the value.""") + load_buffer = param.Integer(default=50, bounds=(0, None), doc=""" + The number of objects loaded on each side of the visible objects. + When scrolled halfway into the buffer, the feed will automatically + load additional objects while unloading objects on the opposite side.""") + scroll_button_threshold = param.Integer(default=100, bounds=(0, None),doc=""" Min pixel distance from the latest object in the Column to display the scroll button. Setting to 0 @@ -211,14 +216,17 @@ def __init__(self, *objects, **params): visible=self.param.visible ) # we separate out chat log for the auto scroll feature - self._chat_log = Column( - *self.objects, + self._chat_log = Feed( + *objects, + load_buffer=self.load_buffer, auto_scroll_limit=self.auto_scroll_limit, scroll_button_threshold=self.scroll_button_threshold, + view_latest=self.view_latest, css_classes=["chat-feed-log"], stylesheets=self._stylesheets, **linked_params ) + self._chat_log.height = None card_params = linked_params.copy() card_stylesheets = ( self._stylesheets + @@ -274,6 +282,12 @@ def _cleanup(self, root: Model | None = None) -> None: self._card._cleanup(root) super()._cleanup(root) + @param.depends("load_buffer", "auto_scroll_limit", "scroll_button_threshold", watch=True) + def _update_chat_log_params(self): + self._chat_log.load_buffer = self.load_buffer + self._chat_log.auto_scroll_limit = self.auto_scroll_limit + self._chat_log.scroll_button_threshold = self.scroll_button_threshold + @param.depends("card_params", watch=True) def _update_card_params(self): card_params = self.card_params.copy() diff --git a/panel/layout/feed.py b/panel/layout/feed.py index d3e72e5679..bec8571bf4 100644 --- a/panel/layout/feed.py +++ b/panel/layout/feed.py @@ -26,9 +26,18 @@ class Feed(Column): When scrolled halfway into the buffer, the feed will automatically load additional objects while unloading objects on the opposite side.""") - scroll = param.Boolean(default=True, doc=""" - Whether to add scrollbars if the content overflows the size - of the container.""") + scroll = param.Selector( + default="y", + objects=[False, True, "both-auto", "y-auto", "x-auto", "both", "x", "y"], + doc="""Whether to add scrollbars if the content overflows the size + of the container. If "both-auto", will only add scrollbars if + the content overflows in either directions. If "x-auto" or "y-auto", + will only add scrollbars if the content overflows in the + respective direction. If "both", will always add scrollbars. + If "x" or "y", will always add scrollbars in the respective + direction. If False, overflowing content will be clipped. + If True, will only add scrollbars in the direction of the container, + (e.g. Column: vertical, Row: horizontal).""") visible_range = param.Range(readonly=True, doc=""" Read-only upper and lower bounds of the currently visible feed objects. @@ -56,6 +65,9 @@ def __init__(self, *objects, **params): @param.depends("visible_range", "load_buffer", watch=True) def _trigger_get_objects(self): + if self.visible_range is None: + return + # visible start, end / synced start, end vs, ve = self.visible_range ss, se = self._last_synced diff --git a/panel/tests/chat/test_feed.py b/panel/tests/chat/test_feed.py index fd08f96138..70099e4de2 100644 --- a/panel/tests/chat/test_feed.py +++ b/panel/tests/chat/test_feed.py @@ -33,7 +33,7 @@ class TestChatFeed: def test_init_with_help_text(self): chat_feed = ChatFeed(help_text="Instructions") - message = chat_feed._chat_log[0] + message = chat_feed.objects[0] assert message.object == "Instructions" assert message.user == "Help" @@ -427,6 +427,18 @@ def test_forward_message_params(self, chat_feed): assert chat_message.reactions == ["like"] assert chat_message.reaction_icons.options == {"like": "thumb-up"} + def test_update_chat_log_params(self, chat_feed): + chat_feed = ChatFeed(load_buffer=5, scroll_button_threshold=5, auto_scroll_limit=5) + assert chat_feed._chat_log.load_buffer == 5 + assert chat_feed._chat_log.scroll_button_threshold == 5 + assert chat_feed._chat_log.auto_scroll_limit == 5 + + chat_feed.load_buffer = 10 + chat_feed.scroll_button_threshold = 10 + chat_feed.auto_scroll_limit = 10 + assert chat_feed._chat_log.load_buffer == 10 + assert chat_feed._chat_log.scroll_button_threshold == 10 + assert chat_feed._chat_log.auto_scroll_limit == 10 @pytest.mark.xdist_group("chat") class TestChatFeedCallback: diff --git a/panel/tests/ui/layout/test_feed.py b/panel/tests/ui/layout/test_feed.py index 41a4e57971..d9d7c0cdb3 100644 --- a/panel/tests/ui/layout/test_feed.py +++ b/panel/tests/ui/layout/test_feed.py @@ -19,7 +19,7 @@ def test_feed_load_entries(page): bbox = feed_el.bounding_box() assert bbox["height"] == 250 - expect(feed_el).to_have_class("bk-panel-models-feed-Feed scrollable-vertical") + expect(feed_el).to_have_class("bk-panel-models-feed-Feed scroll-vertical") children_count = feed_el.evaluate( '(element) => element.shadowRoot.querySelectorAll(".bk-panel-models-markup-HTML").length' @@ -52,7 +52,7 @@ def test_feed_view_latest(page): bbox = feed_el.bounding_box() assert bbox["height"] == 250 - expect(feed_el).to_have_class("bk-panel-models-feed-Feed scrollable-vertical") + expect(feed_el).to_have_class("bk-panel-models-feed-Feed scroll-vertical") # Assert scroll is not at 0 (view_latest) assert feed_el.evaluate('(el) => el.scrollTop') > 0