Skip to content

Commit d0354b9

Browse files
authored
Add messages parameter to chat_ui() and Chat.ui(); revert #1593 (#1736)
1 parent 023de32 commit d0354b9

File tree

7 files changed

+61
-45
lines changed

7 files changed

+61
-45
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2929

3030
* Added [narwhals](https://posit-dev.github.io/py-narwhals) support for `@render.table`. This allows for any eager data frame supported by narwhals to be returned from a `@render.table` output method. (#1570)
3131

32+
* `chat_ui()` and `Chat.ui()` gain a `messages` parameter for providing starting messages. (#1736)
33+
3234
### Other changes
3335

3436
* Incorporated `orjson` for faster data serialization in `@render.data_frame` outputs. (#1570)
@@ -100,7 +102,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
100102
* A few fixes for `ui.Chat()`, including:
101103
* A fix for use inside Shiny modules. (#1582)
102104
* `.messages(format="google")` now returns the correct role. (#1622)
103-
* `ui.Chat(messages)` are no longer dropped when dynamically rendered. (#1593)
104105
* `transform_assistant_response` can now return `None` and correctly handles change of content on the last chunk. (#1641)
105106

106107
* An empty `ui.input_date()` value no longer crashes Shiny. (#1528)

js/chat/chat.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -313,14 +313,7 @@ class ChatContainer extends LightElement {
313313
private resizeObserver!: ResizeObserver;
314314

315315
render(): ReturnType<LitElement["render"]> {
316-
const input_id = this.id + "_user_input";
317-
return html`
318-
<shiny-chat-messages></shiny-chat-messages>
319-
<shiny-chat-input
320-
id=${input_id}
321-
placeholder=${this.placeholder}
322-
></shiny-chat-input>
323-
`;
316+
return html``;
324317
}
325318

326319
firstUpdated(): void {

shiny/ui/_chat.py

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
Callable,
99
Iterable,
1010
Literal,
11+
Optional,
1112
Sequence,
1213
Tuple,
1314
Union,
@@ -193,7 +194,8 @@ def __init__(
193194
reactive.Value(None)
194195
)
195196

196-
# Initialize the chat with the provided messages
197+
# TODO: deprecate messages once we start promoting managing LLM message
198+
# state through other means
197199
@reactive.effect
198200
async def _init_chat():
199201
for msg in messages:
@@ -233,6 +235,7 @@ async def _on_user_input():
233235
def ui(
234236
self,
235237
*,
238+
messages: Optional[Sequence[str | ChatMessage]] = None,
236239
placeholder: str = "Enter a message...",
237240
width: CssUnit = "min(680px, 100%)",
238241
height: CssUnit = "auto",
@@ -247,6 +250,11 @@ def ui(
247250
248251
Parameters
249252
----------
253+
messages
254+
A sequence of messages to display in the chat. Each message can be either a
255+
string or a dictionary with `content` and `role` keys. The `content` key
256+
should contain the message text, and the `role` key can be "assistant" or
257+
"user".
250258
placeholder
251259
Placeholder text for the chat input.
252260
width
@@ -261,6 +269,7 @@ def ui(
261269
"""
262270
return chat_ui(
263271
id=self.id,
272+
messages=messages,
264273
placeholder=placeholder,
265274
width=width,
266275
height=height,
@@ -572,7 +581,7 @@ async def append_message_stream(self, message: Iterable[Any] | AsyncIterable[Any
572581
async def _stream_task():
573582
await self._append_message_stream(message)
574583

575-
self._session.on_flushed(_stream_task, once=True)
584+
_stream_task()
576585

577586
# Since the task runs in the background (outside/beyond the current context,
578587
# if any), we need to manually raise any exceptions that occur
@@ -643,9 +652,7 @@ async def _send_append_message(
643652

644653
# print(msg)
645654

646-
# When streaming (i.e., chunk is truthy), we can send messages immediately
647-
# since we already waited for the flush in order to start the stream
648-
await self._send_custom_message(msg_type, msg, on_flushed=chunk is False)
655+
await self._send_custom_message(msg_type, msg)
649656
# TODO: Joe said it's a good idea to yield here, but I'm not sure why?
650657
# await asyncio.sleep(0)
651658

@@ -1012,29 +1019,22 @@ def destroy(self):
10121019
async def _remove_loading_message(self):
10131020
await self._send_custom_message("shiny-chat-remove-loading-message", None)
10141021

1015-
async def _send_custom_message(
1016-
self, handler: str, obj: ClientMessage | None, on_flushed: bool = True
1017-
):
1018-
async def _do_send():
1019-
await self._session.send_custom_message(
1020-
"shinyChatMessage",
1021-
{
1022-
"id": self.id,
1023-
"handler": handler,
1024-
"obj": obj,
1025-
},
1026-
)
1027-
1028-
if on_flushed:
1029-
self._session.on_flushed(_do_send, once=True)
1030-
else:
1031-
await _do_send()
1022+
async def _send_custom_message(self, handler: str, obj: ClientMessage | None):
1023+
await self._session.send_custom_message(
1024+
"shinyChatMessage",
1025+
{
1026+
"id": self.id,
1027+
"handler": handler,
1028+
"obj": obj,
1029+
},
1030+
)
10321031

10331032

10341033
@add_example(ex_dir="../api-examples/chat")
10351034
def chat_ui(
10361035
id: str,
10371036
*,
1037+
messages: Optional[Sequence[str | ChatMessage]] = None,
10381038
placeholder: str = "Enter a message...",
10391039
width: CssUnit = "min(680px, 100%)",
10401040
height: CssUnit = "auto",
@@ -1052,6 +1052,10 @@ def chat_ui(
10521052
----------
10531053
id
10541054
A unique identifier for the chat UI.
1055+
messages
1056+
A sequence of messages to display in the chat. Each message can be either a string
1057+
or a dictionary with a `content` and `role` key. The `content` key should contain
1058+
the message text, and the `role` key can be "assistant" or "user".
10551059
placeholder
10561060
Placeholder text for the chat input.
10571061
width
@@ -1066,8 +1070,32 @@ def chat_ui(
10661070

10671071
id = resolve_id(id)
10681072

1073+
message_tags: list[Tag] = []
1074+
if messages is None:
1075+
messages = []
1076+
for msg in messages:
1077+
if isinstance(msg, str):
1078+
msg = {"content": msg, "role": "assistant"}
1079+
elif isinstance(msg, dict):
1080+
if "content" not in msg:
1081+
raise ValueError("Each message must have a 'content' key.")
1082+
if "role" not in msg:
1083+
raise ValueError("Each message must have a 'role' key.")
1084+
else:
1085+
raise ValueError("Each message must be a string or a dictionary.")
1086+
1087+
message_tags.append(
1088+
Tag("shiny-chat-message", content=msg["content"], role=msg["role"])
1089+
)
1090+
10691091
res = Tag(
10701092
"shiny-chat-container",
1093+
Tag("shiny-chat-messages", *message_tags),
1094+
Tag(
1095+
"shiny-chat-input",
1096+
id=f"{id}_user_input",
1097+
placeholder=placeholder,
1098+
),
10711099
chat_deps(),
10721100
{
10731101
"style": css(

shiny/www/py-shiny/chat/chat.js

Lines changed: 1 addition & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

shiny/www/py-shiny/chat/chat.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from shiny.express import render, ui
22

3-
chat = ui.Chat(id="chat", messages=["A starting message"])
3+
chat = ui.Chat(id="chat")
44

55

66
@render.ui
77
def chat_output():
8-
return chat.ui()
8+
return chat.ui(messages=["A starting message"])

tests/playwright/shiny/components/chat/stream/test_chat_stream.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ def test_validate_chat(page: Page, local_app: ShinyAppProc) -> None:
2121
expect(chat.loc_input_button).to_be_disabled()
2222

2323
messages = [
24-
"SECOND SECOND SECOND",
25-
"FOURTH FOURTH FOURTH",
2624
"FIRST FIRST FIRST",
25+
"SECOND SECOND SECOND",
2726
"THIRD THIRD THIRD",
27+
"FOURTH FOURTH FOURTH",
2828
"FIFTH FIFTH FIFTH",
2929
]
3030
# Allow for any whitespace between messages

0 commit comments

Comments
 (0)