Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add default help user logo and help_text to ChatFeed #6311

Merged
merged 1 commit into from
Feb 14, 2024
Merged
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
38 changes: 38 additions & 0 deletions examples/reference/chat/ChatFeed.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"* **`header`** (Any): The header of the chat feed; commonly used for the title. Can be a string, pane, or widget.\n",
"* **`callback_user`** (str): The default user name to use for the message provided by the callback.\n",
"* **`callback_avatar`** (str | bytes | BytesIO | pn.pane.ImageBase): The avatar to use for the user. Can be a single character text, an emoji, or anything supported by `pn.pane.Image`. If not set, uses the first character of the name.\n",
"* **`help_text`** (str): If provided, initializes a chat message in the chat log using the provided help text as the message object and `help` as the user. This is useful for providing instructions, and will not be included in the `serialize` method by default.\n",
"* **`placeholder_text`** (any): If placeholder is the default LoadingSpinner, the text to display next to it.\n",
"* **`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",
Expand Down Expand Up @@ -628,6 +629,43 @@
"chat_feed.serialize(filter_by=filter_by_reactions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`help_text` is an easy way to provide instructions to the users about what the feed does."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def say_hi(contents, user, instance):\n",
" return f\"Hi {user}!\"\n",
"\n",
"chat_feed = pn.chat.ChatFeed(help_text=\"This chat feed will respond by saying hi!\", callback=say_hi)\n",
"chat_feed.send(\"Hello there!\")\n",
"chat_feed"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By default, the `serialize` method will exclude the user `help` from its output. It can be changed by updating `exclude_users`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"chat_feed.serialize()"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
77 changes: 23 additions & 54 deletions panel/chat/feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,58 +35,6 @@
from bokeh.model import Model
from pyviz_comms import Comm

USER_LOGO = "🧑"
philippjfr marked this conversation as resolved.
Show resolved Hide resolved
ASSISTANT_LOGO = "🤖"
SYSTEM_LOGO = "⚙️"
ERROR_LOGO = "❌"
GPT_3_LOGO = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/04/ChatGPT_logo.svg/1024px-ChatGPT_logo.svg.png?20230318122128"
GPT_4_LOGO = "https://upload.wikimedia.org/wikipedia/commons/a/a4/GPT-4.png"
WOLFRAM_LOGO = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/WolframCorporateLogo.svg/1920px-WolframCorporateLogo.svg.png"

DEFAULT_AVATARS = {
# User
"client": USER_LOGO,
"customer": USER_LOGO,
"employee": USER_LOGO,
"human": USER_LOGO,
"person": USER_LOGO,
"user": USER_LOGO,
# Assistant
"agent": ASSISTANT_LOGO,
"ai": ASSISTANT_LOGO,
"assistant": ASSISTANT_LOGO,
"bot": ASSISTANT_LOGO,
"chatbot": ASSISTANT_LOGO,
"machine": ASSISTANT_LOGO,
"robot": ASSISTANT_LOGO,
# System
"system": SYSTEM_LOGO,
"exception": ERROR_LOGO,
"error": ERROR_LOGO,
# Human
"adult": "🧑",
"baby": "👶",
"boy": "👦",
"child": "🧒",
"girl": "👧",
"man": "👨",
"woman": "👩",
# Machine
"chatgpt": GPT_3_LOGO,
"gpt3": GPT_3_LOGO,
"gpt4": GPT_4_LOGO,
"dalle": GPT_4_LOGO,
"openai": GPT_4_LOGO,
"huggingface": "🤗",
"calculator": "🧮",
"langchain": "🦜",
"translator": "🌐",
"wolfram": WOLFRAM_LOGO,
"wolfram alpha": WOLFRAM_LOGO,
# Llama
"llama": "🦙",
"llama2": "🐪",
}

PLACEHOLDER_SVG = """
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-loader-3" width="40" height="40" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
Expand Down Expand Up @@ -181,6 +129,12 @@ class ChatFeed(ListPanel):
objects = param.List(default=[], doc="""
The list of child objects that make up the layout.""")

help_text = param.String(default="", doc="""
If provided, initializes a chat message in the chat log
using the provided help text as the message object and
`help` as the user. This is useful for providing instructions,
and will not be included in the `serialize` method by default.""")

placeholder_text = param.String(default="", doc="""
If placeholder is the default LoadingSpinner the text to display
next to it.""")
Expand Down Expand Up @@ -238,6 +192,9 @@ def __init__(self, *objects, **params):

super().__init__(*objects, **params)

if self.help_text:
self.objects = [ChatMessage(self.help_text, user="Help"), *self.objects]

# instantiate the card's column
linked_params = dict(
design=self.param.design,
Expand All @@ -249,7 +206,7 @@ def __init__(self, *objects, **params):
)
# we separate out chat log for the auto scroll feature
self._chat_log = Column(
*objects,
*self.objects,
auto_scroll_limit=self.auto_scroll_limit,
scroll_button_threshold=self.scroll_button_threshold,
css_classes=["chat-feed-log"],
Expand Down Expand Up @@ -776,6 +733,7 @@ def _serialize_for_transformers(

def serialize(
self,
exclude_users: List[str] | None = None,
filter_by: Callable | None = None,
format: Literal["transformers"] = "transformers",
custom_serializer: Callable | None = None,
Expand All @@ -789,6 +747,9 @@ def serialize(
format : str
The format to export the chat log as; currently only
supports "transformers".
exclude_users : list(str) | None
A list of user (case insensitive names) to exclude from serialization.
If not provided, defaults to ["help"]. This will be executed before `filter_by`.
filter_by : callable
A function to filter the chat log by.
The function must accept and return a list of ChatMessage objects.
Expand All @@ -814,7 +775,15 @@ def serialize(
-------
The chat log serialized in the specified format.
"""
messages = self._chat_log.objects.copy()
if exclude_users is None:
exclude_users = ["help"]
else:
exclude_users = [user.lower() for user in exclude_users]
messages = [
message for message in self._chat_log.objects
if message.user.lower() not in exclude_users
]

if filter_by is not None:
messages = filter_by(messages)

Expand Down
2 changes: 2 additions & 0 deletions panel/chat/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
ASSISTANT_LOGO = "🤖"
SYSTEM_LOGO = "⚙️"
ERROR_LOGO = "❌"
HELP_LOGO = "❓"
GPT_3_LOGO = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/04/ChatGPT_logo.svg/1024px-ChatGPT_logo.svg.png?20230318122128"
GPT_4_LOGO = "https://upload.wikimedia.org/wikipedia/commons/a/a4/GPT-4.png"
WOLFRAM_LOGO = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/WolframCorporateLogo.svg/1920px-WolframCorporateLogo.svg.png"
Expand All @@ -72,6 +73,7 @@
"system": SYSTEM_LOGO,
"exception": ERROR_LOGO,
"error": ERROR_LOGO,
"help": HELP_LOGO,
# Human
"adult": "🧑",
"baby": "👶",
Expand Down
34 changes: 34 additions & 0 deletions panel/tests/chat/test_feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ def chat_feed():
@pytest.mark.xdist_group("chat")
class TestChatFeed:

def test_init_with_help_text(self):
chat_feed = ChatFeed(help_text="Instructions")
message = chat_feed._chat_log[0]
assert message.object == "Instructions"
assert message.user == "Help"

def test_hide_header(self, chat_feed):
assert chat_feed.header is None

Expand Down Expand Up @@ -854,6 +860,34 @@ def filter_by_reactions(messages):
assert len(filtered) == 1
assert filtered[0]["content"] == "yes"

def test_serialize_exclude_users_default(self):
def say_hi(contents, user, instance):
return f"Hi {user}!"

chat_feed = ChatFeed(
help_text="This chat feed will respond by saying hi!",
callback=say_hi
)
chat_feed.send("Hello there!")
assert chat_feed.serialize() == [
{"role": "user", "content": "Hello there!"},
{"role": "assistant", "content": "Hi User!"}
]

def test_serialize_exclude_users_custom(self):
def say_hi(contents, user, instance):
return f"Hi {user}!"

chat_feed = ChatFeed(
help_text="This chat feed will respond by saying hi!",
callback=say_hi
)
chat_feed.send("Hello there!")
assert chat_feed.serialize(exclude_users=["assistant"]) == [
{"role": "assistant", "content": "This chat feed will respond by saying hi!"},
{"role": "user", "content": "Hello there!"},
]


@pytest.mark.xdist_group("chat")
class TestChatFeedSerializeBase:
Expand Down
Loading