Skip to content

Commit 93cce86

Browse files
committed
Show error message in shiny console if message can't be handled
1 parent 68233de commit 93cce86

File tree

7 files changed

+106
-37
lines changed

7 files changed

+106
-37
lines changed

js/markdown-stream/markdown-stream.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { sanitize } from "dompurify";
77
import hljs from "highlight.js/lib/common";
88
import { Renderer, parse } from "marked";
99

10-
import { LightElement, createElement, createSVGIcon } from "../utils/_utils";
10+
import {
11+
LightElement,
12+
createElement,
13+
createSVGIcon,
14+
showShinyClientMessage,
15+
} from "../utils/_utils";
1116

1217
type ContentType = "markdown" | "semi-markdown" | "html" | "text";
1318

@@ -30,8 +35,9 @@ function isStreamingMessage(
3035
}
3136

3237
// SVG dot to indicate content is currently streaming
38+
const SVG_DOT_CLASS = "chat-streaming-dot";
3339
const SVG_DOT = createSVGIcon(
34-
'<svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" class="chat-streaming-dot" style="margin-left:.25em;margin-top:-.25em"><circle cx="6" cy="6" r="6"/></svg>'
40+
`<svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" class="${SVG_DOT_CLASS}" style="margin-left:.25em;margin-top:-.25em"><circle cx="6" cy="6" r="6"/></svg>`
3541
);
3642

3743
// For rendering chat output, we use typical Markdown behavior of passing through raw
@@ -92,7 +98,7 @@ class MarkdownElement extends LightElement {
9298
}
9399

94100
#removeStreamingDot(): void {
95-
this.querySelector("svg.chat-streaming-dot")?.remove();
101+
this.querySelector(`svg.${SVG_DOT_CLASS}`)?.remove();
96102
}
97103

98104
// Highlight code blocks after the element is rendered
@@ -123,7 +129,8 @@ class MarkdownElement extends LightElement {
123129
}
124130
}
125131

126-
// TODO: it is a problem if this runs twice in the browser?
132+
// ------- Register custom elements and shiny bindings ---------
133+
127134
if (!customElements.get("shiny-markdown-stream")) {
128135
customElements.define("shiny-markdown-stream", MarkdownElement);
129136
}
@@ -132,7 +139,12 @@ function handleMessage(message: ContentMessage | IsStreamingMessage): void {
132139
const el = document.getElementById(message.id) as MarkdownElement;
133140

134141
if (!el) {
135-
console.error(`Element with id ${message.id} not found`);
142+
const errMsg = `
143+
Cannot handle MarkdownStream message: element with id ${message.id} not found.
144+
Do you need to call .ui() (Express) or fix the id (Core)?
145+
`;
146+
console.error(errMsg);
147+
showShinyClientMessage({ message: errMsg, status: "error" });
136148
return;
137149
}
138150

@@ -151,7 +163,6 @@ function handleMessage(message: ContentMessage | IsStreamingMessage): void {
151163
}
152164
}
153165

154-
// TODO: it is a problem if this runs twice in the browser?
155166
$(function () {
156167
Shiny.addCustomMessageHandler("shinyMarkdownStreamMessage", handleMessage);
157168
});

js/utils/_utils.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { LitElement } from "lit";
22

3+
////////////////////////////////////////////////
4+
// Lit helpers
5+
////////////////////////////////////////////////
6+
37
function createElement(
48
tag_name: string,
59
attrs: { [key: string]: string | null }
@@ -23,5 +27,26 @@ class LightElement extends LitElement {
2327
return this;
2428
}
2529
}
30+
////////////////////////////////////////////////
31+
// Shiny helpers
32+
////////////////////////////////////////////////
33+
34+
export type ShinyClientMessage = {
35+
message: string;
36+
headline?: string;
37+
status?: "error" | "info" | "warning";
38+
};
39+
40+
function showShinyClientMessage({
41+
headline = "",
42+
message,
43+
status = "warning",
44+
}: ShinyClientMessage): void {
45+
document.dispatchEvent(
46+
new CustomEvent("shiny:client-message", {
47+
detail: { headline: headline, message: message, status: status },
48+
})
49+
);
50+
}
2651

27-
export { LightElement, createElement, createSVGIcon };
52+
export { LightElement, createElement, createSVGIcon, showShinyClientMessage };
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import time
2+
3+
import requests
4+
5+
from shiny.express import session, ui
6+
7+
8+
def gen1():
9+
chunks = ["Hello, World!", "Hello, World!"]
10+
for chunk in chunks:
11+
if not session.is_stub_session():
12+
time.sleep(0.02)
13+
yield chunk + " "
14+
15+
16+
def gen2():
17+
chunks = ["Hello, World 2!", "Hello, World 2!"]
18+
for chunk in chunks:
19+
if not session.is_stub_session():
20+
time.sleep(0.02)
21+
yield chunk + " "
22+
23+
24+
md = ui.MarkdownStream("shiny-readme")
25+
md.ui()
26+
md.stream(gen1())
27+
md.stream(gen2(), clear=False)

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

Lines changed: 15 additions & 12 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.

shiny/www/py-shiny/markdown-stream/markdown-stream.js

Lines changed: 15 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

shiny/www/py-shiny/markdown-stream/markdown-stream.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.

0 commit comments

Comments
 (0)