Skip to content

Commit

Permalink
clean user for avatar. remove callback_avatar
Browse files Browse the repository at this point in the history
  • Loading branch information
Your Name authored and Your Name committed Aug 21, 2023
1 parent 830d37e commit a142060
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 30 deletions.
8 changes: 4 additions & 4 deletions panel/tests/widgets/test_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ def test_width_entry_offset_80(self, chat_feed):
chat_feed.send("Message 1")
assert chat_feed.value[0].width == 420

@pytest.mark.parametrize("user", ["system", "System",])
@pytest.mark.parametrize("user", ["system", "System", " System", " system ", "system-"])
def test_default_avatars_default(self, chat_feed, user):
chat_feed.send("Message 1", user=user)

Expand Down Expand Up @@ -592,12 +592,11 @@ def callback(contents, user, instance):

chat_feed.callback = callback
chat_feed.callback_user = "System"
chat_feed.callback_avatar = "👨"
chat_feed.send("Message", respond=True)
time.sleep(0.2)
assert len(chat_feed.value) == 2
assert chat_feed.value[1].user == "System"
assert chat_feed.value[1].avatar == "👨"
assert chat_feed.value[1].avatar == ChatEntry.avatar_lookup("System")

def test_default_avatars(self, chat_feed):
ChatEntry.default_avatars["test1"]="1"
Expand All @@ -613,17 +612,18 @@ def chat_feed(self) -> ChatFeed:
return ChatFeed()

def test_user_avatar(self, chat_feed):
ChatEntry.default_avatars["bob"]="👨"
def echo(contents, user, instance):
return f"{user}: {contents}"

chat_feed.callback = echo
chat_feed.callback_user = "Bob"
chat_feed.callback_avatar = "👨"
chat_feed.send("Message", respond=True)
time.sleep(0.75)
assert len(chat_feed.value) == 2
assert chat_feed.value[1].user == "Bob"
assert chat_feed.value[1].avatar == "👨"
ChatEntry.default_avatars.pop("bob")

def test_return(self, chat_feed):
def echo(contents, user, instance):
Expand Down
63 changes: 37 additions & 26 deletions panel/widgets/chat.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import asyncio
import datetime
import re

from contextlib import ExitStack
from dataclasses import dataclass
from inspect import isasyncgen, isawaitable, isgenerator
from io import BytesIO
from tempfile import NamedTemporaryFile
from typing import (
Any, BinaryIO, ClassVar, List, Optional, Tuple, Type, Union,
Any, BinaryIO, ClassVar, Dict, List, Optional, Tuple, Type, Union,
)

import param
Expand Down Expand Up @@ -38,18 +39,21 @@
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_USER_AVATARS = {
"user": "😊", "User": "😊",
"assistant": "🤖", "Assistant": "🤖",
"system": "⚙️", "System": "⚙️",
"chatgpt": GPT_3_LOGO, "ChatGPT": GPT_3_LOGO,
"gpt3": GPT_3_LOGO, "GPT3": GPT_3_LOGO,
"gpt4": GPT_4_LOGO, "GPT4": GPT_4_LOGO,
"calculator": "🧮", "Calculator": "🧮",
"langchain": "🦜", "LangChain": "🦜",
"translator": "🌐", "Translator": "🌐",
"wolfram": WOLFRAM_LOGO, "Wolfram": WOLFRAM_LOGO,
"wolfram alpha": WOLFRAM_LOGO, "Wolfram Alpha": WOLFRAM_LOGO,
Avatar = str | BytesIO
AvatarDict = Dict[str, Avatar]

DEFAULT_USER_AVATARS: AvatarDict = {
"user": "😊",
"assistant": "🤖",
"system": "⚙️",
"chatgpt": GPT_3_LOGO,
"gpt3": GPT_3_LOGO,
"gpt4": GPT_4_LOGO,
"calculator": "🧮",
"langchain": "🦜",
"translator": "🌐",
"wolfram": WOLFRAM_LOGO,
"wolfram alpha": WOLFRAM_LOGO,
}

@dataclass
Expand Down Expand Up @@ -236,6 +240,13 @@ def _update_icons(self):
if reaction not in self._reactions:
self.value.remove(reaction)

def _to_alpha_numeric(user: str)->str:
return re.sub(r"\W+", "", user).lower()

def _avatar_lookup(user: str)->Avatar:
clean_user = _to_alpha_numeric(user)
return DEFAULT_USER_AVATARS.get(clean_user, "")


class ChatEntry(CompositeWidget):
"""
Expand Down Expand Up @@ -305,16 +316,21 @@ class ChatEntry(CompositeWidget):
user = param.Parameter(default="User", doc="""
Name of the user who sent the message.""")

avatar = param.ClassSelector(default="😊", class_=(str, BinaryIO), doc="""
avatar: Avatar = param.ClassSelector(default="😊", class_=(str, BinaryIO), doc="""
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.""")

default_avatars = param.Dict(default=DEFAULT_USER_AVATARS, readonly=True, doc="""
default_avatars: AvatarDict = param.Dict(default=DEFAULT_USER_AVATARS, readonly=True, doc="""
A default mapping of user names to their corresponding avatars
to use when the user is specified but the avatar is. You can modify, but not replace the
dictionary.""")

avatar_lookup = param.Callable(_avatar_lookup, doc="""
A function that can lookup an `avatar` from a user name. The function signature should be
`(user: str)->Avatar`.
""")

reactions = param.List(doc="""
Reactions to associate with the message.""")

Expand Down Expand Up @@ -353,8 +369,11 @@ def __init__(self, **params):
if isinstance(params["reaction_icons"], dict):
params["reaction_icons"] = ChatReactionIcons(
options=params["reaction_icons"], width=15, height=15)
if "avatar" not in params and "user" in params and params["user"] in self.default_avatars:
params["avatar"]=self.default_avatars[params["user"]]
if "avatar" not in params and "user" in params:
user = params["user"]
avatar = self.avatar_lookup(user)
if avatar:
params["avatar"]=avatar
super().__init__(**params)
self.reaction_icons.link(self, value="reactions", bidirectional=True)
self.param.trigger("reactions")
Expand Down Expand Up @@ -601,8 +620,6 @@ class ChatFeed(CompositeWidget):
and the component `instance`.
callback_user : str
The default user name to use for the entry provided by the callback.
callback_avatar : str | BinaryIO
The default avatar to use for the entry provided by the callback.
placeholder : Any
Placeholder to display while the callback is running.
If not set, defaults to a LoadingSpinner.
Expand Down Expand Up @@ -653,12 +670,6 @@ class ChatFeed(CompositeWidget):
callback_user = param.String(default="Assistant", doc="""
The default user name to use for the entry provided by the callback.""")

callback_avatar = param.ClassSelector(class_=(str, BinaryIO), doc="""
The default avatar to use for the entry provided by the callback.
Takes precedence over `ChatEntry.default_avatars` if set; else, if None,
defaults to the avatar set in `ChatEntry.default_avatars` if matching key exists.
Otherwise defaults to the first character of the `callback_user`.""")

placeholder = param.Parameter(doc="""
Placeholder to display while the callback is running.
If not set, defaults to a LoadingSpinner.""")
Expand Down Expand Up @@ -850,7 +861,7 @@ def _upsert_entry(self, value: Any, entry: Optional[ChatEntry] = None) -> ChatEn
the entry's value with the response.
"""
user = self.callback_user
avatar = self.callback_avatar
avatar = None
if isinstance(value, ChatEntry):
user = value.user
avatar = value.avatar
Expand Down

0 comments on commit a142060

Please sign in to comment.