Merge branch 'main' into beian

This commit is contained in:
Archer
2023-03-11 16:58:46 +08:00
30 changed files with 187 additions and 177 deletions

View File

@@ -43,7 +43,7 @@ docker run -d --network=host --name doc-gpt \
-e MY_MAIL=your email\ -e MY_MAIL=your email\
-e MAILE_CODE=your email code \ -e MAILE_CODE=your email code \
-e TOKEN_KEY=任意一个内容 \ -e TOKEN_KEY=任意一个内容 \
-e MONGODB_URI="mongodb://aha:ROOT_root123@127.0.0.0:27017/?authSource=admin&readPreference=primary&appname=MongoDB%20Compass&ssl=false" \ -e MONGODB_URI="mongodb://user:password@127.0.0.0:27017/?authSource=admin&readPreference=primary&appname=MongoDB%20Compass&ssl=false" \
imageName:tag imageName:tag
docker logs doc-gpt docker logs doc-gpt

View File

@@ -45,7 +45,6 @@
"sass": "^1.58.3", "sass": "^1.58.3",
"sharp": "^0.31.3", "sharp": "^0.31.3",
"tunnel": "^0.0.6", "tunnel": "^0.0.6",
"typescript": "4.9.5",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"zustand": "^4.3.5" "zustand": "^4.3.5"
}, },
@@ -63,7 +62,8 @@
"eslint-config-next": "13.1.6", "eslint-config-next": "13.1.6",
"husky": "^8.0.3", "husky": "^8.0.3",
"lint-staged": "^13.1.2", "lint-staged": "^13.1.2",
"prettier": "^2.8.4" "prettier": "^2.8.4",
"typescript": "4.9.5"
}, },
"lint-staged": { "lint-staged": {
"./src/**/*.{ts,tsx,scss}": "npm run format" "./src/**/*.{ts,tsx,scss}": "npm run format"

View File

@@ -34,7 +34,7 @@ function responseSuccess(response: AxiosResponse<ResponseDataType>) {
*/ */
function checkRes(data: ResponseDataType) { function checkRes(data: ResponseDataType) {
if (data === undefined) { if (data === undefined) {
console.error(data, 'data is empty'); console.log('error->', data, 'data is empty');
return Promise.reject('服务器异常'); return Promise.reject('服务器异常');
} else if (data.code < 200 || data.code >= 400) { } else if (data.code < 200 || data.code >= 400) {
return Promise.reject(data.message); return Promise.reject(data.message);
@@ -46,7 +46,7 @@ function checkRes(data: ResponseDataType) {
* 响应错误 * 响应错误
*/ */
function responseError(err: any) { function responseError(err: any) {
console.error('请求错误', err); console.log('error->', '请求错误', err);
if (!err) { if (!err) {
return Promise.reject({ message: '未知错误' }); return Promise.reject({ message: '未知错误' });

View File

@@ -39,7 +39,7 @@ const Auth = ({ children }: { children: JSX.Element }) => {
} }
}, },
onError(error) { onError(error) {
console.error(error); console.log('error->', error);
router.push('/login'); router.push('/login');
toast(); toast();
}, },

View File

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

View File

@@ -341,7 +341,7 @@
background-color: #f0f0f0; background-color: #f0f0f0;
border: 1px solid #cccccc; border: 1px solid #cccccc;
border-radius: 3px 3px 3px 3px; border-radius: 3px 3px 3px 3px;
font-size: 13px; font-size: max(0.9em, 14px);
line-height: 19px; line-height: 19px;
overflow: auto; overflow: auto;
padding: 6px 10px; padding: 6px 10px;
@@ -352,11 +352,12 @@
border: medium none; border: medium none;
} }
.markdown { .markdown {
font-size: 14px;
line-height: 1.6;
letter-spacing: 0.5px;
text-align: justify; text-align: justify;
word-break: break-all; word-break: break-all;
overflow-y: hidden;
tab-size: 4;
word-spacing: normal;
pre { pre {
display: block; display: block;
width: 100%; width: 100%;
@@ -372,7 +373,6 @@
background-color: #222 !important; background-color: #222 !important;
color: #fff; color: #fff;
width: 100%; 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 { a {

View File

@@ -1,8 +1,6 @@
import React, { memo, useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import styles from './index.module.scss';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { codeLight } from './codeLight';
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { useCopyData } from '@/utils/tools'; import { useCopyData } from '@/utils/tools';
import Icon from '@/components/Icon'; import Icon from '@/components/Icon';
@@ -10,8 +8,12 @@ import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math'; import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex'; 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 Markdown = ({ source, isChatting }: { source: string; isChatting: boolean }) => {
const formatSource = useMemo(() => source.replace(/\n/g, ' \n'), [source]); const formatSource = useMemo(() => source, [source]);
const { copyData } = useCopyData(); const { copyData } = useCopyData();
return ( return (
@@ -25,7 +27,8 @@ const Markdown = ({ source, isChatting }: { source: string; isChatting: boolean
pre: 'div', pre: 'div',
code({ node, inline, className, children, ...props }) { code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || ''); const match = /language-(\w+)/.exec(className || '');
const code = String(children).replace(/\n$/, ''); const code = String(children);
return !inline || match ? ( return !inline || match ? (
<Box my={3} borderRadius={'md'} overflow={'hidden'} backgroundColor={'#222'}> <Box my={3} borderRadius={'md'} overflow={'hidden'} backgroundColor={'#222'}>
<Flex <Flex
@@ -53,7 +56,7 @@ const Markdown = ({ source, isChatting }: { source: string; isChatting: boolean
</Box> </Box>
) : ( ) : (
<code className={className} {...props}> <code className={className} {...props}>
{children} {code}
</code> </code>
); );
} }

View File

@@ -20,24 +20,27 @@ const Button = defineStyleConfig({
baseStyle: {}, baseStyle: {},
sizes: { sizes: {
sm: { sm: {
fontSize: 'sm', fontSize: 'xs',
px: 3, px: 3,
py: 0, py: 0,
fontWeight: 'normal', fontWeight: 'normal',
height: '26px' height: '26px',
lineHeight: '26px'
}, },
md: { md: {
fontSize: 'md', fontSize: 'sm',
px: 6, px: 6,
py: 0, py: 0,
height: '34px', height: '34px',
lineHeight: '34px',
fontWeight: 'normal' fontWeight: 'normal'
}, },
lg: { lg: {
fontSize: 'lg', fontSize: 'md',
px: 8, px: 8,
py: 0, py: 0,
height: '42px', height: '42px',
lineHeight: '42px',
fontWeight: 'normal' fontWeight: 'normal'
} }
}, },
@@ -58,17 +61,12 @@ export const theme = extendTheme({
global: { global: {
'html, body': { 'html, body': {
color: 'blackAlpha.800', 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%', height: '100%',
overflowY: 'auto' maxHeight: '100vh',
overflowY: 'hidden'
} }
} }
}, },
fonts: {
body: 'system-ui, sans-serif'
},
fontSizes: { fontSizes: {
xs: '0.8rem', xs: '0.8rem',
sm: '0.9rem', sm: '0.9rem',

View File

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

View File

@@ -6,6 +6,7 @@ import { getOpenAIApi, authChat } from '@/service/utils/chat';
import { openaiProxy } from '@/service/utils/tools'; import { openaiProxy } from '@/service/utils/tools';
import { ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum } from 'openai'; import { ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum } from 'openai';
import { ChatItemType } from '@/types/chat'; import { ChatItemType } from '@/types/chat';
import { openaiError } from '@/service/errorCode';
/* 发送提示词 */ /* 发送提示词 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -50,13 +51,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const formatPrompts: ChatCompletionRequestMessage[] = filterPrompts.map( const formatPrompts: ChatCompletionRequestMessage[] = filterPrompts.map(
(item: ChatItemType) => ({ (item: ChatItemType) => ({
role: map[item.obj], role: map[item.obj],
content: item.value.replace(/\n/g, ' ') content: item.value
}) })
); );
// 第一句话,强调代码类型 // 第一句话,强调代码类型
formatPrompts.unshift({ formatPrompts.unshift({
role: ChatCompletionRequestMessageRoleEnum.System, role: ChatCompletionRequestMessageRoleEnum.System,
content: '如果你想返回代码,请务必声明代码的类型!' content: '如果你想返回代码,请务必声明代码的类型!并且在代码块前加一个换行符。'
}); });
// 获取 chatAPI // 获取 chatAPI
const chatAPI = getOpenAIApi(userApiKey); const chatAPI = getOpenAIApi(userApiKey);
@@ -74,7 +75,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
httpsAgent: openaiProxy?.httpsAgent httpsAgent: openaiProxy?.httpsAgent
} }
); );
console.log('response success'); console.log(
formatPrompts.reduce((sum, item) => sum + item.content.length, 0),
'response success'
);
let AIResponse = ''; let AIResponse = '';
@@ -95,54 +99,44 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
updateTime: Date.now() updateTime: Date.now()
}); });
res.write('event: done\ndata: \n\n'); res.write('event: done\ndata: \n\n');
res.end();
return; return;
} }
try { try {
const json = JSON.parse(data); const json = JSON.parse(data);
const content: string = json.choices[0].delta.content || ''; const content: string = json?.choices?.[0].delta.content || '\n';
// console.log('content:', content) // console.log('content:', content)
res.write(`event: responseData\ndata: ${content.replace(/\n/g, '<br/>')}\n\n`); res.write(`event: responseData\ndata: ${content.replace(/\n/g, '<br/>')}\n\n`);
AIResponse += content; AIResponse += content;
} catch (e) { } catch (error) {
res.end(); error;
} }
} }
}; };
const parser = createParser(onParse); try {
for await (const chunk of chatResponse.data as any) { for await (const chunk of chatResponse.data as any) {
parser.feed(decoder.decode(chunk)); const parser = createParser(onParse);
parser.feed(decoder.decode(chunk));
}
} catch (error) {
console.log(error, '====');
throw new Error('错误了');
} }
} catch (err: any) { } catch (err: any) {
let errorText = err; // console.log('error->', err?.response, '===');
let errorText = 'OpenAI 服务器访问超时';
if (err.code === 'ECONNRESET') { if (err.code === 'ECONNRESET') {
errorText = '服务器代理出错'; errorText = '服务器代理出错';
} else { } else if (err?.response?.statusText && openaiError[err.response.statusText]) {
switch (err?.response?.data?.error?.code) { errorText = openaiError[err.response.statusText];
case 'invalid_api_key':
errorText = 'API-KEY不合法';
break;
case 'context_length_exceeded':
errorText = '内容超长了,请重置对话';
break;
case 'rate_limit_reached':
errorText = '同时访问用户过多,请稍后再试';
break;
case null:
errorText = 'OpenAI 服务器访问超时';
break;
default:
errorText = '服务器异常';
}
} }
console.error(errorText); console.log('error->', errorText);
res.write(`event: serviceError\ndata: ${errorText}\n\n`); res.write(`event: serviceError\ndata: ${errorText}\n\n`);
res.end();
// 删除最一条数据库记录, 也就是预发送的那一条 // 删除最一条数据库记录, 也就是预发送的那一条
await ChatWindow.findByIdAndUpdate(windowId, { await ChatWindow.findByIdAndUpdate(windowId, {
$pop: { content: 1 }, $pop: { content: 1 },
updateTime: Date.now() updateTime: Date.now()
}); });
res.end();
} }
} }

View File

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

View File

@@ -10,7 +10,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const { authorization } = req.headers; const { authorization } = req.headers;
if (!authorization) { if (!authorization) {
throw new Error('缺少参数'); throw new Error('缺少登录凭证');
} }
const userId = await authToken(authorization); const userId = await authToken(authorization);

View File

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

@@ -61,13 +61,11 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
title: `密码已找回`, title: `密码已找回`,
status: 'success' status: 'success'
}); });
} catch (error) { } catch (error: any) {
typeof error === 'string' && toast({
toast({ title: error.message || '修改密码异常',
title: error, status: 'error'
status: 'error', });
position: 'top'
});
} }
setRequesting(false); setRequesting(false);
}, },

View File

@@ -42,13 +42,11 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
title: '登录成功', title: '登录成功',
status: 'success' status: 'success'
}); });
} catch (error) { } catch (error: any) {
typeof error === 'string' && toast({
toast({ title: error.message || '登录异常',
title: error, status: 'error'
status: 'error', });
position: 'top'
});
} }
setRequesting(false); setRequesting(false);
}, },

View File

@@ -61,14 +61,11 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
title: `注册成功`, title: `注册成功`,
status: 'success' status: 'success'
}); });
} catch (error) { } catch (error: any) {
typeof error === 'string' && toast({
toast({ title: error.message || '注册异常',
title: error, status: 'error'
status: 'error', });
duration: 4000,
isClosable: true
});
} }
setRequesting(false); setRequesting(false);
}, },

View File

@@ -1,4 +1,4 @@
import React, { useState, useCallback, useMemo } from 'react'; import React, { useState, useCallback, useEffect } from 'react';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { Box, Flex, Image } from '@chakra-ui/react'; import { Box, Flex, Image } from '@chakra-ui/react';
import { PageTypeEnum } from '@/constants/user'; import { PageTypeEnum } from '@/constants/user';
@@ -21,7 +21,7 @@ const Login = () => {
const loginSuccess = useCallback( const loginSuccess = useCallback(
(res: ResLogin) => { (res: ResLogin) => {
setUserInfo(res.user, res.token); setUserInfo(res.user, res.token);
router.push('/'); router.push('/model/list');
}, },
[router, setUserInfo] [router, setUserInfo]
); );
@@ -38,6 +38,10 @@ const Login = () => {
return <Component setPageType={setPageType} loginSuccess={loginSuccess} />; return <Component setPageType={setPageType} loginSuccess={loginSuccess} />;
} }
useEffect(() => {
router.prefetch('/model/list');
}, [router]);
return ( return (
<Box className={styles.loginPage} h={'100%'} p={isPc ? '10vh 10vw' : 0}> <Box className={styles.loginPage} h={'100%'} p={isPc ? '10vh 10vw' : 0}>
<Flex <Flex

View File

@@ -34,7 +34,7 @@ const ModelEditForm = ({ model }: { model?: ModelType }) => {
status: 'success' status: 'success'
}); });
} catch (err) { } catch (err) {
console.error(err); console.log('error->', err);
toast({ toast({
title: err as string, title: err as string,
status: 'success' status: 'success'

View File

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

View File

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

View File

@@ -29,7 +29,7 @@ const Training = ({ model }: { model: ModelType }) => {
const res = await getModelTrainings(id); const res = await getModelTrainings(id);
setRecords(res); setRecords(res);
} catch (error) { } catch (error) {
console.error(error); console.log('error->', error);
} }
}, []); }, []);

View File

@@ -42,14 +42,15 @@ const ModelDetail = () => {
res.security.expiredTime /= 60 * 60 * 1000; res.security.expiredTime /= 60 * 60 * 1000;
setModel(res); setModel(res);
} catch (err) { } catch (err) {
console.error(err); console.log('error->', err);
} }
setLoading(false); setLoading(false);
}, [modelId, setLoading]); }, [modelId, setLoading]);
useEffect(() => { useEffect(() => {
loadModel(); loadModel();
}, [loadModel, modelId]); router.prefetch('/chat');
}, [loadModel, modelId, router]);
/* 点击删除 */ /* 点击删除 */
const handleDelModel = useCallback(async () => { const handleDelModel = useCallback(async () => {
@@ -63,7 +64,7 @@ const ModelDetail = () => {
}); });
router.replace('/model/list'); router.replace('/model/list');
} catch (err) { } catch (err) {
console.error(err); console.log('error->', err);
} }
setLoading(false); setLoading(false);
}, [setLoading, model, router, toast]); }, [setLoading, model, router, toast]);
@@ -77,7 +78,7 @@ const ModelDetail = () => {
router.push(`/chat?chatId=${chatId}`); router.push(`/chat?chatId=${chatId}`);
} catch (err) { } catch (err) {
console.error(err); console.log('error->', err);
} }
setLoading(false); setLoading(false);
}, [setLoading, model, router]); }, [setLoading, model, router]);
@@ -105,7 +106,7 @@ const ModelDetail = () => {
title: typeof err === 'string' ? err : '文件格式错误', title: typeof err === 'string' ? err : '文件格式错误',
status: 'error' status: 'error'
}); });
console.error(err); console.log('error->', err);
} }
setLoading(false); setLoading(false);
}, },
@@ -120,11 +121,15 @@ const ModelDetail = () => {
try { try {
await putModelTrainingStatus(model._id); await putModelTrainingStatus(model._id);
loadModel(); loadModel();
} catch (error) { } catch (error: any) {
console.error(error); console.log('error->', error);
toast({
title: error.message || '更新失败',
status: 'error'
});
} }
setLoading(false); setLoading(false);
}, [setLoading, loadModel, model]); }, [model, setLoading, loadModel, toast]);
return ( return (
<> <>

View File

@@ -10,10 +10,12 @@ import { useScreen } from '@/hooks/useScreen';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useLoading } from '@/hooks/useLoading'; import { useLoading } from '@/hooks/useLoading';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useToast } from '@/hooks/useToast';
const CreateModel = dynamic(() => import('./components/CreateModel')); const CreateModel = dynamic(() => import('./components/CreateModel'));
const ModelList = () => { const ModelList = () => {
const { toast } = useToast();
const { isPc } = useScreen(); const { isPc } = useScreen();
const router = useRouter(); const router = useRouter();
const [models, setModels] = useState<ModelType[]>([]); const [models, setModels] = useState<ModelType[]>([]);
@@ -43,12 +45,16 @@ const ModelList = () => {
router.push(`/chat?chatId=${chatId}`, undefined, { router.push(`/chat?chatId=${chatId}`, undefined, {
shallow: true shallow: true
}); });
} catch (err) { } catch (err: any) {
console.error(err); console.log('error->', err);
toast({
title: err.message || '出现一些异常',
status: 'error'
});
} }
setIsLoading(false); setIsLoading(false);
}, },
[router, setIsLoading] [router, setIsLoading, toast]
); );
return ( return (

View File

@@ -1,3 +1,6 @@
export const openaiError: Record<string, string> = { export const openaiError: Record<string, string> = {
context_length_exceeded: '内容超出长度' context_length_exceeded: '内容超长了,请重置对话',
Unauthorized: 'API-KEY 不合法',
rate_limit_reached: '同时访问用户过多,请稍后再试',
'Bad Request': '内容太多了~'
}; };

View File

@@ -4,23 +4,23 @@ import mongoose from 'mongoose';
* 连接 MongoDB 数据库 * 连接 MongoDB 数据库
*/ */
export async function connectToDatabase(): Promise<void> { export async function connectToDatabase(): Promise<void> {
// @ts-ignore
if (global.mongodb) { if (global.mongodb) {
return; return;
} }
// @ts-ignore
global.mongodb = 'connecting'; global.mongodb = 'connecting';
console.log('connect mongo'); console.log('connect mongo');
try { try {
// @ts-ignore mongoose.set('strictQuery', true);
global.mongodb = await mongoose.connect(process.env.MONGODB_URI as string, { global.mongodb = await mongoose.connect(process.env.MONGODB_URI as string, {
bufferCommands: true,
dbName: 'doc_gpt', dbName: 'doc_gpt',
maxPoolSize: 10, maxPoolSize: 5,
minPoolSize: 1 minPoolSize: 1,
maxConnecting: 5
}); });
} catch (error) { } catch (error) {
console.error('mongo connect error'); console.log('error->', 'mongo connect error');
// @ts-ignore
global.mongodb = null; global.mongodb = null;
} }
} }

View File

@@ -27,8 +27,8 @@ export const jsonRes = (
msg = openaiError[error?.response?.data?.message]; msg = openaiError[error?.response?.data?.message];
} }
console.error(error); console.log('error->', error);
console.error(msg); console.log('error->', msg);
} }
res.json({ res.json({

View File

@@ -34,7 +34,7 @@ export const sendCode = (email: string, code: string, type: `${EmailTypeEnum}`)
}; };
mailTransport.sendMail(options, function (err, msg) { mailTransport.sendMail(options, function (err, msg) {
if (err) { if (err) {
console.error(err); console.log('error->', err);
reject('邮箱异常'); reject('邮箱异常');
} else { } else {
resolve(''); resolve('');
@@ -53,7 +53,7 @@ export const sendTrainSucceed = (email: string, modelName: string) => {
}; };
mailTransport.sendMail(options, function (err, msg) { mailTransport.sendMail(options, function (err, msg) {
if (err) { if (err) {
console.error(err); console.log('error->', err);
reject('邮箱异常'); reject('邮箱异常');
} else { } else {
resolve(''); resolve('');

View File

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

View File

@@ -1,9 +1,6 @@
import type { Mongoose } from 'mongoose'; import type { Mongoose } from 'mongoose';
declare global { declare global {
interface Global { var mongodb: Mongoose | string | null;
mongodb: Mongoose;
}
} }
export {};
export type a = string;

View File

@@ -21,7 +21,7 @@ export const useCopyData = () => {
duration: 1000 duration: 1000
}); });
} catch (error) { } catch (error) {
console.error(error); console.log('error->', error);
toast({ toast({
title: '复制失败', title: '复制失败',
status: 'error' status: 'error'