From cd0d92be7f5a8bb7d23a4e3b49665a8a60f2fca0 Mon Sep 17 00:00:00 2001 From: Carson Date: Mon, 29 Jul 2024 14:24:07 -0500 Subject: [PATCH 1/5] Close #1581: resolve_id() inside ui.Chat() --- shiny/ui/_chat.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shiny/ui/_chat.py b/shiny/ui/_chat.py index 61088e7d0..7996e7e5b 100644 --- a/shiny/ui/_chat.py +++ b/shiny/ui/_chat.py @@ -20,7 +20,7 @@ from .. import _utils, reactive from .._docstring import add_example -from .._namespaces import resolve_id +from .._namespaces import ResolvedId, resolve_id from ..session import require_active_session, session_context from ..types import MISSING, MISSING_TYPE, NotifyException from ..ui.css import CssUnit, as_css_unit @@ -148,8 +148,9 @@ def __init__( tokenizer: TokenEncoding | MISSING_TYPE | None = MISSING, ): + id = resolve_id(id) self.id = id - self.user_input_id = f"{id}_user_input" + self.user_input_id = ResolvedId(f"{id}_user_input") self._transform_user: TransformUserInputAsync | None = None self._transform_assistant: TransformAssistantResponseChunkAsync | None = None if isinstance(tokenizer, MISSING_TYPE): From 4ce4a8f00444c45c39555d01ba1f88258a74d59c Mon Sep 17 00:00:00 2001 From: Carson Date: Mon, 29 Jul 2024 14:26:43 -0500 Subject: [PATCH 2/5] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00bc4d622..7b6fe9c8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * An empty `input_date()` value no longer crashes Shiny. (#1528) +* `ui.Chat()` now works as expected inside Shiny modules. (#1582) + * Fixed bug where calling `.update_filter(None)` on a data frame renderer did not visually reset non-numeric column filters. (It did reset the column's filtering, just not the label). Now it resets filter's label. (#1557) * Require shinyswatch >= 0.7.0 and updated examples accordingly. (#1558) From d60b44c320e87fe5cf8fb747445654ab56a3e756 Mon Sep 17 00:00:00 2001 From: Carson Date: Mon, 29 Jul 2024 14:38:23 -0500 Subject: [PATCH 3/5] Add a playwright test --- .../shiny/components/chat/module/app.py | 30 +++++++++++++++++++ .../chat/module/test_chat_module.py | 18 +++++++++++ 2 files changed, 48 insertions(+) create mode 100644 tests/playwright/shiny/components/chat/module/app.py create mode 100644 tests/playwright/shiny/components/chat/module/test_chat_module.py diff --git a/tests/playwright/shiny/components/chat/module/app.py b/tests/playwright/shiny/components/chat/module/app.py new file mode 100644 index 000000000..eb086104e --- /dev/null +++ b/tests/playwright/shiny/components/chat/module/app.py @@ -0,0 +1,30 @@ +from shiny import App, module, ui + + +@module.ui +def chat_mod_ui(): + return ui.chat_ui(id="chat") + + +@module.server +def chat_mod_server(input, output, session): + chat = ui.Chat(id="chat") + + @chat.on_user_submit + async def _(): + user = chat.user_input() + await chat.append_message(f"You said: {user}") + + +app_ui = ui.page_fillable( + ui.panel_title("Hello Shiny Chat"), + chat_mod_ui("foo"), + fillable_mobile=True, +) + + +def server(input, output, session): + chat_mod_server("foo") + + +app = App(app_ui, server) diff --git a/tests/playwright/shiny/components/chat/module/test_chat_module.py b/tests/playwright/shiny/components/chat/module/test_chat_module.py new file mode 100644 index 000000000..6e74c123f --- /dev/null +++ b/tests/playwright/shiny/components/chat/module/test_chat_module.py @@ -0,0 +1,18 @@ +from playwright.sync_api import Page, expect +from utils.deploy_utils import skip_on_webkit + +from shiny.playwright import controller +from shiny.run import ShinyAppProc + + +@skip_on_webkit +def test_validate_chat_append_user_message(page: Page, local_app: ShinyAppProc) -> None: + page.goto(local_app.url) + + chat = controller.Chat(page, "foo-chat") + + # Verify starting state + expect(chat.loc).to_be_visible(timeout=30 * 1000) + chat.set_user_input("A user message") + chat.send_user_input() + chat.expect_latest_message("You said: A user message", timeout=30 * 1000) From 500a856efccddc9ff5180629e95149c13ad23eb9 Mon Sep 17 00:00:00 2001 From: Carson Date: Mon, 29 Jul 2024 14:45:08 -0500 Subject: [PATCH 4/5] Proper typing in test app --- tests/playwright/shiny/components/chat/module/app.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/playwright/shiny/components/chat/module/app.py b/tests/playwright/shiny/components/chat/module/app.py index eb086104e..79ebbd23c 100644 --- a/tests/playwright/shiny/components/chat/module/app.py +++ b/tests/playwright/shiny/components/chat/module/app.py @@ -1,13 +1,15 @@ -from shiny import App, module, ui +from htmltools import Tag + +from shiny import App, Inputs, Outputs, Session, module, ui @module.ui -def chat_mod_ui(): +def chat_mod_ui() -> Tag: return ui.chat_ui(id="chat") @module.server -def chat_mod_server(input, output, session): +def chat_mod_server(input: Inputs, output: Outputs, session: Session): chat = ui.Chat(id="chat") @chat.on_user_submit @@ -23,7 +25,7 @@ async def _(): ) -def server(input, output, session): +def server(input: Inputs, output: Outputs, session: Session): chat_mod_server("foo") From 8724968ea7af45aaf70d0faaf712e166886e1a09 Mon Sep 17 00:00:00 2001 From: Carson Date: Mon, 29 Jul 2024 15:25:56 -0500 Subject: [PATCH 5/5] Make sure id is a string --- shiny/ui/_chat.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shiny/ui/_chat.py b/shiny/ui/_chat.py index 7996e7e5b..bf0293b2f 100644 --- a/shiny/ui/_chat.py +++ b/shiny/ui/_chat.py @@ -147,10 +147,11 @@ def __init__( on_error: Literal["auto", "actual", "sanitize", "unhandled"] = "auto", tokenizer: TokenEncoding | MISSING_TYPE | None = MISSING, ): + if not isinstance(id, str): + raise TypeError("`id` must be a string.") - id = resolve_id(id) - self.id = id - self.user_input_id = ResolvedId(f"{id}_user_input") + self.id = resolve_id(id) + self.user_input_id = ResolvedId(f"{self.id}_user_input") self._transform_user: TransformUserInputAsync | None = None self._transform_assistant: TransformAssistantResponseChunkAsync | None = None if isinstance(tokenizer, MISSING_TYPE):