mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 13:03:50 +00:00
feat: 模型介绍和温度调整。完善聊天页提示
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
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 { 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 }) =>
|
||||
POST<ModelType>('/model/create', data);
|
||||
POST<ModelSchema>('/model/create', data);
|
||||
|
||||
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) =>
|
||||
PUT(`/model/update?modelId=${id}`, data);
|
||||
|
1
src/api/response/chat.d.ts
vendored
1
src/api/response/chat.d.ts
vendored
@@ -6,6 +6,7 @@ export type InitChatResponse = {
|
||||
modelId: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
intro: string;
|
||||
secret: ModelSchema.secret;
|
||||
chatModel: ModelSchema.service.ChatModel; // 模型名
|
||||
history: ChatItemType[];
|
||||
|
@@ -172,7 +172,7 @@
|
||||
}
|
||||
.markdown ul,
|
||||
.markdown ol {
|
||||
padding-left: 30px;
|
||||
padding-left: 1em;
|
||||
}
|
||||
.markdown ul.no-list,
|
||||
.markdown ol.no-list {
|
||||
|
@@ -12,7 +12,7 @@ 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 = false }: { source: string; isChatting?: boolean }) => {
|
||||
const formatSource = useMemo(() => source, [source]);
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
|
82
src/components/Slider/index.tsx
Normal file
82
src/components/Slider/index.tsx
Normal 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;
|
@@ -39,3 +39,25 @@ export const introPage = `
|
||||
### 其他问题
|
||||
还有其他问题,可以加我 wx: YNyiqi,拉个交流群大家一起聊聊。
|
||||
`;
|
||||
|
||||
export const chatProblem = `
|
||||
**代理出错**
|
||||
服务器代理不稳定,可以过一会儿再尝试。
|
||||
|
||||
**API key 问题**
|
||||
请把 openai 的 API key 粘贴到账号里再创建对话。如果是使用分享的对话,不需要填写 API key。
|
||||
`;
|
||||
|
||||
export const versionIntro = `
|
||||
* 分享对话:使用的是分享者的 Api Key 生成一个对话窗口进行分享。
|
||||
* 分享空白对话:为该模型创建一个空白的聊天分享出去。
|
||||
* 分享当前对话:会把当前聊天的内容也分享出去,但是要注意不要多个人同时用一个聊天内容。
|
||||
* 增加模型介绍:可以在模型编辑页添加对模型的介绍,方便提示模型的范围。
|
||||
* 温度调整:可以在模型编辑页调整模型温度,以便适应不同类型的对话。例如,翻译类的模型可以把温度拉低;创作类的模型可以把温度拉高。
|
||||
`;
|
||||
|
||||
export const shareHint = `
|
||||
你正准备分享对话,请确保分享链接不会滥用,因为它是使用的是你的 API key。
|
||||
* 分享空白对话:为该模型创建一个空白的聊天分享出去。
|
||||
* 分享当前对话:会把当前聊天的内容也分享出去,但是要注意不要多个人同时用一个聊天内容。
|
||||
`;
|
||||
|
@@ -1,23 +1,37 @@
|
||||
import type { ServiceName } from '@/types/mongoSchema';
|
||||
import { ModelSchema } from '../types/mongoSchema';
|
||||
|
||||
export enum ChatModelNameEnum {
|
||||
GPT35 = 'gpt-3.5-turbo',
|
||||
GPT3 = 'text-davinci-003'
|
||||
}
|
||||
export const OpenAiList = [
|
||||
|
||||
export type ModelConstantsData = {
|
||||
name: string;
|
||||
model: `${ChatModelNameEnum}`;
|
||||
trainName: string; // 空字符串代表不能训练
|
||||
maxToken: number;
|
||||
maxTemperature: number;
|
||||
};
|
||||
|
||||
export const ModelList: Record<ServiceName, ModelConstantsData[]> = {
|
||||
openai: [
|
||||
{
|
||||
name: 'chatGPT',
|
||||
model: ChatModelNameEnum.GPT35,
|
||||
trainName: 'turbo',
|
||||
canTraining: false,
|
||||
maxToken: 4060
|
||||
maxToken: 4000,
|
||||
maxTemperature: 2
|
||||
},
|
||||
{
|
||||
name: 'GPT3',
|
||||
model: ChatModelNameEnum.GPT3,
|
||||
trainName: 'davinci',
|
||||
canTraining: true,
|
||||
maxToken: 4060
|
||||
maxToken: 4000,
|
||||
maxTemperature: 2
|
||||
}
|
||||
];
|
||||
]
|
||||
};
|
||||
|
||||
export enum TrainingStatusEnum {
|
||||
pending = 'pending',
|
||||
@@ -51,3 +65,29 @@ export const formatModelStatus = {
|
||||
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
|
||||
}
|
||||
};
|
||||
|
@@ -8,6 +8,7 @@ import { ChatItemType } from '@/types/chat';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { PassThrough } from 'stream';
|
||||
import { ModelList } from '@/constants/model';
|
||||
|
||||
/* 发送提示词 */
|
||||
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
|
||||
const chatAPI = getOpenAIApi(userApiKey);
|
||||
let startTime = Date.now();
|
||||
@@ -63,8 +73,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const chatResponse = await chatAPI.createChatCompletion(
|
||||
{
|
||||
model: model.service.chatModel,
|
||||
temperature: 1,
|
||||
// max_tokens: model.security.contentMaxLen,
|
||||
temperature: temperature,
|
||||
max_tokens: modelConstantsData.maxToken,
|
||||
messages: formatPrompts,
|
||||
stream: true
|
||||
},
|
||||
|
@@ -5,6 +5,7 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import { getOpenAIApi, authChat } from '@/service/utils/chat';
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { httpsAgent } from '@/service/utils/tools';
|
||||
import { ModelList } from '@/constants/model';
|
||||
|
||||
/* 发送提示词 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -27,13 +28,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
// prompt处理
|
||||
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(
|
||||
{
|
||||
model: model.service.modelName,
|
||||
prompt: formatPrompt,
|
||||
temperature: 0.5,
|
||||
max_tokens: model.security.contentMaxLen,
|
||||
temperature: temperature,
|
||||
max_tokens: modelConstantsData.maxToken,
|
||||
top_p: 1,
|
||||
frequency_penalty: 0,
|
||||
presence_penalty: 0.6,
|
||||
|
@@ -47,6 +47,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
modelId: model._id,
|
||||
name: model.name,
|
||||
avatar: model.avatar,
|
||||
intro: model.intro,
|
||||
secret: model.security,
|
||||
chatModel: model.service.chatModel,
|
||||
history: chat.content
|
||||
|
@@ -3,12 +3,21 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
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';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
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;
|
||||
|
||||
if (!authorization) {
|
||||
@@ -22,10 +31,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
// 凭证校验
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
const modelItem = OpenAiList.find((item) => item.model === serviceModelName);
|
||||
const modelItem = ModelList[serviceModelCompany].find(
|
||||
(item) => item.model === serviceModelName
|
||||
);
|
||||
|
||||
if (!modelItem) {
|
||||
throw new Error('模型错误');
|
||||
throw new Error('模型不存在');
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
@@ -43,8 +54,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
const authCount = await Model.countDocuments({
|
||||
userId
|
||||
});
|
||||
if (authCount >= 10) {
|
||||
throw new Error('上限 10 个模型');
|
||||
if (authCount >= 20) {
|
||||
throw new Error('上限 20 个模型');
|
||||
}
|
||||
|
||||
// 创建模型
|
||||
|
@@ -3,7 +3,7 @@ import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
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>) {
|
||||
@@ -26,7 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
await connectToDatabase();
|
||||
|
||||
// 根据 userId 获取模型信息
|
||||
const model: ModelType | null = await Model.findOne({
|
||||
const model = await Model.findOne<ModelSchema>({
|
||||
userId,
|
||||
_id: modelId
|
||||
});
|
||||
|
@@ -6,7 +6,7 @@ import formidable from 'formidable';
|
||||
import { authToken, getUserOpenaiKey } from '@/service/utils/tools';
|
||||
import { join } from 'path';
|
||||
import fs from 'fs';
|
||||
import type { ModelType } from '@/types/model';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import type { OpenAIApi } from 'openai';
|
||||
import { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model';
|
||||
import { httpsAgent } from '@/service/utils/tools';
|
||||
|
@@ -3,7 +3,7 @@ import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Model, Training } from '@/service/mongo';
|
||||
import { getOpenAIApi } from '@/service/utils/chat';
|
||||
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 { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model';
|
||||
import { OpenAiTuneStatusEnum } from '@/service/constants/training';
|
||||
@@ -26,7 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await connectToDatabase();
|
||||
|
||||
// 获取模型
|
||||
const model: ModelType | null = await Model.findById(modelId);
|
||||
const model = await Model.findById<ModelSchema>(modelId);
|
||||
|
||||
if (!model || model.status !== 'training') {
|
||||
throw new Error('模型不在训练中');
|
||||
|
@@ -7,7 +7,7 @@ import formidable from 'formidable';
|
||||
import { authToken, getUserOpenaiKey } from '@/service/utils/tools';
|
||||
import { join } from 'path';
|
||||
import fs from 'fs';
|
||||
import type { ModelType } from '@/types/model';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import type { OpenAIApi } from 'openai';
|
||||
import { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model';
|
||||
import { httpsAgent } from '@/service/utils/tools';
|
||||
@@ -38,7 +38,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await connectToDatabase();
|
||||
|
||||
// 获取模型的状态
|
||||
const model: ModelType | null = await Model.findById(modelId);
|
||||
const model = await Model.findById<ModelSchema>(modelId);
|
||||
|
||||
if (!model || model.status !== 'running') {
|
||||
throw new Error('模型正忙');
|
||||
|
@@ -8,7 +8,8 @@ import type { ModelUpdateParams } from '@/types/model';
|
||||
/* 获取我的模型 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
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 { authorization } = req.headers;
|
||||
|
||||
@@ -33,8 +34,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
},
|
||||
{
|
||||
name,
|
||||
service,
|
||||
systemPrompt,
|
||||
intro,
|
||||
temperature,
|
||||
service,
|
||||
security
|
||||
}
|
||||
);
|
||||
|
@@ -1,23 +1,44 @@
|
||||
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 (
|
||||
<Flex h={'100%'} alignItems={'center'} justifyContent={'center'}>
|
||||
<Card p={5} w={'70%'}>
|
||||
<Box fontSize={'xl'} fontWeight={'bold'} textAlign={'center'} pb={2}>
|
||||
Fast Gpt version1.3
|
||||
</Box>
|
||||
<Box>
|
||||
更新了聊天的数据结构,如果出现问题,请手动删除左侧旧的历史记录,并重新从模型页生成对话框进入。
|
||||
</Box>
|
||||
<Box>分享聊天使用的是分享者的 Api Key 进行收费,请确认分享安全</Box>
|
||||
<br />
|
||||
<Box>分享空白聊天,会分享一个该模型的空白聊天页</Box>
|
||||
<br />
|
||||
<Box>分享当前聊天,会把当前聊天的内容分享出去,请注意不会多人同时使用一个对话框</Box>
|
||||
<Box
|
||||
minH={'100%'}
|
||||
w={'85%'}
|
||||
maxW={'600px'}
|
||||
m={'auto'}
|
||||
py={'5vh'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
>
|
||||
{!!intro && (
|
||||
<Card p={4} mb={10}>
|
||||
<Header>模型介绍</Header>
|
||||
<Box>{intro}</Box>
|
||||
</Card>
|
||||
</Flex>
|
||||
)}
|
||||
<Card p={4} mb={10}>
|
||||
<Header>常见问题</Header>
|
||||
<Markdown source={chatProblem} />
|
||||
</Card>
|
||||
{/* version intro */}
|
||||
<Card p={4}>
|
||||
<Header>Fast Gpt version1.4</Header>
|
||||
<Box>
|
||||
聊天的数据结构发生了比较大的改动。如果出现问题,请手动删除左侧旧的历史记录,并重新从模型页生成对话框进入。
|
||||
</Box>
|
||||
<br />
|
||||
<Markdown source={versionIntro} />
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Button } from '@chakra-ui/react';
|
||||
import { AddIcon, ChatIcon, EditIcon, DeleteIcon } from '@chakra-ui/icons';
|
||||
import { AddIcon, ChatIcon, DeleteIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionButton,
|
||||
@@ -9,16 +10,26 @@ import {
|
||||
AccordionIcon,
|
||||
Flex,
|
||||
Divider,
|
||||
IconButton
|
||||
IconButton,
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useScreen } from '@/hooks/useScreen';
|
||||
import { getToken } from '@/utils/user';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { useCopyData } from '@/utils/tools';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import { shareHint } from '@/constants/common';
|
||||
import { getChatSiteId } from '@/api/chat';
|
||||
|
||||
const SlideBar = ({
|
||||
name,
|
||||
@@ -36,10 +47,13 @@ const SlideBar = ({
|
||||
const router = useRouter();
|
||||
const { copyData } = useCopyData();
|
||||
const { myModels, getMyModels } = useUserStore();
|
||||
const { chatHistory, removeChatHistoryByWindowId, generateChatWindow, updateChatHistory } =
|
||||
useChatStore();
|
||||
const { isSuccess } = useQuery(['init'], getMyModels);
|
||||
const { chatHistory, removeChatHistoryByWindowId } = useChatStore();
|
||||
const [hasReady, setHasReady] = useState(false);
|
||||
const { isOpen: isOpenShare, onOpen: onOpenShare, onClose: onCloseShare } = useDisclosure();
|
||||
|
||||
const { isSuccess } = useQuery(['init'], getMyModels, {
|
||||
cacheTime: 5 * 60 * 1000
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setHasReady(true);
|
||||
@@ -119,19 +133,8 @@ const SlideBar = ({
|
||||
|
||||
{/* 我的模型 & 历史记录 折叠框*/}
|
||||
<Box flex={'1 0 0'} px={3} h={0} overflowY={'auto'}>
|
||||
{isSuccess ? (
|
||||
<Accordion defaultIndex={[0]} allowToggle>
|
||||
<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>
|
||||
{isSuccess && (
|
||||
<AccordionItem borderTop={0} borderBottom={0}>
|
||||
<AccordionButton borderRadius={'md'} pl={1}>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
@@ -161,7 +164,7 @@ const SlideBar = ({
|
||||
: {})}
|
||||
onClick={async () => {
|
||||
if (item.name === name) return;
|
||||
router.push(`/chat?chatId=${await generateChatWindow(item._id)}`);
|
||||
router.push(`/chat?chatId=${await getChatSiteId(item._id)}`);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
@@ -173,22 +176,24 @@ const SlideBar = ({
|
||||
))}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
) : (
|
||||
<>
|
||||
<Box mb={4} textAlign={'center'}>
|
||||
)}
|
||||
<AccordionItem borderTop={0} borderBottom={0}>
|
||||
<AccordionButton borderRadius={'md'} pl={1}>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
历史记录
|
||||
</Box>
|
||||
<RenderHistory />
|
||||
</>
|
||||
)}
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={0} px={0}>
|
||||
{hasReady && <RenderHistory />}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Box>
|
||||
|
||||
<Divider my={4} />
|
||||
|
||||
<Box px={3}>
|
||||
{/* 分享 */}
|
||||
{getToken() && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
@@ -197,34 +202,57 @@ const SlideBar = ({
|
||||
_hover={{
|
||||
backgroundColor: 'rgba(255,255,255,0.2)'
|
||||
}}
|
||||
onClick={async () => {
|
||||
copyData(
|
||||
`${location.origin}/chat?chatId=${await generateChatWindow(modelId)}`,
|
||||
'已复制分享链接'
|
||||
);
|
||||
onClick={() => {
|
||||
onOpenShare();
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<MyIcon name="share" fill={'white'} w={'16px'} h={'16px'} mr={4} />
|
||||
分享空白对话
|
||||
</Flex>
|
||||
)}
|
||||
<Flex
|
||||
mt={4}
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
_hover={{
|
||||
backgroundColor: 'rgba(255,255,255,0.2)'
|
||||
}}
|
||||
onClick={async () => {
|
||||
copyData(`${location.origin}/chat?chatId=${chatId}`, '已复制分享链接');
|
||||
}}
|
||||
>
|
||||
<MyIcon name="share" fill={'white'} w={'16px'} h={'16px'} mr={4} />
|
||||
分享当前对话
|
||||
分享对话
|
||||
</Flex>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
@@ -51,6 +51,7 @@ const Chat = ({ chatId }: { chatId: string }) => {
|
||||
modelId: '',
|
||||
name: '',
|
||||
avatar: '',
|
||||
intro: '',
|
||||
secret: {},
|
||||
chatModel: '',
|
||||
history: [],
|
||||
@@ -113,7 +114,11 @@ const Chat = ({ chatId }: { chatId: string }) => {
|
||||
status: 'finish'
|
||||
}))
|
||||
});
|
||||
if (res.history.length > 0) {
|
||||
setTimeout(() => {
|
||||
scrollToBottom();
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
onError(e: any) {
|
||||
toast({
|
||||
@@ -433,7 +438,7 @@ const Chat = ({ chatId }: { chatId: string }) => {
|
||||
</Flex>
|
||||
</Box>
|
||||
))}
|
||||
{chatData.history.length === 0 && <Empty />}
|
||||
{chatData.history.length === 0 && <Empty intro={chatData.intro} />}
|
||||
</Box>
|
||||
{/* 发送区 */}
|
||||
<Box
|
||||
|
@@ -16,8 +16,8 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { postCreateModel } from '@/api/model';
|
||||
import { ModelType } from '@/types/model';
|
||||
import { OpenAiList } from '@/constants/model';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { ModelList } from '@/constants/model';
|
||||
|
||||
interface CreateFormType {
|
||||
name: string;
|
||||
@@ -29,7 +29,7 @@ const CreateModel = ({
|
||||
onSuccess
|
||||
}: {
|
||||
setCreateModelOpen: Dispatch<boolean>;
|
||||
onSuccess: Dispatch<ModelType>;
|
||||
onSuccess: Dispatch<ModelSchema>;
|
||||
}) => {
|
||||
const [requesting, setRequesting] = useState(false);
|
||||
const toast = useToast({
|
||||
@@ -42,7 +42,7 @@ const CreateModel = ({
|
||||
formState: { errors }
|
||||
} = useForm<CreateFormType>({
|
||||
defaultValues: {
|
||||
serviceModelName: OpenAiList[0].model
|
||||
serviceModelName: ModelList['openai'][0].model
|
||||
}
|
||||
});
|
||||
|
||||
@@ -95,7 +95,7 @@ const CreateModel = ({
|
||||
required: '底层模型不能为空'
|
||||
})}
|
||||
>
|
||||
{OpenAiList.map((item) => (
|
||||
{ModelList['openai'].map((item) => (
|
||||
<option key={item.model} value={item.model}>
|
||||
{item.name}
|
||||
</option>
|
||||
|
@@ -1,86 +1,37 @@
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { Grid, Box, Card, Flex, Button, FormControl, Input, Textarea } from '@chakra-ui/react';
|
||||
import type { ModelType } from '@/types/model';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { putModelById } from '@/api/model';
|
||||
import { useScreen } from '@/hooks/useScreen';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
Flex,
|
||||
FormControl,
|
||||
Input,
|
||||
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 isInit = useRef(false);
|
||||
const {
|
||||
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]);
|
||||
const ModelEditForm = ({ formHooks }: { formHooks: UseFormReturn<ModelSchema> }) => {
|
||||
const { register, setValue, getValues } = formHooks;
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
return (
|
||||
<Grid gridTemplateColumns={media('1fr 1fr', '1fr')} gridGap={5}>
|
||||
<>
|
||||
<Card p={4}>
|
||||
<Flex justifyContent={'space-between'} alignItems={'center'}>
|
||||
<Box fontWeight={'bold'} fontSize={'lg'}>
|
||||
修改模型信息
|
||||
</Box>
|
||||
<Button onClick={handleSubmit(onclickSave, submitError)}>保存</Button>
|
||||
<Box fontWeight={'bold'}>基本信息</Box>
|
||||
</Flex>
|
||||
<FormControl mt={5}>
|
||||
<FormControl mt={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'}>展示名称:</Box>
|
||||
<Box flex={'0 0 50px'} w={0}>
|
||||
名称:
|
||||
</Box>
|
||||
<Input
|
||||
{...register('name', {
|
||||
required: '展示名称不能为空'
|
||||
@@ -88,30 +39,79 @@ const ModelEditForm = ({ model }: { model?: ModelType }) => {
|
||||
></Input>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
<FormControl mt={5}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'}>对话模型:</Box>
|
||||
<Box>{model?.service.modelName}</Box>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
<FormControl mt={5}>
|
||||
<FormControl mt={4}>
|
||||
<Box mb={1}>介绍:</Box>
|
||||
<Textarea
|
||||
rows={4}
|
||||
rows={5}
|
||||
maxLength={500}
|
||||
{...register('systemPrompt')}
|
||||
placeholder={
|
||||
'模型默认的 prompt 词,可以通过调整该内容,生成一个限定范围的模型,更方便的去使用。'
|
||||
}
|
||||
{...register('intro')}
|
||||
placeholder={'模型的介绍,仅做展示,不影响模型的效果'}
|
||||
/>
|
||||
</FormControl>
|
||||
</Card>
|
||||
<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>
|
||||
</Card>
|
||||
<Card p={4}>
|
||||
<Box fontWeight={'bold'}>安全策略</Box>
|
||||
<FormControl mt={2}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 120px'}>单句最大长度:</Box>
|
||||
<Box flex={'0 0 120px'} w={0}>
|
||||
单句最大长度:
|
||||
</Box>
|
||||
<Input
|
||||
flex={1}
|
||||
type={'number'}
|
||||
@@ -132,7 +132,9 @@ const ModelEditForm = ({ model }: { model?: ModelType }) => {
|
||||
</FormControl>
|
||||
<FormControl mt={5}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 120px'}>上下文最大长度:</Box>
|
||||
<Box flex={'0 0 120px'} w={0}>
|
||||
上下文最大长度:
|
||||
</Box>
|
||||
<Input
|
||||
flex={1}
|
||||
type={'number'}
|
||||
@@ -153,7 +155,9 @@ const ModelEditForm = ({ model }: { model?: ModelType }) => {
|
||||
</FormControl>
|
||||
<FormControl mt={5}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 120px'}>聊天过期时间:</Box>
|
||||
<Box flex={'0 0 120px'} w={0}>
|
||||
聊天过期时间:
|
||||
</Box>
|
||||
<Input
|
||||
flex={1}
|
||||
type={'number'}
|
||||
@@ -175,7 +179,9 @@ const ModelEditForm = ({ model }: { model?: ModelType }) => {
|
||||
</FormControl>
|
||||
<FormControl mt={5} pb={5}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 130px'}>聊天最大加载次数:</Box>
|
||||
<Box flex={'0 0 130px'} w={0}>
|
||||
聊天最大加载次数:
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Input
|
||||
type={'number'}
|
||||
@@ -196,7 +202,7 @@ const ModelEditForm = ({ model }: { model?: ModelType }) => {
|
||||
</Flex>
|
||||
</FormControl>
|
||||
</Card>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect } from '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 dayjs from 'dayjs';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -9,7 +9,7 @@ const ModelPhoneList = ({
|
||||
models,
|
||||
handlePreviewChat
|
||||
}: {
|
||||
models: ModelType[];
|
||||
models: ModelSchema[];
|
||||
handlePreviewChat: (_: string) => void;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
|
@@ -14,14 +14,14 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { formatModelStatus } from '@/constants/model';
|
||||
import dayjs from 'dayjs';
|
||||
import type { ModelType } from '@/types/model';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const ModelTable = ({
|
||||
models = [],
|
||||
handlePreviewChat
|
||||
}: {
|
||||
models: ModelType[];
|
||||
models: ModelSchema[];
|
||||
handlePreviewChat: (_: string) => void;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
@@ -34,13 +34,13 @@ const ModelTable = ({
|
||||
{
|
||||
title: '最后更新时间',
|
||||
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: '状态',
|
||||
key: 'status',
|
||||
dataIndex: 'status',
|
||||
render: (item: ModelType) => (
|
||||
render: (item: ModelSchema) => (
|
||||
<Tag
|
||||
colorScheme={formatModelStatus[item.status].colorTheme}
|
||||
variant="solid"
|
||||
@@ -54,7 +54,7 @@ const ModelTable = ({
|
||||
{
|
||||
title: 'AI模型',
|
||||
key: 'service',
|
||||
render: (item: ModelType) => (
|
||||
render: (item: ModelSchema) => (
|
||||
<Box wordBreak={'break-all'} whiteSpace={'pre-wrap'} maxW={'200px'}>
|
||||
{item.service.modelName}
|
||||
</Box>
|
||||
@@ -68,7 +68,7 @@ const ModelTable = ({
|
||||
{
|
||||
title: '操作',
|
||||
key: 'control',
|
||||
render: (item: ModelType) => (
|
||||
render: (item: ModelSchema) => (
|
||||
<>
|
||||
<Button mr={3} onClick={() => handlePreviewChat(item._id)}>
|
||||
对话
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import React, { useEffect, useCallback, useState } from '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 type { TrainingItemType } from '@/types/training';
|
||||
|
||||
const Training = ({ model }: { model: ModelType }) => {
|
||||
const Training = ({ model }: { model: ModelSchema }) => {
|
||||
const columns: {
|
||||
title: string;
|
||||
key: keyof TrainingItemType;
|
||||
|
@@ -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 { getModelById, delModelById, postTrainModel, putModelTrainingStatus } from '@/api/model';
|
||||
import {
|
||||
getModelById,
|
||||
delModelById,
|
||||
postTrainModel,
|
||||
putModelTrainingStatus,
|
||||
putModelById
|
||||
} from '@/api/model';
|
||||
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 { useToast } from '@/hooks/useToast';
|
||||
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 { useScreen } from '@/hooks/useScreen';
|
||||
import ModelEditForm from './components/ModelEditForm';
|
||||
import Icon from '@/components/Iconfont';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const Training = dynamic(() => import('./components/Training'));
|
||||
|
||||
const ModelDetail = () => {
|
||||
const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const { isPc, media } = useScreen();
|
||||
@@ -24,33 +32,35 @@ const ModelDetail = () => {
|
||||
content: '确认删除该模型?'
|
||||
});
|
||||
const SelectFileDom = useRef<HTMLInputElement>(null);
|
||||
|
||||
const { modelId } = router.query as { modelId: string };
|
||||
const [model, setModel] = useState<ModelType>();
|
||||
const [model, setModel] = useState<ModelSchema>(defaultModel);
|
||||
const formHooks = useForm<ModelSchema>({
|
||||
defaultValues: model
|
||||
});
|
||||
|
||||
const canTrain = useMemo(() => {
|
||||
const openai = OpenAiList.find((item) => item.model === model?.service.modelName);
|
||||
return openai && openai.canTraining === true;
|
||||
const openai = ModelList[model.service.company].find(
|
||||
(item) => item.model === model?.service.modelName
|
||||
);
|
||||
return openai && openai.trainName;
|
||||
}, [model]);
|
||||
|
||||
/* 加载模型数据 */
|
||||
const loadModel = useCallback(async () => {
|
||||
if (!modelId) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getModelById(modelId as string);
|
||||
const res = await getModelById(modelId);
|
||||
console.log(res);
|
||||
res.security.expiredTime /= 60 * 60 * 1000;
|
||||
setModel(res);
|
||||
formHooks.reset(res);
|
||||
} catch (err) {
|
||||
console.log('error->', err);
|
||||
}
|
||||
setLoading(false);
|
||||
}, [modelId, setLoading]);
|
||||
return null;
|
||||
}, [formHooks, modelId, setLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
loadModel();
|
||||
router.prefetch('/chat');
|
||||
}, [loadModel, modelId, router]);
|
||||
useQuery([modelId], loadModel);
|
||||
|
||||
/* 点击删除 */
|
||||
const handleDelModel = useCallback(async () => {
|
||||
@@ -71,7 +81,6 @@ const ModelDetail = () => {
|
||||
|
||||
/* 点前往聊天预览页 */
|
||||
const handlePreviewChat = useCallback(async () => {
|
||||
if (!model) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const chatId = await getChatSiteId(model._id);
|
||||
@@ -131,6 +140,65 @@ const ModelDetail = () => {
|
||||
setLoading(false);
|
||||
}, [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 (
|
||||
<>
|
||||
{/* 头部 */}
|
||||
@@ -138,9 +206,8 @@ const ModelDetail = () => {
|
||||
{isPc ? (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontSize={'xl'} fontWeight={'bold'}>
|
||||
{model?.name || '模型'} 配置
|
||||
{model.name}
|
||||
</Box>
|
||||
{!!model && (
|
||||
<Tag
|
||||
ml={2}
|
||||
variant="solid"
|
||||
@@ -150,38 +217,37 @@ const ModelDetail = () => {
|
||||
>
|
||||
{formatModelStatus[model.status].text}
|
||||
</Tag>
|
||||
)}
|
||||
<Box flex={1} />
|
||||
<Button variant={'outline'} onClick={handlePreviewChat}>
|
||||
对话体验
|
||||
</Button>
|
||||
<Button ml={4} onClick={formHooks.handleSubmit(saveSubmitSuccess, saveSubmitError)}>
|
||||
保存修改
|
||||
</Button>
|
||||
</Flex>
|
||||
) : (
|
||||
<>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box as={'h3'} fontSize={'xl'} fontWeight={'bold'} flex={1}>
|
||||
{model?.name || '模型'} 配置
|
||||
{model?.name}
|
||||
</Box>
|
||||
{!!model && (
|
||||
<Tag ml={2} colorScheme={formatModelStatus[model.status].colorTheme}>
|
||||
{formatModelStatus[model.status].text}
|
||||
</Tag>
|
||||
)}
|
||||
</Flex>
|
||||
<Box mt={4} textAlign={'right'}>
|
||||
<Button variant={'outline'} onClick={handlePreviewChat}>
|
||||
对话体验
|
||||
</Button>
|
||||
<Button ml={4} onClick={formHooks.handleSubmit(saveSubmitSuccess, saveSubmitError)}>
|
||||
保存修改
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
{/* 基本信息编辑 */}
|
||||
<Box mt={5}>
|
||||
<ModelEditForm model={model} />
|
||||
</Box>
|
||||
{/* 其他配置 */}
|
||||
<Grid mt={5} gridTemplateColumns={media('1fr 1fr', '1fr')} gridGap={5}>
|
||||
<ModelEditForm formHooks={formHooks} />
|
||||
<Card p={4}>{!!model && <Training model={model} />}</Card>
|
||||
<Card p={4}>
|
||||
<Box fontWeight={'bold'} fontSize={'lg'}>
|
||||
@@ -241,6 +307,8 @@ const ModelDetail = () => {
|
||||
</Flex>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* 文件选择 */}
|
||||
<Box position={'absolute'} w={0} h={0} overflow={'hidden'}>
|
||||
<input ref={SelectFileDom} type="file" accept=".jsonl" onChange={startTraining} />
|
||||
</Box>
|
||||
@@ -250,3 +318,11 @@ const ModelDetail = () => {
|
||||
};
|
||||
|
||||
export default ModelDetail;
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
const modelId = context.query?.modelId || '';
|
||||
|
||||
return {
|
||||
props: { modelId }
|
||||
};
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { Box, Button, Flex, Card } from '@chakra-ui/react';
|
||||
import { getChatSiteId } from '@/api/chat';
|
||||
import { ModelType } from '@/types/model';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { useRouter } from 'next/router';
|
||||
import ModelTable from './components/ModelTable';
|
||||
import ModelPhoneList from './components/ModelPhoneList';
|
||||
@@ -27,7 +27,7 @@ const ModelList = () => {
|
||||
|
||||
/* 创建成功回调 */
|
||||
const createModelSuccess = useCallback(
|
||||
(data: ModelType) => {
|
||||
(data: ModelSchema) => {
|
||||
setMyModels([data, ...myModels]);
|
||||
},
|
||||
[myModels, setMyModels]
|
||||
|
@@ -25,7 +25,8 @@ const ChatSchema = new Schema({
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
content: [
|
||||
content: {
|
||||
type: [
|
||||
{
|
||||
obj: {
|
||||
type: String,
|
||||
@@ -37,7 +38,9 @@ const ChatSchema = new Schema({
|
||||
required: true
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
default: []
|
||||
}
|
||||
});
|
||||
|
||||
export const Chat = models['chat'] || model('chat', ChatSchema);
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { Schema, model, models } from 'mongoose';
|
||||
|
||||
const ModelSchema = new Schema({
|
||||
userId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user',
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
@@ -10,13 +15,14 @@ const ModelSchema = new Schema({
|
||||
default: '/imgs/modelAvatar.png'
|
||||
},
|
||||
systemPrompt: {
|
||||
// 系统提示词
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
userId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user',
|
||||
required: true
|
||||
intro: {
|
||||
// 模型介绍
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
@@ -31,6 +37,12 @@ const ModelSchema = new Schema({
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
temperature: {
|
||||
type: Number,
|
||||
min: 1,
|
||||
max: 10,
|
||||
default: 5
|
||||
},
|
||||
service: {
|
||||
company: {
|
||||
type: String,
|
||||
|
@@ -2,7 +2,7 @@ import { create } from 'zustand';
|
||||
import { devtools } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
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 { getMyModels } from '@/api/model';
|
||||
|
||||
@@ -10,9 +10,9 @@ type State = {
|
||||
userInfo: UserType | null;
|
||||
setUserInfo: (user: UserType, token?: string) => void;
|
||||
updateUserInfo: (user: UserUpdateParams) => void;
|
||||
myModels: ModelType[];
|
||||
myModels: ModelSchema[];
|
||||
getMyModels: () => void;
|
||||
setMyModels: (data: ModelType[]) => void;
|
||||
setMyModels: (data: ModelSchema[]) => void;
|
||||
};
|
||||
|
||||
export const useUserStore = create<State>()(
|
||||
@@ -42,7 +42,7 @@ export const useUserStore = create<State>()(
|
||||
});
|
||||
return res;
|
||||
}),
|
||||
setMyModels(data: ModelType[]) {
|
||||
setMyModels(data: ModelSchema[]) {
|
||||
set((state) => {
|
||||
state.myModels = data;
|
||||
});
|
||||
|
2
src/types/chat.d.ts
vendored
2
src/types/chat.d.ts
vendored
@@ -1,5 +1,3 @@
|
||||
import type { ModelType } from './model';
|
||||
|
||||
export type ChatItemType = {
|
||||
obj: 'Human' | 'AI' | 'SYSTEM';
|
||||
value: string;
|
||||
|
40
src/types/model.d.ts
vendored
40
src/types/model.d.ts
vendored
@@ -1,40 +1,10 @@
|
||||
import { ModelStatusEnum } from '@/constants/model';
|
||||
export interface ModelType {
|
||||
_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;
|
||||
};
|
||||
}
|
||||
|
||||
import type { ModelSchema } from './mongoSchema';
|
||||
export interface ModelUpdateParams {
|
||||
name: string;
|
||||
systemPrompt: string;
|
||||
service: {
|
||||
company: 'openai'; // 关联的厂商
|
||||
modelName: string; // 关联的模型
|
||||
};
|
||||
security: {
|
||||
domain: string[];
|
||||
contentMaxLen: number;
|
||||
contextMaxLen: number;
|
||||
expiredTime: number;
|
||||
maxLoadAmount: number;
|
||||
};
|
||||
intro: string;
|
||||
temperature: number;
|
||||
service: ModelSchema.service;
|
||||
security: ModelSchema.security;
|
||||
}
|
||||
|
8
src/types/mongoSchema.d.ts
vendored
8
src/types/mongoSchema.d.ts
vendored
@@ -25,15 +25,17 @@ export interface ModelSchema {
|
||||
name: string;
|
||||
avatar: string;
|
||||
systemPrompt: string;
|
||||
intro: string;
|
||||
userId: string;
|
||||
status: `${ModelStatusEnum}`;
|
||||
updateTime: number;
|
||||
trainingTimes: number;
|
||||
temperature: number;
|
||||
service: {
|
||||
company: ServiceName;
|
||||
trainId: string;
|
||||
chatModel: `${ChatModelNameEnum}`;
|
||||
modelName: string;
|
||||
trainId: string; // 训练的模型,训练后就是训练的模型id
|
||||
chatModel: string; // 聊天时用的模型,训练后就是训练的模型
|
||||
modelName: `${ChatModelNameEnum}`; // 底层模型名称,不会变
|
||||
};
|
||||
security: {
|
||||
domain: string[];
|
||||
|
Reference in New Issue
Block a user