diff --git a/js/chat/chat.ts b/js/chat/chat.ts index 1265a0382..059b6bdac 100644 --- a/js/chat/chat.ts +++ b/js/chat/chat.ts @@ -198,6 +198,10 @@ class ChatContainer extends LightElement { this.#onAppendChunk ); this.addEventListener("shiny-chat-clear-messages", this.#onClear); + this.addEventListener( + "shiny-chat-remove-placeholder", + this.#onRemovePlaceholder + ); } disconnectedCallback(): void { @@ -209,6 +213,10 @@ class ChatContainer extends LightElement { this.#onAppendChunk ); this.removeEventListener("shiny-chat-clear-messages", this.#onClear); + this.removeEventListener( + "shiny-chat-remove-placeholder", + this.#onRemovePlaceholder + ); } #onInputSent(event: CustomEvent): void { @@ -287,6 +295,11 @@ class ChatContainer extends LightElement { this.messages.innerHTML = ""; } + #onRemovePlaceholder(): void { + this.#removePlaceholder(); + this.#enableInput(); + } + #enableInput(): void { this.input.disabled = false; } diff --git a/shiny/reactive/_reactives.py b/shiny/reactive/_reactives.py index c8bf7e920..c83fd49a9 100644 --- a/shiny/reactive/_reactives.py +++ b/shiny/reactive/_reactives.py @@ -34,7 +34,13 @@ from .._docstring import add_example from .._utils import is_async_callable, run_coro_sync from .._validation import req -from ..types import MISSING, MISSING_TYPE, ActionButtonValue, SilentException +from ..types import ( + MISSING, + MISSING_TYPE, + ActionButtonValue, + NotifyException, + SilentException, +) from ._core import Context, Dependents, ReactiveWarning, isolate if TYPE_CHECKING: @@ -583,6 +589,19 @@ async def _run(self) -> None: except SilentException: # It's OK for SilentException to cause an Effect to stop running pass + except NotifyException as e: + traceback.print_exc() + + if self._session: + from .._app import SANITIZE_ERROR_MSG + from ..ui import notification_show + + msg = "Error in Effect: " + str(e) + if e.sanitize: + msg = SANITIZE_ERROR_MSG + notification_show(msg, type="error", duration=5000) + if e.close: + await self._session._unhandled_error(e) except Exception as e: traceback.print_exc() diff --git a/shiny/types.py b/shiny/types.py index e3f60adf4..236dfa792 100644 --- a/shiny/types.py +++ b/shiny/types.py @@ -160,6 +160,30 @@ class SilentOperationInProgressException(SilentException): pass +class NotifyException(Exception): + """ + This exception can be raised in a (non-output) reactive effect + to display a message to the user. + + Parameters + ---------- + message + The message to display to the user. + sanitize + If ``True``, the message is sanitized to prevent leaking sensitive information. + close + If ``True``, the session is closed after the message is displayed. + """ + + sanitize: bool + close: bool + + def __init__(self, message: str, sanitize: bool = True, close: bool = False): + super().__init__(message) + self.sanitize = sanitize + self.close = close + + class ActionButtonValue(int): pass diff --git a/shiny/ui/_chat.py b/shiny/ui/_chat.py index 90eca8cbc..ff9602ebf 100644 --- a/shiny/ui/_chat.py +++ b/shiny/ui/_chat.py @@ -15,10 +15,10 @@ from htmltools import Tag -from .. import _utils, reactive, ui -from .._app import SANITIZE_ERROR_MSG +from .. import _utils, reactive from .._namespaces import resolve_id from ..session import Session, require_active_session, session_context +from ..types import NotifyException from ._chat_types import ( ChatMessage, ChatMessageChunk, @@ -79,7 +79,7 @@ def __call__( width: str = "min(680px, 100%)", fill: bool = True, ) -> Tag: - if not self._is_express(): + if not _express_is_active(): raise RuntimeError( "The `__call__()` method of the `ui.Chat` class only works in a Shiny Express context." " Use `ui.chat_ui()` instead in Shiny Core to locate the chat UI." @@ -92,47 +92,40 @@ def __call__( fill=fill, ) - # TODO: maybe this should be a utility function in express? - @staticmethod - def _is_express() -> bool: - from ..express._run import get_top_level_recall_context_manager - - try: - get_top_level_recall_context_manager() - return True - except RuntimeError: - return False - def on_user_submit( self, - func: SubmitFunction | SubmitFunctionAsync, - errors: Literal["sanitize", "show", "none"] = "sanitize", - ) -> reactive.Effect_: + fn: SubmitFunction | SubmitFunctionAsync | None = None, + *, + error: Literal["sanitize", "actual", "unhandled"] = "sanitize", + ) -> ( + reactive.Effect_ + | Callable[[SubmitFunction | SubmitFunctionAsync], reactive.Effect_] + ): """ Register a callback to run when the user submits a message. """ - afunc = _utils.wrap_async(func) + def create_effect(fn: SubmitFunction | SubmitFunctionAsync): + afunc = _utils.wrap_async(fn) - @reactive.effect - @reactive.event(self.user_input) - async def wrapper(): - try: - await afunc() - # TODO: does this handle req() correctly? - except Exception as e: - if errors == "sanitize": - ui.notification_show( - SANITIZE_ERROR_MSG, type="error", duration=5000 - ) - elif errors == "show": - ui.notification_show( - ui.markdown(str(e)), type="error", duration=5000 - ) - - raise e - - return wrapper + @reactive.effect + @reactive.event(self.user_input) + async def _(): + if error == "unhandled": + await afunc() + else: + try: + await afunc() + except Exception as e: + await self._remove_placeholder() + raise NotifyException(str(e), sanitize=error == "sanitize") + + return _ + + if fn is None: + return create_effect + else: + return create_effect(fn) def user_input(self) -> str: """ @@ -235,6 +228,9 @@ async def clear_messages(self): await self._send_custom_message("shiny-chat-clear-messages", None) + async def _remove_placeholder(self): + await self._send_custom_message("shiny-chat-remove-placeholder", None) + async def _send_custom_message( self, handler: str, obj: ChatMessage | ChatMessageChunk | None ): @@ -294,3 +290,13 @@ def chat_ui( res = as_fillable_container(as_fill_item(res)) return res + + +def _express_is_active() -> bool: + from ..express._run import get_top_level_recall_context_manager + + try: + get_top_level_recall_context_manager() + return True + except RuntimeError: + return False diff --git a/shiny/www/shared/py-shiny/chat/chat.css.map b/shiny/www/shared/py-shiny/chat/chat.css.map index cb11bd67c..325829f3f 100644 --- a/shiny/www/shared/py-shiny/chat/chat.css.map +++ b/shiny/www/shared/py-shiny/chat/chat.css.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../../../../js/chat/chat.scss"], - "sourcesContent": ["/************************************************************\n From ../node_modules/highlight.js/styles/atom-one-light.css\n with minor adjustments\n************************************************************/\n/************************************************************\n From ../node_modules/highlight.js/styles/atom-one-dark.css\n with minor adjustments\n************************************************************/\nshiny-chat-container {\n display: flex;\n flex-direction: column;\n margin: 0 auto;\n gap: 1rem;\n}\nshiny-chat-container p:last-child {\n margin-bottom: 0;\n}\nshiny-chat-container shiny-chat-messages {\n display: flex;\n flex-direction: column;\n overflow: auto;\n gap: 1rem;\n}\nshiny-chat-container shiny-chat-messages shiny-chat-message {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n gap: 1rem;\n /* Vertically center the message content */\n /* Align the user message to the right */\n /* Make badges more circular */\n}\nshiny-chat-container shiny-chat-messages shiny-chat-message .message-content {\n height: fit-content;\n align-self: center;\n}\nshiny-chat-container shiny-chat-messages shiny-chat-message .message-user {\n grid-column: 2;\n justify-self: end;\n padding: 0.5rem 0.75rem;\n border-radius: 26px;\n background-color: var(--bs-light-bg-subtle, #fcfcfc);\n color: var(--bs-light-text-emphasis, #48505f);\n border: var(--bs-border-width, 1px) solid var(--bs-border-color, #e9ecef);\n}\nshiny-chat-container shiny-chat-messages shiny-chat-message .badge {\n --bs-badge-padding-y: 0.65em;\n height: fit-content;\n}\nshiny-chat-container shiny-chat-input {\n margin-top: auto;\n position: sticky;\n bottom: 0;\n padding: 0.25rem;\n background-color: var(--bs-body-bg, white);\n}\nshiny-chat-container shiny-chat-input .btn {\n --bs-btn-padding-x: 0.75em;\n --bs-btn-padding-y: 0.25em;\n}\nshiny-chat-container shiny-chat-input textarea {\n resize: none;\n}\n\n/*\n Disable the page-level pulse when the chat input is disabled\n (i.e., when a response is being generated and brought into the chat)\n*/\n.shiny-busy:has(shiny-chat-input[disabled])::after {\n display: none;\n}\n\n/* Code highlighting */\npre code.hljs {\n display: block;\n overflow-x: auto;\n padding: 1em;\n}\n\ncode.hljs {\n padding: 3px 5px;\n}\n\n/*\n\nAtom One Light by Daniel Gamage\nOriginal One Light Syntax theme from https://github.com/atom/one-light-syntax\n\nbase: #fafafa\nmono-1: #383a42\nmono-2: #686b77\nmono-3: #a0a1a7\nhue-1: #0184bb\nhue-2: #4078f2\nhue-3: #a626a4\nhue-4: #50a14f\nhue-5: #e45649\nhue-5-2: #c91243\nhue-6: #986801\nhue-6-2: #c18401\n\n*/\npre:has(> code.hljs) {\n color: #383a42;\n background: #fafafa;\n}\n\n.hljs-comment,\n.hljs-quote {\n color: #a0a1a7;\n font-style: italic;\n}\n\n.hljs-doctag,\n.hljs-keyword,\n.hljs-formula {\n color: #a626a4;\n}\n\n.hljs-section,\n.hljs-name,\n.hljs-selector-tag,\n.hljs-deletion,\n.hljs-subst {\n color: #e45649;\n}\n\n.hljs-literal {\n color: #0184bb;\n}\n\n.hljs-string,\n.hljs-regexp,\n.hljs-addition,\n.hljs-attribute,\n.hljs-meta .hljs-string {\n color: #50a14f;\n}\n\n.hljs-attr,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-type,\n.hljs-selector-class,\n.hljs-selector-attr,\n.hljs-selector-pseudo,\n.hljs-number {\n color: #986801;\n}\n\n.hljs-symbol,\n.hljs-bullet,\n.hljs-link,\n.hljs-meta,\n.hljs-selector-id,\n.hljs-title {\n color: #4078f2;\n}\n\n.hljs-built_in,\n.hljs-title.class_,\n.hljs-class .hljs-title {\n color: #c18401;\n}\n\n.hljs-emphasis {\n font-style: italic;\n}\n\n.hljs-strong {\n font-weight: bold;\n}\n\n.hljs-link {\n text-decoration: underline;\n}\n\n[data-bs-theme=dark] {\n /*\n\n Atom One Dark by Daniel Gamage\n Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax\n\n base: #282c34\n mono-1: #abb2bf\n mono-2: #818896\n mono-3: #5c6370\n hue-1: #56b6c2\n hue-2: #61aeee\n hue-3: #c678dd\n hue-4: #98c379\n hue-5: #e06c75\n hue-5-2: #be5046\n hue-6: #d19a66\n hue-6-2: #e6c07b\n\n */\n}\n[data-bs-theme=dark] pre code.hljs {\n display: block;\n overflow-x: auto;\n padding: 1em;\n}\n[data-bs-theme=dark] code.hljs {\n padding: 3px 5px;\n}\n[data-bs-theme=dark] pre:has(> code.hljs) {\n color: #abb2bf;\n background: #282c34;\n}\n[data-bs-theme=dark] .hljs-comment,\n[data-bs-theme=dark] .hljs-quote {\n color: #5c6370;\n font-style: italic;\n}\n[data-bs-theme=dark] .hljs-doctag,\n[data-bs-theme=dark] .hljs-keyword,\n[data-bs-theme=dark] .hljs-formula {\n color: #c678dd;\n}\n[data-bs-theme=dark] .hljs-section,\n[data-bs-theme=dark] .hljs-name,\n[data-bs-theme=dark] .hljs-selector-tag,\n[data-bs-theme=dark] .hljs-deletion,\n[data-bs-theme=dark] .hljs-subst {\n color: #e06c75;\n}\n[data-bs-theme=dark] .hljs-literal {\n color: #56b6c2;\n}\n[data-bs-theme=dark] .hljs-string,\n[data-bs-theme=dark] .hljs-regexp,\n[data-bs-theme=dark] .hljs-addition,\n[data-bs-theme=dark] .hljs-attribute,\n[data-bs-theme=dark] .hljs-meta .hljs-string {\n color: #98c379;\n}\n[data-bs-theme=dark] .hljs-attr,\n[data-bs-theme=dark] .hljs-variable,\n[data-bs-theme=dark] .hljs-template-variable,\n[data-bs-theme=dark] .hljs-type,\n[data-bs-theme=dark] .hljs-selector-class,\n[data-bs-theme=dark] .hljs-selector-attr,\n[data-bs-theme=dark] .hljs-selector-pseudo,\n[data-bs-theme=dark] .hljs-number {\n color: #d19a66;\n}\n[data-bs-theme=dark] .hljs-symbol,\n[data-bs-theme=dark] .hljs-bullet,\n[data-bs-theme=dark] .hljs-link,\n[data-bs-theme=dark] .hljs-meta,\n[data-bs-theme=dark] .hljs-selector-id,\n[data-bs-theme=dark] .hljs-title {\n color: #61aeee;\n}\n[data-bs-theme=dark] .hljs-built_in,\n[data-bs-theme=dark] .hljs-title.class_,\n[data-bs-theme=dark] .hljs-class .hljs-title {\n color: #e6c07b;\n}\n[data-bs-theme=dark] .hljs-emphasis {\n font-style: italic;\n}\n[data-bs-theme=dark] .hljs-strong {\n font-weight: bold;\n}\n[data-bs-theme=dark] .hljs-link {\n text-decoration: underline;\n}\n\n/*\n Styling for the code-copy button (inspired by Quarto's code-copy feature)\n*/\npre:has(.code-copy-button) {\n position: relative;\n}\n\n.code-copy-button {\n position: absolute;\n top: 0;\n right: 0;\n border: 0;\n margin-top: 5px;\n margin-right: 5px;\n z-index: 3;\n background-color: transparent;\n}\n.code-copy-button > .bi {\n display: flex;\n gap: 0.25em;\n}\n.code-copy-button > .bi::after {\n content: \"\";\n display: block;\n height: 1rem;\n width: 1rem;\n mask-image: url('data:image/svg+xml,');\n background-color: var(--bs-body-color, #222);\n}\n\n.code-copy-button-checked > .bi::before {\n content: \"Copied!\";\n font-size: 0.75em;\n vertical-align: 0.25em;\n}\n.code-copy-button-checked > .bi::after {\n mask-image: url('data:image/svg+xml,');\n background-color: var(--bs-success, #198754);\n}"], + "sourcesContent": ["/************************************************************\n From ../node_modules/highlight.js/styles/atom-one-light.css\n with minor adjustments\n************************************************************/\n/************************************************************\n From ../node_modules/highlight.js/styles/atom-one-dark.css\n with minor adjustments\n************************************************************/\nshiny-chat-container {\n display: flex;\n flex-direction: column;\n margin: 0 auto;\n gap: 1rem;\n}\nshiny-chat-container p:last-child {\n margin-bottom: 0;\n}\nshiny-chat-container shiny-chat-messages {\n display: flex;\n flex-direction: column;\n overflow: auto;\n gap: 1rem;\n}\nshiny-chat-container shiny-chat-messages shiny-chat-message {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n gap: 1rem;\n /* Vertically center the message content */\n /* Align the user message to the right */\n /* Make badges more circular */\n}\nshiny-chat-container shiny-chat-messages shiny-chat-message .message-content {\n height: fit-content;\n align-self: center;\n}\nshiny-chat-container shiny-chat-messages shiny-chat-message .message-user {\n grid-column: 2;\n justify-self: end;\n padding: 0.5rem 0.75rem;\n border-radius: 26px;\n background-color: var(--bs-light-bg-subtle, #fcfcfc);\n color: var(--bs-light-text-emphasis, #48505f);\n border: var(--bs-border-width, 1px) solid var(--bs-border-color, #e9ecef);\n}\nshiny-chat-container shiny-chat-messages shiny-chat-message .badge {\n --bs-badge-padding-y: 0.65em;\n height: fit-content;\n}\nshiny-chat-container shiny-chat-input {\n margin-top: auto;\n position: sticky;\n bottom: 0;\n padding: 0.25rem;\n background-color: var(--bs-body-bg, white);\n}\nshiny-chat-container shiny-chat-input .btn {\n --bs-btn-padding-x: 0.75em;\n --bs-btn-padding-y: 0.25em;\n}\nshiny-chat-container shiny-chat-input textarea {\n resize: none;\n}\n\n/*\n Disable the page-level pulse when the chat input is disabled\n (i.e., when a response is being generated and brought into the chat)\n*/\n.shiny-busy:has(shiny-chat-input[disabled])::after {\n display: none;\n}\n\n/* Code highlighting (for both light and dark mode) */\npre code.hljs {\n display: block;\n overflow-x: auto;\n padding: 1em;\n}\n\ncode.hljs {\n padding: 3px 5px;\n}\n\n/*\n\nAtom One Light by Daniel Gamage\nOriginal One Light Syntax theme from https://github.com/atom/one-light-syntax\n\nbase: #fafafa\nmono-1: #383a42\nmono-2: #686b77\nmono-3: #a0a1a7\nhue-1: #0184bb\nhue-2: #4078f2\nhue-3: #a626a4\nhue-4: #50a14f\nhue-5: #e45649\nhue-5-2: #c91243\nhue-6: #986801\nhue-6-2: #c18401\n\n*/\npre:has(> code.hljs) {\n color: #383a42;\n background: #fafafa;\n}\n\n.hljs-comment,\n.hljs-quote {\n color: #a0a1a7;\n font-style: italic;\n}\n\n.hljs-doctag,\n.hljs-keyword,\n.hljs-formula {\n color: #a626a4;\n}\n\n.hljs-section,\n.hljs-name,\n.hljs-selector-tag,\n.hljs-deletion,\n.hljs-subst {\n color: #e45649;\n}\n\n.hljs-literal {\n color: #0184bb;\n}\n\n.hljs-string,\n.hljs-regexp,\n.hljs-addition,\n.hljs-attribute,\n.hljs-meta .hljs-string {\n color: #50a14f;\n}\n\n.hljs-attr,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-type,\n.hljs-selector-class,\n.hljs-selector-attr,\n.hljs-selector-pseudo,\n.hljs-number {\n color: #986801;\n}\n\n.hljs-symbol,\n.hljs-bullet,\n.hljs-link,\n.hljs-meta,\n.hljs-selector-id,\n.hljs-title {\n color: #4078f2;\n}\n\n.hljs-built_in,\n.hljs-title.class_,\n.hljs-class .hljs-title {\n color: #c18401;\n}\n\n.hljs-emphasis {\n font-style: italic;\n}\n\n.hljs-strong {\n font-weight: bold;\n}\n\n.hljs-link {\n text-decoration: underline;\n}\n\n[data-bs-theme=dark] {\n /*\n\n Atom One Dark by Daniel Gamage\n Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax\n\n base: #282c34\n mono-1: #abb2bf\n mono-2: #818896\n mono-3: #5c6370\n hue-1: #56b6c2\n hue-2: #61aeee\n hue-3: #c678dd\n hue-4: #98c379\n hue-5: #e06c75\n hue-5-2: #be5046\n hue-6: #d19a66\n hue-6-2: #e6c07b\n\n */\n}\n[data-bs-theme=dark] pre code.hljs {\n display: block;\n overflow-x: auto;\n padding: 1em;\n}\n[data-bs-theme=dark] code.hljs {\n padding: 3px 5px;\n}\n[data-bs-theme=dark] pre:has(> code.hljs) {\n color: #abb2bf;\n background: #282c34;\n}\n[data-bs-theme=dark] .hljs-comment,\n[data-bs-theme=dark] .hljs-quote {\n color: #5c6370;\n font-style: italic;\n}\n[data-bs-theme=dark] .hljs-doctag,\n[data-bs-theme=dark] .hljs-keyword,\n[data-bs-theme=dark] .hljs-formula {\n color: #c678dd;\n}\n[data-bs-theme=dark] .hljs-section,\n[data-bs-theme=dark] .hljs-name,\n[data-bs-theme=dark] .hljs-selector-tag,\n[data-bs-theme=dark] .hljs-deletion,\n[data-bs-theme=dark] .hljs-subst {\n color: #e06c75;\n}\n[data-bs-theme=dark] .hljs-literal {\n color: #56b6c2;\n}\n[data-bs-theme=dark] .hljs-string,\n[data-bs-theme=dark] .hljs-regexp,\n[data-bs-theme=dark] .hljs-addition,\n[data-bs-theme=dark] .hljs-attribute,\n[data-bs-theme=dark] .hljs-meta .hljs-string {\n color: #98c379;\n}\n[data-bs-theme=dark] .hljs-attr,\n[data-bs-theme=dark] .hljs-variable,\n[data-bs-theme=dark] .hljs-template-variable,\n[data-bs-theme=dark] .hljs-type,\n[data-bs-theme=dark] .hljs-selector-class,\n[data-bs-theme=dark] .hljs-selector-attr,\n[data-bs-theme=dark] .hljs-selector-pseudo,\n[data-bs-theme=dark] .hljs-number {\n color: #d19a66;\n}\n[data-bs-theme=dark] .hljs-symbol,\n[data-bs-theme=dark] .hljs-bullet,\n[data-bs-theme=dark] .hljs-link,\n[data-bs-theme=dark] .hljs-meta,\n[data-bs-theme=dark] .hljs-selector-id,\n[data-bs-theme=dark] .hljs-title {\n color: #61aeee;\n}\n[data-bs-theme=dark] .hljs-built_in,\n[data-bs-theme=dark] .hljs-title.class_,\n[data-bs-theme=dark] .hljs-class .hljs-title {\n color: #e6c07b;\n}\n[data-bs-theme=dark] .hljs-emphasis {\n font-style: italic;\n}\n[data-bs-theme=dark] .hljs-strong {\n font-weight: bold;\n}\n[data-bs-theme=dark] .hljs-link {\n text-decoration: underline;\n}\n\n/*\n Styling for the code-copy button (inspired by Quarto's code-copy feature)\n*/\npre:has(.code-copy-button) {\n position: relative;\n}\n\n.code-copy-button {\n position: absolute;\n top: 0;\n right: 0;\n border: 0;\n margin-top: 5px;\n margin-right: 5px;\n z-index: 3;\n background-color: transparent;\n}\n.code-copy-button > .bi {\n display: flex;\n gap: 0.25em;\n}\n.code-copy-button > .bi::after {\n content: \"\";\n display: block;\n height: 1rem;\n width: 1rem;\n mask-image: url('data:image/svg+xml,');\n background-color: var(--bs-body-color, #222);\n}\n\n.code-copy-button-checked > .bi::before {\n content: \"Copied!\";\n font-size: 0.75em;\n vertical-align: 0.25em;\n}\n.code-copy-button-checked > .bi::after {\n mask-image: url('data:image/svg+xml,');\n background-color: var(--bs-success, #198754);\n}"], "mappings": "AAQA,qBACE,aACA,sBAVF,cAYE,SAEF,kCACE,gBAEF,yCACE,aACA,sBACA,cACA,SAEF,4DACE,aACA,yCACA,SAKF,6EACE,mBACA,kBAEF,0EACE,cACA,iBArCF,wCAwCE,oDACA,6CACA,yEAEF,mEACE,4BACA,mBAEF,sCACE,gBACA,gBACA,SAnDF,eAqDE,0CAEF,2CACE,0BACA,0BAEF,+CACE,YAOF,kDACE,aAIF,cACE,cACA,gBA1EF,YA8EA,UA9EA,gBAqGA,qBACE,cACA,mBAGF,0BAEE,cACA,kBAGF,yCAGE,cAGF,uEAKE,cAGF,cACE,cAGF,iFAKE,cAGF,yIAQE,cAGF,8EAME,cAGF,0DAGE,cAGF,eACE,kBAGF,aACE,gBAGF,WACE,0BAwBF,mCACE,cACA,gBAvMF,YA0MA,+BA1MA,gBA6MA,0CACE,cACA,mBAEF,oEAEE,cACA,kBAEF,wGAGE,cAEF,gLAKE,cAEF,mCACE,cAEF,0LAKE,cAEF,iTAQE,cAEF,4MAME,cAEF,yHAGE,cAEF,oCACE,kBAEF,kCACE,gBAEF,gCACE,0BAMF,2BACE,kBAGF,kBACE,kBACA,MACA,QACA,SACA,eACA,iBACA,UACA,6BAEF,sBACE,aACA,UAEF,4BACE,WACA,cACA,YACA,WACA,ufACA,4CAGF,qCACE,kBACA,gBACA,qBAEF,oCACE,6RACA", "names": [] } diff --git a/shiny/www/shared/py-shiny/chat/chat.js b/shiny/www/shared/py-shiny/chat/chat.js index 187bea0dc..398ad93d3 100644 --- a/shiny/www/shared/py-shiny/chat/chat.js +++ b/shiny/www/shared/py-shiny/chat/chat.js @@ -12683,6 +12683,10 @@ var ChatContainer = class extends LightElement { this.#onAppendChunk ); this.addEventListener("shiny-chat-clear-messages", this.#onClear); + this.addEventListener( + "shiny-chat-remove-placeholder", + this.#onRemovePlaceholder + ); } disconnectedCallback() { super.disconnectedCallback(); @@ -12693,6 +12697,10 @@ var ChatContainer = class extends LightElement { this.#onAppendChunk ); this.removeEventListener("shiny-chat-clear-messages", this.#onClear); + this.removeEventListener( + "shiny-chat-remove-placeholder", + this.#onRemovePlaceholder + ); } #onInputSent(event) { this.#appendMessage(event.detail); @@ -12751,6 +12759,10 @@ var ChatContainer = class extends LightElement { #onClear() { this.messages.innerHTML = ""; } + #onRemovePlaceholder() { + this.#removePlaceholder(); + this.#enableInput(); + } #enableInput() { this.input.disabled = false; }