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

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

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

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

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[]) => {
@@ -165,13 +179,13 @@ const Chat = () => {
event.addEventListener('serviceError', ({ data: err }) => {
clearTimeout(timer);
event.close();
console.error(err, '===');
console.log('error->', err, '===');
reject(typeof err === 'string' ? err : '对话出现不知名错误~');
});
event.onerror = (err) => {
clearTimeout(timer);
event.close();
console.error(err);
console.log('error->', err);
reject(typeof err === 'string' ? err : '对话出现不知名错误~');
};
});
@@ -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}
@@ -360,11 +361,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>
@@ -438,3 +435,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

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

View File

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

View File

@@ -61,14 +61,11 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
title: `注册成功`,
status: 'success'
});
} catch (error) {
typeof error === 'string' &&
toast({
title: error,
status: 'error',
duration: 4000,
isClosable: true
});
} catch (error: any) {
toast({
title: error.message || '注册异常',
status: 'error'
});
}
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 { Box, Flex, Image } from '@chakra-ui/react';
import { PageTypeEnum } from '@/constants/user';
@@ -21,7 +21,7 @@ const Login = () => {
const loginSuccess = useCallback(
(res: ResLogin) => {
setUserInfo(res.user, res.token);
router.push('/');
router.push('/model/list');
},
[router, setUserInfo]
);
@@ -38,6 +38,10 @@ const Login = () => {
return <Component setPageType={setPageType} loginSuccess={loginSuccess} />;
}
useEffect(() => {
router.prefetch('/model/list');
}, [router]);
return (
<Box className={styles.loginPage} h={'100%'} p={isPc ? '10vh 10vw' : 0}>
<Flex

View File

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

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

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

View File

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

View File

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