markdown and textarea input
This commit is contained in:
parent
1105fbf6ec
commit
db61d3f7f9
1050
frontend/package-lock.json
generated
1050
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||||
|
@ -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
|
||||||
|
31
frontend/src/components/MarkdownRender.tsx
Normal file
31
frontend/src/components/MarkdownRender.tsx
Normal 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;
|
@ -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';
|
||||||
|
@ -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)}
|
||||||
|
@ -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
94
frontend/src/style.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user