Merge #64 into develop_web from feature/20251219_yjp
feat(knowledgeBase): add media dataset support and improve file handling * feature/20251219_yjp: (1 commits) feat(knowledgeBase): add media dataset support and improve file handling Signed-off-by: vrhs@163.com <accounts_660b6454a0eb398d3f8d2c76@mail.teambition.com> Merged-by: vrhs@163.com <accounts_660b6454a0eb398d3f8d2c76@mail.teambition.com> CR-link: https://codeup.aliyun.com/redbearai/python/redbear-mem-open/change/64
This commit is contained in:
@@ -505,6 +505,7 @@ export const en = {
|
|||||||
createImport: 'Create/Import',
|
createImport: 'Create/Import',
|
||||||
textDataSet: 'Text Dataset',
|
textDataSet: 'Text Dataset',
|
||||||
imageDataSet: 'Image Dataset',
|
imageDataSet: 'Image Dataset',
|
||||||
|
mediaDataSet: 'Media Dataset',
|
||||||
blankDataset: 'Blank Dataset',
|
blankDataset: 'Blank Dataset',
|
||||||
customTextDataset: 'Custom Text Dataset',
|
customTextDataset: 'Custom Text Dataset',
|
||||||
text: 'Text',
|
text: 'Text',
|
||||||
@@ -581,6 +582,7 @@ export const en = {
|
|||||||
datasetName: 'Dataset Name',
|
datasetName: 'Dataset Name',
|
||||||
pleaseEnterDatasetName: 'Please enter dataset name',
|
pleaseEnterDatasetName: 'Please enter dataset name',
|
||||||
uploadImages: 'Upload Images',
|
uploadImages: 'Upload Images',
|
||||||
|
uploadMedia: 'Upload Media files',
|
||||||
pleaseUploadImages: 'Please upload images',
|
pleaseUploadImages: 'Please upload images',
|
||||||
embedding_id: 'Embedding',
|
embedding_id: 'Embedding',
|
||||||
llm_id: 'LLM',
|
llm_id: 'LLM',
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ export const zh = {
|
|||||||
createImport: '新建/导入',
|
createImport: '新建/导入',
|
||||||
textDataSet: '文本数据集',
|
textDataSet: '文本数据集',
|
||||||
imageDataSet: '图片数据集',
|
imageDataSet: '图片数据集',
|
||||||
|
mediaDataSet: '媒体数据集',
|
||||||
blankDataset: '空白数据集',
|
blankDataset: '空白数据集',
|
||||||
emptyDataSet: '空白数据集',
|
emptyDataSet: '空白数据集',
|
||||||
customTextDataset: '自定义文本数据集',
|
customTextDataset: '自定义文本数据集',
|
||||||
@@ -204,6 +205,7 @@ export const zh = {
|
|||||||
datasetName: '数据集名称',
|
datasetName: '数据集名称',
|
||||||
pleaseEnterDatasetName: '请输入数据集名称',
|
pleaseEnterDatasetName: '请输入数据集名称',
|
||||||
uploadImages: '上传图片',
|
uploadImages: '上传图片',
|
||||||
|
uploadMedia: '上传媒体文件',
|
||||||
pleaseUploadImages: '请上传图片',
|
pleaseUploadImages: '请上传图片',
|
||||||
embedding_id: '嵌入模型',
|
embedding_id: '嵌入模型',
|
||||||
llm_id: '大语言模型',
|
llm_id: '大语言模型',
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ interface CreateDatasetLocationState {
|
|||||||
knowledgeBaseId?: string;
|
knowledgeBaseId?: string;
|
||||||
parentId?: string;
|
parentId?: string;
|
||||||
startStep?: StepKey;
|
startStep?: StepKey;
|
||||||
fileId?: string;
|
fileId?: string | string[];
|
||||||
fileIds?: string[];
|
fileIds?: string | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateDataset = () => {
|
const CreateDataset = () => {
|
||||||
@@ -67,7 +67,11 @@ const CreateDataset = () => {
|
|||||||
const knowledgeBaseId = locationState.knowledgeBaseId || routeKnowledgeBaseId;
|
const knowledgeBaseId = locationState.knowledgeBaseId || routeKnowledgeBaseId;
|
||||||
const parentId = locationState.parentId;
|
const parentId = locationState.parentId;
|
||||||
const initialStepKey = locationState.startStep ?? 'selectFile';
|
const initialStepKey = locationState.startStep ?? 'selectFile';
|
||||||
const initialFileIds = locationState.fileIds ?? (locationState.fileId ? [locationState.fileId] : []);
|
const initialFileIds = (() => {
|
||||||
|
const fileIds = locationState.fileIds || locationState.fileId;
|
||||||
|
if (!fileIds) return [];
|
||||||
|
return Array.isArray(fileIds) ? fileIds : [fileIds];
|
||||||
|
})();
|
||||||
const [current, setCurrent] = useState<number>(stepIndexMap[initialStepKey]);
|
const [current, setCurrent] = useState<number>(stepIndexMap[initialStepKey]);
|
||||||
const tableRef = useRef<TableRef>(null);
|
const tableRef = useRef<TableRef>(null);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import folderIcon from '@/assets/images/knowledgeBase/folder.png';
|
|||||||
import textIcon from '@/assets/images/knowledgeBase/text.png';
|
import textIcon from '@/assets/images/knowledgeBase/text.png';
|
||||||
import editIcon from '@/assets/images/knowledgeBase/edit.png';
|
import editIcon from '@/assets/images/knowledgeBase/edit.png';
|
||||||
import blankIcon from '@/assets/images/knowledgeBase/blankDocument.png';
|
import blankIcon from '@/assets/images/knowledgeBase/blankDocument.png';
|
||||||
|
import imageIcon from '@/assets/images/knowledgeBase/image.png'
|
||||||
import { getKnowledgeBaseDetail, deleteDocument, downloadFile, updateKnowledgeBase } from '@/api/knowledgeBase';
|
import { getKnowledgeBaseDetail, deleteDocument, downloadFile, updateKnowledgeBase } from '@/api/knowledgeBase';
|
||||||
import {
|
import {
|
||||||
type CreateModalRef,
|
type CreateModalRef,
|
||||||
@@ -327,15 +328,15 @@ const Private: FC = () => {
|
|||||||
// handleCreate('folder'); // 传入 type: 'folder'
|
// handleCreate('folder'); // 传入 type: 'folder'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 暂时未实现
|
{
|
||||||
// {
|
key: '3',
|
||||||
// key: '3',
|
icon: <img src={imageIcon} alt="image" style={{ width: 16, height: 16 }} />,
|
||||||
// icon: <img src={imageIcon} alt="image" style={{ width: 16, height: 16 }} />,
|
label: t('knowledgeBase.mediaDataSet'),
|
||||||
// label: t('knowledgeBase.imageDataSet'),
|
onClick: () => {
|
||||||
// onClick: () => {
|
createImageDataset?.current?.handleOpen(knowledgeBaseId || '', parentId || '')
|
||||||
// createImageDataset?.current?.handleOpen(knowledgeBaseId || '', parentId || '')
|
},
|
||||||
// },
|
},
|
||||||
// },
|
// 暂时未实现
|
||||||
// {
|
// {
|
||||||
// key: '4',
|
// key: '4',
|
||||||
// icon: <img src={blankIcon} alt="blank" style={{ width: 16, height: 16 }} />,
|
// icon: <img src={blankIcon} alt="blank" style={{ width: 16, height: 16 }} />,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
|
import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
|
||||||
import { Form, Input } from 'antd';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { Form, Input, message } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { UploadFile } from 'antd';
|
import type { UploadFile } from 'antd';
|
||||||
import type { CreateSetModalRef, CreateSetMoealRefProps, UploadFileResponse } from '@/views/KnowledgeBase/types';
|
import type { CreateSetModalRef, CreateSetMoealRefProps, UploadFileResponse } from '@/views/KnowledgeBase/types';
|
||||||
@@ -16,12 +18,16 @@ interface ImageDatasetFormData {
|
|||||||
const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>(
|
const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>(
|
||||||
({ refreshTable }, ref) => {
|
({ refreshTable }, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [messageApi, contextHolder] = message.useMessage();
|
||||||
|
|
||||||
const [form] = Form.useForm<ImageDatasetFormData>();
|
const [form] = Form.useForm<ImageDatasetFormData>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [kbId, setKbId] = useState<string>('');
|
const [kbId, setKbId] = useState<string>('');
|
||||||
const [parentId, setParentId] = useState<string>('');
|
const [parentId, setParentId] = useState<string>('');
|
||||||
const uploadRef = useRef<{ fileList: UploadFile[]; clearFiles: () => void }>(null);
|
const uploadRef = useRef<{ fileList: UploadFile[]; clearFiles: () => void }>(null);
|
||||||
|
// const fileIds = [];
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
@@ -50,22 +56,23 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
|
|||||||
if (fileList.length === 0) {
|
if (fileList.length === 0) {
|
||||||
throw new Error(t('knowledgeBase.pleaseUploadImages'));
|
throw new Error(t('knowledgeBase.pleaseUploadImages'));
|
||||||
}
|
}
|
||||||
|
const ids = fileList.map((file) => file.response?.id);
|
||||||
|
handleChunking(kbId, parentId, ids)
|
||||||
|
// // 上传所有图片
|
||||||
|
// const uploadPromises = fileList.map(async (file) => {
|
||||||
|
// if (file.originFileObj) {
|
||||||
|
// const formData = new FormData();
|
||||||
|
// formData.append('file', file.originFileObj);
|
||||||
|
|
||||||
// 上传所有图片
|
// return uploadFile(formData, {
|
||||||
const uploadPromises = fileList.map(async (file) => {
|
// kb_id: kbId,
|
||||||
if (file.originFileObj) {
|
// parent_id: parentId,
|
||||||
const formData = new FormData();
|
// });
|
||||||
formData.append('file', file.originFileObj);
|
// }
|
||||||
|
// return null;
|
||||||
|
// });
|
||||||
|
|
||||||
return uploadFile(formData, {
|
// await Promise.all(uploadPromises);
|
||||||
kb_id: kbId,
|
|
||||||
parent_id: parentId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(uploadPromises);
|
|
||||||
|
|
||||||
if (refreshTable) {
|
if (refreshTable) {
|
||||||
await refreshTable();
|
await refreshTable();
|
||||||
@@ -78,13 +85,73 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const handleChunking = (kb_id: string, parent_id: string, file_id: Array<string>) => {
|
||||||
|
if (!kb_id) return;
|
||||||
|
const targetFileId = file_id
|
||||||
|
navigate(`/knowledge-base/${kb_id}/create-dataset`, {
|
||||||
|
state: {
|
||||||
|
source: 'local',
|
||||||
|
knowledgeBaseId: kb_id,
|
||||||
|
parentId: parent_id ?? kb_id,
|
||||||
|
startStep: 'parameterSettings',
|
||||||
|
fileId: targetFileId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
}));
|
}));
|
||||||
|
// 检查媒体文件时长的辅助函数
|
||||||
|
const checkMediaDuration = (file: File): Promise<number> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = URL.createObjectURL(file);
|
||||||
|
const media = document.createElement(file.type.startsWith('video/') ? 'video' : 'audio');
|
||||||
|
|
||||||
|
media.onloadedmetadata = () => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
resolve(media.duration);
|
||||||
|
};
|
||||||
|
|
||||||
|
media.onerror = () => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
reject(new Error('无法读取媒体文件'));
|
||||||
|
};
|
||||||
|
|
||||||
|
media.src = url;
|
||||||
|
});
|
||||||
|
};
|
||||||
// 上传文件
|
// 上传文件
|
||||||
const handleUpload = (options: UploadRequestOption) => {
|
const handleUpload = async (options: UploadRequestOption) => {
|
||||||
const { file, onSuccess, onError, onProgress, filename = 'file' } = options;
|
const { file, onSuccess, onError, onProgress, filename = 'file' } = options;
|
||||||
|
// 获取文件扩展名
|
||||||
|
const fileExtension = (file as File).name.split('.').pop()?.toLowerCase();
|
||||||
|
const mediaExtensions = ['mp3', 'mp4', 'mov', 'wav'];
|
||||||
|
|
||||||
|
// 如果是媒体文件,进行大小和时长检查
|
||||||
|
if (fileExtension && mediaExtensions.includes(fileExtension)) {
|
||||||
|
const fileSizeInMB = (file as File).size / (1024 * 1024);
|
||||||
|
|
||||||
|
// 检查文件大小(256MB限制)
|
||||||
|
if (fileSizeInMB > 256) {
|
||||||
|
messageApi.error(`${t('knowledgeBase.sizeLimitError')}:${fileSizeInMB.toFixed(2)}MB`);
|
||||||
|
onError?.(new Error(`${t('knowledgeBase.fileSizeExceeds')}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查媒体时长(150秒限制)
|
||||||
|
const duration = await checkMediaDuration(file as File);
|
||||||
|
if (duration > 150) {
|
||||||
|
messageApi.error(`${t('knowledgeBase.fileDurationLimitError')}:${Math.round(duration)}秒`);
|
||||||
|
onError?.(new Error(`${t('knowledgeBase.fileDurationExceeds')}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
messageApi.error(`${t('knowledgeBase.unableReadFile')}`);
|
||||||
|
onError?.(error as Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
formData.append(filename, file as File);
|
formData.append(filename, file as File);
|
||||||
@@ -108,6 +175,7 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
|
|||||||
onSuccess?.(res, new XMLHttpRequest());
|
onSuccess?.(res, new XMLHttpRequest());
|
||||||
if (res?.id) {
|
if (res?.id) {
|
||||||
// 上传成功
|
// 上传成功
|
||||||
|
// fileIds.push(res.id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -115,6 +183,8 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{contextHolder}
|
||||||
<RbModal
|
<RbModal
|
||||||
title={`${t('knowledgeBase.createA')} ${t('knowledgeBase.imageDataSet')}`}
|
title={`${t('knowledgeBase.createA')} ${t('knowledgeBase.imageDataSet')}`}
|
||||||
open={visible}
|
open={visible}
|
||||||
@@ -124,27 +194,28 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
|
|||||||
confirmLoading={loading}
|
confirmLoading={loading}
|
||||||
>
|
>
|
||||||
<Form form={form} layout="vertical">
|
<Form form={form} layout="vertical">
|
||||||
<Form.Item
|
{/* <Form.Item
|
||||||
name="name"
|
name="name"
|
||||||
label={t('knowledgeBase.datasetName')}
|
label={t('knowledgeBase.datasetName')}
|
||||||
rules={[{ required: true, message: t('knowledgeBase.pleaseEnterDatasetName') }]}
|
rules={[{ required: true, message: t('knowledgeBase.pleaseEnterDatasetName') }]}
|
||||||
>
|
>
|
||||||
<Input placeholder={t('knowledgeBase.pleaseEnterDatasetName')} />
|
<Input placeholder={t('knowledgeBase.pleaseEnterDatasetName')} />
|
||||||
</Form.Item>
|
</Form.Item> */}
|
||||||
|
|
||||||
<Form.Item label={t('knowledgeBase.uploadImages')}>
|
<Form.Item label={t('knowledgeBase.uploadMedia')}>
|
||||||
<UploadFiles
|
<UploadFiles
|
||||||
|
ref={uploadRef}
|
||||||
isCanDrag={true}
|
isCanDrag={true}
|
||||||
fileSize={50}
|
fileSize={100}
|
||||||
multiple={true}
|
multiple={true}
|
||||||
maxCount={99}
|
maxCount={99}
|
||||||
fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']}
|
fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'mp3', 'mp4', 'mov', 'wav']}
|
||||||
customRequest={handleUpload}
|
customRequest={handleUpload}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</RbModal>
|
</RbModal>
|
||||||
);
|
</>);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user