feat: 模型数据导入
This commit is contained in:
@@ -5,8 +5,8 @@ import { authToken } from '@/service/utils/tools';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
let { modelId } = req.query as {
|
||||
modelId: string;
|
||||
let { dataId } = req.query as {
|
||||
dataId: string;
|
||||
};
|
||||
const { authorization } = req.headers;
|
||||
|
||||
@@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
throw new Error('无权操作');
|
||||
}
|
||||
|
||||
if (!modelId) {
|
||||
if (!dataId) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
await connectToDatabase();
|
||||
|
||||
await ModelData.deleteOne({
|
||||
modelId,
|
||||
_id: dataId,
|
||||
userId
|
||||
});
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, ModelData, Model } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { ModelDataSchema } from '@/types/mongoSchema';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { modelId, data } = req.body as {
|
||||
modelId: string;
|
||||
data: { q: string; a: string }[];
|
||||
data: { text: ModelDataSchema['text']; q: ModelDataSchema['q'] }[];
|
||||
};
|
||||
const { authorization } = req.headers;
|
||||
|
||||
57
src/pages/api/model/data/pushModelDataSelectData.ts
Normal file
57
src/pages/api/model/data/pushModelDataSelectData.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, DataItem, ModelData } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
let { dataIds, modelId } = req.body as { dataIds: string[]; modelId: string };
|
||||
|
||||
if (!dataIds) {
|
||||
throw new Error('参数错误');
|
||||
}
|
||||
await connectToDatabase();
|
||||
|
||||
const { authorization } = req.headers;
|
||||
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
const dataItems = (
|
||||
await Promise.all(
|
||||
dataIds.map((dataId) =>
|
||||
DataItem.find<{ _id: string; result: { q: string }[]; text: string }>(
|
||||
{
|
||||
userId,
|
||||
dataId
|
||||
},
|
||||
'result text'
|
||||
)
|
||||
)
|
||||
)
|
||||
).flat();
|
||||
|
||||
// push data
|
||||
await ModelData.insertMany(
|
||||
dataItems.map((item) => ({
|
||||
modelId: modelId,
|
||||
userId,
|
||||
text: item.text,
|
||||
q: item.result.map((item) => ({
|
||||
id: nanoid(),
|
||||
text: item.q
|
||||
}))
|
||||
}))
|
||||
);
|
||||
|
||||
jsonRes(res, {
|
||||
data: dataItems
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import { authToken } from '@/service/utils/tools';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
let { modelId, answer } = req.body as {
|
||||
modelId: string;
|
||||
answer: string;
|
||||
let { dataId, text } = req.body as {
|
||||
dataId: string;
|
||||
text: string;
|
||||
};
|
||||
const { authorization } = req.headers;
|
||||
|
||||
@@ -15,7 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
throw new Error('无权操作');
|
||||
}
|
||||
|
||||
if (!modelId) {
|
||||
if (!dataId) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
|
||||
await ModelData.updateOne(
|
||||
{
|
||||
modelId,
|
||||
_id: dataId,
|
||||
userId
|
||||
},
|
||||
{
|
||||
a: answer
|
||||
text
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -10,30 +10,134 @@ import {
|
||||
Td,
|
||||
IconButton,
|
||||
Flex,
|
||||
Button
|
||||
Button,
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
ModalCloseButton,
|
||||
useDisclosure,
|
||||
Input,
|
||||
Textarea,
|
||||
Stack
|
||||
} 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 {
|
||||
getModelDataList,
|
||||
postModelDataInput,
|
||||
postModelDataSelect,
|
||||
delOneModelData,
|
||||
putModelDataById
|
||||
} from '@/api/model';
|
||||
import { getDataList } from '@/api/data';
|
||||
import { DeleteIcon } from '@chakra-ui/icons';
|
||||
import { useForm, useFieldArray } from 'react-hook-form';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
|
||||
type FormData = { text: string; q: { val: string }[] };
|
||||
type TabType = 'input' | 'select';
|
||||
const defaultValues = {
|
||||
text: '',
|
||||
q: [{ val: '' }]
|
||||
};
|
||||
|
||||
const ModelDataCard = ({ model }: { model: ModelSchema }) => {
|
||||
const {
|
||||
nextPage,
|
||||
isLoadAll,
|
||||
requesting,
|
||||
data: dataList,
|
||||
total
|
||||
data: modelDataList,
|
||||
total,
|
||||
setData,
|
||||
getData
|
||||
} = usePaging<ModelDataSchema>({
|
||||
api: getModelDataList,
|
||||
pageSize: 10,
|
||||
pageSize: 20,
|
||||
params: {
|
||||
modelId: model._id
|
||||
}
|
||||
});
|
||||
const { toast } = useToast();
|
||||
const {
|
||||
isOpen: isOpenImportModal,
|
||||
onOpen: onOpenImportModal,
|
||||
onClose: onCloseImportModal
|
||||
} = useDisclosure();
|
||||
const { register, handleSubmit, reset, control } = useForm<FormData>({
|
||||
defaultValues
|
||||
});
|
||||
const {
|
||||
fields: inputQ,
|
||||
append: appendQ,
|
||||
remove: removeQ
|
||||
} = useFieldArray({
|
||||
control,
|
||||
name: 'q'
|
||||
});
|
||||
|
||||
const importDataTypes: { id: TabType; label: string }[] = [
|
||||
{ id: 'input', label: '手动输入' },
|
||||
{ id: 'select', label: '数据集导入' }
|
||||
];
|
||||
const [importDataType, setImportDataType] = useState<TabType>(importDataTypes[0].id);
|
||||
const [importing, setImporting] = useState(false);
|
||||
|
||||
const updateAnswer = useCallback(async (dataId: string, text: string) => {
|
||||
putModelDataById({
|
||||
dataId,
|
||||
text
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { data: dataList = [] } = useQuery(['getDataList'], getDataList);
|
||||
const [selectDataId, setSelectDataId] = useState<string[]>([]);
|
||||
|
||||
const sureImportData = useCallback(
|
||||
async (e: FormData) => {
|
||||
setImporting(true);
|
||||
|
||||
try {
|
||||
if (importDataType === 'input') {
|
||||
await postModelDataInput({
|
||||
modelId: model._id,
|
||||
data: [
|
||||
{
|
||||
text: e.text,
|
||||
q: e.q.map((item) => ({
|
||||
id: nanoid(),
|
||||
text: item.val
|
||||
}))
|
||||
}
|
||||
]
|
||||
});
|
||||
} else if (importDataType === 'select') {
|
||||
const res = await postModelDataSelect(model._id, selectDataId);
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
toast({
|
||||
title: '导入数据成功,需要一段时间训练',
|
||||
status: 'success'
|
||||
});
|
||||
onCloseImportModal();
|
||||
getData(1, true);
|
||||
reset(defaultValues);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
setImporting(false);
|
||||
},
|
||||
[getData, importDataType, model._id, onCloseImportModal, reset, toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -41,18 +145,17 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
|
||||
<Box fontWeight={'bold'} fontSize={'lg'} flex={1}>
|
||||
模型数据: {total}组
|
||||
</Box>
|
||||
<Button size={'sm'}>导入</Button>
|
||||
<Button size={'sm'} onClick={onOpenImportModal}>
|
||||
导入
|
||||
</Button>
|
||||
</Flex>
|
||||
<ScrollData
|
||||
flex={'1 0 0'}
|
||||
h={0}
|
||||
h={'100%'}
|
||||
px={6}
|
||||
mt={3}
|
||||
isLoadAll={isLoadAll}
|
||||
requesting={requesting}
|
||||
nextPage={nextPage}
|
||||
fontSize={'xs'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
>
|
||||
<TableContainer mt={4}>
|
||||
<Table variant={'simple'}>
|
||||
@@ -65,13 +168,55 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{dataList.map((item) => (
|
||||
{modelDataList.map((item) => (
|
||||
<Tr key={item._id}>
|
||||
<Td>{item.q}</Td>
|
||||
<Td>{item.a}</Td>
|
||||
<Td>
|
||||
{item.q.map((item, i) => (
|
||||
<Box
|
||||
key={item.id}
|
||||
fontSize={'xs'}
|
||||
maxW={'350px'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
_notLast={{ mb: 1 }}
|
||||
>
|
||||
Q{i + 1}: {item.text}
|
||||
</Box>
|
||||
))}
|
||||
</Td>
|
||||
<Td w={'350px'}>
|
||||
<Textarea
|
||||
w={'100%'}
|
||||
h={'100%'}
|
||||
defaultValue={item.text}
|
||||
fontSize={'xs'}
|
||||
resize={'both'}
|
||||
onBlur={(e) => {
|
||||
const oldVal = modelDataList.find((data) => item._id === data._id)?.text;
|
||||
if (oldVal !== e.target.value) {
|
||||
updateAnswer(item._id, e.target.value);
|
||||
setData((state) =>
|
||||
state.map((data) => ({
|
||||
...data,
|
||||
text: data._id === item._id ? e.target.value : data.text
|
||||
}))
|
||||
);
|
||||
}
|
||||
}}
|
||||
></Textarea>
|
||||
</Td>
|
||||
<Td>{ModelDataStatusMap[item.status]}</Td>
|
||||
<Td>
|
||||
<IconButton icon={<DeleteIcon />} aria-label={'delete'} />
|
||||
<IconButton
|
||||
icon={<DeleteIcon />}
|
||||
variant={'outline'}
|
||||
colorScheme={'gray'}
|
||||
aria-label={'delete'}
|
||||
size={'sm'}
|
||||
onClick={async () => {
|
||||
delOneModelData(item._id);
|
||||
setData((state) => state.filter((data) => data._id !== item._id));
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
@@ -79,6 +224,101 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</ScrollData>
|
||||
<Modal isOpen={isOpenImportModal} onClose={onCloseImportModal}>
|
||||
<ModalOverlay />
|
||||
<ModalContent maxW={'min(900px, 90vw)'} maxH={'80vh'} position={'relative'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<ModalHeader whiteSpace={'nowrap'}>选择数据导入</ModalHeader>
|
||||
<Box>
|
||||
{importDataTypes.map((item) => (
|
||||
<Button
|
||||
key={item.id}
|
||||
size={'sm'}
|
||||
mr={5}
|
||||
variant={item.id === importDataType ? 'solid' : 'outline'}
|
||||
onClick={() => setImportDataType(item.id)}
|
||||
>
|
||||
{item.label}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
</Flex>
|
||||
<ModalCloseButton />
|
||||
<Box px={6} pb={2} overflowY={'auto'}>
|
||||
{importDataType === 'input' && (
|
||||
<>
|
||||
<Box mb={2}>知识点:</Box>
|
||||
<Textarea
|
||||
mb={4}
|
||||
placeholder="知识点"
|
||||
rows={3}
|
||||
maxH={'200px'}
|
||||
{...register(`text`, {
|
||||
required: '知识点'
|
||||
})}
|
||||
/>
|
||||
{inputQ.map((item, index) => (
|
||||
<Box key={item.id} mb={5}>
|
||||
<Box mb={2}>问法{index + 1}:</Box>
|
||||
<Flex>
|
||||
<Input
|
||||
placeholder="问法"
|
||||
{...register(`q.${index}.val`, {
|
||||
required: '问法不能为空'
|
||||
})}
|
||||
></Input>
|
||||
{inputQ.length > 1 && (
|
||||
<IconButton
|
||||
icon={<DeleteIcon />}
|
||||
aria-label={'delete'}
|
||||
colorScheme={'gray'}
|
||||
variant={'unstyled'}
|
||||
onClick={() => removeQ(index)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{importDataType === 'select' && (
|
||||
<CheckboxGroup colorScheme="blue" onChange={(e) => setSelectDataId(e as string[])}>
|
||||
{dataList.map((item) => (
|
||||
<Box mb={2} key={item._id}>
|
||||
<Checkbox value={item._id}>
|
||||
<Box fontWeight={'bold'} as={'span'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
<Box as={'span'} ml={2} fontSize={'sm'}>
|
||||
({item.totalData}条数据)
|
||||
</Box>
|
||||
</Checkbox>
|
||||
</Box>
|
||||
))}
|
||||
</CheckboxGroup>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Flex px={6} pt={2} pb={4}>
|
||||
{importDataType === 'input' && (
|
||||
<Button
|
||||
alignSelf={'flex-start'}
|
||||
variant={'outline'}
|
||||
onClick={() => appendQ({ val: '' })}
|
||||
>
|
||||
增加问法
|
||||
</Button>
|
||||
)}
|
||||
<Box flex={1}></Box>
|
||||
<Button variant={'outline'} mr={3} onClick={onCloseImportModal}>
|
||||
取消
|
||||
</Button>
|
||||
<Button isLoading={importing} onClick={handleSubmit(sureImportData)}>
|
||||
确认导入
|
||||
</Button>
|
||||
</Flex>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -255,7 +255,7 @@ const ModelDetail = ({ modelId }: { modelId: string }) => {
|
||||
<Training model={model} />
|
||||
</Card>
|
||||
)} */}
|
||||
<Card p={4} height={'400px'} gridColumnStart={1} gridColumnEnd={3}>
|
||||
<Card p={4} height={'500px'} gridColumnStart={1} gridColumnEnd={3}>
|
||||
{model._id && <ModelDataCard model={model} />}
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user