Skip to content

Commit

Permalink
feat: improve messages render performance (casibase#777)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinRepo authored Mar 20, 2024
1 parent cc4370f commit b0a78f2
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 73 deletions.
74 changes: 1 addition & 73 deletions web/src/ChatBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,10 @@
import React from "react";
import {Avatar, ChatContainer, ConversationHeader, MainContainer, Message, MessageInput, MessageList} from "@chatscope/chat-ui-kit-react";
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
import {marked} from "marked";
import DOMPurify from "dompurify";
import katex from "katex";
import "katex/dist/katex.min.css";
import hljs from "highlight.js";
import "highlight.js/styles/atom-one-dark-reasonable.css";
import * as Conf from "./Conf";
import * as Setting from "./Setting";
import i18next from "i18next";

marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: true,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: true,
});

class ChatBox extends React.Component {
constructor(props) {
super(props);
Expand Down Expand Up @@ -117,61 +100,6 @@ class ChatBox extends React.Component {
reader.readAsDataURL(file);
};

renderMarkdown(text) {
if (text === "") {
text = this.state.dots;
}
const rawHtml = marked(text);
let cleanHtml = DOMPurify.sanitize(rawHtml);
/* replace <p></p> with <div></div>, reduce paragraph spacing. */
cleanHtml = cleanHtml.replace(/<p>/g, "<div>").replace(/<\/p>/g, "</div>");
/* h2 is larger than h1, h2 is the largest, so replace h1 with h2, and set margin as 20px. */
cleanHtml = cleanHtml.replace(/<h1>/g, "<h2>").replace(/<(h[1-6])>/g, "<$1 style='margin-top: 20px; margin-bottom: 20px'>");
/* adjust margin and internal gap for unordered list and ordered list. */
cleanHtml = cleanHtml.replace(/<(ul)>/g, "<ul style='display: flex; flex-direction: column; gap: 10px; margin-top: 10px; margin-bottom: 10px'>").replace(/<(ol)>/g, "<ol style='display: flex; flex-direction: column; gap: 0px; margin-top: 20px; margin-bottom: 20px'>");
/* adjust code block, for auto line feed. */
cleanHtml = cleanHtml.replace(/<pre>/g, "<pre style='white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;'>");
return cleanHtml;
}

renderLatex(text) {
const inlineLatexRegex = /\(\s*(([a-zA-Z])|(\\.+?)|([^)]*?[_^!].*?))\s*\)/g;
const blockLatexRegex = /\[\s*(.+?)\s*\]/g;

text = text.replace(blockLatexRegex, (match, formula) => {
try {
return katex.renderToString(formula, {throwOnError: false, displayMode: true});
} catch (error) {
return match;
}
});

return text.replace(inlineLatexRegex, (match, formula) => {
try {
return katex.renderToString(formula, {throwOnError: false, displayMode: false});
} catch (error) {
return match;
}
});
}

renderCode(text) {
const tempDiv = document.createElement("div");
tempDiv.innerHTML = text;
tempDiv.querySelectorAll("pre code").forEach((block) => {
hljs.highlightBlock(block);
});
return tempDiv.innerHTML;
}

renderText(text) {
let html;
html = this.renderMarkdown(text);
html = this.renderLatex(html);
html = this.renderCode(html);
return <div dangerouslySetInnerHTML={{__html: html}} style={{display: "flex", flexDirection: "column", gap: "0px"}} />;
}

render() {
let title = Setting.getUrlParam("title");
if (title === null) {
Expand Down Expand Up @@ -202,7 +130,7 @@ class ChatBox extends React.Component {
}} avatarPosition={message.author === "AI" ? "tl" : "tr"}>
<Avatar src={message.author === "AI" ? Conf.AiAvatar : (this.props.hideInput === true ? "https://cdn.casdoor.com/casdoor/resource/built-in/admin/casibase-user.png" : this.props.account.avatar)} name="GPT" />
<Message.CustomContent>
{this.renderText(message.text)}
{message.text === "" ? this.state.dots : message.html}
</Message.CustomContent>
</Message>
))}
Expand Down
4 changes: 4 additions & 0 deletions web/src/ChatEditPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as ChatBackend from "./backend/ChatBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
import ChatBox from "./ChatBox";
import {renderText} from "./ChatMessageRender";
import * as MessageBackend from "./backend/MessageBackend";

const {Option} = Select;
Expand Down Expand Up @@ -56,6 +57,9 @@ class ChatEditPage extends React.Component {
getMessages(chatName) {
MessageBackend.getChatMessages("admin", chatName)
.then((res) => {
res.data.map((message) => {
message.html = renderText(message.text);
});
this.setState({
messages: res.data,
});
Expand Down
4 changes: 4 additions & 0 deletions web/src/ChatListPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import i18next from "i18next";
import * as Conf from "./Conf";
import * as MessageBackend from "./backend/MessageBackend";
import ChatBox from "./ChatBox";
import {renderText} from "./ChatMessageRender";

class ChatListPage extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -66,6 +67,9 @@ class ChatListPage extends React.Component {
MessageBackend.getChatMessages("admin", chatName)
.then((res) => {
const messagesMap = this.state.messagesMap;
res.data.map((message) => {
message.html = renderText(message.text);
});
messagesMap[chatName] = res.data;
this.setState({
messagesMap: messagesMap,
Expand Down
91 changes: 91 additions & 0 deletions web/src/ChatMessageRender.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2023 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {marked} from "marked";
import DOMPurify from "dompurify";
import katex from "katex";
import "katex/dist/katex.min.css";
import hljs from "highlight.js";
import "highlight.js/styles/atom-one-dark-reasonable.css";

marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: true,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: true,
});

export function renderMarkdown(text) {
const rawHtml = marked(text);
let cleanHtml = DOMPurify.sanitize(rawHtml);
/* replace <p></p> with <div></div>, reduce paragraph spacing. */
cleanHtml = cleanHtml.replace(/<p>/g, "<div>").replace(/<\/p>/g, "</div>");
/* h2 is larger than h1, h2 is the largest, so replace h1 with h2, and set margin as 20px. */
cleanHtml = cleanHtml.replace(/<h1>/g, "<h2>").replace(/<(h[1-6])>/g, "<$1 style='margin-top: 20px; margin-bottom: 20px'>");
/* adjust margin and internal gap for unordered list and ordered list. */
cleanHtml = cleanHtml.replace(/<(ul)>/g, "<ul style='display: flex; flex-direction: column; gap: 10px; margin-top: 10px; margin-bottom: 10px'>").replace(/<(ol)>/g, "<ol style='display: flex; flex-direction: column; gap: 0px; margin-top: 20px; margin-bottom: 20px'>");
/* adjust code block, for auto line feed. */
cleanHtml = cleanHtml.replace(/<pre>/g, "<pre style='white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;'>");
return cleanHtml;
}

export function renderLatex(text) {
const inlineLatexRegex = /\(\s*(([a-zA-Z])|(\\.+?)|([^)]*?[_^!].*?))\s*\)/g;
const blockLatexRegex = /\[\s*(.+?)\s*\]/g;

text = text.replace(blockLatexRegex, (match, formula) => {
try {
return katex.renderToString(formula, {
throwOnError: false,
displayMode: true,
});
} catch (error) {
return match;
}
});

return text.replace(inlineLatexRegex, (match, formula) => {
try {
return katex.renderToString(formula, {
throwOnError: false,
displayMode: false,
});
} catch (error) {
return match;
}
});
}

export function renderCode(text) {
const tempDiv = document.createElement("div");
tempDiv.innerHTML = text;
tempDiv.querySelectorAll("pre code").forEach((block) => {
hljs.highlightBlock(block);
});
return tempDiv.innerHTML;
}

export function renderText(text) {
let html;
html = renderMarkdown(text);
html = renderLatex(html);
html = renderCode(html);
return (
<div dangerouslySetInnerHTML={{__html: html}} style={{display: "flex", flexDirection: "column", gap: "0px"}} />
);
}
10 changes: 10 additions & 0 deletions web/src/ChatPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {Modal, Spin} from "antd";
import moment from "moment";
import ChatMenu from "./ChatMenu";
import ChatBox from "./ChatBox";
import {renderText} from "./ChatMessageRender";
import * as Setting from "./Setting";
import * as ChatBackend from "./backend/ChatBackend";
import * as MessageBackend from "./backend/MessageBackend";
Expand Down Expand Up @@ -150,6 +151,9 @@ class ChatPage extends BaseListPage {
getMessages(chat) {
MessageBackend.getChatMessages("admin", chat.name)
.then((res) => {
res.data.map((message) => {
message.html = renderText(message.text);
});
this.setState({
messages: res.data,
});
Expand Down Expand Up @@ -178,6 +182,9 @@ class ChatPage extends BaseListPage {
text += jsonData.text;
lastMessage2.text = text;
res.data[res.data.length - 1] = lastMessage2;
res.data.map((message) => {
message.html = renderText(message.text);
});
this.setState({
messages: res.data,
disableInput: false,
Expand All @@ -188,6 +195,9 @@ class ChatPage extends BaseListPage {
const lastMessage2 = Setting.deepCopy(lastMessage);
lastMessage2.text = error;
res.data[res.data.length - 1] = lastMessage2;
res.data.map((message) => {
message.html = renderText(message.text);
});
this.setState({
messages: res.data,
disableInput: true,
Expand Down

0 comments on commit b0a78f2

Please sign in to comment.