Skip to content

Commit 55fcc48

Browse files
authored
Merge pull request #2205 from cardstack/cs-8040-visual-bugs-while-code-is-streaming-1
AI message: do not render html when it's in a code block
2 parents 0a7099f + ab8a4b7 commit 55fcc48

File tree

2 files changed

+40
-5
lines changed

2 files changed

+40
-5
lines changed

packages/host/tests/integration/components/room-message-test.gts

+31-4
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ module('Integration | Component | RoomMessage', function (hooks) {
2323
isStreaming: boolean,
2424
timeAgoForCreated: number,
2525
timeAgoForUpdated: number,
26+
messageContent: string,
2627
) {
2728
let message = {
2829
author: { userId: '@aibot:localhost' },
29-
message: 'Hello,',
30-
formattedMessage: 'Hello, ',
30+
message: messageContent,
31+
formattedMessage: messageContent,
3132
created: new Date(new Date().getTime() - timeAgoForCreated * 60 * 1000),
3233
updated: new Date(new Date().getTime() - timeAgoForUpdated * 60 * 1000),
3334
};
@@ -67,7 +68,7 @@ module('Integration | Component | RoomMessage', function (hooks) {
6768
}
6869

6970
test('it shows an error when AI bot message streaming timeouts', async function (assert) {
70-
let testScenario = await setupTestScenario(true, 2, 1); // Streaming, created 2 mins ago, updated 1 min ago
71+
let testScenario = await setupTestScenario(true, 2, 1, 'Hello,'); // Streaming, created 2 mins ago, updated 1 min ago
7172
await renderRoomMessageComponent(testScenario);
7273

7374
await waitUntil(
@@ -85,7 +86,7 @@ module('Integration | Component | RoomMessage', function (hooks) {
8586
});
8687

8788
test('it does not show an error when last streaming chunk is still within reasonable time limit', async function (assert) {
88-
let testScenario = await setupTestScenario(true, 2, 0.5); // Streaming, created 2 mins ago, updated 30 seconds ago
89+
let testScenario = await setupTestScenario(true, 2, 0.5, 'Hello,'); // Streaming, created 2 mins ago, updated 30 seconds ago
8990
await renderRoomMessageComponent(testScenario);
9091

9192
assert
@@ -96,4 +97,30 @@ module('Integration | Component | RoomMessage', function (hooks) {
9697
.dom('[data-test-ai-message-content] span.streaming-text')
9798
.includesText('Hello,');
9899
});
100+
101+
test('it escapes html code that is in code tags', async function (assert) {
102+
let testScenario = await setupTestScenario(
103+
true,
104+
0,
105+
0,
106+
`
107+
\`\`\`typescript
108+
<template>
109+
<h1>Hello, world!</h1>
110+
</template>
111+
\`\`\`
112+
`,
113+
);
114+
await renderRoomMessageComponent(testScenario);
115+
116+
let content = document.querySelector(
117+
'[data-test-ai-message-content]',
118+
)?.innerHTML;
119+
assert.ok(
120+
content?.includes(`&lt;template&gt;
121+
&lt;h1&gt;Hello, world!&lt;/h1&gt;
122+
&lt;/template&gt;`),
123+
'rendered code snippet in a streaming message should contain escaped HTML template so that we see code, not rendered html',
124+
);
125+
});
99126
});

packages/runtime-common/marked-sync.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function markedSync(markdown: string) {
2222
// also note that since we are in common, we don't have ember-window-mock
2323
// available to us.
2424
globalThis.localStorage?.setItem(id, code);
25-
return `<pre id="${id}" class="language-${language}" data-codeblock="${language}">${code}</pre></div>`;
25+
return `<pre id="${id}" class="language-${language}" data-codeblock="${language}">${escapeHtmlInPreTags(code)}</pre></div>`;
2626
},
2727
},
2828
})
@@ -32,3 +32,11 @@ export function markedSync(markdown: string) {
3232
export function markdownToHtml(markdown: string | null | undefined): string {
3333
return markdown ? sanitizeHtml(markedSync(markdown)) : '';
3434
}
35+
36+
function escapeHtmlInPreTags(html: string) {
37+
// For example, html can be <pre><code><h1>Hello</h1></code></pre>
38+
// We want to escape the <h1>Hello</h1> so that it is rendered as
39+
// <pre><code>&lt;h1&gt;Hello&lt;/h1&gt;</code></pre>, otherwise the h1 will
40+
// be rendered as a real header, not code (same applies for other html tags, such as <template>, <style>, ...)
41+
return html.replace(/</g, '&lt;').replace(/>/g, '&gt;');
42+
}

0 commit comments

Comments
 (0)