Skip to content

Commit 4d7a3f5

Browse files
authored
feat(Chat): improved styling/readability of markdown tables (#1973)
1 parent 646e69f commit 4d7a3f5

File tree

5 files changed

+49
-30
lines changed

5 files changed

+49
-30
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to Shiny for Python will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [UNRELEASED]
9+
10+
### Improvements
11+
12+
* Improved the styling and readability of markdown tables rendered by `ui.Chat()` and `ui.MarkdownStream()`. (#1973)
13+
814
## [1.4.0] - 2025-04-08
915

1016
## New features

js/markdown-stream/markdown-stream.ts

+22-12
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,38 @@ const SVG_DOT = createSVGIcon(
4545
`<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>`
4646
);
4747

48-
// For rendering chat output, we use typical Markdown behavior of passing through raw
49-
// HTML (albeit sanitizing afterwards).
50-
//
51-
// For echoing chat input, we escape HTML. This is not for security reasons but just
52-
// because it's confusing if the user is using tag-like syntax to demarcate parts of
53-
// their prompt for other reasons (like <User>/<Assistant> for providing examples to the
54-
// chat model), and those tags simply vanish.
55-
const rendererEscapeHTML = new Renderer();
56-
rendererEscapeHTML.html = (html: string) =>
48+
// 'markdown' renderer (for assistant messages)
49+
const markdownRenderer = new Renderer();
50+
51+
// Add some basic Bootstrap styling to markdown tables
52+
markdownRenderer.table = (header: string, body: string) => {
53+
return `<table class="table table-striped table-bordered">
54+
<thead>${header}</thead>
55+
<tbody>${body}</tbody>
56+
</table>`;
57+
};
58+
59+
// 'semi-markdown' renderer (for user messages)
60+
const semiMarkdownRenderer = new Renderer();
61+
62+
// Escape HTML, not for security reasons, but just because it's confusing if the user is
63+
// using tag-like syntax to demarcate parts of their prompt for other reasons (like
64+
// <User>/<Assistant> for providing examples to the model), and those tags vanish.
65+
semiMarkdownRenderer.html = (html: string) =>
5766
html
5867
.replaceAll("&", "&amp;")
5968
.replaceAll("<", "&lt;")
6069
.replaceAll(">", "&gt;")
6170
.replaceAll('"', "&quot;")
6271
.replaceAll("'", "&#039;");
63-
const markedEscapeOpts = { renderer: rendererEscapeHTML };
6472

6573
function contentToHTML(content: string, content_type: ContentType) {
6674
if (content_type === "markdown") {
67-
return unsafeHTML(sanitizeHTML(parse(content) as string));
75+
const html = parse(content, { renderer: markdownRenderer });
76+
return unsafeHTML(sanitizeHTML(html as string));
6877
} else if (content_type === "semi-markdown") {
69-
return unsafeHTML(sanitizeHTML(parse(content, markedEscapeOpts) as string));
78+
const html = parse(content, { renderer: semiMarkdownRenderer });
79+
return unsafeHTML(sanitizeHTML(html as string));
7080
} else if (content_type === "html") {
7181
return unsafeHTML(sanitizeHTML(content));
7282
} else if (content_type === "text") {

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ dev = [
100100
"isort>=5.10.1",
101101
"libsass>=0.23.0",
102102
"brand_yml>=0.1.0",
103-
"pyright>=1.1.398",
103+
"pyright==1.1.398",
104104
"pre-commit>=2.15.0",
105105
"wheel",
106106
"matplotlib",

0 commit comments

Comments
 (0)