Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions examples/reference/chat/ChatFeed.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
"* **`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",
"* **`user_messages_styles`** (dict): A dictionary mapping the user name to styles to pass to their message objects. \n",
"* **`user_messages_stylesheets`** (dict): A dictionary mapping the user name to stylesheets to pass to their message objects.\n",
"\n",
"#### Methods\n",
"\n",
Expand Down Expand Up @@ -1315,6 +1317,64 @@
"chat_feed"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### Customisation per user: Per-user styles and stylesheets"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can now easily customise the appearance of messages for each user individually using the `user_messages_styles` and `user_messages_stylesheets` parameters.\n",
"`user_messages_styles` lets you provide a dictionary mapping user names to a dictionary of CSS style properties.\n",
"`user_messages_stylesheets` lets you provide a dictionary mapping user names to a list of CSS stylesheets that will be applied only to that user's messages.\n",
"This gives you fine-grained control over the look and feel of each user's messages, such as background colour, text colour, font, or even custom CSS rules.\n",
"\n",
"**Example: Per-user styles**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"chat_feed = pn.chat.ChatFeed()\n",
"chat_feed.user_messages_styles = {\n",
" 'Alice': {'background': 'lavender', 'color': 'navy'},\n",
" 'Bob': {'background': 'mintcream', 'color': 'darkgreen', 'fontWeight': 'bold'},\n",
"}\n",
"chat_feed.send('Hello from Alice!', user='Alice')\n",
"chat_feed.send('Hi from Bob!', user='Bob')\n",
"chat_feed"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Example: Per-user stylesheets**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"chat_feed = pn.chat.ChatFeed()\n",
"chat_feed.user_messages_stylesheets = {\n",
" 'Alice': [\".message { border: 2px solid purple; border-radius: 8px; }\"],\n",
" 'Bob': [\".message { box-shadow: 0 0 8px #0a0; }\"],\n",
"}\n",
"chat_feed.send('Alice with a border', user='Alice')\n",
"chat_feed.send('Bob with a shadow', user='Bob')\n",
"chat_feed"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
24 changes: 24 additions & 0 deletions panel/chat/feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,16 @@ class ChatFeed(ListPanel):
Whether to scroll to the latest object on init. If not
enabled the view will be on the first object.""")

user_messages_styles = param.Dict(default={}, doc="""
A dictionary mapping user names to a dict of CSS style properties.
E.g. {"User": {"background": "lightblue"}, "Assistant": {"background": "lightgrey"}}
""")

user_messages_stylesheets = param.Dict(default={}, doc="""
A dictionary mapping user names to a list of CSS stylesheets to apply to messages by that user only.
E.g. {"User": [".message.user"], "Assistant": [".message {background: lightgrey;}"]}
""")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably combine these under user_message_kwargs or similar.


_placeholder = param.ClassSelector(class_=ChatMessage, allow_refs=False, doc="""
The placeholder wrapped in a ChatMessage object;
primarily to prevent recursion error in _update_placeholder.""")
Expand Down Expand Up @@ -437,6 +447,20 @@ def _build_message(
message_params["width"] = int(self.width - 80)
message_params.update(input_message_params)

# Apply per-user styles
user_styles = self.user_messages_styles.get(user, {})
if user_styles:
message_params["styles"] = {**message_params.get("styles", {}), **user_styles}

# Ensure stylesheets key exists and is a list
if "stylesheets" not in message_params or not isinstance(message_params["stylesheets"], list):
message_params["stylesheets"] = []

# Apply per-user stylesheets
user_stylesheets = self.user_messages_stylesheets.get(user, [])
if user_stylesheets:
message_params["stylesheets"].extend(user_stylesheets)

if "show_edit_icon" not in message_params:
user = message_params.get("user", "")
message_params["show_edit_icon"] = (
Expand Down
36 changes: 36 additions & 0 deletions panel/tests/chat/test_feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,42 @@ async def test_send_with_user_avatar(self, chat_feed):
assert message.user == user
assert message.avatar == avatar

async def test_user_messages_styles(self, chat_feed):
chat_feed.user_messages_styles = {
"Bob": {"background": "red", "color": "white"},
"Alice": {"background": "blue", "color": "yellow"},
}
chat_feed.send("Hi", user="Bob")
chat_feed.send("Hello", user="Alice")
assert chat_feed.objects[0].styles["background"] == "red"
assert chat_feed.objects[0].styles["color"] == "white"
assert chat_feed.objects[1].styles["background"] == "blue"
assert chat_feed.objects[1].styles["color"] == "yellow"

async def test_user_messages_stylesheets(self, chat_feed):
chat_feed.user_messages_stylesheets = {
"Bob": ["bob.css"],
"Alice": ["alice.css", "common.css"],
}
chat_feed.send("Hi", user="Bob")
chat_feed.send("Hello", user="Alice")
assert chat_feed.objects[0].stylesheets == ["bob.css"]
assert chat_feed.objects[1].stylesheets == ["alice.css", "common.css"]

async def test_user_messages_styles_and_stylesheets_absent(self, chat_feed):
chat_feed.user_messages_styles = {"Bob": {"background": "red"}}
chat_feed.user_messages_stylesheets = {"Bob": ["bob.css"]}
chat_feed.send("Hi", user="Charlie")
assert "background" not in chat_feed.objects[0].styles
assert not chat_feed.objects[0].stylesheets

async def test_user_messages_styles_and_stylesheets_together(self, chat_feed):
chat_feed.user_messages_styles = {"Bob": {"background": "red"}}
chat_feed.user_messages_stylesheets = {"Bob": ["bob.css"]}
chat_feed.send("Hi", user="Bob")
assert chat_feed.objects[0].styles["background"] == "red"
assert chat_feed.objects[0].stylesheets == ["bob.css"]

async def test_send_dict(self, chat_feed):
message = chat_feed.send({"object": "Message", "user": "Bob", "avatar": "👨"})
assert len(chat_feed.objects) == 1
Expand Down
Loading