feat: 模型介绍和温度调整。完善聊天页提示

This commit is contained in:
Archer
2023-03-18 12:32:55 +08:00
parent 1c364eca35
commit 00b90f071d
32 changed files with 628 additions and 327 deletions

View File

@@ -1,16 +1,16 @@
import { GET, POST, DELETE, PUT } from './request'; import { GET, POST, DELETE, PUT } from './request';
import type { ModelType } from '@/types/model'; import type { ModelSchema } from '@/types/mongoSchema';
import { ModelUpdateParams } from '@/types/model'; import { ModelUpdateParams } from '@/types/model';
import { TrainingItemType } from '../types/training'; import { TrainingItemType } from '../types/training';
export const getMyModels = () => GET<ModelType[]>('/model/list'); export const getMyModels = () => GET<ModelSchema[]>('/model/list');
export const postCreateModel = (data: { name: string; serviceModelName: string }) => export const postCreateModel = (data: { name: string; serviceModelName: string }) =>
POST<ModelType>('/model/create', data); POST<ModelSchema>('/model/create', data);
export const delModelById = (id: string) => DELETE(`/model/del?modelId=${id}`); export const delModelById = (id: string) => DELETE(`/model/del?modelId=${id}`);
export const getModelById = (id: string) => GET<ModelType>(`/model/detail?modelId=${id}`); export const getModelById = (id: string) => GET<ModelSchema>(`/model/detail?modelId=${id}`);
export const putModelById = (id: string, data: ModelUpdateParams) => export const putModelById = (id: string, data: ModelUpdateParams) =>
PUT(`/model/update?modelId=${id}`, data); PUT(`/model/update?modelId=${id}`, data);

View File

@@ -6,6 +6,7 @@ export type InitChatResponse = {
modelId: string; modelId: string;
name: string; name: string;
avatar: string; avatar: string;
intro: string;
secret: ModelSchema.secret; secret: ModelSchema.secret;
chatModel: ModelSchema.service.ChatModel; // 模型名 chatModel: ModelSchema.service.ChatModel; // 模型名
history: ChatItemType[]; history: ChatItemType[];

View File

@@ -172,7 +172,7 @@
} }
.markdown ul, .markdown ul,
.markdown ol { .markdown ol {
padding-left: 30px; padding-left: 1em;
} }
.markdown ul.no-list, .markdown ul.no-list,
.markdown ol.no-list { .markdown ol.no-list {

View File

@@ -12,7 +12,7 @@ import 'katex/dist/katex.min.css';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { codeLight } from './codeLight'; import { codeLight } from './codeLight';
const Markdown = ({ source, isChatting }: { source: string; isChatting: boolean }) => { const Markdown = ({ source, isChatting = false }: { source: string; isChatting?: boolean }) => {
const formatSource = useMemo(() => source, [source]); const formatSource = useMemo(() => source, [source]);
const { copyData } = useCopyData(); const { copyData } = useCopyData();

View File

@@ -0,0 +1,82 @@
import React, { useMemo } from 'react';
import {
Slider,
SliderTrack,
SliderFilledTrack,
SliderThumb,
SliderMark,
Box
} from '@chakra-ui/react';
const MySlider = ({
markList,
setVal,
activeVal,
max = 100,
min = 0,
step = 1
}: {
markList: {
label: string | number;
value: number;
}[];
activeVal?: number;
setVal: (index: number) => void;
max?: number;
min?: number;
step?: number;
}) => {
const startEndPointStyle = {
content: '""',
borderRadius: '10px',
width: '10px',
height: '10px',
backgroundColor: '#ffffff',
border: '2px solid #D7DBE2',
position: 'absolute',
zIndex: 1,
top: 0,
transform: 'translateY(-3px)'
};
const value = useMemo(() => {
const index = markList.findIndex((item) => item.value === activeVal);
return index > -1 ? index : 0;
}, [activeVal, markList]);
return (
<Slider max={max} min={min} step={step} size={'lg'} value={value} onChange={setVal}>
{markList.map((item, i) => (
<SliderMark
key={item.value}
value={i}
mt={3}
fontSize={'sm'}
transform={'translateX(-50%)'}
{...(activeVal === item.value ? { color: 'blue.500', fontWeight: 'bold' } : {})}
>
<Box px={3} cursor={'pointer'}>
{item.label}
</Box>
</SliderMark>
))}
<SliderTrack
bg={'#EAEDF3'}
overflow={'visible'}
h={'4px'}
_before={{
...startEndPointStyle,
left: '-5px'
}}
_after={{
...startEndPointStyle,
right: '-5px'
}}
>
<SliderFilledTrack />
</SliderTrack>
<SliderThumb border={'2.5px solid'} borderColor={'blue.500'}></SliderThumb>
</Slider>
);
};
export default MySlider;

View File

@@ -39,3 +39,25 @@ export const introPage = `
### 其他问题 ### 其他问题
还有其他问题,可以加我 wx: YNyiqi拉个交流群大家一起聊聊。 还有其他问题,可以加我 wx: YNyiqi拉个交流群大家一起聊聊。
`; `;
export const chatProblem = `
**代理出错**
服务器代理不稳定,可以过一会儿再尝试。
**API key 问题**
请把 openai 的 API key 粘贴到账号里再创建对话。如果是使用分享的对话,不需要填写 API key。
`;
export const versionIntro = `
* 分享对话:使用的是分享者的 Api Key 生成一个对话窗口进行分享。
* 分享空白对话:为该模型创建一个空白的聊天分享出去。
* 分享当前对话:会把当前聊天的内容也分享出去,但是要注意不要多个人同时用一个聊天内容。
* 增加模型介绍:可以在模型编辑页添加对模型的介绍,方便提示模型的范围。
* 温度调整:可以在模型编辑页调整模型温度,以便适应不同类型的对话。例如,翻译类的模型可以把温度拉低;创作类的模型可以把温度拉高。
`;
export const shareHint = `
你正准备分享对话,请确保分享链接不会滥用,因为它是使用的是你的 API key。
* 分享空白对话:为该模型创建一个空白的聊天分享出去。
* 分享当前对话:会把当前聊天的内容也分享出去,但是要注意不要多个人同时用一个聊天内容。
`;

View File

@@ -1,23 +1,37 @@
import type { ServiceName } from '@/types/mongoSchema';
import { ModelSchema } from '../types/mongoSchema';
export enum ChatModelNameEnum { export enum ChatModelNameEnum {
GPT35 = 'gpt-3.5-turbo', GPT35 = 'gpt-3.5-turbo',
GPT3 = 'text-davinci-003' GPT3 = 'text-davinci-003'
} }
export const OpenAiList = [
{ export type ModelConstantsData = {
name: 'chatGPT', name: string;
model: ChatModelNameEnum.GPT35, model: `${ChatModelNameEnum}`;
trainName: 'turbo', trainName: string; // 空字符串代表不能训练
canTraining: false, maxToken: number;
maxToken: 4060 maxTemperature: number;
}, };
{
name: 'GPT3', export const ModelList: Record<ServiceName, ModelConstantsData[]> = {
model: ChatModelNameEnum.GPT3, openai: [
trainName: 'davinci', {
canTraining: true, name: 'chatGPT',
maxToken: 4060 model: ChatModelNameEnum.GPT35,
} trainName: 'turbo',
]; maxToken: 4000,
maxTemperature: 2
},
{
name: 'GPT3',
model: ChatModelNameEnum.GPT3,
trainName: 'davinci',
maxToken: 4000,
maxTemperature: 2
}
]
};
export enum TrainingStatusEnum { export enum TrainingStatusEnum {
pending = 'pending', pending = 'pending',
@@ -51,3 +65,29 @@ export const formatModelStatus = {
text: '已关闭' text: '已关闭'
} }
}; };
export const defaultModel: ModelSchema = {
_id: '',
userId: '',
name: '',
avatar: '',
status: ModelStatusEnum.pending,
updateTime: Date.now(),
trainingTimes: 0,
systemPrompt: '',
intro: '',
temperature: 5,
service: {
company: 'openai',
trainId: '',
chatModel: ChatModelNameEnum.GPT35,
modelName: ChatModelNameEnum.GPT35
},
security: {
domain: ['*'],
contextMaxLen: 1,
contentMaxLen: 1,
expiredTime: 9999,
maxLoadAmount: 1
}
};

View File

@@ -8,6 +8,7 @@ import { ChatItemType } from '@/types/chat';
import { jsonRes } from '@/service/response'; import { jsonRes } from '@/service/response';
import type { ModelSchema } from '@/types/mongoSchema'; import type { ModelSchema } from '@/types/mongoSchema';
import { PassThrough } from 'stream'; import { PassThrough } from 'stream';
import { ModelList } from '@/constants/model';
/* 发送提示词 */ /* 发送提示词 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -56,6 +57,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}); });
} }
// 计算温度
const modelConstantsData = ModelList['openai'].find(
(item) => item.model === model.service.modelName
);
if (!modelConstantsData) {
throw new Error('模型异常');
}
const temperature = modelConstantsData.maxTemperature * (model.temperature / 10);
// 获取 chatAPI // 获取 chatAPI
const chatAPI = getOpenAIApi(userApiKey); const chatAPI = getOpenAIApi(userApiKey);
let startTime = Date.now(); let startTime = Date.now();
@@ -63,8 +73,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const chatResponse = await chatAPI.createChatCompletion( const chatResponse = await chatAPI.createChatCompletion(
{ {
model: model.service.chatModel, model: model.service.chatModel,
temperature: 1, temperature: temperature,
// max_tokens: model.security.contentMaxLen, max_tokens: modelConstantsData.maxToken,
messages: formatPrompts, messages: formatPrompts,
stream: true stream: true
}, },

View File

@@ -5,6 +5,7 @@ import { connectToDatabase } from '@/service/mongo';
import { getOpenAIApi, authChat } from '@/service/utils/chat'; import { getOpenAIApi, authChat } from '@/service/utils/chat';
import { ChatItemType } from '@/types/chat'; import { ChatItemType } from '@/types/chat';
import { httpsAgent } from '@/service/utils/tools'; import { httpsAgent } from '@/service/utils/tools';
import { ModelList } from '@/constants/model';
/* 发送提示词 */ /* 发送提示词 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -27,13 +28,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// prompt处理 // prompt处理
const formatPrompt = prompt.map((item) => `${item.value}\n\n###\n\n`).join(''); const formatPrompt = prompt.map((item) => `${item.value}\n\n###\n\n`).join('');
// 计算温度
const modelConstantsData = ModelList['openai'].find(
(item) => item.model === model.service.modelName
);
if (!modelConstantsData) {
throw new Error('模型异常');
}
const temperature = modelConstantsData.maxTemperature * (model.temperature / 10);
// 发送请求 // 发送请求
const response = await chatAPI.createCompletion( const response = await chatAPI.createCompletion(
{ {
model: model.service.modelName, model: model.service.modelName,
prompt: formatPrompt, prompt: formatPrompt,
temperature: 0.5, temperature: temperature,
max_tokens: model.security.contentMaxLen, max_tokens: modelConstantsData.maxToken,
top_p: 1, top_p: 1,
frequency_penalty: 0, frequency_penalty: 0,
presence_penalty: 0.6, presence_penalty: 0.6,

View File

@@ -47,6 +47,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
modelId: model._id, modelId: model._id,
name: model.name, name: model.name,
avatar: model.avatar, avatar: model.avatar,
intro: model.intro,
secret: model.security, secret: model.security,
chatModel: model.service.chatModel, chatModel: model.service.chatModel,
history: chat.content history: chat.content

View File

@@ -3,12 +3,21 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response'; import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/tools'; import { authToken } from '@/service/utils/tools';
import { ModelStatusEnum, OpenAiList } from '@/constants/model'; import { ModelStatusEnum, ModelList, ChatModelNameEnum } from '@/constants/model';
import type { ServiceName } from '@/types/mongoSchema';
import { Model } from '@/service/models/model'; import { Model } from '@/service/models/model';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
const { name, serviceModelName, serviceModelCompany = 'openai' } = req.body; const {
name,
serviceModelName,
serviceModelCompany = 'openai'
} = req.body as {
name: string;
serviceModelName: `${ChatModelNameEnum}`;
serviceModelCompany: ServiceName;
};
const { authorization } = req.headers; const { authorization } = req.headers;
if (!authorization) { if (!authorization) {
@@ -22,10 +31,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 凭证校验 // 凭证校验
const userId = await authToken(authorization); const userId = await authToken(authorization);
const modelItem = OpenAiList.find((item) => item.model === serviceModelName); const modelItem = ModelList[serviceModelCompany].find(
(item) => item.model === serviceModelName
);
if (!modelItem) { if (!modelItem) {
throw new Error('模型错误'); throw new Error('模型不存在');
} }
await connectToDatabase(); await connectToDatabase();
@@ -43,8 +54,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const authCount = await Model.countDocuments({ const authCount = await Model.countDocuments({
userId userId
}); });
if (authCount >= 10) { if (authCount >= 20) {
throw new Error('上限 10 个模型'); throw new Error('上限 20 个模型');
} }
// 创建模型 // 创建模型

View File

@@ -3,7 +3,7 @@ import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/tools'; import { authToken } from '@/service/utils/tools';
import { Model } from '@/service/models/model'; import { Model } from '@/service/models/model';
import { ModelType } from '@/types/model'; import type { ModelSchema } from '@/types/mongoSchema';
/* 获取我的模型 */ /* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -26,7 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await connectToDatabase(); await connectToDatabase();
// 根据 userId 获取模型信息 // 根据 userId 获取模型信息
const model: ModelType | null = await Model.findOne({ const model = await Model.findOne<ModelSchema>({
userId, userId,
_id: modelId _id: modelId
}); });

View File

@@ -6,7 +6,7 @@ import formidable from 'formidable';
import { authToken, getUserOpenaiKey } from '@/service/utils/tools'; import { authToken, getUserOpenaiKey } from '@/service/utils/tools';
import { join } from 'path'; import { join } from 'path';
import fs from 'fs'; import fs from 'fs';
import type { ModelType } from '@/types/model'; import type { ModelSchema } from '@/types/mongoSchema';
import type { OpenAIApi } from 'openai'; import type { OpenAIApi } from 'openai';
import { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model'; import { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model';
import { httpsAgent } from '@/service/utils/tools'; import { httpsAgent } from '@/service/utils/tools';

View File

@@ -3,7 +3,7 @@ import { jsonRes } from '@/service/response';
import { connectToDatabase, Model, Training } from '@/service/mongo'; import { connectToDatabase, Model, Training } from '@/service/mongo';
import { getOpenAIApi } from '@/service/utils/chat'; import { getOpenAIApi } from '@/service/utils/chat';
import { authToken, getUserOpenaiKey } from '@/service/utils/tools'; import { authToken, getUserOpenaiKey } from '@/service/utils/tools';
import type { ModelType } from '@/types/model'; import type { ModelSchema } from '@/types/mongoSchema';
import { TrainingItemType } from '@/types/training'; import { TrainingItemType } from '@/types/training';
import { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model'; import { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model';
import { OpenAiTuneStatusEnum } from '@/service/constants/training'; import { OpenAiTuneStatusEnum } from '@/service/constants/training';
@@ -26,7 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase(); await connectToDatabase();
// 获取模型 // 获取模型
const model: ModelType | null = await Model.findById(modelId); const model = await Model.findById<ModelSchema>(modelId);
if (!model || model.status !== 'training') { if (!model || model.status !== 'training') {
throw new Error('模型不在训练中'); throw new Error('模型不在训练中');

View File

@@ -7,7 +7,7 @@ import formidable from 'formidable';
import { authToken, getUserOpenaiKey } from '@/service/utils/tools'; import { authToken, getUserOpenaiKey } from '@/service/utils/tools';
import { join } from 'path'; import { join } from 'path';
import fs from 'fs'; import fs from 'fs';
import type { ModelType } from '@/types/model'; import type { ModelSchema } from '@/types/mongoSchema';
import type { OpenAIApi } from 'openai'; import type { OpenAIApi } from 'openai';
import { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model'; import { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model';
import { httpsAgent } from '@/service/utils/tools'; import { httpsAgent } from '@/service/utils/tools';
@@ -38,7 +38,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase(); await connectToDatabase();
// 获取模型的状态 // 获取模型的状态
const model: ModelType | null = await Model.findById(modelId); const model = await Model.findById<ModelSchema>(modelId);
if (!model || model.status !== 'running') { if (!model || model.status !== 'running') {
throw new Error('模型正忙'); throw new Error('模型正忙');

View File

@@ -8,7 +8,8 @@ import type { ModelUpdateParams } from '@/types/model';
/* 获取我的模型 */ /* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
const { name, service, security, systemPrompt } = req.body as ModelUpdateParams; const { name, service, security, systemPrompt, intro, temperature } =
req.body as ModelUpdateParams;
const { modelId } = req.query as { modelId: string }; const { modelId } = req.query as { modelId: string };
const { authorization } = req.headers; const { authorization } = req.headers;
@@ -33,8 +34,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}, },
{ {
name, name,
service,
systemPrompt, systemPrompt,
intro,
temperature,
service,
security security
} }
); );

View File

@@ -1,23 +1,44 @@
import React from 'react'; import React from 'react';
import { Card, Flex, Box } from '@chakra-ui/react'; import { Card, Box, Mark } from '@chakra-ui/react';
import { versionIntro, chatProblem } from '@/constants/common';
import Markdown from '@/components/Markdown';
const Empty = () => { const Empty = ({ intro }: { intro: string }) => {
const Header = ({ children }: { children: string }) => (
<Box fontSize={'lg'} fontWeight={'bold'} textAlign={'center'} pb={2}>
{children}
</Box>
);
return ( return (
<Flex h={'100%'} alignItems={'center'} justifyContent={'center'}> <Box
<Card p={5} w={'70%'}> minH={'100%'}
<Box fontSize={'xl'} fontWeight={'bold'} textAlign={'center'} pb={2}> w={'85%'}
Fast Gpt version1.3 maxW={'600px'}
</Box> m={'auto'}
<Box> py={'5vh'}
alignItems={'center'}
</Box> justifyContent={'center'}
<Box>使 Api Key </Box> >
<br /> {!!intro && (
<Box></Box> <Card p={4} mb={10}>
<br /> <Header></Header>
<Box>使</Box> <Box>{intro}</Box>
</Card>
)}
<Card p={4} mb={10}>
<Header></Header>
<Markdown source={chatProblem} />
</Card> </Card>
</Flex> {/* version intro */}
<Card p={4}>
<Header>Fast Gpt version1.4</Header>
<Box>
</Box>
<br />
<Markdown source={versionIntro} />
</Card>
</Box>
); );
}; };

View File

@@ -1,7 +1,8 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Box, Button } from '@chakra-ui/react'; import { AddIcon, ChatIcon, DeleteIcon } from '@chakra-ui/icons';
import { AddIcon, ChatIcon, EditIcon, DeleteIcon } from '@chakra-ui/icons';
import { import {
Box,
Button,
Accordion, Accordion,
AccordionItem, AccordionItem,
AccordionButton, AccordionButton,
@@ -9,16 +10,26 @@ import {
AccordionIcon, AccordionIcon,
Flex, Flex,
Divider, Divider,
IconButton IconButton,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
useDisclosure
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useUserStore } from '@/store/user'; import { useUserStore } from '@/store/user';
import { useChatStore } from '@/store/chat'; import { useChatStore } from '@/store/chat';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useScreen } from '@/hooks/useScreen';
import { getToken } from '@/utils/user'; import { getToken } from '@/utils/user';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import { useCopyData } from '@/utils/tools'; import { useCopyData } from '@/utils/tools';
import Markdown from '@/components/Markdown';
import { shareHint } from '@/constants/common';
import { getChatSiteId } from '@/api/chat';
const SlideBar = ({ const SlideBar = ({
name, name,
@@ -36,10 +47,13 @@ const SlideBar = ({
const router = useRouter(); const router = useRouter();
const { copyData } = useCopyData(); const { copyData } = useCopyData();
const { myModels, getMyModels } = useUserStore(); const { myModels, getMyModels } = useUserStore();
const { chatHistory, removeChatHistoryByWindowId, generateChatWindow, updateChatHistory } = const { chatHistory, removeChatHistoryByWindowId } = useChatStore();
useChatStore();
const { isSuccess } = useQuery(['init'], getMyModels);
const [hasReady, setHasReady] = useState(false); const [hasReady, setHasReady] = useState(false);
const { isOpen: isOpenShare, onOpen: onOpenShare, onClose: onCloseShare } = useDisclosure();
const { isSuccess } = useQuery(['init'], getMyModels, {
cacheTime: 5 * 60 * 1000
});
useEffect(() => { useEffect(() => {
setHasReady(true); setHasReady(true);
@@ -119,19 +133,8 @@ const SlideBar = ({
{/* 我的模型 & 历史记录 折叠框*/} {/* 我的模型 & 历史记录 折叠框*/}
<Box flex={'1 0 0'} px={3} h={0} overflowY={'auto'}> <Box flex={'1 0 0'} px={3} h={0} overflowY={'auto'}>
{isSuccess ? ( <Accordion defaultIndex={[0]} allowToggle>
<Accordion defaultIndex={[0]} allowToggle> {isSuccess && (
<AccordionItem borderTop={0} borderBottom={0}>
<AccordionButton borderRadius={'md'} pl={1}>
<Box as="span" flex="1" textAlign="left">
</Box>
<AccordionIcon />
</AccordionButton>
<AccordionPanel pb={0} px={0}>
{hasReady && <RenderHistory />}
</AccordionPanel>
</AccordionItem>
<AccordionItem borderTop={0} borderBottom={0}> <AccordionItem borderTop={0} borderBottom={0}>
<AccordionButton borderRadius={'md'} pl={1}> <AccordionButton borderRadius={'md'} pl={1}>
<Box as="span" flex="1" textAlign="left"> <Box as="span" flex="1" textAlign="left">
@@ -161,7 +164,7 @@ const SlideBar = ({
: {})} : {})}
onClick={async () => { onClick={async () => {
if (item.name === name) return; if (item.name === name) return;
router.push(`/chat?chatId=${await generateChatWindow(item._id)}`); router.push(`/chat?chatId=${await getChatSiteId(item._id)}`);
onClose(); onClose();
}} }}
> >
@@ -173,43 +176,25 @@ const SlideBar = ({
))} ))}
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
</Accordion> )}
) : ( <AccordionItem borderTop={0} borderBottom={0}>
<> <AccordionButton borderRadius={'md'} pl={1}>
<Box mb={4} textAlign={'center'}> <Box as="span" flex="1" textAlign="left">
</Box> </Box>
<RenderHistory /> <AccordionIcon />
</> </AccordionButton>
)} <AccordionPanel pb={0} px={0}>
{hasReady && <RenderHistory />}
</AccordionPanel>
</AccordionItem>
</Accordion>
</Box> </Box>
<Divider my={4} /> <Divider my={4} />
<Box px={3}> <Box px={3}>
{/* 分享 */}
{getToken() && (
<Flex
alignItems={'center'}
p={2}
cursor={'pointer'}
borderRadius={'md'}
_hover={{
backgroundColor: 'rgba(255,255,255,0.2)'
}}
onClick={async () => {
copyData(
`${location.origin}/chat?chatId=${await generateChatWindow(modelId)}`,
'已复制分享链接'
);
}}
>
<MyIcon name="share" fill={'white'} w={'16px'} h={'16px'} mr={4} />
</Flex>
)}
<Flex <Flex
mt={4}
alignItems={'center'} alignItems={'center'}
p={2} p={2}
cursor={'pointer'} cursor={'pointer'}
@@ -217,14 +202,57 @@ const SlideBar = ({
_hover={{ _hover={{
backgroundColor: 'rgba(255,255,255,0.2)' backgroundColor: 'rgba(255,255,255,0.2)'
}} }}
onClick={async () => { onClick={() => {
copyData(`${location.origin}/chat?chatId=${chatId}`, '已复制分享链接'); onOpenShare();
onClose();
}} }}
> >
<MyIcon name="share" fill={'white'} w={'16px'} h={'16px'} mr={4} /> <MyIcon name="share" fill={'white'} w={'16px'} h={'16px'} mr={4} />
</Flex> </Flex>
</Box> </Box>
{/* 分享提示modal */}
<Modal isOpen={isOpenShare} onClose={onCloseShare}>
<ModalOverlay />
<ModalContent>
<ModalHeader></ModalHeader>
<ModalCloseButton />
<ModalBody>
<Markdown source={shareHint} />
</ModalBody>
<ModalFooter>
<Button colorScheme="gray" variant={'outline'} mr={3} onClick={onCloseShare}>
Close
</Button>
{getToken() && (
<Button
variant="outline"
mr={3}
onClick={async () => {
copyData(
`${location.origin}/chat?chatId=${await getChatSiteId(modelId)}`,
'已复制分享链接'
);
onCloseShare();
}}
>
</Button>
)}
<Button
onClick={() => {
copyData(`${location.origin}/chat?chatId=${chatId}`, '已复制分享链接');
onCloseShare();
}}
>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</Flex> </Flex>
); );
}; };

View File

@@ -51,6 +51,7 @@ const Chat = ({ chatId }: { chatId: string }) => {
modelId: '', modelId: '',
name: '', name: '',
avatar: '', avatar: '',
intro: '',
secret: {}, secret: {},
chatModel: '', chatModel: '',
history: [], history: [],
@@ -113,7 +114,11 @@ const Chat = ({ chatId }: { chatId: string }) => {
status: 'finish' status: 'finish'
})) }))
}); });
scrollToBottom(); if (res.history.length > 0) {
setTimeout(() => {
scrollToBottom();
}, 500);
}
}, },
onError(e: any) { onError(e: any) {
toast({ toast({
@@ -433,7 +438,7 @@ const Chat = ({ chatId }: { chatId: string }) => {
</Flex> </Flex>
</Box> </Box>
))} ))}
{chatData.history.length === 0 && <Empty />} {chatData.history.length === 0 && <Empty intro={chatData.intro} />}
</Box> </Box>
{/* 发送区 */} {/* 发送区 */}
<Box <Box

View File

@@ -16,8 +16,8 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { postCreateModel } from '@/api/model'; import { postCreateModel } from '@/api/model';
import { ModelType } from '@/types/model'; import type { ModelSchema } from '@/types/mongoSchema';
import { OpenAiList } from '@/constants/model'; import { ModelList } from '@/constants/model';
interface CreateFormType { interface CreateFormType {
name: string; name: string;
@@ -29,7 +29,7 @@ const CreateModel = ({
onSuccess onSuccess
}: { }: {
setCreateModelOpen: Dispatch<boolean>; setCreateModelOpen: Dispatch<boolean>;
onSuccess: Dispatch<ModelType>; onSuccess: Dispatch<ModelSchema>;
}) => { }) => {
const [requesting, setRequesting] = useState(false); const [requesting, setRequesting] = useState(false);
const toast = useToast({ const toast = useToast({
@@ -42,7 +42,7 @@ const CreateModel = ({
formState: { errors } formState: { errors }
} = useForm<CreateFormType>({ } = useForm<CreateFormType>({
defaultValues: { defaultValues: {
serviceModelName: OpenAiList[0].model serviceModelName: ModelList['openai'][0].model
} }
}); });
@@ -95,7 +95,7 @@ const CreateModel = ({
required: '底层模型不能为空' required: '底层模型不能为空'
})} })}
> >
{OpenAiList.map((item) => ( {ModelList['openai'].map((item) => (
<option key={item.model} value={item.model}> <option key={item.model} value={item.model}>
{item.name} {item.name}
</option> </option>

View File

@@ -1,86 +1,37 @@
import React, { useCallback, useEffect, useRef } from 'react'; import React, { useState } from 'react';
import { Grid, Box, Card, Flex, Button, FormControl, Input, Textarea } from '@chakra-ui/react'; import {
import type { ModelType } from '@/types/model'; Box,
import { useForm } from 'react-hook-form'; Card,
import { useToast } from '@/hooks/useToast'; Flex,
import { putModelById } from '@/api/model'; FormControl,
import { useScreen } from '@/hooks/useScreen'; Input,
import { useGlobalStore } from '@/store/global'; Textarea,
Slider,
SliderTrack,
SliderFilledTrack,
SliderThumb,
SliderMark,
Tooltip
} from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import type { ModelSchema } from '@/types/mongoSchema';
import { UseFormReturn } from 'react-hook-form';
const ModelEditForm = ({ model }: { model?: ModelType }) => { const ModelEditForm = ({ formHooks }: { formHooks: UseFormReturn<ModelSchema> }) => {
const isInit = useRef(false); const { register, setValue, getValues } = formHooks;
const { const [refresh, setRefresh] = useState(false);
register,
handleSubmit,
reset,
formState: { errors }
} = useForm<ModelType>();
const { setLoading } = useGlobalStore();
const { toast } = useToast();
const { media } = useScreen();
const onclickSave = useCallback(
async (data: ModelType) => {
setLoading(true);
try {
await putModelById(data._id, {
name: data.name,
systemPrompt: data.systemPrompt,
service: data.service,
security: data.security
});
toast({
title: '更新成功',
status: 'success'
});
} catch (err) {
console.log('error->', err);
toast({
title: err as string,
status: 'success'
});
}
setLoading(false);
},
[setLoading, toast]
);
const submitError = useCallback(() => {
// deep search message
const deepSearch = (obj: any): string => {
if (!obj) return '提交表单错误';
if (!!obj.message) {
return obj.message;
}
return deepSearch(Object.values(obj)[0]);
};
toast({
title: deepSearch(errors),
status: 'error',
duration: 4000,
isClosable: true
});
}, [errors, toast]);
/* model 只会改变一次 */
useEffect(() => {
if (model && !isInit.current) {
reset(model);
isInit.current = true;
}
}, [model, reset]);
return ( return (
<Grid gridTemplateColumns={media('1fr 1fr', '1fr')} gridGap={5}> <>
<Card p={4}> <Card p={4}>
<Flex justifyContent={'space-between'} alignItems={'center'}> <Flex justifyContent={'space-between'} alignItems={'center'}>
<Box fontWeight={'bold'} fontSize={'lg'}> <Box fontWeight={'bold'}></Box>
</Box>
<Button onClick={handleSubmit(onclickSave, submitError)}></Button>
</Flex> </Flex>
<FormControl mt={5}> <FormControl mt={4}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box flex={'0 0 80px'}>:</Box> <Box flex={'0 0 50px'} w={0}>
:
</Box>
<Input <Input
{...register('name', { {...register('name', {
required: '展示名称不能为空' required: '展示名称不能为空'
@@ -88,30 +39,79 @@ const ModelEditForm = ({ model }: { model?: ModelType }) => {
></Input> ></Input>
</Flex> </Flex>
</FormControl> </FormControl>
<FormControl mt={5}> <FormControl mt={4}>
<Flex alignItems={'center'}> <Box mb={1}>:</Box>
<Box flex={'0 0 80px'}>:</Box>
<Box>{model?.service.modelName}</Box>
</Flex>
</FormControl>
<FormControl mt={5}>
<Textarea <Textarea
rows={4} rows={5}
maxLength={500} maxLength={500}
{...register('systemPrompt')} {...register('intro')}
placeholder={ placeholder={'模型的介绍,仅做展示,不影响模型的效果'}
'模型默认的 prompt 词,可以通过调整该内容,生成一个限定范围的模型,更方便的去使用。'
}
/> />
</FormControl> </FormControl>
</Card> </Card>
<Card p={4}> <Card p={4}>
<Box fontWeight={'bold'} fontSize={'lg'}> <Box fontWeight={'bold'}></Box>
<FormControl mt={4}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'} w={0}>
<Box as={'span'} mr={2}>
</Box>
<Tooltip label={'温度越高,模型的发散能力越强;温度越低,内容越严谨。'}>
<QuestionOutlineIcon />
</Tooltip>
</Box>
<Slider
aria-label="slider-ex-1"
min={1}
max={10}
step={1}
value={getValues('temperature')}
onChange={(e) => {
setValue('temperature', e);
setRefresh(!refresh);
}}
>
<SliderMark
value={getValues('temperature')}
textAlign="center"
bg="blue.500"
color="white"
w={'18px'}
h={'18px'}
borderRadius={'100px'}
fontSize={'xs'}
transform={'translate(-50%, -200%)'}
>
{getValues('temperature')}
</SliderMark>
<SliderTrack>
<SliderFilledTrack />
</SliderTrack>
<SliderThumb />
</Slider>
</Flex>
</FormControl>
<Box mt={4}>
<Box mb={1}></Box>
<Textarea
rows={6}
maxLength={500}
{...register('systemPrompt')}
placeholder={
'模型默认的 prompt 词,通过调整该内容,可以生成一个限定范围的模型。\n\n注意改功能会影响对话的整体朝向'
}
/>
</Box> </Box>
</Card>
<Card p={4}>
<Box fontWeight={'bold'}></Box>
<FormControl mt={2}> <FormControl mt={2}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box flex={'0 0 120px'}>:</Box> <Box flex={'0 0 120px'} w={0}>
:
</Box>
<Input <Input
flex={1} flex={1}
type={'number'} type={'number'}
@@ -132,7 +132,9 @@ const ModelEditForm = ({ model }: { model?: ModelType }) => {
</FormControl> </FormControl>
<FormControl mt={5}> <FormControl mt={5}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box flex={'0 0 120px'}>:</Box> <Box flex={'0 0 120px'} w={0}>
:
</Box>
<Input <Input
flex={1} flex={1}
type={'number'} type={'number'}
@@ -153,7 +155,9 @@ const ModelEditForm = ({ model }: { model?: ModelType }) => {
</FormControl> </FormControl>
<FormControl mt={5}> <FormControl mt={5}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box flex={'0 0 120px'}>:</Box> <Box flex={'0 0 120px'} w={0}>
:
</Box>
<Input <Input
flex={1} flex={1}
type={'number'} type={'number'}
@@ -175,7 +179,9 @@ const ModelEditForm = ({ model }: { model?: ModelType }) => {
</FormControl> </FormControl>
<FormControl mt={5} pb={5}> <FormControl mt={5} pb={5}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box flex={'0 0 130px'}>:</Box> <Box flex={'0 0 130px'} w={0}>
:
</Box>
<Box flex={1}> <Box flex={1}>
<Input <Input
type={'number'} type={'number'}
@@ -196,7 +202,7 @@ const ModelEditForm = ({ model }: { model?: ModelType }) => {
</Flex> </Flex>
</FormControl> </FormControl>
</Card> </Card>
</Grid> </>
); );
}; };

View File

@@ -1,6 +1,6 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Box, Button, Flex, Tag } from '@chakra-ui/react'; import { Box, Button, Flex, Tag } from '@chakra-ui/react';
import type { ModelType } from '@/types/model'; import type { ModelSchema } from '@/types/mongoSchema';
import { formatModelStatus } from '@/constants/model'; import { formatModelStatus } from '@/constants/model';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
@@ -9,7 +9,7 @@ const ModelPhoneList = ({
models, models,
handlePreviewChat handlePreviewChat
}: { }: {
models: ModelType[]; models: ModelSchema[];
handlePreviewChat: (_: string) => void; handlePreviewChat: (_: string) => void;
}) => { }) => {
const router = useRouter(); const router = useRouter();

View File

@@ -14,14 +14,14 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { formatModelStatus } from '@/constants/model'; import { formatModelStatus } from '@/constants/model';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import type { ModelType } from '@/types/model'; import type { ModelSchema } from '@/types/mongoSchema';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
const ModelTable = ({ const ModelTable = ({
models = [], models = [],
handlePreviewChat handlePreviewChat
}: { }: {
models: ModelType[]; models: ModelSchema[];
handlePreviewChat: (_: string) => void; handlePreviewChat: (_: string) => void;
}) => { }) => {
const router = useRouter(); const router = useRouter();
@@ -34,13 +34,13 @@ const ModelTable = ({
{ {
title: '最后更新时间', title: '最后更新时间',
key: 'updateTime', key: 'updateTime',
render: (item: ModelType) => dayjs(item.updateTime).format('YYYY-MM-DD HH:mm') render: (item: ModelSchema) => dayjs(item.updateTime).format('YYYY-MM-DD HH:mm')
}, },
{ {
title: '状态', title: '状态',
key: 'status', key: 'status',
dataIndex: 'status', dataIndex: 'status',
render: (item: ModelType) => ( render: (item: ModelSchema) => (
<Tag <Tag
colorScheme={formatModelStatus[item.status].colorTheme} colorScheme={formatModelStatus[item.status].colorTheme}
variant="solid" variant="solid"
@@ -54,7 +54,7 @@ const ModelTable = ({
{ {
title: 'AI模型', title: 'AI模型',
key: 'service', key: 'service',
render: (item: ModelType) => ( render: (item: ModelSchema) => (
<Box wordBreak={'break-all'} whiteSpace={'pre-wrap'} maxW={'200px'}> <Box wordBreak={'break-all'} whiteSpace={'pre-wrap'} maxW={'200px'}>
{item.service.modelName} {item.service.modelName}
</Box> </Box>
@@ -68,7 +68,7 @@ const ModelTable = ({
{ {
title: '操作', title: '操作',
key: 'control', key: 'control',
render: (item: ModelType) => ( render: (item: ModelSchema) => (
<> <>
<Button mr={3} onClick={() => handlePreviewChat(item._id)}> <Button mr={3} onClick={() => handlePreviewChat(item._id)}>

View File

@@ -1,10 +1,10 @@
import React, { useEffect, useCallback, useState } from 'react'; import React, { useEffect, useCallback, useState } from 'react';
import { Box, TableContainer, Table, Thead, Tbody, Tr, Th, Td } from '@chakra-ui/react'; import { Box, TableContainer, Table, Thead, Tbody, Tr, Th, Td } from '@chakra-ui/react';
import { ModelType } from '@/types/model'; import type { ModelSchema } from '@/types/mongoSchema';
import { getModelTrainings } from '@/api/model'; import { getModelTrainings } from '@/api/model';
import type { TrainingItemType } from '@/types/training'; import type { TrainingItemType } from '@/types/training';
const Training = ({ model }: { model: ModelType }) => { const Training = ({ model }: { model: ModelSchema }) => {
const columns: { const columns: {
title: string; title: string;
key: keyof TrainingItemType; key: keyof TrainingItemType;

View File

@@ -1,21 +1,29 @@
import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react'; import React, { useCallback, useState, useRef, useMemo, useEffect } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { getModelById, delModelById, postTrainModel, putModelTrainingStatus } from '@/api/model'; import {
getModelById,
delModelById,
postTrainModel,
putModelTrainingStatus,
putModelById
} from '@/api/model';
import { getChatSiteId } from '@/api/chat'; import { getChatSiteId } from '@/api/chat';
import type { ModelType } from '@/types/model'; import type { ModelSchema } from '@/types/mongoSchema';
import { Card, Box, Flex, Button, Tag, Grid } from '@chakra-ui/react'; import { Card, Box, Flex, Button, Tag, Grid } from '@chakra-ui/react';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { useConfirm } from '@/hooks/useConfirm'; import { useConfirm } from '@/hooks/useConfirm';
import { formatModelStatus, ModelStatusEnum, OpenAiList } from '@/constants/model'; import { useForm } from 'react-hook-form';
import { formatModelStatus, ModelStatusEnum, ModelList, defaultModel } from '@/constants/model';
import { useGlobalStore } from '@/store/global'; import { useGlobalStore } from '@/store/global';
import { useScreen } from '@/hooks/useScreen'; import { useScreen } from '@/hooks/useScreen';
import ModelEditForm from './components/ModelEditForm'; import ModelEditForm from './components/ModelEditForm';
import Icon from '@/components/Iconfont'; import Icon from '@/components/Iconfont';
import { useQuery } from '@tanstack/react-query';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
const Training = dynamic(() => import('./components/Training')); const Training = dynamic(() => import('./components/Training'));
const ModelDetail = () => { const ModelDetail = ({ modelId }: { modelId: string }) => {
const { toast } = useToast(); const { toast } = useToast();
const router = useRouter(); const router = useRouter();
const { isPc, media } = useScreen(); const { isPc, media } = useScreen();
@@ -24,33 +32,35 @@ const ModelDetail = () => {
content: '确认删除该模型?' content: '确认删除该模型?'
}); });
const SelectFileDom = useRef<HTMLInputElement>(null); const SelectFileDom = useRef<HTMLInputElement>(null);
const [model, setModel] = useState<ModelSchema>(defaultModel);
const { modelId } = router.query as { modelId: string }; const formHooks = useForm<ModelSchema>({
const [model, setModel] = useState<ModelType>(); defaultValues: model
});
const canTrain = useMemo(() => { const canTrain = useMemo(() => {
const openai = OpenAiList.find((item) => item.model === model?.service.modelName); const openai = ModelList[model.service.company].find(
return openai && openai.canTraining === true; (item) => item.model === model?.service.modelName
);
return openai && openai.trainName;
}, [model]); }, [model]);
/* 加载模型数据 */ /* 加载模型数据 */
const loadModel = useCallback(async () => { const loadModel = useCallback(async () => {
if (!modelId) return;
setLoading(true); setLoading(true);
try { try {
const res = await getModelById(modelId as string); const res = await getModelById(modelId);
console.log(res);
res.security.expiredTime /= 60 * 60 * 1000; res.security.expiredTime /= 60 * 60 * 1000;
setModel(res); setModel(res);
formHooks.reset(res);
} catch (err) { } catch (err) {
console.log('error->', err); console.log('error->', err);
} }
setLoading(false); setLoading(false);
}, [modelId, setLoading]); return null;
}, [formHooks, modelId, setLoading]);
useEffect(() => { useQuery([modelId], loadModel);
loadModel();
router.prefetch('/chat');
}, [loadModel, modelId, router]);
/* 点击删除 */ /* 点击删除 */
const handleDelModel = useCallback(async () => { const handleDelModel = useCallback(async () => {
@@ -71,7 +81,6 @@ const ModelDetail = () => {
/* 点前往聊天预览页 */ /* 点前往聊天预览页 */
const handlePreviewChat = useCallback(async () => { const handlePreviewChat = useCallback(async () => {
if (!model) return;
setLoading(true); setLoading(true);
try { try {
const chatId = await getChatSiteId(model._id); const chatId = await getChatSiteId(model._id);
@@ -131,6 +140,65 @@ const ModelDetail = () => {
setLoading(false); setLoading(false);
}, [model, setLoading, loadModel, toast]); }, [model, setLoading, loadModel, toast]);
// 提交保存模型修改
const saveSubmitSuccess = useCallback(
async (data: ModelSchema) => {
setLoading(true);
try {
await putModelById(data._id, {
name: data.name,
systemPrompt: data.systemPrompt,
intro: data.intro,
temperature: data.temperature,
service: data.service,
security: data.security
});
toast({
title: '更新成功',
status: 'success'
});
} catch (err) {
console.log('error->', err);
toast({
title: err as string,
status: 'success'
});
}
setLoading(false);
},
[setLoading, toast]
);
// 提交保存表单失败
const saveSubmitError = useCallback(() => {
// deep search message
const deepSearch = (obj: any): string => {
if (!obj) return '提交表单错误';
if (!!obj.message) {
return obj.message;
}
return deepSearch(Object.values(obj)[0]);
};
toast({
title: deepSearch(formHooks.formState.errors),
status: 'error',
duration: 4000,
isClosable: true
});
}, [formHooks.formState.errors, toast]);
useEffect(() => {
router.prefetch('/chat');
window.onbeforeunload = (e) => {
e.preventDefault();
e.returnValue = '内容已修改,确认离开页面吗?';
};
return () => {
window.onbeforeunload = null;
};
}, []);
return ( return (
<> <>
{/* 头部 */} {/* 头部 */}
@@ -138,50 +206,48 @@ const ModelDetail = () => {
{isPc ? ( {isPc ? (
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box fontSize={'xl'} fontWeight={'bold'}> <Box fontSize={'xl'} fontWeight={'bold'}>
{model?.name || '模型'} {model.name}
</Box> </Box>
{!!model && ( <Tag
<Tag ml={2}
ml={2} variant="solid"
variant="solid" colorScheme={formatModelStatus[model.status].colorTheme}
colorScheme={formatModelStatus[model.status].colorTheme} cursor={model.status === ModelStatusEnum.training ? 'pointer' : 'default'}
cursor={model.status === ModelStatusEnum.training ? 'pointer' : 'default'} onClick={handleClickUpdateStatus}
onClick={handleClickUpdateStatus} >
> {formatModelStatus[model.status].text}
{formatModelStatus[model.status].text} </Tag>
</Tag>
)}
<Box flex={1} /> <Box flex={1} />
<Button variant={'outline'} onClick={handlePreviewChat}> <Button variant={'outline'} onClick={handlePreviewChat}>
</Button> </Button>
<Button ml={4} onClick={formHooks.handleSubmit(saveSubmitSuccess, saveSubmitError)}>
</Button>
</Flex> </Flex>
) : ( ) : (
<> <>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box as={'h3'} fontSize={'xl'} fontWeight={'bold'} flex={1}> <Box as={'h3'} fontSize={'xl'} fontWeight={'bold'} flex={1}>
{model?.name || '模型'} {model?.name}
</Box> </Box>
{!!model && ( <Tag ml={2} colorScheme={formatModelStatus[model.status].colorTheme}>
<Tag ml={2} colorScheme={formatModelStatus[model.status].colorTheme}> {formatModelStatus[model.status].text}
{formatModelStatus[model.status].text} </Tag>
</Tag>
)}
</Flex> </Flex>
<Box mt={4} textAlign={'right'}> <Box mt={4} textAlign={'right'}>
<Button variant={'outline'} onClick={handlePreviewChat}> <Button variant={'outline'} onClick={handlePreviewChat}>
</Button> </Button>
<Button ml={4} onClick={formHooks.handleSubmit(saveSubmitSuccess, saveSubmitError)}>
</Button>
</Box> </Box>
</> </>
)} )}
</Card> </Card>
{/* 基本信息编辑 */}
<Box mt={5}>
<ModelEditForm model={model} />
</Box>
{/* 其他配置 */}
<Grid mt={5} gridTemplateColumns={media('1fr 1fr', '1fr')} gridGap={5}> <Grid mt={5} gridTemplateColumns={media('1fr 1fr', '1fr')} gridGap={5}>
<ModelEditForm formHooks={formHooks} />
<Card p={4}>{!!model && <Training model={model} />}</Card> <Card p={4}>{!!model && <Training model={model} />}</Card>
<Card p={4}> <Card p={4}>
<Box fontWeight={'bold'} fontSize={'lg'}> <Box fontWeight={'bold'} fontSize={'lg'}>
@@ -241,6 +307,8 @@ const ModelDetail = () => {
</Flex> </Flex>
</Card> </Card>
</Grid> </Grid>
{/* 文件选择 */}
<Box position={'absolute'} w={0} h={0} overflow={'hidden'}> <Box position={'absolute'} w={0} h={0} overflow={'hidden'}>
<input ref={SelectFileDom} type="file" accept=".jsonl" onChange={startTraining} /> <input ref={SelectFileDom} type="file" accept=".jsonl" onChange={startTraining} />
</Box> </Box>
@@ -250,3 +318,11 @@ const ModelDetail = () => {
}; };
export default ModelDetail; export default ModelDetail;
export async function getServerSideProps(context: any) {
const modelId = context.query?.modelId || '';
return {
props: { modelId }
};
}

View File

@@ -1,7 +1,7 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { Box, Button, Flex, Card } from '@chakra-ui/react'; import { Box, Button, Flex, Card } from '@chakra-ui/react';
import { getChatSiteId } from '@/api/chat'; import { getChatSiteId } from '@/api/chat';
import { ModelType } from '@/types/model'; import type { ModelSchema } from '@/types/mongoSchema';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import ModelTable from './components/ModelTable'; import ModelTable from './components/ModelTable';
import ModelPhoneList from './components/ModelPhoneList'; import ModelPhoneList from './components/ModelPhoneList';
@@ -27,7 +27,7 @@ const ModelList = () => {
/* 创建成功回调 */ /* 创建成功回调 */
const createModelSuccess = useCallback( const createModelSuccess = useCallback(
(data: ModelType) => { (data: ModelSchema) => {
setMyModels([data, ...myModels]); setMyModels([data, ...myModels]);
}, },
[myModels, setMyModels] [myModels, setMyModels]

View File

@@ -25,19 +25,22 @@ const ChatSchema = new Schema({
type: Number, type: Number,
required: true required: true
}, },
content: [ content: {
{ type: [
obj: { {
type: String, obj: {
required: true, type: String,
enum: ['Human', 'AI', 'SYSTEM'] required: true,
}, enum: ['Human', 'AI', 'SYSTEM']
value: { },
type: String, value: {
required: true type: String,
required: true
}
} }
} ],
] default: []
}
}); });
export const Chat = models['chat'] || model('chat', ChatSchema); export const Chat = models['chat'] || model('chat', ChatSchema);

View File

@@ -1,6 +1,11 @@
import { Schema, model, models } from 'mongoose'; import { Schema, model, models } from 'mongoose';
const ModelSchema = new Schema({ const ModelSchema = new Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
},
name: { name: {
type: String, type: String,
required: true required: true
@@ -10,13 +15,14 @@ const ModelSchema = new Schema({
default: '/imgs/modelAvatar.png' default: '/imgs/modelAvatar.png'
}, },
systemPrompt: { systemPrompt: {
// 系统提示词
type: String, type: String,
default: '' default: ''
}, },
userId: { intro: {
type: Schema.Types.ObjectId, // 模型介绍
ref: 'user', type: String,
required: true default: ''
}, },
status: { status: {
type: String, type: String,
@@ -31,6 +37,12 @@ const ModelSchema = new Schema({
type: Number, type: Number,
default: 0 default: 0
}, },
temperature: {
type: Number,
min: 1,
max: 10,
default: 5
},
service: { service: {
company: { company: {
type: String, type: String,

View File

@@ -2,7 +2,7 @@ import { create } from 'zustand';
import { devtools } from 'zustand/middleware'; import { devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
import type { UserType, UserUpdateParams } from '@/types/user'; import type { UserType, UserUpdateParams } from '@/types/user';
import type { ModelType } from '@/types/model'; import type { ModelSchema } from '@/types/mongoSchema';
import { setToken } from '@/utils/user'; import { setToken } from '@/utils/user';
import { getMyModels } from '@/api/model'; import { getMyModels } from '@/api/model';
@@ -10,9 +10,9 @@ type State = {
userInfo: UserType | null; userInfo: UserType | null;
setUserInfo: (user: UserType, token?: string) => void; setUserInfo: (user: UserType, token?: string) => void;
updateUserInfo: (user: UserUpdateParams) => void; updateUserInfo: (user: UserUpdateParams) => void;
myModels: ModelType[]; myModels: ModelSchema[];
getMyModels: () => void; getMyModels: () => void;
setMyModels: (data: ModelType[]) => void; setMyModels: (data: ModelSchema[]) => void;
}; };
export const useUserStore = create<State>()( export const useUserStore = create<State>()(
@@ -42,7 +42,7 @@ export const useUserStore = create<State>()(
}); });
return res; return res;
}), }),
setMyModels(data: ModelType[]) { setMyModels(data: ModelSchema[]) {
set((state) => { set((state) => {
state.myModels = data; state.myModels = data;
}); });

2
src/types/chat.d.ts vendored
View File

@@ -1,5 +1,3 @@
import type { ModelType } from './model';
export type ChatItemType = { export type ChatItemType = {
obj: 'Human' | 'AI' | 'SYSTEM'; obj: 'Human' | 'AI' | 'SYSTEM';
value: string; value: string;

40
src/types/model.d.ts vendored
View File

@@ -1,40 +1,10 @@
import { ModelStatusEnum } from '@/constants/model'; import { ModelStatusEnum } from '@/constants/model';
export interface ModelType { import type { ModelSchema } from './mongoSchema';
_id: string;
userId: string;
name: string;
avatar: string;
status: `${ModelStatusEnum}`;
updateTime: Date;
trainingTimes: number;
systemPrompt: string;
service: {
company: 'openai'; // 关联的厂商
trainId: string; // 训练时需要的ID
chatModel: string; // 聊天时用的模型
modelName: string; // 关联的模型
};
security: {
domain: string[];
contentMaxLen: number;
contextMaxLen: number;
expiredTime: number;
maxLoadAmount: number;
};
}
export interface ModelUpdateParams { export interface ModelUpdateParams {
name: string; name: string;
systemPrompt: string; systemPrompt: string;
service: { intro: string;
company: 'openai'; // 关联的厂商 temperature: number;
modelName: string; // 关联的模型 service: ModelSchema.service;
}; security: ModelSchema.security;
security: {
domain: string[];
contentMaxLen: number;
contextMaxLen: number;
expiredTime: number;
maxLoadAmount: number;
};
} }

View File

@@ -25,15 +25,17 @@ export interface ModelSchema {
name: string; name: string;
avatar: string; avatar: string;
systemPrompt: string; systemPrompt: string;
intro: string;
userId: string; userId: string;
status: `${ModelStatusEnum}`; status: `${ModelStatusEnum}`;
updateTime: number; updateTime: number;
trainingTimes: number; trainingTimes: number;
temperature: number;
service: { service: {
company: ServiceName; company: ServiceName;
trainId: string; trainId: string; // 训练的模型训练后就是训练的模型id
chatModel: `${ChatModelNameEnum}`; chatModel: string; // 聊天时用的模型,训练后就是训练的模型
modelName: string; modelName: `${ChatModelNameEnum}`; // 底层模型名称,不会变
}; };
security: { security: {
domain: string[]; domain: string[];