markdown and textarea input

This commit is contained in:
josc146 2023-05-19 15:40:17 +08:00
parent 1105fbf6ec
commit db61d3f7f9
8 changed files with 1201 additions and 44 deletions

File diff suppressed because it is too large Load Diff

View File

@ -13,15 +13,21 @@
"@fluentui/react-icons": "^2.0.201", "@fluentui/react-icons": "^2.0.201",
"@microsoft/fetch-event-source": "^2.0.1", "@microsoft/fetch-event-source": "^2.0.1",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"github-markdown-css": "^5.2.0",
"i18next": "^22.4.15", "i18next": "^22.4.15",
"mobx": "^6.9.0", "mobx": "^6.9.0",
"mobx-react-lite": "^3.4.3", "mobx-react-lite": "^3.4.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^12.2.2", "react-i18next": "^12.2.2",
"react-markdown": "^8.0.7",
"react-router": "^6.11.1", "react-router": "^6.11.1",
"react-router-dom": "^6.11.1", "react-router-dom": "^6.11.1",
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",
"rehype-highlight": "^6.0.0",
"rehype-raw": "^6.1.1",
"remark-breaks": "^3.0.3",
"remark-gfm": "^3.0.1",
"usehooks-ts": "^2.9.1", "usehooks-ts": "^2.9.1",
"uuid": "^9.0.0" "uuid": "^9.0.0"
}, },
@ -33,6 +39,7 @@
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"rollup-plugin-visualizer": "^5.9.0", "rollup-plugin-visualizer": "^5.9.0",
"sass": "^1.62.1",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"vite": "^4.3.6" "vite": "^4.3.6"

View File

@ -47,7 +47,9 @@ const App: FC = observer(() => {
useEffect(() => setPath(location.pathname), [location]); useEffect(() => setPath(location.pathname), [location]);
return ( return (
<FluentProvider theme={commonStore.settings.darkMode ? webDarkTheme : webLightTheme} className="h-screen"> <FluentProvider className="h-screen"
theme={commonStore.settings.darkMode ? webDarkTheme : webLightTheme}
data-theme={commonStore.settings.darkMode ? 'dark' : 'light'}>
<div className="flex h-full"> <div className="flex h-full">
<div className="flex flex-col w-16 sm:w-48 p-2 justify-between"> <div className="flex flex-col w-16 sm:w-48 p-2 justify-between">
<TabList <TabList

View File

@ -0,0 +1,31 @@
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import rehypeHighlight from 'rehype-highlight';
import remarkGfm from 'remark-gfm';
import remarkBreaks from 'remark-breaks';
import {FC} from 'react';
import {ReactMarkdownOptions} from 'react-markdown/lib/react-markdown';
export const MarkdownRender: FC<ReactMarkdownOptions> = (props) => {
return (
<div dir="auto" className="markdown-body">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkBreaks]}
rehypePlugins={[
rehypeRaw,
[
rehypeHighlight,
{
detect: true,
ignoreMissing: true
}
]
]}
>
{props.children}
</ReactMarkdown>
</div>
);
};
export default MarkdownRender;

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import {createRoot} from 'react-dom/client'; import {createRoot} from 'react-dom/client';
import './style.css'; import './style.scss';
import 'react-toastify/dist/ReactToastify.css'; import 'react-toastify/dist/ReactToastify.css';
import App from './App'; import App from './App';
import {HashRouter} from 'react-router-dom'; import {HashRouter} from 'react-router-dom';

View File

@ -1,7 +1,7 @@
import React, {FC, useRef, useState} from 'react'; import React, {FC, useEffect, useRef, useState} from 'react';
import {useTranslation} from 'react-i18next'; import {useTranslation} from 'react-i18next';
import {RunButton} from '../components/RunButton'; import {RunButton} from '../components/RunButton';
import {Avatar, Divider, Input, PresenceBadge, Text} from '@fluentui/react-components'; import {Avatar, Divider, PresenceBadge, Text, Textarea} from '@fluentui/react-components';
import commonStore, {ModelStatus} from '../stores/commonStore'; import commonStore, {ModelStatus} from '../stores/commonStore';
import {observer} from 'mobx-react-lite'; import {observer} from 'mobx-react-lite';
import {PresenceBadgeStatus} from '@fluentui/react-badge'; import {PresenceBadgeStatus} from '@fluentui/react-badge';
@ -11,6 +11,7 @@ import classnames from 'classnames';
import {fetchEventSource} from '@microsoft/fetch-event-source'; import {fetchEventSource} from '@microsoft/fetch-event-source';
import {ConversationPair, getConversationPairs, Record} from '../utils/get-conversation-pairs'; import {ConversationPair, getConversationPairs, Record} from '../utils/get-conversation-pairs';
import logo from '../../../build/appicon.png'; import logo from '../../../build/appicon.png';
import MarkdownRender from '../components/MarkdownRender';
const userName = 'M E'; const userName = 'M E';
const botName = 'A I'; const botName = 'A I';
@ -45,8 +46,14 @@ const ChatPanel: FC = observer(() => {
const [conversations, setConversations] = useState<Conversations>({}); const [conversations, setConversations] = useState<Conversations>({});
const [conversationsOrder, setConversationsOrder] = useState<string[]>([]); const [conversationsOrder, setConversationsOrder] = useState<string[]>([]);
const bodyRef = useRef<HTMLDivElement>(null); const bodyRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null);
const port = commonStore.getCurrentModelConfig().apiParameters.apiPort; const port = commonStore.getCurrentModelConfig().apiParameters.apiPort;
useEffect(() => {
if (inputRef.current)
inputRef.current.style.maxHeight = '16rem';
}, []);
const scrollToBottom = () => { const scrollToBottom = () => {
if (bodyRef.current) if (bodyRef.current)
bodyRef.current.scrollTop = bodyRef.current.scrollHeight; bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
@ -159,7 +166,7 @@ const ChatPanel: FC = observer(() => {
return <div return <div
key={uuid} key={uuid}
className={classnames( className={classnames(
'flex gap-2 mb-2', 'flex gap-2 mb-2 overflow-hidden',
conversation.side === 'left' ? 'flex-row' : 'flex-row-reverse' conversation.side === 'left' ? 'flex-row' : 'flex-row-reverse'
)} )}
> >
@ -170,12 +177,12 @@ const ChatPanel: FC = observer(() => {
/> />
<div <div
className={classnames( className={classnames(
'p-2 rounded-lg', 'p-2 rounded-lg overflow-hidden',
conversation.side === 'left' ? 'bg-gray-200' : 'bg-blue-500', conversation.side === 'left' ? 'bg-gray-200' : 'bg-blue-500',
conversation.side === 'left' ? 'text-gray-600' : 'text-white' conversation.side === 'left' ? 'text-gray-600' : 'text-white'
)} )}
> >
{conversation.content} <MarkdownRender>{conversation.content}</MarkdownRender>
</div> </div>
{(conversation.type === MessageType.Error || !conversation.done) && {(conversation.type === MessageType.Error || !conversation.done) &&
<PresenceBadge status={ <PresenceBadge status={
@ -185,17 +192,18 @@ const ChatPanel: FC = observer(() => {
</div>; </div>;
})} })}
</div> </div>
<div className="flex"> <div className="flex items-end">
<button className="bg-blue-500 text-white rounded-lg py-2 px-4 mr-2" onClick={() => { <button className="bg-blue-500 text-white rounded-lg py-2 px-4 mr-2" onClick={() => {
setConversations({}); setConversations({});
setConversationsOrder([]); setConversationsOrder([]);
}}> }}>
{t('Clear')} {t('Clear')}
</button> </button>
<form onSubmit={handleSubmit} className="flex grow"> <form onSubmit={handleSubmit} className="flex items-end grow gap-2">
<Input <Textarea
type="text" ref={inputRef}
className="grow mr-2" className="grow"
resize="vertical"
placeholder={t('Type your message here')!} placeholder={t('Type your message here')!}
value={message} value={message}
onChange={(e) => setMessage(e.target.value)} onChange={(e) => setMessage(e.target.value)}

View File

@ -1,29 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
overflow: hidden;
width: 100%;
height: 100%;
}
* {
scrollbar-width: thin;
}
/* Works on Chrome, Edge, and Safari */
*::-webkit-scrollbar {
width: 9px;
}
*::-webkit-scrollbar-thumb {
background-color: rgba(155, 155, 155, 0.5);
border-radius: 20px;
border: transparent;
}
*::-webkit-scrollbar-track {
background: transparent;
}

94
frontend/src/style.scss Normal file
View File

@ -0,0 +1,94 @@
[data-theme='dark'] {
@import 'highlight.js/scss/github-dark.scss';
@import 'github-markdown-css/github-markdown-dark.css';
--color-neutral-muted: rgba(110, 118, 129, 0.4);
}
[data-theme='light'] {
@import 'highlight.js/scss/github.scss';
@import 'github-markdown-css/github-markdown-light.css';
--color-neutral-muted: rgba(150, 160, 170, 0.3);
}
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
overflow: hidden;
width: 100%;
height: 100%;
}
* {
scrollbar-width: thin;
}
/* Works on Chrome, Edge, and Safari */
*::-webkit-scrollbar {
width: 9px;
}
*::-webkit-scrollbar-thumb {
background-color: rgba(155, 155, 155, 0.5);
border-radius: 20px;
border: transparent;
}
*::-webkit-scrollbar-track {
background: transparent;
}
*::-webkit-scrollbar-corner {
background: transparent;
}
.markdown-body {
overflow-y: auto;
overflow-x: hidden;
ul,
ol {
padding-left: 1.5em;
}
ol {
list-style: none;
counter-reset: item;
li {
counter-increment: item;
&::marker {
content: counter(item) '. ';
}
}
}
pre {
padding: 0;
code {
font-size: 14px;
}
}
p {
margin: 0 0 10px;
}
code {
padding: 0 0.4em;
margin: 0;
white-space: pre-wrap;
word-break: break-word;
border-radius: 8px;
background-color: var(--color-neutral-muted);
font-size: 11px;
.hljs {
padding: 0;
}
}
}