diff --git a/src/components/Chat/ChatContent/Message/MessageContent.tsx b/src/components/Chat/ChatContent/Message/MessageContent.tsx
index b6f0eba..87e9b71 100644
--- a/src/components/Chat/ChatContent/Message/MessageContent.tsx
+++ b/src/components/Chat/ChatContent/Message/MessageContent.tsx
@@ -1,13 +1,18 @@
-import React, { useEffect, useState } from 'react';
+import React, {
+ DetailedHTMLProps,
+ HTMLAttributes,
+ useEffect,
+ useState,
+} from 'react';
import ReactMarkdown from 'react-markdown';
+import { CodeProps, ReactMarkdownProps } from 'react-markdown/lib/ast-to-react';
import rehypeKatex from 'rehype-katex';
+import rehypeSanitize from 'rehype-sanitize';
+import rehypeHighlight from 'rehype-highlight';
import remarkMath from 'remark-math';
import remarkGfm from 'remark-gfm';
-import hljs from 'highlight.js';
-import DOMPurify from 'dompurify';
import useStore from '@store/store';
-import CopyIcon from '@icon/CopyIcon';
import EditIcon2 from '@icon/EditIcon2';
import DeleteIcon from '@icon/DeleteIcon';
import TickIcon from '@icon/TickIcon';
@@ -20,6 +25,8 @@ import useSubmit from '@hooks/useSubmit';
import { ChatInterface } from '@type/chat';
import PopupModal from '@components/PopupModal';
+import CodeBlock from './CodeBlock';
+import { codeLanguageSubset } from '@constants/chat';
const MessageContent = ({
role,
@@ -100,6 +107,14 @@ const ContentView = React.memo(
setChats(updatedChats);
};
+ const handleMoveUp = () => {
+ handleMove('up');
+ };
+
+ const handleMoveDown = () => {
+ handleMove('down');
+ };
+
const handleRefresh = () => {
const updatedChats: ChatInterface[] = JSON.parse(
JSON.stringify(useStore.getState().chats)
@@ -114,70 +129,26 @@ const ContentView = React.memo(
<>
{children};
- const [copied, setCopied] = useState(false);
-
- let highlight;
- const match = /language-(\w+)/.exec(className || '');
- const lang = match && match[1];
- const isMatch = lang && hljs.getLanguage(lang);
- if (isMatch)
- highlight = hljs.highlight(children.toString(), {
- language: lang,
- });
- else highlight = hljs.highlightAuto(children.toString());
-
- return (
-
-
- {highlight.language}
-
-
-
-
- );
- },
- p({ className, children, ...props }) {
- return {children}
;
- },
+ code,
+ p,
}}
>
{content}
@@ -189,11 +160,9 @@ const ContentView = React.memo(
{role === 'assistant' && messageIndex === lastMessageIndex && (
)}
- {messageIndex !== 0 && (
- handleMove('up')} />
- )}
+ {messageIndex !== 0 && }
{messageIndex !== lastMessageIndex && (
- handleMove('down')} />
+
)}
@@ -219,6 +188,33 @@ const ContentView = React.memo(
}
);
+const code = React.memo((props: CodeProps) => {
+ const { inline, className, children } = props;
+ const match = /language-(\w+)/.exec(className || '');
+ const lang = match && match[1];
+
+ if (inline) {
+ return {children}
;
+ } else {
+ return ;
+ }
+});
+
+const p = React.memo(
+ (
+ props?: Omit<
+ DetailedHTMLProps<
+ HTMLAttributes,
+ HTMLParagraphElement
+ >,
+ 'ref'
+ > &
+ ReactMarkdownProps
+ ) => {
+ return {props?.children}
;
+ }
+);
+
const MessageButton = ({
onClick,
icon,
@@ -238,23 +234,29 @@ const MessageButton = ({
);
};
-const EditButton = ({
- setIsEdit,
-}: {
- setIsEdit: React.Dispatch>;
-}) => {
- return } onClick={() => setIsEdit(true)} />;
-};
+const EditButton = React.memo(
+ ({
+ setIsEdit,
+ }: {
+ setIsEdit: React.Dispatch>;
+ }) => {
+ return (
+ } onClick={() => setIsEdit(true)} />
+ );
+ }
+);
-const DeleteButton = ({
- setIsDelete,
-}: {
- setIsDelete: React.Dispatch>;
-}) => {
- return (
- } onClick={() => setIsDelete(true)} />
- );
-};
+const DeleteButton = React.memo(
+ ({
+ setIsDelete,
+ }: {
+ setIsDelete: React.Dispatch>;
+ }) => {
+ return (
+ } onClick={() => setIsDelete(true)} />
+ );
+ }
+);
const DownButton = ({
onClick,
@@ -263,7 +265,6 @@ const DownButton = ({
}) => {
return } onClick={onClick} />;
};
-
const UpButton = ({
onClick,
}: {
@@ -276,7 +277,6 @@ const UpButton = ({
/>
);
};
-
const RefreshButton = ({
onClick,
}: {
@@ -284,7 +284,6 @@ const RefreshButton = ({
}) => {
return } onClick={onClick} />;
};
-
const EditView = ({
content,
setIsEdit,
@@ -394,6 +393,40 @@ const EditView = ({
rows={1}
>
+
+ {isModalOpen && (
+
+ )}
+ >
+ );
+};
+
+const EditViewButtons = React.memo(
+ ({
+ sticky = false,
+ handleSaveAndSubmit,
+ handleSave,
+ setIsModalOpen,
+ setIsEdit,
+ }: {
+ sticky?: boolean;
+ handleSaveAndSubmit: () => void;
+ handleSave: () => void;
+ setIsModalOpen: React.Dispatch
>;
+ setIsEdit: React.Dispatch>;
+ }) => {
+ return (
{sticky && (
- {isModalOpen && (
-
- )}
- >
- );
-};
+ );
+ }
+);
export default MessageContent;
diff --git a/src/components/Chat/ChatContent/Message/RoleSelector.tsx b/src/components/Chat/ChatContent/Message/RoleSelector.tsx
index 3806b03..b127e45 100644
--- a/src/components/Chat/ChatContent/Message/RoleSelector.tsx
+++ b/src/components/Chat/ChatContent/Message/RoleSelector.tsx
@@ -4,65 +4,67 @@ import useStore from '@store/store';
import DownChevronArrow from '@icon/DownChevronArrow';
import { ChatInterface, Role, roles } from '@type/chat';
-const RoleSelector = ({
- role,
- messageIndex,
- sticky,
-}: {
- role: Role;
- messageIndex: number;
- sticky?: boolean;
-}) => {
- const setInputRole = useStore((state) => state.setInputRole);
- const setChats = useStore((state) => state.setChats);
- const currentChatIndex = useStore((state) => state.currentChatIndex);
+const RoleSelector = React.memo(
+ ({
+ role,
+ messageIndex,
+ sticky,
+ }: {
+ role: Role;
+ messageIndex: number;
+ sticky?: boolean;
+ }) => {
+ const setInputRole = useStore((state) => state.setInputRole);
+ const setChats = useStore((state) => state.setChats);
+ const currentChatIndex = useStore((state) => state.currentChatIndex);
- const [dropDown, setDropDown] = useState(false);
+ const [dropDown, setDropDown] = useState(false);
- return (
-
-
-
-
+
+ {role.charAt(0).toUpperCase() + role.slice(1)}
+
+
+
+
+ {roles.map((r) => (
+ - {
+ if (!sticky) {
+ const updatedChats: ChatInterface[] = JSON.parse(
+ JSON.stringify(useStore.getState().chats)
+ );
+ updatedChats[currentChatIndex].messages[messageIndex].role =
+ r;
+ setChats(updatedChats);
+ } else {
+ setInputRole(r);
+ }
+ setDropDown(false);
+ }}
+ key={r}
+ >
+ {r.charAt(0).toUpperCase() + r.slice(1)}
+
+ ))}
+
+
-
- );
-};
+ );
+ }
+);
export default RoleSelector;
diff --git a/src/components/Chat/ChatContent/ScrollToBottomButton.tsx b/src/components/Chat/ChatContent/ScrollToBottomButton.tsx
index 976c005..cd31f26 100644
--- a/src/components/Chat/ChatContent/ScrollToBottomButton.tsx
+++ b/src/components/Chat/ChatContent/ScrollToBottomButton.tsx
@@ -3,7 +3,7 @@ import { useAtBottom, useScrollToBottom } from 'react-scroll-to-bottom';
import DownArrow from '@icon/DownArrow';
-const ScrollToBottomButton = () => {
+const ScrollToBottomButton = React.memo(() => {
const scrollToBottom = useScrollToBottom();
const [atBottom] = useAtBottom();
@@ -17,6 +17,6 @@ const ScrollToBottomButton = () => {
);
-};
+});
export default ScrollToBottomButton;
diff --git a/src/constants/chat.ts b/src/constants/chat.ts
index e7a6779..6a8c692 100644
--- a/src/constants/chat.ts
+++ b/src/constants/chat.ts
@@ -24,3 +24,41 @@ export const generateDefaultChat = (title?: string): ChatInterface => ({
config: { ...defaultChatConfig },
titleSet: false,
});
+
+export const codeLanguageSubset = [
+ 'python',
+ 'javascript',
+ 'java',
+ 'go',
+ 'bash',
+ 'c',
+ 'cpp',
+ 'csharp',
+ 'css',
+ 'diff',
+ 'graphql',
+ 'json',
+ 'kotlin',
+ 'less',
+ 'lua',
+ 'makefile',
+ 'markdown',
+ 'objectivec',
+ 'perl',
+ 'php',
+ 'php-template',
+ 'plaintext',
+ 'python-repl',
+ 'r',
+ 'ruby',
+ 'rust',
+ 'scss',
+ 'shell',
+ 'sql',
+ 'swift',
+ 'typescript',
+ 'vbnet',
+ 'wasm',
+ 'xml',
+ 'yaml',
+];
diff --git a/yarn.lock b/yarn.lock
index 6f6e1b2..c132c1e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -742,11 +742,6 @@ dompurify@^2.2.0:
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.5.tgz#0e89a27601f0bad978f9a924e7a05d5d2cccdd87"
integrity sha512-jggCCd+8Iqp4Tsz0nIvpcb22InKEBrGz5dw3EQJMs8HPJDsKbFIO3STYtAvCfDx26Muevn1MHVI0XxjgFfmiSA==
-dompurify@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.1.tgz#a0933f38931b3238934dd632043b727e53004289"
- integrity sha512-60tsgvPKwItxZZdfLmamp0MTcecCta3avOhsLgPZ0qcWt96OasFfhkeIRbJ6br5i0fQawT1/RBGB5L58/Jpwuw==
-
electron-to-chromium@^1.4.284:
version "1.4.317"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.317.tgz#9a3d38a1a37f26a417d3d95dafe198ff11ed072b"
@@ -830,6 +825,13 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
+fault@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/fault/-/fault-2.0.1.tgz#d47ca9f37ca26e4bd38374a7c500b5a384755b6c"
+ integrity sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==
+ dependencies:
+ format "^0.2.0"
+
fflate@^0.4.8:
version "0.4.8"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
@@ -847,6 +849,11 @@ find-root@^1.1.0:
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
+format@^0.2.0:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
+ integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==
+
fraction.js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
@@ -916,7 +923,14 @@ hast-util-parse-selector@^3.0.0:
dependencies:
"@types/hast" "^2.0.0"
-hast-util-to-text@^3.1.0:
+hast-util-sanitize@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/hast-util-sanitize/-/hast-util-sanitize-4.1.0.tgz#d90f8521f5083547095c5c63a7e03150303e0286"
+ integrity sha512-Hd9tU0ltknMGRDv+d6Ro/4XKzBqQnP/EZrpiTbpFYfXv/uOhWeKc+2uajcbEvAEH98VZd7eII2PiXm13RihnLw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+
+hast-util-to-text@^3.0.0, hast-util-to-text@^3.1.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-3.1.2.tgz#ecf30c47141f41e91a5d32d0b1e1859fd2ac04f2"
integrity sha512-tcllLfp23dJJ+ju5wCCZHVpzsQQ43+moJbqVX3jNWPB7z/KFC4FyZD6R7y94cHL6MQ33YtMZL8Z0aIXXI4XFTw==
@@ -942,7 +956,7 @@ hastscript@^7.0.0:
property-information "^6.0.0"
space-separated-tokens "^2.0.0"
-highlight.js@^11.7.0:
+highlight.js@~11.7.0:
version "11.7.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e"
integrity sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==
@@ -1095,6 +1109,15 @@ loose-envify@^1.1.0, loose-envify@^1.4.0:
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
+lowlight@^2.0.0:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-2.8.1.tgz#5f54016ebd1b2f66b3d0b94d10ef6dd5df4f2e42"
+ integrity sha512-HCaGL61RKc1MYzEYn3rFoGkK0yslzCVDFJEanR19rc2L0mb8i58XM55jSRbzp9jcQrFzschPlwooC0vuNitk8Q==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ fault "^2.0.0"
+ highlight.js "~11.7.0"
+
markdown-table@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.3.tgz#e6331d30e493127e031dd385488b5bd326e4a6bd"
@@ -1829,6 +1852,17 @@ regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.7:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
+rehype-highlight@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/rehype-highlight/-/rehype-highlight-6.0.0.tgz#8097219d8813b51f4c2b6d92db27dac6cbc9a641"
+ integrity sha512-q7UtlFicLhetp7K48ZgZiJgchYscMma7XjzX7t23bqEJF8m6/s+viXQEe4oHjrATTIZpX7RG8CKD7BlNZoh9gw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ hast-util-to-text "^3.0.0"
+ lowlight "^2.0.0"
+ unified "^10.0.0"
+ unist-util-visit "^4.0.0"
+
rehype-katex@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/rehype-katex/-/rehype-katex-6.0.2.tgz#20197bbc10bdf79f6b999bffa6689d7f17226c35"
@@ -1853,6 +1887,15 @@ rehype-parse@^8.0.0:
parse5 "^6.0.0"
unified "^10.0.0"
+rehype-sanitize@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/rehype-sanitize/-/rehype-sanitize-5.0.1.tgz#dac01a7417bdd329260c74c74449697b4be5eb56"
+ integrity sha512-da/jIOjq8eYt/1r9GN6GwxIR3gde7OZ+WV8pheu1tL8K0D9KxM2AyMh+UEfke+FfdM3PvGHeYJU0Td5OWa7L5A==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ hast-util-sanitize "^4.0.0"
+ unified "^10.0.0"
+
remark-gfm@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-3.0.1.tgz#0b180f095e3036545e9dddac0e8df3fa5cfee54f"