Skip to content

Commit d767c02

Browse files
authored
render chat messages as markdown (#263)
### Description When requesting `generate a table of fruits` or `generate some code to count from 1 to 200` the response will contain markdown: ![image](https://github.com/microsoft/chat-copilot/assets/13119203/12b11e19-1e2b-4335-b45a-e3dd97919de2) However Markdown can not be rendered in the frontend. This PR fixes it by adding a markdown rendering library and making use of it: ![image](https://github.com/microsoft/chat-copilot/assets/13119203/726b6fe9-4673-4021-87c9-923d48a1d64d) This also avoids using `dangerouslySetInnerHTML` attribute. The library `remark-gfm` is used because `react-markdown` does not support tables out of the box. This is how urls or links would be rendered: ![image](https://github.com/microsoft/chat-copilot/assets/13119203/0b084d58-9602-4682-b5d2-ea77dbd6bbd9)
1 parent ce5a0a1 commit d767c02

File tree

4 files changed

+701
-35
lines changed

4 files changed

+701
-35
lines changed

webapp/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
"microsoft-cognitiveservices-speech-sdk": "^1.31.0",
2828
"react": "^18.2.0",
2929
"react-dom": "^18.2.0",
30-
"react-redux": "^8.1.2"
30+
"react-markdown": "^8.0.7",
31+
"react-redux": "^8.1.2",
32+
"remark-gfm": "^3.0.1"
3133
},
3234
"devDependencies": {
3335
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",

webapp/src/components/chat/chat-history/ChatHistoryTextContent.tsx

+8-8
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import { makeStyles } from '@fluentui/react-components';
44
import React from 'react';
5+
import ReactMarkdown from 'react-markdown';
6+
import remarkGfm from 'remark-gfm';
57
import { IChatMessage } from '../../../libs/models/ChatMessage';
6-
import { convertToAnchorTags } from '../../utils/TextUtils';
78
import * as utils from './../../utils/TextUtils';
89

910
const useClasses = makeStyles({
@@ -18,12 +19,11 @@ interface ChatHistoryTextContentProps {
1819

1920
export const ChatHistoryTextContent: React.FC<ChatHistoryTextContentProps> = ({ message }) => {
2021
const classes = useClasses();
22+
const content = utils.formatChatTextContent(message.content);
2123

22-
let content = message.content.trim().replace(/[\u00A0-\u9999<>&]/g, function (i: string) {
23-
return `&#${i.charCodeAt(0)};`;
24-
});
25-
content = utils.formatChatTextContent(content);
26-
content = content.replace(/\n/g, '<br />').replace(/ {2}/g, '&nbsp;&nbsp;');
27-
28-
return <div className={classes.content} dangerouslySetInnerHTML={{ __html: convertToAnchorTags(content) }} />;
24+
return (
25+
<div className={classes.content}>
26+
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
27+
</div>
28+
);
2929
};

webapp/src/components/utils/TextUtils.tsx

-21
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,5 @@
11
import { Body1, tokens } from '@fluentui/react-components';
22

3-
/*
4-
* Function to detect and convert URLs within a string into clickable links.
5-
* It wraps each link matched with anchor tags and applies safe href attributes.
6-
*/
7-
export function convertToAnchorTags(htmlString: string) {
8-
// Regular expression to match links, excluding any HTML tags at the end
9-
// Since response from bot is plain text, sometimes line breaks and other html tags are included in the response for readability.
10-
const linkRegex =
11-
/(?:https?):\/\/(\w+:?\w*)?(\S+)(:\d+)?(\/|\/([\w#!:.?+=&%!\-/]))?(?=(<br|<p|<div|<span)\s*\/>|$)/g;
12-
13-
const result = htmlString.replace(linkRegex, function (link) {
14-
// Parse URL first -- URL class handles cybersecurity concerns related to URL parsing and manipulation
15-
const safeHref = new URL(link).toString();
16-
17-
// Replace each link with anchor tags
18-
return `<a href="${safeHref}">${link}</a>`;
19-
});
20-
21-
return result;
22-
}
23-
243
/*
254
* Function to check if date is today.
265
*/

0 commit comments

Comments
 (0)