feat: md数学表达式;perf: 字体样式;fix: 发送验证码错误提醒。聊天二次加载

This commit is contained in:
Archer
2023-03-11 16:56:27 +08:00
parent 9f96593136
commit fd8135f50c
12 changed files with 91 additions and 82 deletions

View File

@@ -2,9 +2,7 @@ import React from 'react';
export const codeLight: { [key: string]: React.CSSProperties } = {
'code[class*=language-]': {
color: '#d4d4d4',
fontSize: '13px',
textShadow: 'none',
fontFamily: 'Menlo,Monaco,Consolas,"Andale Mono","Ubuntu Mono","Courier New",monospace',
direction: 'ltr',
textAlign: 'left',
whiteSpace: 'pre',
@@ -21,9 +19,7 @@ export const codeLight: { [key: string]: React.CSSProperties } = {
},
'pre[class*=language-]': {
color: '#d4d4d4',
fontSize: '13px',
textShadow: 'none',
fontFamily: 'Menlo,Monaco,Consolas,"Andale Mono","Ubuntu Mono","Courier New",monospace',
direction: 'ltr',
textAlign: 'left',
whiteSpace: 'pre',

View File

@@ -341,7 +341,7 @@
background-color: #f0f0f0;
border: 1px solid #cccccc;
border-radius: 3px 3px 3px 3px;
font-size: 13px;
font-size: max(0.9em, 14px);
line-height: 19px;
overflow: auto;
padding: 6px 10px;
@@ -352,11 +352,12 @@
border: medium none;
}
.markdown {
font-size: 14px;
line-height: 1.6;
letter-spacing: 0.5px;
text-align: justify;
word-break: break-all;
overflow-y: hidden;
tab-size: 4;
word-spacing: normal;
pre {
display: block;
width: 100%;
@@ -372,7 +373,6 @@
background-color: #222 !important;
color: #fff;
width: 100%;
font-family: 'Söhne,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,Helvetica Neue,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji';
}
a {

View File

@@ -1,8 +1,6 @@
import React, { memo, useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import styles from './index.module.scss';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { codeLight } from './codeLight';
import { Box, Flex } from '@chakra-ui/react';
import { useCopyData } from '@/utils/tools';
import Icon from '@/components/Icon';
@@ -10,8 +8,12 @@ import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
import 'katex/dist/katex.min.css';
import styles from './index.module.scss';
import { codeLight } from './codeLight';
const Markdown = ({ source, isChatting }: { source: string; isChatting: boolean }) => {
const formatSource = useMemo(() => source.replace(/\n/g, ' \n'), [source]);
const formatSource = useMemo(() => source, [source]);
const { copyData } = useCopyData();
return (

View File

@@ -20,24 +20,27 @@ const Button = defineStyleConfig({
baseStyle: {},
sizes: {
sm: {
fontSize: 'sm',
fontSize: 'xs',
px: 3,
py: 0,
fontWeight: 'normal',
height: '26px'
height: '26px',
lineHeight: '26px'
},
md: {
fontSize: 'md',
fontSize: 'sm',
px: 6,
py: 0,
height: '34px',
lineHeight: '34px',
fontWeight: 'normal'
},
lg: {
fontSize: 'lg',
fontSize: 'md',
px: 8,
py: 0,
height: '42px',
lineHeight: '42px',
fontWeight: 'normal'
}
},
@@ -58,17 +61,12 @@ export const theme = extendTheme({
global: {
'html, body': {
color: 'blackAlpha.800',
fontSize: '14px',
fontFamily:
'Söhne,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,Helvetica Neue,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji',
height: '100%',
overflowY: 'auto'
maxHeight: '100vh',
overflowY: 'hidden'
}
}
},
fonts: {
body: 'system-ui, sans-serif'
},
fontSizes: {
xs: '0.8rem',
sm: '0.9rem',

View File

@@ -1,14 +1,11 @@
import { useState, useMemo, useCallback } from 'react';
import { sendCodeToEmail } from '@/api/user';
import { EmailTypeEnum } from '@/constants/common';
import { useToast } from '@chakra-ui/react';
let timer: any;
import { useToast } from './useToast';
export const useSendCode = () => {
const toast = useToast({
position: 'top',
duration: 2000
});
const { toast } = useToast();
const [codeSending, setCodeSending] = useState(false);
const [codeCountDown, setCodeCountDown] = useState(0);
const sendCodeText = useMemo(() => {
@@ -43,13 +40,11 @@ export const useSendCode = () => {
status: 'success',
position: 'top'
});
} catch (error) {
typeof error === 'string' &&
toast({
title: error,
status: 'error',
position: 'top'
});
} catch (error: any) {
toast({
title: error.message || '发送验证码异常',
status: 'error'
});
}
setCodeSending(false);
},

View File

@@ -20,7 +20,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (type === EmailTypeEnum.register) {
const maxCount = process.env.MAX_USER ? +process.env.MAX_USER : Infinity;
const userCount = await User.count();
if (userCount >= maxCount) {
throw new Error('当前注册用户已满,请等待名额~');
}

View File

@@ -22,11 +22,10 @@ const Markdown = dynamic(() => import('@/components/Markdown'));
const textareaMinH = '22px';
const Chat = () => {
const Chat = ({ chatId, windowId }: { chatId: string; windowId?: string }) => {
const { toast } = useToast();
const router = useRouter();
const { isPc, media } = useScreen();
const { chatId, windowId } = router.query as { chatId: string; windowId?: string };
const ChatBox = useRef<HTMLDivElement>(null);
const TextareaDom = useRef<HTMLTextAreaElement>(null);
@@ -40,7 +39,6 @@ const Chat = () => {
// 滚动到底部
const scrollToBottom = useCallback(() => {
// 滚动到底部
setTimeout(() => {
ChatBox.current &&
ChatBox.current.scrollTo({
@@ -52,16 +50,14 @@ const Chat = () => {
// 初始化聊天框
useQuery(
[chatId, windowId],
['initData'],
() => {
if (!chatId) return null;
setLoading(true);
return getInitChatSiteInfo(chatId, windowId);
},
{
cacheTime: 5 * 60 * 1000,
onSuccess(res) {
if (!res) return;
// 可能没有 windowId给它设置一下
router.replace(`/chat?chatId=${chatId}&windowId=${res.windowId}`);
setChatSiteData(res.chatSite);
@@ -72,7 +68,6 @@ const Chat = () => {
}))
);
scrollToBottom();
setLoading(false);
},
onError(e: any) {
toast({
@@ -81,11 +76,30 @@ const Chat = () => {
isClosable: true,
duration: 5000
});
},
onSettled() {
setLoading(false);
}
}
);
// 重置输入内容
const resetInputVal = useCallback((val: string) => {
setInputVal(val);
setTimeout(() => {
/* 回到最小高度 */
if (TextareaDom.current) {
TextareaDom.current.style.height =
val === '' ? textareaMinH : `${TextareaDom.current.scrollHeight}px`;
}
}, 100);
}, []);
// 重载对话
const resetChat = useCallback(() => {
window.open(`/chat?chatId=${chatId}`, '_self');
}, [chatId]);
// gpt3 方法
const gpt3ChatPrompt = useCallback(
async (newChatList: ChatSiteItemType[]) => {
@@ -210,16 +224,8 @@ const Chat = () => {
// 插入内容
setChatList(newChatList);
setInputVal('');
// 滚动到底部
setTimeout(() => {
scrollToBottom();
/* 回到最小高度 */
if (TextareaDom.current) {
TextareaDom.current.style.height = textareaMinH;
}
}, 100);
resetInputVal('');
scrollToBottom();
const fnMap: { [key: string]: any } = {
[OpenAiModelEnum.GPT35]: chatGPTPrompt,
@@ -239,13 +245,13 @@ const Chat = () => {
}
} catch (err) {
toast({
title: typeof err === 'string' ? err : '聊天已过期',
title: typeof err === 'string' ? err : '聊天出错了~',
status: 'warning',
duration: 5000,
isClosable: true
});
setInputVal(storeInput);
resetInputVal(storeInput);
setChatList(newChatList.slice(0, newChatList.length - 2));
}
@@ -256,6 +262,7 @@ const Chat = () => {
gpt3ChatPrompt,
inputVal,
isChatting,
resetInputVal,
scrollToBottom,
toast
]);
@@ -267,16 +274,10 @@ const Chat = () => {
await delLastMessage(windowId);
const val = chatList[chatList.length - 1].value;
setInputVal(val);
resetInputVal(val);
setChatList(chatList.slice(0, -1));
setTimeout(() => {
if (TextareaDom.current) {
TextareaDom.current.style.height = val.split('\n').length * 22 + 'px';
}
}, 100);
}, [chatList, windowId]);
}, [chatList, resetInputVal, windowId]);
return (
<Flex height={'100%'} flexDirection={'column'}>
@@ -290,13 +291,9 @@ const Chat = () => {
zIndex={1}
>
<Box flex={1}>{chatSiteData?.name}</Box>
{/* 重置按键 */}
<Box cursor={'pointer'} onClick={() => router.replace(`/chat?chatId=${chatId}`)}>
<Icon name={'icon-zhongzhi'} width={20} height={20} color={'#718096'}></Icon>
</Box>
{/* 滚动到底部按键 */}
{ChatBox.current && ChatBox.current.scrollHeight > 2 * ChatBox.current.clientHeight && (
<Box ml={10} cursor={'pointer'} onClick={scrollToBottom}>
<Box mr={10} cursor={'pointer'} onClick={scrollToBottom}>
<Icon
name={'icon-xiangxiazhankai-xianxingyuankuang'}
width={25}
@@ -305,6 +302,10 @@ const Chat = () => {
></Icon>
</Box>
)}
{/* 重置按键 */}
<Button size={'sm'} colorScheme={'gray'} onClick={resetChat}>
</Button>
</Flex>
{/* 聊天内容 */}
<Box ref={ChatBox} flex={'1 0 0'} h={0} w={'100%'} px={0} pb={10} overflowY={'auto'}>
@@ -312,7 +313,7 @@ const Chat = () => {
<Box
key={index}
py={media(9, 6)}
px={media(4, 3)}
px={media(4, 2)}
backgroundColor={index % 2 === 0 ? 'rgba(247,247,248,1)' : '#fff'}
borderBottom={'1px solid rgba(0,0,0,0.1)'}
>
@@ -321,11 +322,11 @@ const Chat = () => {
<Image
src={item.obj === 'Human' ? '/icon/human.png' : '/icon/logo.png'}
alt="/icon/logo.png"
width={30}
height={30}
width={media(30, 20)}
height={media(30, 20)}
/>
</Box>
<Box flex={'1 0 0'} w={0} overflowX={'hidden'}>
<Box flex={'1 0 0'} w={0} overflow={'hidden'}>
{item.obj === 'AI' ? (
<Markdown
source={item.value}
@@ -350,11 +351,7 @@ const Chat = () => {
<Box textAlign={'center'}>
<Box color={'red'}></Box>
<Flex py={5} justifyContent={'center'}>
<Button
mr={20}
onClick={() => router.replace(`/chat?chatId=${chatId}`)}
colorScheme={'green'}
>
<Button mr={20} onClick={resetChat} colorScheme={'green'}>
</Button>
<Button onClick={reEdit}></Button>
@@ -428,3 +425,12 @@ const Chat = () => {
};
export default Chat;
export async function getServerSideProps(context: any) {
const chatId = context.query?.chatId || '';
const windowId = context.query?.windowId || '';
return {
props: { chatId, windowId }
};
}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Box, Button, Flex, Heading, Tag } from '@chakra-ui/react';
import React, { useEffect } from 'react';
import { Box, Button, Flex, Tag } from '@chakra-ui/react';
import type { ModelType } from '@/types/model';
import { formatModelStatus } from '@/constants/model';
import dayjs from 'dayjs';
@@ -14,6 +14,10 @@ const ModelPhoneList = ({
}) => {
const router = useRouter();
useEffect(() => {
router.prefetch('/chat');
}, [router]);
return (
<Box borderRadius={'md'} overflow={'hidden'} mb={5}>
{models.map((model) => (

View File

@@ -1,4 +1,4 @@
import React from 'react';
import { useEffect } from 'react';
import {
Button,
Table,
@@ -84,6 +84,10 @@ const ModelTable = ({
}
];
useEffect(() => {
router.prefetch('/chat');
}, [router]);
return (
<Card py={3}>
<TableContainer>

View File

@@ -49,7 +49,8 @@ const ModelDetail = () => {
useEffect(() => {
loadModel();
}, [loadModel, modelId]);
router.prefetch('/chat');
}, [loadModel, modelId, router]);
/* 点击删除 */
const handleDelModel = useCallback(async () => {

View File

@@ -11,6 +11,7 @@ export async function connectToDatabase(): Promise<void> {
global.mongodb = 'connecting';
console.log('connect mongo');
try {
mongoose.set('strictQuery', true);
global.mongodb = await mongoose.connect(process.env.MONGODB_URI as string, {
bufferCommands: true,
dbName: 'doc_gpt',

View File

@@ -50,6 +50,9 @@ svg {
}
@media (max-width: 900px) {
html {
font-size: 14px;
}
::-webkit-scrollbar,
::-webkit-scrollbar {
width: 2px;