Skip to content

Commit

Permalink
Better error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
cpsievert committed Jun 6, 2024
1 parent 532b4f2 commit 86983c1
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 18 deletions.
13 changes: 13 additions & 0 deletions js/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<Message>): void {
Expand Down Expand Up @@ -287,6 +295,11 @@ class ChatContainer extends LightElement {
this.messages.innerHTML = "";
}

#onRemovePlaceholder(): void {
this.#removePlaceholder();
this.#enableInput();
}

#enableInput(): void {
this.input.disabled = false;
}
Expand Down
19 changes: 18 additions & 1 deletion shiny/reactive/_reactives.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -583,6 +589,17 @@ 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 = SANITIZE_ERROR_MSG if e.sanitize else str(e)
notification_show(msg, type="error", duration=5000)
if e.close:
await self._session._unhandled_error(e)
except Exception as e:
traceback.print_exc()

Expand Down
24 changes: 24 additions & 0 deletions shiny/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
29 changes: 13 additions & 16 deletions shiny/ui/_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -106,7 +106,7 @@ def _is_express() -> bool:
def on_user_submit(
self,
func: SubmitFunction | SubmitFunctionAsync,
errors: Literal["sanitize", "show", "none"] = "sanitize",
error: Literal["sanitize", "actual", "unhandled"] = "sanitize",
) -> reactive.Effect_:
"""
Register a callback to run when the user submits a message.
Expand All @@ -117,20 +117,14 @@ def on_user_submit(
@reactive.effect
@reactive.event(self.user_input)
async def wrapper():
try:
if error == "unhandled":
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
else:
try:
await afunc()
except Exception as e:
await self._remove_placeholder()
raise NotifyException(str(e), sanitize=error == "sanitize")

return wrapper

Expand Down Expand Up @@ -235,6 +229,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
):
Expand Down
Loading

0 comments on commit 86983c1

Please sign in to comment.