mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-20 02:34:52 +00:00
feat: 模型数据管理
This commit is contained in:
@@ -2,6 +2,9 @@ import { GET, POST, DELETE, PUT } from './request';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { ModelUpdateParams } from '@/types/model';
|
||||
import { TrainingItemType } from '../types/training';
|
||||
import { PagingData } from '@/types';
|
||||
import { RequestPaging } from '../types/index';
|
||||
import { Obj2Query } from '@/utils/tools';
|
||||
|
||||
export const getMyModels = () => GET<ModelSchema[]>('/model/list');
|
||||
|
||||
@@ -16,13 +19,30 @@ export const putModelById = (id: string, data: ModelUpdateParams) =>
|
||||
PUT(`/model/update?modelId=${id}`, data);
|
||||
|
||||
export const postTrainModel = (id: string, form: FormData) =>
|
||||
POST(`/model/train?modelId=${id}`, form, {
|
||||
POST(`/model/train/train?modelId=${id}`, form, {
|
||||
headers: {
|
||||
'content-type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
|
||||
export const putModelTrainingStatus = (id: string) => PUT(`/model/putTrainStatus?modelId=${id}`);
|
||||
export const putModelTrainingStatus = (id: string) =>
|
||||
PUT(`/model/train/putTrainStatus?modelId=${id}`);
|
||||
|
||||
export const getModelTrainings = (id: string) =>
|
||||
GET<TrainingItemType[]>(`/model/getTrainings?modelId=${id}`);
|
||||
GET<TrainingItemType[]>(`/model/train/getTrainings?modelId=${id}`);
|
||||
|
||||
/* 模型 data */
|
||||
|
||||
type GetModelDataListProps = RequestPaging & {
|
||||
modelId: string;
|
||||
};
|
||||
export const getModelDataList = (props: GetModelDataListProps) =>
|
||||
GET(`/model/data/getModelData?${Obj2Query(props)}`);
|
||||
|
||||
export const postModelData = (data: { modelId: string; data: { q: string; a: string }[] }) =>
|
||||
POST(`/model/data/pushModelData`, data);
|
||||
|
||||
export const putModelDataById = (data: { modelId: string; answer: string }) =>
|
||||
PUT('/model/data/putModelData', data);
|
||||
export const DelOneModelData = (modelId: string) =>
|
||||
DELETE(`/model/data/delModelDataById?modelId=${modelId}`);
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import type { ServiceName } from '@/types/mongoSchema';
|
||||
import { ModelSchema } from '../types/mongoSchema';
|
||||
import type { ServiceName, ModelDataType, ModelSchema } from '@/types/mongoSchema';
|
||||
|
||||
export enum ChatModelNameEnum {
|
||||
GPT35 = 'gpt-3.5-turbo',
|
||||
@@ -76,6 +75,12 @@ export const formatModelStatus = {
|
||||
}
|
||||
};
|
||||
|
||||
export const ModelDataStatusMap: Record<ModelDataType, string> = {
|
||||
0: '训练完成',
|
||||
1: '等待训练',
|
||||
2: '训练中'
|
||||
};
|
||||
|
||||
export const defaultModel: ModelSchema = {
|
||||
_id: '',
|
||||
userId: '',
|
||||
|
@@ -8,7 +8,7 @@ export const usePaging = <T = any>({
|
||||
pageSize = 10,
|
||||
params = {}
|
||||
}: {
|
||||
api: (data: any) => Promise<PagingData<T>>;
|
||||
api: (data: any) => any;
|
||||
pageSize?: number;
|
||||
params?: Record<string, any>;
|
||||
}) => {
|
||||
@@ -30,7 +30,7 @@ export const usePaging = <T = any>({
|
||||
setRequesting(true);
|
||||
|
||||
try {
|
||||
const res = await api({
|
||||
const res: PagingData<T> = await api({
|
||||
pageNum: num,
|
||||
pageSize,
|
||||
...params
|
||||
|
@@ -14,6 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
pageNum: string;
|
||||
pageSize: string;
|
||||
};
|
||||
|
||||
const { authorization } = req.headers;
|
||||
|
||||
pageNum = +pageNum;
|
||||
@@ -41,7 +42,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
.limit(pageSize);
|
||||
|
||||
jsonRes(res, {
|
||||
data
|
||||
data: {
|
||||
pageNum,
|
||||
pageSize,
|
||||
data,
|
||||
total: await ModelData.countDocuments({
|
||||
modelId,
|
||||
userId
|
||||
})
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
|
86
src/pages/model/components/ModelDataCard.tsx
Normal file
86
src/pages/model/components/ModelDataCard.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React, { useEffect, useCallback, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
TableContainer,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
IconButton,
|
||||
Flex,
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { ModelDataSchema } from '@/types/mongoSchema';
|
||||
import { ModelDataStatusMap } from '@/constants/model';
|
||||
import { usePaging } from '@/hooks/usePaging';
|
||||
import ScrollData from '@/components/ScrollData';
|
||||
import { getModelDataList } from '@/api/model';
|
||||
import { DeleteIcon } from '@chakra-ui/icons';
|
||||
|
||||
const ModelDataCard = ({ model }: { model: ModelSchema }) => {
|
||||
const {
|
||||
nextPage,
|
||||
isLoadAll,
|
||||
requesting,
|
||||
data: dataList,
|
||||
total
|
||||
} = usePaging<ModelDataSchema>({
|
||||
api: getModelDataList,
|
||||
pageSize: 10,
|
||||
params: {
|
||||
modelId: model._id
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex>
|
||||
<Box fontWeight={'bold'} fontSize={'lg'} flex={1}>
|
||||
模型数据: {total}组
|
||||
</Box>
|
||||
<Button size={'sm'}>导入</Button>
|
||||
</Flex>
|
||||
<ScrollData
|
||||
flex={'1 0 0'}
|
||||
h={0}
|
||||
px={6}
|
||||
mt={3}
|
||||
isLoadAll={isLoadAll}
|
||||
requesting={requesting}
|
||||
nextPage={nextPage}
|
||||
fontSize={'xs'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
>
|
||||
<TableContainer mt={4}>
|
||||
<Table variant={'simple'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Question</Th>
|
||||
<Th>Text</Th>
|
||||
<Th>Status</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{dataList.map((item) => (
|
||||
<Tr key={item._id}>
|
||||
<Td>{item.q}</Td>
|
||||
<Td>{item.a}</Td>
|
||||
<Td>{ModelDataStatusMap[item.status]}</Td>
|
||||
<Td>
|
||||
<IconButton icon={<DeleteIcon />} aria-label={'delete'} />
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</ScrollData>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelDataCard;
|
@@ -11,13 +11,26 @@ import {
|
||||
SliderFilledTrack,
|
||||
SliderThumb,
|
||||
SliderMark,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { modelList } from '@/constants/model';
|
||||
import { formatPrice } from '@/utils/user';
|
||||
import { useConfirm } from '@/hooks/useConfirm';
|
||||
|
||||
const ModelEditForm = ({ formHooks }: { formHooks: UseFormReturn<ModelSchema> }) => {
|
||||
const ModelEditForm = ({
|
||||
formHooks,
|
||||
handleDelModel
|
||||
}: {
|
||||
formHooks: UseFormReturn<ModelSchema>;
|
||||
handleDelModel: () => void;
|
||||
}) => {
|
||||
const { openConfirm, ConfirmChild } = useConfirm({
|
||||
content: '确认删除该模型?'
|
||||
});
|
||||
const { register, setValue, getValues } = formHooks;
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
@@ -29,7 +42,7 @@ const ModelEditForm = ({ formHooks }: { formHooks: UseFormReturn<ModelSchema> })
|
||||
</Flex>
|
||||
<FormControl mt={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 50px'} w={0}>
|
||||
<Box flex={'0 0 80px'} w={0}>
|
||||
名称:
|
||||
</Box>
|
||||
<Input
|
||||
@@ -39,7 +52,36 @@ const ModelEditForm = ({ formHooks }: { formHooks: UseFormReturn<ModelSchema> })
|
||||
></Input>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
<FormControl mt={4}>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Box flex={'0 0 80px'} w={0}>
|
||||
底层模型:
|
||||
</Box>
|
||||
<Box>{getValues('service.modelName')}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Box flex={'0 0 80px'} w={0}>
|
||||
价格:
|
||||
</Box>
|
||||
<Box>
|
||||
{formatPrice(
|
||||
modelList.find((item) => item.model === getValues('service.modelName'))?.price || 0,
|
||||
1000
|
||||
)}
|
||||
元/1K tokens(包括上下文和回答)
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'}>删除:</Box>
|
||||
<Button
|
||||
colorScheme={'gray'}
|
||||
variant={'outline'}
|
||||
size={'sm'}
|
||||
onClick={openConfirm(handleDelModel)}
|
||||
>
|
||||
删除模型
|
||||
</Button>
|
||||
</Flex>
|
||||
{/* <FormControl mt={4}>
|
||||
<Box mb={1}>介绍:</Box>
|
||||
<Textarea
|
||||
rows={5}
|
||||
@@ -47,7 +89,7 @@ const ModelEditForm = ({ formHooks }: { formHooks: UseFormReturn<ModelSchema> })
|
||||
{...register('intro')}
|
||||
placeholder={'模型的介绍,仅做展示,不影响模型的效果'}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormControl> */}
|
||||
</Card>
|
||||
<Card p={4}>
|
||||
<Box fontWeight={'bold'}>模型效果</Box>
|
||||
@@ -202,6 +244,7 @@ const ModelEditForm = ({ formHooks }: { formHooks: UseFormReturn<ModelSchema> })
|
||||
</Flex>
|
||||
</FormControl>
|
||||
</Card> */}
|
||||
<ConfirmChild />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -11,36 +11,34 @@ import { getChatSiteId } from '@/api/chat';
|
||||
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 { 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 Icon from '@/components/Iconfont';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
// import dynamic from 'next/dynamic';
|
||||
import ModelDataCard from './components/ModelDataCard';
|
||||
|
||||
const Training = dynamic(() => import('./components/Training'));
|
||||
// const Training = dynamic(() => import('./components/Training'));
|
||||
|
||||
const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const { isPc, media } = useScreen();
|
||||
const { setLoading } = useGlobalStore();
|
||||
const { openConfirm, ConfirmChild } = useConfirm({
|
||||
content: '确认删除该模型?'
|
||||
});
|
||||
|
||||
const SelectFileDom = useRef<HTMLInputElement>(null);
|
||||
const [model, setModel] = useState<ModelSchema>(defaultModel);
|
||||
const formHooks = useForm<ModelSchema>({
|
||||
defaultValues: model
|
||||
});
|
||||
|
||||
const canTrain = useMemo(() => {
|
||||
const openai = modelList.find((item) => item.model === model?.service.modelName);
|
||||
return openai && openai.trainName;
|
||||
}, [model]);
|
||||
// const canTrain = useMemo(() => {
|
||||
// const openai = modelList.find((item) => item.model === model?.service.modelName);
|
||||
// return openai && openai.trainName;
|
||||
// }, [model]);
|
||||
|
||||
/* 加载模型数据 */
|
||||
const loadModel = useCallback(async () => {
|
||||
@@ -250,79 +248,15 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
)}
|
||||
</Card>
|
||||
<Grid mt={5} gridTemplateColumns={media('1fr 1fr', '1fr')} gridGap={5}>
|
||||
<ModelEditForm formHooks={formHooks} />
|
||||
<ModelEditForm formHooks={formHooks} handleDelModel={handleDelModel} />
|
||||
|
||||
{canTrain && (
|
||||
{/* {canTrain && (
|
||||
<Card p={4}>
|
||||
<Training model={model} />
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card p={4}>
|
||||
<Box fontWeight={'bold'} fontSize={'lg'}>
|
||||
神奇操作
|
||||
</Box>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'}>模型微调:</Box>
|
||||
<Button
|
||||
size={'sm'}
|
||||
onClick={() => {
|
||||
SelectFileDom.current?.click();
|
||||
}}
|
||||
title={!canTrain ? '模型不支持微调' : ''}
|
||||
isDisabled={!canTrain}
|
||||
>
|
||||
上传数据集
|
||||
</Button>
|
||||
<Flex
|
||||
as={'a'}
|
||||
href="/TrainingTemplate.jsonl"
|
||||
download
|
||||
ml={5}
|
||||
cursor={'pointer'}
|
||||
alignItems={'center'}
|
||||
color={'blue.500'}
|
||||
>
|
||||
<Icon name={'icon-yunxiazai'} color={'#3182ce'} />
|
||||
下载模板
|
||||
</Flex>
|
||||
</Flex>
|
||||
{/* 提示 */}
|
||||
<Box mt={3} py={3} color={'blackAlpha.600'}>
|
||||
<Box as={'li'} lineHeight={1.9}>
|
||||
暂时需要使用自己的openai key
|
||||
</Box>
|
||||
<Box as={'li'} lineHeight={1.9}>
|
||||
可以使用
|
||||
<Box
|
||||
as={'span'}
|
||||
fontWeight={'bold'}
|
||||
textDecoration={'underline'}
|
||||
color={'blackAlpha.800'}
|
||||
mx={2}
|
||||
cursor={'pointer'}
|
||||
onClick={() => router.push('/data/list')}
|
||||
>
|
||||
数据拆分
|
||||
</Box>
|
||||
功能,从任意文本中提取数据集。
|
||||
</Box>
|
||||
<Box as={'li'} lineHeight={1.9}>
|
||||
每行包括一个 prompt 和一个 completion
|
||||
</Box>
|
||||
<Box as={'li'} lineHeight={1.9}>
|
||||
prompt 必须以 {'</s>'} 结尾
|
||||
</Box>
|
||||
<Box as={'li'} lineHeight={1.9}>
|
||||
completion 开头必须有一个空格,必须以 {'</s>'} 结尾
|
||||
</Box>
|
||||
</Box>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'}>删除模型:</Box>
|
||||
<Button colorScheme={'red'} size={'sm'} onClick={openConfirm(handleDelModel)}>
|
||||
删除模型
|
||||
</Button>
|
||||
</Flex>
|
||||
)} */}
|
||||
<Card p={4} height={'400px'} gridColumnStart={1} gridColumnEnd={3}>
|
||||
{model._id && <ModelDataCard model={model} />}
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
@@ -330,7 +264,6 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
<Box position={'absolute'} w={0} h={0} overflow={'hidden'}>
|
||||
<input ref={SelectFileDom} type="file" accept=".jsonl" onChange={startTraining} />
|
||||
</Box>
|
||||
<ConfirmChild />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { generateQA } from './events/generateQA';
|
||||
import { generateAbstract } from './events/generateAbstract';
|
||||
import { connectRedis } from './redis';
|
||||
|
||||
/**
|
||||
* 连接 MongoDB 数据库
|
||||
@@ -28,6 +29,7 @@ export async function connectToDatabase(): Promise<void> {
|
||||
|
||||
generateQA();
|
||||
generateAbstract();
|
||||
// connectRedis();
|
||||
}
|
||||
|
||||
export * from './models/authCode';
|
||||
|
74
src/service/redis.ts
Normal file
74
src/service/redis.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { createClient, SchemaFieldTypes } from 'redis';
|
||||
|
||||
export const connectRedis = async () => {
|
||||
// 断开了,重连
|
||||
if (global.redisClient && !global.redisClient.isOpen) {
|
||||
await global.redisClient.disconnect();
|
||||
} else if (global.redisClient) {
|
||||
// 没断开,不再连接
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
global.redisClient = createClient({
|
||||
url: 'redis://:121914yu@120.76.193.200:8100'
|
||||
});
|
||||
|
||||
global.redisClient.on('error', (err) => {
|
||||
console.log('Redis Client Error', err);
|
||||
global.redisClient = null;
|
||||
});
|
||||
global.redisClient.on('end', () => {
|
||||
global.redisClient = null;
|
||||
});
|
||||
global.redisClient.on('ready', () => {
|
||||
console.log('redis connected');
|
||||
});
|
||||
|
||||
await global.redisClient.connect();
|
||||
|
||||
// 0 - 测试库,1 - 正式
|
||||
await global.redisClient.select(0);
|
||||
|
||||
// 创建索引
|
||||
await global.redisClient.ft.create(
|
||||
'vec:question',
|
||||
{
|
||||
'$.vector': SchemaFieldTypes.VECTOR,
|
||||
'$.modelId': {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
AS: 'modelId'
|
||||
},
|
||||
'$.userId': {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
AS: 'userId'
|
||||
},
|
||||
'$.status': {
|
||||
type: SchemaFieldTypes.NUMERIC,
|
||||
AS: 'status'
|
||||
}
|
||||
},
|
||||
{
|
||||
ON: 'JSON',
|
||||
PREFIX: 'fastgpt:modeldata'
|
||||
}
|
||||
);
|
||||
|
||||
// await global.redisClient.json.set('fastgpt:modeldata:1', '$', {
|
||||
// vector: [],
|
||||
// modelId: 'daf',
|
||||
// userId: 'adfd',
|
||||
// q: 'fasf',
|
||||
// a: 'afasf',
|
||||
// status: 0,
|
||||
// createTime: new Date()
|
||||
// });
|
||||
// const value = await global.redisClient.get('fastgpt:modeldata:1');
|
||||
|
||||
// console.log(value);
|
||||
} catch (error) {
|
||||
console.log(error, '==');
|
||||
global.redisClient = null;
|
||||
return Promise.reject('redis 连接失败');
|
||||
}
|
||||
};
|
2
src/types/index.d.ts
vendored
2
src/types/index.d.ts
vendored
@@ -1,7 +1,9 @@
|
||||
import type { Mongoose } from 'mongoose';
|
||||
import type { RedisClientType } from 'redis';
|
||||
|
||||
declare global {
|
||||
var mongodb: Mongoose | string | null;
|
||||
var redisClient: RedisClientType | null;
|
||||
var generatingQA: boolean;
|
||||
var generatingAbstract: boolean;
|
||||
var QRCode: any;
|
||||
|
3
src/types/mongoSchema.d.ts
vendored
3
src/types/mongoSchema.d.ts
vendored
@@ -51,11 +51,12 @@ export interface ModelPopulate extends ModelSchema {
|
||||
userId: UserModelSchema;
|
||||
}
|
||||
|
||||
export type ModelDataType = 0 | 1 | 2;
|
||||
export interface ModelDataSchema {
|
||||
_id: string;
|
||||
q: string;
|
||||
a: string;
|
||||
status: 0 | 1 | 2;
|
||||
status: ModelDataType;
|
||||
createTime: Date;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user