Skip to content

Commit 86983c1

Browse files
committed
Better error handling
1 parent 532b4f2 commit 86983c1

File tree

6 files changed

+81
-18
lines changed

6 files changed

+81
-18
lines changed

js/chat/chat.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,10 @@ class ChatContainer extends LightElement {
198198
this.#onAppendChunk
199199
);
200200
this.addEventListener("shiny-chat-clear-messages", this.#onClear);
201+
this.addEventListener(
202+
"shiny-chat-remove-placeholder",
203+
this.#onRemovePlaceholder
204+
);
201205
}
202206

203207
disconnectedCallback(): void {
@@ -209,6 +213,10 @@ class ChatContainer extends LightElement {
209213
this.#onAppendChunk
210214
);
211215
this.removeEventListener("shiny-chat-clear-messages", this.#onClear);
216+
this.removeEventListener(
217+
"shiny-chat-remove-placeholder",
218+
this.#onRemovePlaceholder
219+
);
212220
}
213221

214222
#onInputSent(event: CustomEvent<Message>): void {
@@ -287,6 +295,11 @@ class ChatContainer extends LightElement {
287295
this.messages.innerHTML = "";
288296
}
289297

298+
#onRemovePlaceholder(): void {
299+
this.#removePlaceholder();
300+
this.#enableInput();
301+
}
302+
290303
#enableInput(): void {
291304
this.input.disabled = false;
292305
}

shiny/reactive/_reactives.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@
3434
from .._docstring import add_example
3535
from .._utils import is_async_callable, run_coro_sync
3636
from .._validation import req
37-
from ..types import MISSING, MISSING_TYPE, ActionButtonValue, SilentException
37+
from ..types import (
38+
MISSING,
39+
MISSING_TYPE,
40+
ActionButtonValue,
41+
NotifyException,
42+
SilentException,
43+
)
3844
from ._core import Context, Dependents, ReactiveWarning, isolate
3945

4046
if TYPE_CHECKING:
@@ -583,6 +589,17 @@ async def _run(self) -> None:
583589
except SilentException:
584590
# It's OK for SilentException to cause an Effect to stop running
585591
pass
592+
except NotifyException as e:
593+
traceback.print_exc()
594+
595+
if self._session:
596+
from .._app import SANITIZE_ERROR_MSG
597+
from ..ui import notification_show
598+
599+
msg = SANITIZE_ERROR_MSG if e.sanitize else str(e)
600+
notification_show(msg, type="error", duration=5000)
601+
if e.close:
602+
await self._session._unhandled_error(e)
586603
except Exception as e:
587604
traceback.print_exc()
588605

shiny/types.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,30 @@ class SilentOperationInProgressException(SilentException):
160160
pass
161161

162162

163+
class NotifyException(Exception):
164+
"""
165+
This exception can be raised in a (non-output) reactive effect
166+
to display a message to the user.
167+
168+
Parameters
169+
----------
170+
message
171+
The message to display to the user.
172+
sanitize
173+
If ``True``, the message is sanitized to prevent leaking sensitive information.
174+
close
175+
If ``True``, the session is closed after the message is displayed.
176+
"""
177+
178+
sanitize: bool
179+
close: bool
180+
181+
def __init__(self, message: str, sanitize: bool = True, close: bool = False):
182+
super().__init__(message)
183+
self.sanitize = sanitize
184+
self.close = close
185+
186+
163187
class ActionButtonValue(int):
164188
pass
165189

shiny/ui/_chat.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515

1616
from htmltools import Tag
1717

18-
from .. import _utils, reactive, ui
19-
from .._app import SANITIZE_ERROR_MSG
18+
from .. import _utils, reactive
2019
from .._namespaces import resolve_id
2120
from ..session import Session, require_active_session, session_context
21+
from ..types import NotifyException
2222
from ._chat_types import (
2323
ChatMessage,
2424
ChatMessageChunk,
@@ -106,7 +106,7 @@ def _is_express() -> bool:
106106
def on_user_submit(
107107
self,
108108
func: SubmitFunction | SubmitFunctionAsync,
109-
errors: Literal["sanitize", "show", "none"] = "sanitize",
109+
error: Literal["sanitize", "actual", "unhandled"] = "sanitize",
110110
) -> reactive.Effect_:
111111
"""
112112
Register a callback to run when the user submits a message.
@@ -117,20 +117,14 @@ def on_user_submit(
117117
@reactive.effect
118118
@reactive.event(self.user_input)
119119
async def wrapper():
120-
try:
120+
if error == "unhandled":
121121
await afunc()
122-
# TODO: does this handle req() correctly?
123-
except Exception as e:
124-
if errors == "sanitize":
125-
ui.notification_show(
126-
SANITIZE_ERROR_MSG, type="error", duration=5000
127-
)
128-
elif errors == "show":
129-
ui.notification_show(
130-
ui.markdown(str(e)), type="error", duration=5000
131-
)
132-
133-
raise e
122+
else:
123+
try:
124+
await afunc()
125+
except Exception as e:
126+
await self._remove_placeholder()
127+
raise NotifyException(str(e), sanitize=error == "sanitize")
134128

135129
return wrapper
136130

@@ -235,6 +229,9 @@ async def clear_messages(self):
235229

236230
await self._send_custom_message("shiny-chat-clear-messages", None)
237231

232+
async def _remove_placeholder(self):
233+
await self._send_custom_message("shiny-chat-remove-placeholder", None)
234+
238235
async def _send_custom_message(
239236
self, handler: str, obj: ChatMessage | ChatMessageChunk | None
240237
):

0 commit comments

Comments
 (0)