Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
76abfed
feat: Configurable chat icon
gadenbuie Feb 12, 2025
ebefd4f
chore: npm run build
gadenbuie Feb 12, 2025
da305e9
refactor: Undo modularization of test app
gadenbuie Feb 12, 2025
c2c0c27
chore: rename `userIcon`
gadenbuie Feb 12, 2025
11bc384
chore: remove icon template private method
gadenbuie Feb 12, 2025
0b87414
chore: Add artificial delay to message response
gadenbuie Feb 12, 2025
33f94fb
chore: Image sizing edge cases in css
gadenbuie Feb 12, 2025
d6b43a1
chore: small refactor for readability
gadenbuie Feb 12, 2025
45e662e
tests: use PNG icon instead of svg for test
gadenbuie Feb 12, 2025
2b2f105
Merge branch 'main' into feat/chat-icon
cpsievert Feb 14, 2025
fbce8d2
Add the ability to change the icon when appending a message; rely les…
cpsievert Feb 14, 2025
2f14281
Merge branch 'main' into feat/chat-icon
cpsievert Feb 14, 2025
048a36b
refactor: Use shadow root and slots in ChatContainer and ChatMessage
gadenbuie Feb 14, 2025
da8803c
fix: Create `<shiny-content-stream>` in a content slot in `<shiny-cha…
gadenbuie Feb 14, 2025
277f681
tests(chat-icon): Fix tests
gadenbuie Feb 14, 2025
4158e7a
fix: don't need to clone the node
gadenbuie Feb 14, 2025
12d6de3
chore: rename `icon` in `append_message()`
gadenbuie Feb 14, 2025
b2d5d43
tests: Add tests for dynamic chat icon
gadenbuie Feb 14, 2025
ced4c5c
Merge 'origin/main' into branch feat/chat-icon
gadenbuie Feb 14, 2025
68bac09
chore: make check-fix
gadenbuie Feb 14, 2025
658a562
tests: Explicitly test returning to no icon
gadenbuie Feb 14, 2025
0eb0d36
chore: fix types
gadenbuie Feb 14, 2025
0d64647
refactor: Return to icon attribute method
gadenbuie Feb 20, 2025
4907d7f
Merge branch 'main' into feat/chat-icon
gadenbuie Feb 20, 2025
f252c2c
feat: Make images (i.e. avatars) full-bleed
gadenbuie Feb 20, 2025
ee51869
docs: remove stray space
gadenbuie Feb 20, 2025
a24fffc
chore: Support TagList for `icon_assistant`, too
gadenbuie Feb 20, 2025
23742f4
Merge 'origin/main' into branch feat/chat-icon
gadenbuie Feb 24, 2025
cebb4ef
Merge 'origin/main' into branch feat/chat-icon
gadenbuie Feb 24, 2025
c1445b2
docs: Add changelog item
gadenbuie Feb 24, 2025
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
11 changes: 10 additions & 1 deletion js/chat/chat.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ shiny-chat-container {
p:last-child {
margin-bottom: 0;
}

[data-icon="assistant"] {
display: none;
}
}

shiny-chat-messages {
Expand All @@ -30,10 +34,15 @@ shiny-chat-message {
.message-icon {
border-radius: 50%;
border: var(--shiny-chat-border);
height: 2rem;
width: 2rem;
display: grid;
place-items: center;

> * {
margin: 0.5rem;
height: 20px;
width: 20px;
margin: 0 !important;
}
}
/* Vertically center the 2nd column (message content) */
Expand Down
24 changes: 22 additions & 2 deletions js/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,16 @@ class ChatMessage extends LightElement {

render() {
const noContent = this.content.trim().length === 0;
const icon = noContent ? ICONS.dots_fade : ICONS.robot;
const defaultIcon = noContent ? ICONS.dots_fade : ICONS.robot;

// Check if there's an existing message-icon element
const existingIcon = this.querySelector(".message-icon");
const icon = existingIcon
? existingIcon
: html`<div class="message-icon">${unsafeHTML(defaultIcon)}</div>`;

return html`
<div class="message-icon">${unsafeHTML(icon)}</div>
${icon}
<shiny-markdown-stream
content=${this.content}
content-type=${this.content_type}
Expand Down Expand Up @@ -204,6 +210,10 @@ class ChatContainer extends LightElement {
return last ? (last as ChatMessage) : null;
}

private get iconTemplate(): HTMLDivElement | null {
return this.querySelector('div[data-icon="assistant"]');
}

render() {
return html``;
}
Expand Down Expand Up @@ -266,6 +276,16 @@ class ChatContainer extends LightElement {
const TAG_NAME =
message.role === "user" ? CHAT_USER_MESSAGE_TAG : CHAT_MESSAGE_TAG;
const msg = createElement(TAG_NAME, message);

if (message.role !== "user") {
const iconTemplate = this.iconTemplate;
if (iconTemplate) {
const icon = iconTemplate.cloneNode(true) as HTMLDivElement;
icon.className = "message-icon";
msg.appendChild(icon);
}
}

this.messages.appendChild(msg);

if (finalize) {
Expand Down
39 changes: 29 additions & 10 deletions shiny/ui/_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
)
from weakref import WeakValueDictionary

from htmltools import HTML, Tag, TagAttrValue, css
from htmltools import HTML, Tag, TagAttrValue, css, div

from .. import _utils, reactive
from .._deprecated import warn_deprecated
Expand Down Expand Up @@ -761,7 +761,6 @@ async def _transform_message(
chunk: ChunkOption = False,
chunk_content: str | None = None,
) -> TransformedMessage | None:

res = as_transformed_message(message)
key = res["transform_key"]

Expand Down Expand Up @@ -791,7 +790,6 @@ def _store_message(
chunk: ChunkOption = False,
index: int | None = None,
) -> None:

# Don't actually store chunks until the end
if chunk is True or chunk == "start":
return None
Expand All @@ -817,7 +815,6 @@ def _trim_messages(
token_limits: tuple[int, int],
format: MISSING_TYPE | ProviderMessageFormat,
) -> tuple[TransformedMessage, ...]:

n_total, n_reserve = token_limits
if n_total <= n_reserve:
raise ValueError(
Expand Down Expand Up @@ -878,7 +875,6 @@ def _trim_anthropic_messages(
self,
messages: tuple[TransformedMessage, ...],
) -> tuple[TransformedMessage, ...]:

if any(m["role"] == "system" for m in messages):
raise ValueError(
"Anthropic requires a system prompt to be specified in it's `.create()` method "
Expand Down Expand Up @@ -1006,7 +1002,6 @@ async def _send_custom_message(self, handler: str, obj: ClientMessage | None):

@add_example(ex_dir="../templates/chat/starters/hello")
class ChatExpress(Chat):

def ui(
self,
*,
Expand All @@ -1015,6 +1010,7 @@ def ui(
width: CssUnit = "min(680px, 100%)",
height: CssUnit = "auto",
fill: bool = True,
icon_assistant: HTML | Tag | None = None,
**kwargs: TagAttrValue,
) -> Tag:
"""
Expand All @@ -1036,6 +1032,10 @@ def ui(
fill
Whether the chat should vertically take available space inside a fillable
container.
icon_assistant
The icon to use for the assistant chat messages. Can be a HTML or a tag in
the form of :class:`~htmltools.HTML` or :class:`~htmltools.Tag`. If `None`,
a default robot icon is used.
kwargs
Additional attributes for the chat container element.
"""
Expand All @@ -1046,6 +1046,7 @@ def ui(
width=width,
height=height,
fill=fill,
icon_assistant=icon_assistant,
**kwargs,
)

Expand All @@ -1059,6 +1060,7 @@ def chat_ui(
width: CssUnit = "min(680px, 100%)",
height: CssUnit = "auto",
fill: bool = True,
icon_assistant: HTML | Tag | None = None,
**kwargs: TagAttrValue,
) -> Tag:
"""
Expand All @@ -1084,6 +1086,10 @@ def chat_ui(
The height of the chat container.
fill
Whether the chat should vertically take available space inside a fillable container.
icon_assistant
The icon to use for the assistant chat messages. Can be a HTML or a tag in
the form of :class:`~htmltools.HTML` or :class:`~htmltools.Tag`. If `None`,
a default robot icon is used.
kwargs
Additional attributes for the chat container element.
"""
Expand All @@ -1105,14 +1111,27 @@ def chat_ui(
raise ValueError("Each message must be a string or a dictionary.")

if msg["role"] == "user":
tag_name = "shiny-user-message"
message_tags.append(Tag("shiny-user-message", content=msg["content"]))
else:
tag_name = "shiny-chat-message"

message_tags.append(Tag(tag_name, content=msg["content"]))
message_tags.append(
Tag(
"shiny-chat-message",
(
None
if icon_assistant is None
else div(icon_assistant, class_="message-icon")
),
content=msg["content"],
),
)

res = Tag(
"shiny-chat-container",
(
None
if icon_assistant is None
else div(HTML(icon_assistant), data_icon="assistant")
),
Tag("shiny-chat-messages", *message_tags),
Tag(
"shiny-chat-input",
Expand Down
2 changes: 1 addition & 1 deletion shiny/www/py-shiny/chat/chat.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions shiny/www/py-shiny/chat/chat.css.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading