feat(knowledgeBase): enhance file upload and dataset creation with abort support and improved UX
- Add AbortSignal support to uploadFile API for cancellable uploads - Implement custom onRemove callback in UploadFiles component with confirmation dialog - Add i18n translations for file removal confirmation and error messages - Update supported file types documentation to include IMAGE and MEDIA formats - Improve file removal UI with cursor pointer styling - Refactor getModelList API to remove unused type parameter - Add Form import and UploadFile type for better type safety in CreateDataset - Enhance error handling and user feedback for file operations
This commit is contained in:
@@ -64,8 +64,8 @@ export const getModelTypeList = async () => {
|
|||||||
return response as any[];
|
return response as any[];
|
||||||
};
|
};
|
||||||
// 获取模型列表
|
// 获取模型列表
|
||||||
export const getModelList = async (type: string | string[], pageInfo: PageRequest) => {
|
export const getModelList = async (pageInfo: PageRequest) => {
|
||||||
const response = await request.get(`${apiPrefix}/models`, { type, ...pageInfo });
|
const response = await request.get(`${apiPrefix}/models`, pageInfo);
|
||||||
return response as any;
|
return response as any;
|
||||||
};
|
};
|
||||||
//获取模型提供者
|
//获取模型提供者
|
||||||
@@ -135,16 +135,18 @@ interface UploadFileOptions {
|
|||||||
kb_id?: string;
|
kb_id?: string;
|
||||||
parent_id?: string;
|
parent_id?: string;
|
||||||
onUploadProgress?: (event: AxiosProgressEvent) => void;
|
onUploadProgress?: (event: AxiosProgressEvent) => void;
|
||||||
|
signal?: AbortSignal;
|
||||||
}
|
}
|
||||||
// 上传文件
|
// 上传文件
|
||||||
export const uploadFile = async (data: FormData, options?: UploadFileOptions) => {
|
export const uploadFile = async (data: FormData, options?: UploadFileOptions) => {
|
||||||
const { kb_id, parent_id, onUploadProgress } = options || {};
|
const { kb_id, parent_id, onUploadProgress, signal } = options || {};
|
||||||
const params: Record<string, string> = {};
|
const params: Record<string, string> = {};
|
||||||
if (kb_id) params.kb_id = kb_id;
|
if (kb_id) params.kb_id = kb_id;
|
||||||
if (parent_id) params.parent_id = parent_id;
|
if (parent_id) params.parent_id = parent_id;
|
||||||
const response = await request.uploadFile(`${apiPrefix}/files/file`, data, {
|
const response = await request.uploadFile(`${apiPrefix}/files/file`, data, {
|
||||||
params,
|
params,
|
||||||
onUploadProgress,
|
onUploadProgress,
|
||||||
|
signal,
|
||||||
});
|
});
|
||||||
return response as UploadFileResponse;
|
return response as UploadFileResponse;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ interface UploadFilesProps extends Omit<UploadProps, 'onChange'> {
|
|||||||
maxCount?: number;
|
maxCount?: number;
|
||||||
/** 是否支持拖拽上传,默认为false */
|
/** 是否支持拖拽上传,默认为false */
|
||||||
isCanDrag?: boolean;
|
isCanDrag?: boolean;
|
||||||
|
/** 自定义移除文件回调 */
|
||||||
|
onRemove?: (file: UploadFile) => boolean | void | Promise<boolean | void>;
|
||||||
}
|
}
|
||||||
const ALL_FILE_TYPE: {
|
const ALL_FILE_TYPE: {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
@@ -77,6 +79,7 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
|||||||
isAutoUpload = true,
|
isAutoUpload = true,
|
||||||
maxCount = 1,
|
maxCount = 1,
|
||||||
isCanDrag = false,
|
isCanDrag = false,
|
||||||
|
onRemove: customOnRemove,
|
||||||
...props
|
...props
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -86,11 +89,20 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
|||||||
|
|
||||||
// 处理文件移除
|
// 处理文件移除
|
||||||
const handleRemove = (file: UploadFile) => {
|
const handleRemove = (file: UploadFile) => {
|
||||||
|
// 如果有自定义的 onRemove 回调,先执行它
|
||||||
|
if (customOnRemove) {
|
||||||
|
const result = customOnRemove(file);
|
||||||
|
// 如果返回 false,阻止移除
|
||||||
|
if (result === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
confirm({
|
confirm({
|
||||||
title: '确定要删除此文件吗?',
|
title: `${t('common.confirmRemoveFile')}`,
|
||||||
okText: '确定',
|
okText: `${t('common.confirm')}`,
|
||||||
okType: 'danger',
|
okType: 'danger',
|
||||||
cancelText: '取消',
|
cancelText: `${t('common.cancel')}`,
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
const newFileList = fileList.filter((item) => item.uid !== file.uid);
|
const newFileList = fileList.filter((item) => item.uid !== file.uid);
|
||||||
setFileList(newFileList);
|
setFileList(newFileList);
|
||||||
@@ -236,7 +248,7 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
|||||||
<div key={file.uid} className="rb:relative rb:w-full rb:pt-[8px] rb:pl-[10px] rb:pr-[10px] rb-pb-[10px] rb:border-1 rb:border-[#EBEBEB] rb:rounded rb:p-2 rb:mt-2 rb:bg-white">
|
<div key={file.uid} className="rb:relative rb:w-full rb:pt-[8px] rb:pl-[10px] rb:pr-[10px] rb-pb-[10px] rb:border-1 rb:border-[#EBEBEB] rb:rounded rb:p-2 rb:mt-2 rb:bg-white">
|
||||||
<div className="rb:text-[12px] rb:flex rb:items-center rb:justify-between rb:mb-[2px]">
|
<div className="rb:text-[12px] rb:flex rb:items-center rb:justify-between rb:mb-[2px]">
|
||||||
{file.name}
|
{file.name}
|
||||||
<span className="rb:text-[#5B6167]" onClick={() => actions?.remove()}>Cancel</span>
|
<span className="rb:text-[#5B6167] rb:cursor-pointer" onClick={() => actions?.remove()}>Cancel</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress percent={file.percent || 0} strokeColor={file.status === 'error' ? '#FF5D34' : '#155EEF'} size="small" showInfo={false} />
|
<Progress percent={file.percent || 0} strokeColor={file.status === 'error' ? '#FF5D34' : '#155EEF'} size="small" showInfo={false} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -293,7 +293,9 @@ export const en = {
|
|||||||
add: 'Add',
|
add: 'Add',
|
||||||
addOption: 'Add Option',
|
addOption: 'Add Option',
|
||||||
viewDetail: 'View Detail',
|
viewDetail: 'View Detail',
|
||||||
|
confirmRemoveFile: 'Are you sure you want to remove this file?',
|
||||||
deleteSuccess: 'Delete successfully',
|
deleteSuccess: 'Delete successfully',
|
||||||
|
deleteFailed: 'Delete Failure',
|
||||||
foldUp: 'Collapse',
|
foldUp: 'Collapse',
|
||||||
expanded: 'Expand',
|
expanded: 'Expand',
|
||||||
clickUploadIcon: 'click on the upload icon',
|
clickUploadIcon: 'click on the upload icon',
|
||||||
@@ -468,7 +470,7 @@ export const en = {
|
|||||||
knowledgeBase: 'Knowledge Base',
|
knowledgeBase: 'Knowledge Base',
|
||||||
selectDataSource: 'Select Source',
|
selectDataSource: 'Select Source',
|
||||||
localFile: 'Local File',
|
localFile: 'Local File',
|
||||||
uploadFileTypes: 'Upload PDF, TXT, DOCX and other format files',
|
uploadFileTypes: 'Upload PDF, TXT, DOCX, IMAGE, MEDIA and other format files',
|
||||||
webLink: 'Web Link',
|
webLink: 'Web Link',
|
||||||
webLinkPlaceholder:'Please enter',
|
webLinkPlaceholder:'Please enter',
|
||||||
webLinkDesc: 'Only static links are supported. If the uploaded data shows as empty, the link may not be readable. One per line, with a maximum of {{count}} links at a time',
|
webLinkDesc: 'Only static links are supported. If the uploaded data shows as empty, the link may not be readable. One per line, with a maximum of {{count}} links at a time',
|
||||||
@@ -476,6 +478,7 @@ export const en = {
|
|||||||
readStaticWebPage: 'Read static web page content as dataset',
|
readStaticWebPage: 'Read static web page content as dataset',
|
||||||
customText: 'Custom Text',
|
customText: 'Custom Text',
|
||||||
customContent: 'Custom Content',
|
customContent: 'Custom Content',
|
||||||
|
createContentError: 'Failed to create custom content',
|
||||||
manuallyInputText: 'Manually input a text as dataset',
|
manuallyInputText: 'Manually input a text as dataset',
|
||||||
importTemplate: 'Template Import',
|
importTemplate: 'Template Import',
|
||||||
importBackup: 'Backup Import',
|
importBackup: 'Backup Import',
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const zh = {
|
|||||||
operation: '操作',
|
operation: '操作',
|
||||||
selectDataSource: '选择来源',
|
selectDataSource: '选择来源',
|
||||||
localFile: '本地文件',
|
localFile: '本地文件',
|
||||||
uploadFileTypes: '上传 PDF、TXT、DOCX 等格式的文件',
|
uploadFileTypes: '上传 PDF、TXT、DOCX, IMAGE, MEDIA 等格式的文件',
|
||||||
webLink: '网页链接',
|
webLink: '网页链接',
|
||||||
webLinkPlaceholder: '请输入',
|
webLinkPlaceholder: '请输入',
|
||||||
webLinkDesc: '仅支持静态链接。如果上传的数据显示为空,则该链接可能无法读取。每行一个,一次最多{{count}}个链接',
|
webLinkDesc: '仅支持静态链接。如果上传的数据显示为空,则该链接可能无法读取。每行一个,一次最多{{count}}个链接',
|
||||||
@@ -99,6 +99,7 @@ export const zh = {
|
|||||||
readStaticWebPage: '读取静态网页内容作为数据集',
|
readStaticWebPage: '读取静态网页内容作为数据集',
|
||||||
customText: '自定义文本',
|
customText: '自定义文本',
|
||||||
customContent: '自定义内容',
|
customContent: '自定义内容',
|
||||||
|
createContentError: '创建自定义文件失败',
|
||||||
manuallyInputText: '手动输入一段文本作为数据集',
|
manuallyInputText: '手动输入一段文本作为数据集',
|
||||||
openKnowledgeBase: '打开知识库',
|
openKnowledgeBase: '打开知识库',
|
||||||
searchPlaceholder: '搜索',
|
searchPlaceholder: '搜索',
|
||||||
@@ -784,7 +785,9 @@ export const zh = {
|
|||||||
add: '添加',
|
add: '添加',
|
||||||
addOption: '添加选项',
|
addOption: '添加选项',
|
||||||
viewDetail: '查看详情',
|
viewDetail: '查看详情',
|
||||||
|
confirmRemoveFile: '确定要移除此文件吗?',
|
||||||
deleteSuccess: '删除成功',
|
deleteSuccess: '删除成功',
|
||||||
|
deleteFailed: '删除失败',
|
||||||
foldUp: '收起',
|
foldUp: '收起',
|
||||||
expanded: '展开',
|
expanded: '展开',
|
||||||
clickUploadIcon: '点击上传图标',
|
clickUploadIcon: '点击上传图标',
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { useMemo,useRef, useState, useEffect } from 'react';
|
import { useMemo,useRef, useState, useEffect } from 'react';
|
||||||
import { Button, Flex, Radio, Steps, Modal, Input, Spin, message, Checkbox, Select} from 'antd';
|
import { Button, Flex, Radio, Steps, Modal, Input, Spin, message, Checkbox, Select, Form} from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import Table, { type TableRef } from '@/components/Table'
|
import Table, { type TableRef } from '@/components/Table'
|
||||||
import type { AnyObject } from 'antd/es/_util/type';
|
import type { AnyObject } from 'antd/es/_util/type';
|
||||||
import type { UploadFileResponse,KnowledgeBaseDocumentData } from '@/views/KnowledgeBase/types';
|
import type { UploadFileResponse,KnowledgeBaseDocumentData } from '@/views/KnowledgeBase/types';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
|
import type { UploadFile } from 'antd';
|
||||||
import UploadFiles from '@/components/Upload/UploadFiles';
|
import UploadFiles from '@/components/Upload/UploadFiles';
|
||||||
import type { UploadRequestOption } from 'rc-upload/lib/interface';
|
import type { UploadRequestOption } from 'rc-upload/lib/interface';
|
||||||
import { uploadFile, getDocumentList, parseDocument, updateDocument, deleteDocument } from '@/api/knowledgeBase';
|
import { uploadFile, getDocumentList, parseDocument, updateDocument, deleteDocument, createDocumentAndUpload } from '@/api/knowledgeBase';
|
||||||
import exitIcon from '@/assets/images/knowledgeBase/exit.png';
|
import exitIcon from '@/assets/images/knowledgeBase/exit.png';
|
||||||
|
|
||||||
import SliderInput from '@/components/SliderInput';
|
import SliderInput from '@/components/SliderInput';
|
||||||
@@ -56,7 +57,10 @@ interface CreateDatasetLocationState {
|
|||||||
fileId?: string | string[];
|
fileId?: string | string[];
|
||||||
fileIds?: string | string[];
|
fileIds?: string | string[];
|
||||||
}
|
}
|
||||||
|
interface ContentFormData {
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
const CreateDataset = () => {
|
const CreateDataset = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -75,7 +79,7 @@ const CreateDataset = () => {
|
|||||||
const [current, setCurrent] = useState<number>(stepIndexMap[initialStepKey]);
|
const [current, setCurrent] = useState<number>(stepIndexMap[initialStepKey]);
|
||||||
const tableRef = useRef<TableRef>(null);
|
const tableRef = useRef<TableRef>(null);
|
||||||
|
|
||||||
|
const [form] = Form.useForm<ContentFormData>();
|
||||||
const [data, setData] = useState<KnowledgeBaseDocumentData[]>([]);
|
const [data, setData] = useState<KnowledgeBaseDocumentData[]>([]);
|
||||||
const [rechunkFileIds, setRechunkFileIds] = useState<string[]>(initialFileIds);
|
const [rechunkFileIds, setRechunkFileIds] = useState<string[]>(initialFileIds);
|
||||||
|
|
||||||
@@ -98,12 +102,15 @@ const CreateDataset = () => {
|
|||||||
],
|
],
|
||||||
[t],
|
[t],
|
||||||
);
|
);
|
||||||
|
// 存储每个文件的 AbortController,用于取消上传
|
||||||
const handleNext = () => {
|
const abortControllersRef = useRef<Map<string, AbortController>>(new Map());
|
||||||
|
const uploadRef = useRef<{ fileList: UploadFile[]; clearFiles: () => void }>(null);
|
||||||
|
console.log('上传文件',uploadRef.current?.fileList.length)
|
||||||
|
const handleNext = async () => {
|
||||||
// 暂时隐藏第三步:调整步骤索引(0->1->2 对应 选择文件->参数设置->确认上传)
|
// 暂时隐藏第三步:调整步骤索引(0->1->2 对应 选择文件->参数设置->确认上传)
|
||||||
let nextStep = current + 1;
|
let nextStep = current + 1;
|
||||||
|
|
||||||
if(nextStep === 1) {
|
if(nextStep === 1 && source === 'local') {
|
||||||
// 检查是否有文件已上传
|
// 检查是否有文件已上传
|
||||||
if (rechunkFileIds.length === 0) {
|
if (rechunkFileIds.length === 0) {
|
||||||
// 如果没有文件,提示用户先上传文件
|
// 如果没有文件,提示用户先上传文件
|
||||||
@@ -113,6 +120,27 @@ const CreateDataset = () => {
|
|||||||
});
|
});
|
||||||
return; // 不进入下一步
|
return; // 不进入下一步
|
||||||
}
|
}
|
||||||
|
}else if(nextStep === 1 && source === 'text'){
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
// setLoading(true);
|
||||||
|
|
||||||
|
// TODO: 这里需要调用相应的API来保存内容
|
||||||
|
const params = {
|
||||||
|
// ...values,
|
||||||
|
kb_id: knowledgeBaseId,
|
||||||
|
parent_id: parentId,
|
||||||
|
};
|
||||||
|
const response = await createDocumentAndUpload(values, params)
|
||||||
|
if(response) {
|
||||||
|
setRechunkFileIds([response.id])
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
messageApi.error(t('knowledgeBase.createContentError'));
|
||||||
|
} finally {
|
||||||
|
// setLoading(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从参数设置进入确认上传时的处理
|
// 从参数设置进入确认上传时的处理
|
||||||
@@ -262,7 +290,7 @@ const CreateDataset = () => {
|
|||||||
|
|
||||||
media.onerror = () => {
|
media.onerror = () => {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
reject(new Error('无法读取媒体文件'));
|
reject(new Error(`${t('knowledgeBase.unableReadFile')}`));
|
||||||
};
|
};
|
||||||
|
|
||||||
media.src = url;
|
media.src = url;
|
||||||
@@ -273,18 +301,24 @@ const CreateDataset = () => {
|
|||||||
const handleUpload = async (options: UploadRequestOption) => {
|
const handleUpload = async (options: UploadRequestOption) => {
|
||||||
const { file, onSuccess, onError, onProgress, filename = 'file' } = options;
|
const { file, onSuccess, onError, onProgress, filename = 'file' } = options;
|
||||||
|
|
||||||
|
// 创建 AbortController 用于取消上传
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const fileUid = (file as any).uid;
|
||||||
|
abortControllersRef.current.set(fileUid, abortController);
|
||||||
|
|
||||||
// 获取文件扩展名
|
// 获取文件扩展名
|
||||||
const fileExtension = (file as File).name.split('.').pop()?.toLowerCase();
|
const fileExtension = (file as File).name.split('.').pop()?.toLowerCase();
|
||||||
const mediaExtensions = ['mp3', 'mp4', 'mov', 'wav'];
|
const mediaExtensions = ['mp3', 'mp4', 'mov', 'wav'];
|
||||||
|
|
||||||
// 如果是媒体文件,进行大小和时长检查
|
// 如果是媒体文件,进行大小和时长检查
|
||||||
if (fileExtension && mediaExtensions.includes(fileExtension)) {
|
if (fileExtension && mediaExtensions.includes(fileExtension)) {
|
||||||
const fileSizeInMB = (file as File).size / (1024 * 1024);
|
const fileSizeInMB = (file as File).size / (100 * 1024);
|
||||||
|
|
||||||
// 检查文件大小(256MB限制)
|
// 检查文件大小(50MB限制)
|
||||||
if (fileSizeInMB > 256) {
|
if (fileSizeInMB > 100) {
|
||||||
messageApi.error(`${t('knowledgeBase.sizeLimitError')}:${fileSizeInMB.toFixed(2)}MB`);
|
messageApi.error(`${t('knowledgeBase.sizeLimitError')}:${fileSizeInMB.toFixed(2)}MB`);
|
||||||
onError?.(new Error(`${t('knowledgeBase.fileSizeExceeds')}`));
|
onError?.(new Error(`${t('knowledgeBase.fileSizeExceeds')}`));
|
||||||
|
abortControllersRef.current.delete(fileUid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,11 +328,13 @@ const CreateDataset = () => {
|
|||||||
if (duration > 150) {
|
if (duration > 150) {
|
||||||
messageApi.error(`${t('knowledgeBase.fileDurationLimitError')}:${Math.round(duration)}秒`);
|
messageApi.error(`${t('knowledgeBase.fileDurationLimitError')}:${Math.round(duration)}秒`);
|
||||||
onError?.(new Error(`${t('knowledgeBase.fileDurationExceeds')}`));
|
onError?.(new Error(`${t('knowledgeBase.fileDurationExceeds')}`));
|
||||||
|
abortControllersRef.current.delete(fileUid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
messageApi.error(`${t('knowledgeBase.unableReadFile')}`);
|
messageApi.error(`${t('knowledgeBase.unableReadFile')}`);
|
||||||
onError?.(error as Error);
|
onError?.(error as Error);
|
||||||
|
abortControllersRef.current.delete(fileUid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,6 +351,7 @@ const CreateDataset = () => {
|
|||||||
uploadFile(formData, {
|
uploadFile(formData, {
|
||||||
kb_id: knowledgeBaseId,
|
kb_id: knowledgeBaseId,
|
||||||
parent_id: parentId,
|
parent_id: parentId,
|
||||||
|
signal: abortController.signal,
|
||||||
onUploadProgress: (event) => {
|
onUploadProgress: (event) => {
|
||||||
if (!event.total) return;
|
if (!event.total) return;
|
||||||
const percent = Math.round((event.loaded / event.total) * 100);
|
const percent = Math.round((event.loaded / event.total) * 100);
|
||||||
@@ -332,6 +369,14 @@ const CreateDataset = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
// 移除 AbortController
|
||||||
|
abortControllersRef.current.delete(fileUid);
|
||||||
|
|
||||||
|
// 如果是用户主动取消,不显示错误信息
|
||||||
|
if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') {
|
||||||
|
console.log('上传已取消:', (file as File).name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
onError?.(error as Error);
|
onError?.(error as Error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -419,23 +464,33 @@ const CreateDataset = () => {
|
|||||||
setBlockSize(value);
|
setBlockSize(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 删除已上传的文件
|
||||||
// 当从其他页面跳转过来且带有 fileIds 时,加载对应的文档数据
|
const handleDeleteFile = async (fileId: string) => {
|
||||||
useEffect(() => {
|
try {
|
||||||
if (initialFileIds.length > 0 && initialStepKey !== 'selectFile' && knowledgeBaseId && parentId) {
|
await deleteDocument(fileId);
|
||||||
// 加载文档列表数据
|
// 删除成功,从 rechunkFileIds 中移除该 id
|
||||||
getDocumentList(knowledgeBaseId,{
|
setRechunkFileIds((prev) => prev.filter((id) => id !== fileId));
|
||||||
document_ids: initialFileIds.join(','),
|
console.log(`${t('common.deleteSuccess')}`);
|
||||||
})
|
} catch (error) {
|
||||||
.then((res: any) => {
|
messageApi.error(`${t('common.deleteFailed')}`);
|
||||||
const documents = res.items || [];
|
|
||||||
setData(documents);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('加载文档列表失败:', error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, []);
|
};
|
||||||
|
// 当从其他页面跳转过来且带有 fileIds 时,加载对应的文档数据
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (initialFileIds.length > 0 && initialStepKey !== 'selectFile' && knowledgeBaseId && parentId) {
|
||||||
|
// // 加载文档列表数据
|
||||||
|
// getDocumentList(knowledgeBaseId,{
|
||||||
|
// document_ids: initialFileIds.join(','),
|
||||||
|
// })
|
||||||
|
// .then((res: any) => {
|
||||||
|
// const documents = res.items || [];
|
||||||
|
// setData(documents);
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// console.error('加载文档列表失败:', error);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }, []);
|
||||||
|
|
||||||
// 清理函数:组件卸载时清除定时器和 loading 状态
|
// 清理函数:组件卸载时清除定时器和 loading 状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -480,7 +535,40 @@ const CreateDataset = () => {
|
|||||||
{current === 0 && (
|
{current === 0 && (
|
||||||
<div className='rb:flex rb:w-full rb:mt-10'>
|
<div className='rb:flex rb:w-full rb:mt-10'>
|
||||||
{source && source === 'local' && (
|
{source && source === 'local' && (
|
||||||
<UploadFiles isCanDrag={true} fileSize={50} multiple={true} maxCount={99} fileType={fileType} customRequest={handleUpload} />
|
<UploadFiles
|
||||||
|
ref={uploadRef}
|
||||||
|
isCanDrag={true}
|
||||||
|
fileSize={100}
|
||||||
|
multiple={true}
|
||||||
|
maxCount={99}
|
||||||
|
fileType={fileType}
|
||||||
|
customRequest={handleUpload}
|
||||||
|
onChange={(fileList) => {
|
||||||
|
console.log('文件列表变化:', fileList);
|
||||||
|
}}
|
||||||
|
onRemove={async (file) => {
|
||||||
|
// 如果文件正在上传,取消上传
|
||||||
|
const fileUid = file.uid;
|
||||||
|
const abortController = abortControllersRef.current.get(fileUid);
|
||||||
|
if (abortController) {
|
||||||
|
abortController.abort();
|
||||||
|
abortControllersRef.current.delete(fileUid);
|
||||||
|
|
||||||
|
}
|
||||||
|
console.log('文件移除前:', uploadRef.current?.fileList);
|
||||||
|
// 如果文件已经上传成功,删除服务器上的文件并从rechunkFileIds中移除对应的ID
|
||||||
|
if (file.response?.id) {
|
||||||
|
try {
|
||||||
|
await deleteDocument(file.response.id);
|
||||||
|
setRechunkFileIds(prev => prev.filter(id => id !== file.response.id));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除文件失败:', error);
|
||||||
|
messageApi.error('删除文件失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // 允许移除文件
|
||||||
|
}} />
|
||||||
)}
|
)}
|
||||||
{source && source === 'link' && (
|
{source && source === 'link' && (
|
||||||
<div className='rb:flex rb:w-full rb:flex-col rb:mt-10 rb:px-40'>
|
<div className='rb:flex rb:w-full rb:flex-col rb:mt-10 rb:px-40'>
|
||||||
@@ -500,15 +588,36 @@ const CreateDataset = () => {
|
|||||||
)}
|
)}
|
||||||
{source && source === 'text' && (
|
{source && source === 'text' && (
|
||||||
<div className='rb:flex rb:w-full rb:flex-col rb:mt-10 rb:px-40'>
|
<div className='rb:flex rb:w-full rb:flex-col rb:mt-10 rb:px-40'>
|
||||||
|
<Form form={form} layout="vertical">
|
||||||
|
<Form.Item
|
||||||
|
name="title"
|
||||||
|
label={t('knowledgeBase.title')}
|
||||||
|
rules={[{ required: true, message: t('knowledgeBase.pleaseEnterTitle') }]}
|
||||||
|
>
|
||||||
|
<Input placeholder={t('knowledgeBase.pleaseEnterTitle')} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mb-3'>
|
<Form.Item
|
||||||
|
name="content"
|
||||||
|
label={t('knowledgeBase.customContent')}
|
||||||
|
rules={[{ required: true, message: t('knowledgeBase.pleaseEnterContent') }]}
|
||||||
|
>
|
||||||
|
<Input.TextArea
|
||||||
|
placeholder={t('knowledgeBase.pleaseEnterContent')}
|
||||||
|
rows={8}
|
||||||
|
showCount
|
||||||
|
maxLength={5000}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
{/* <div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mb-3'>
|
||||||
{t('knowledgeBase.customText')}
|
{t('knowledgeBase.customText')}
|
||||||
</div>
|
</div>
|
||||||
<Input className='rb:w-full' placeholder={t('knowledgeBase.webLinkPlaceholder')}/>
|
<Input className='rb:w-full' placeholder={t('knowledgeBase.webLinkPlaceholder')}/>
|
||||||
<div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mt-10 rb:mb-3'>
|
<div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mt-10 rb:mb-3'>
|
||||||
{t('knowledgeBase.customContent')}
|
{t('knowledgeBase.customContent')}
|
||||||
</div>
|
</div>
|
||||||
<TextArea rows={6} placeholder={t('knowledgeBase.webLinkPlaceholder')} />
|
<TextArea rows={6} placeholder={t('knowledgeBase.webLinkPlaceholder')} /> */}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -700,7 +809,7 @@ const CreateDataset = () => {
|
|||||||
<Button
|
<Button
|
||||||
type='primary'
|
type='primary'
|
||||||
onClick={current === 2 ? handleStartUpload : handleNext}
|
onClick={current === 2 ? handleStartUpload : handleNext}
|
||||||
disabled={pollingLoading}
|
disabled={pollingLoading || (current === 0 && rechunkFileIds.length === 0)}
|
||||||
loading={pollingLoading}
|
loading={pollingLoading}
|
||||||
>
|
>
|
||||||
{current === 2 ? t('knowledgeBase.startUploading') || 'Start Upload' : t('common.next') || 'Next'}
|
{current === 2 ? t('knowledgeBase.startUploading') || 'Start Upload' : t('common.next') || 'Next'}
|
||||||
|
|||||||
@@ -321,28 +321,27 @@ const Private: FC = () => {
|
|||||||
{
|
{
|
||||||
key: '2',
|
key: '2',
|
||||||
icon: <img src={textIcon} alt="text" style={{ width: 16, height: 16 }} />,
|
icon: <img src={textIcon} alt="text" style={{ width: 16, height: 16 }} />,
|
||||||
label: (<span>{t('knowledgeBase.text')} {t('knowledgeBase.dataset')}</span>),
|
label: (<span>{t('knowledgeBase.createA')} {t('knowledgeBase.dataset')}</span>),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
datasetModalRef?.current?.handleOpen(knowledgeBase?.id,folder?.parent_id ?? knowledgeBase?.id ?? '');
|
datasetModalRef?.current?.handleOpen(knowledgeBase?.id,folder?.parent_id ?? knowledgeBase?.id ?? '');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
key: '8',
|
// key: '8',
|
||||||
icon: <img src={blankIcon} alt="Custome Text" style={{ width: 16, height: 16 }} />,
|
// icon: <img src={blankIcon} alt="Custome Text" style={{ width: 16, height: 16 }} />,
|
||||||
label: t('knowledgeBase.customTextDataset'),
|
// label: t('knowledgeBase.mediaDataSet'),
|
||||||
onClick: () => {
|
// onClick: () => {
|
||||||
createContentModalRef?.current?.handleOpen(knowledgeBase?.id ?? '', folder?.parent_id ?? knowledgeBase?.id ?? '');
|
// createContentModalRef?.current?.handleOpen(knowledgeBase?.id ?? '', folder?.parent_id ?? knowledgeBase?.id ?? '');
|
||||||
// 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.imageDataSet'),
|
||||||
label: t('knowledgeBase.mediaDataSet'),
|
// onClick: () => {
|
||||||
onClick: () => {
|
// createImageDataset?.current?.handleOpen(knowledgeBaseId || '', parentId || '')
|
||||||
createImageDataset?.current?.handleOpen(knowledgeBaseId || '', parentId || '')
|
// },
|
||||||
},
|
// },
|
||||||
},
|
|
||||||
// 暂时未实现
|
// 暂时未实现
|
||||||
// {
|
// {
|
||||||
// key: '4',
|
// key: '4',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* @Author: yujiangping
|
* @Author: yujiangping
|
||||||
* @Date: 2025-11-10 18:52:55
|
* @Date: 2025-11-10 18:52:55
|
||||||
* @LastEditors: yujiangping
|
* @LastEditors: yujiangping
|
||||||
* @LastEditTime: 2025-11-24 11:23:33
|
* @LastEditTime: 2025-12-29 16:09:13
|
||||||
*/
|
*/
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import type { RadioChangeEvent } from 'antd';
|
import type { RadioChangeEvent } from 'antd';
|
||||||
@@ -51,10 +51,10 @@ const CreateDatasetModal = forwardRef<CreateDatasetModalRef,CreateDatasetModalRe
|
|||||||
// title: t('knowledgeBase.webLink'),
|
// title: t('knowledgeBase.webLink'),
|
||||||
// description: t('knowledgeBase.readStaticWebPage')
|
// description: t('knowledgeBase.readStaticWebPage')
|
||||||
// },
|
// },
|
||||||
// {
|
{
|
||||||
// title: t('knowledgeBase.customText'),
|
title: t('knowledgeBase.customText'),
|
||||||
// description: t('knowledgeBase.manuallyInputText')
|
description: t('knowledgeBase.manuallyInputText')
|
||||||
// },
|
},
|
||||||
]
|
]
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
// 封装取消方法,添加关闭弹窗逻辑
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
@@ -111,7 +111,7 @@ const CreateDatasetModal = forwardRef<CreateDatasetModalRef,CreateDatasetModalRe
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RbModal
|
<RbModal
|
||||||
title={t('knowledgeBase.createA') + ' ' + t('knowledgeBase.text') + ' ' + t('knowledgeBase.dataset')}
|
title={t('knowledgeBase.createA') + ' ' + t('knowledgeBase.dataset')}
|
||||||
open={visible}
|
open={visible}
|
||||||
onCancel={handleClose}
|
onCancel={handleClose}
|
||||||
okText={t('common.create')}
|
okText={t('common.create')}
|
||||||
@@ -133,13 +133,13 @@ const CreateDatasetModal = forwardRef<CreateDatasetModalRef,CreateDatasetModalRe
|
|||||||
<span className='rb:text-base rb:font-medium rb:text-gray-800'>{items[1].title}</span>
|
<span className='rb:text-base rb:font-medium rb:text-gray-800'>{items[1].title}</span>
|
||||||
<span className='rb:text-xs rb:text-gray-500'>{items[1].description}</span>
|
<span className='rb:text-xs rb:text-gray-500'>{items[1].description}</span>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Radio>
|
</Radio> */}
|
||||||
<Radio value={2} style={getActiveRadioStyle(value === 2)} className='rb:w-full'>
|
<Radio value={2} style={getActiveRadioStyle(value === 2)} className='rb:w-full'>
|
||||||
<Flex gap="small" align='start' justify='start' vertical>
|
<Flex gap="small" align='start' justify='start' vertical>
|
||||||
<span className='rb:text-base rb:font-medium rb:text-gray-800'>{items[2].title}</span>
|
<span className='rb:text-base rb:font-medium rb:text-gray-800'>{items[1].title}</span>
|
||||||
<span className='rb:text-xs rb:text-gray-500'>{items[2].description}</span>
|
<span className='rb:text-xs rb:text-gray-500'>{items[1].description}</span>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Radio> */}
|
</Radio>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
|
import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { Form, Input, message } from 'antd';
|
import { Form, 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 } from '@/views/KnowledgeBase/types';
|
||||||
import type { UploadRequestOption } from 'rc-upload/lib/interface';
|
import type { UploadRequestOption } from 'rc-upload/lib/interface';
|
||||||
import RbModal from '@/components/RbModal';
|
import RbModal from '@/components/RbModal';
|
||||||
import UploadFiles from '@/components/Upload/UploadFiles';
|
import UploadFiles from '@/components/Upload/UploadFiles';
|
||||||
import { uploadFile } from '@/api/knowledgeBase';
|
import { uploadFile, deleteDocument } from '@/api/knowledgeBase';
|
||||||
|
|
||||||
interface ImageDatasetFormData {
|
interface ImageDatasetFormData {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -26,16 +26,26 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
|
|||||||
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 [hasFiles, setHasFiles] = useState(false);
|
||||||
const uploadRef = useRef<{ fileList: UploadFile[]; clearFiles: () => void }>(null);
|
const uploadRef = useRef<{ fileList: UploadFile[]; clearFiles: () => void }>(null);
|
||||||
|
// 存储每个文件的 AbortController,用于取消上传
|
||||||
|
const abortControllersRef = useRef<Map<string, AbortController>>(new Map());
|
||||||
// const fileIds = [];
|
// const fileIds = [];
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
|
// 取消所有正在进行的上传
|
||||||
|
abortControllersRef.current.forEach((controller) => {
|
||||||
|
controller.abort();
|
||||||
|
});
|
||||||
|
abortControllersRef.current.clear();
|
||||||
|
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
uploadRef.current?.clearFiles();
|
uploadRef.current?.clearFiles();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
setKbId('');
|
setKbId('');
|
||||||
setParentId('');
|
setParentId('');
|
||||||
|
setHasFiles(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpen = (kb_id: string, parent_id: string) => {
|
const handleOpen = (kb_id: string, parent_id: string) => {
|
||||||
@@ -43,6 +53,7 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
|
|||||||
setParentId(parent_id);
|
setParentId(parent_id);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
uploadRef.current?.clearFiles();
|
uploadRef.current?.clearFiles();
|
||||||
|
setHasFiles(false);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -120,21 +131,38 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
|
|||||||
media.src = url;
|
media.src = url;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
// 删除已上传的文件
|
||||||
|
const handleDeleteFile = async (fileId: string) => {
|
||||||
|
try {
|
||||||
|
await deleteDocument(fileId);
|
||||||
|
console.log(`${t('common.deleteSuccess')}`);
|
||||||
|
} catch (error) {
|
||||||
|
messageApi.error(`${t('common.deleteFailed')}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 上传文件
|
// 上传文件
|
||||||
const handleUpload = async (options: UploadRequestOption) => {
|
const handleUpload = async (options: UploadRequestOption) => {
|
||||||
const { file, onSuccess, onError, onProgress, filename = 'file' } = options;
|
const { file, onSuccess, onError, onProgress, filename = 'file' } = options;
|
||||||
|
|
||||||
|
// 创建 AbortController 用于取消上传
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const fileUid = (file as any).uid;
|
||||||
|
abortControllersRef.current.set(fileUid, abortController);
|
||||||
|
|
||||||
// 获取文件扩展名
|
// 获取文件扩展名
|
||||||
const fileExtension = (file as File).name.split('.').pop()?.toLowerCase();
|
const fileExtension = (file as File).name.split('.').pop()?.toLowerCase();
|
||||||
const mediaExtensions = ['mp3', 'mp4', 'mov', 'wav'];
|
const mediaExtensions = ['mp3', 'mp4', 'mov', 'wav'];
|
||||||
|
|
||||||
// 如果是媒体文件,进行大小和时长检查
|
// 如果是媒体文件,进行大小和时长检查
|
||||||
if (fileExtension && mediaExtensions.includes(fileExtension)) {
|
if (fileExtension && mediaExtensions.includes(fileExtension)) {
|
||||||
const fileSizeInMB = (file as File).size / (1024 * 1024);
|
const fileSizeInMB = (file as File).size / (50 * 1024);
|
||||||
|
|
||||||
// 检查文件大小(256MB限制)
|
// 检查文件大小(50MB限制)
|
||||||
if (fileSizeInMB > 256) {
|
if (fileSizeInMB > 50) {
|
||||||
messageApi.error(`${t('knowledgeBase.sizeLimitError')}:${fileSizeInMB.toFixed(2)}MB`);
|
messageApi.error(`${t('knowledgeBase.sizeLimitError')}:${fileSizeInMB.toFixed(2)}MB`);
|
||||||
onError?.(new Error(`${t('knowledgeBase.fileSizeExceeds')}`));
|
onError?.(new Error(`${t('knowledgeBase.fileSizeExceeds')}`));
|
||||||
|
abortControllersRef.current.delete(fileUid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,11 +172,13 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
|
|||||||
if (duration > 150) {
|
if (duration > 150) {
|
||||||
messageApi.error(`${t('knowledgeBase.fileDurationLimitError')}:${Math.round(duration)}秒`);
|
messageApi.error(`${t('knowledgeBase.fileDurationLimitError')}:${Math.round(duration)}秒`);
|
||||||
onError?.(new Error(`${t('knowledgeBase.fileDurationExceeds')}`));
|
onError?.(new Error(`${t('knowledgeBase.fileDurationExceeds')}`));
|
||||||
|
abortControllersRef.current.delete(fileUid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
messageApi.error(`${t('knowledgeBase.unableReadFile')}`);
|
messageApi.error(`${t('knowledgeBase.unableReadFile')}`);
|
||||||
onError?.(error as Error);
|
onError?.(error as Error);
|
||||||
|
abortControllersRef.current.delete(fileUid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,36 +192,53 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
|
|||||||
formData.append('parent_id', parentId);
|
formData.append('parent_id', parentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadFile(formData, {
|
try {
|
||||||
kb_id: kbId,
|
const res = await uploadFile(formData, {
|
||||||
parent_id: parentId,
|
kb_id: kbId,
|
||||||
onUploadProgress: (event) => {
|
parent_id: parentId,
|
||||||
if (!event.total) return;
|
signal: abortController.signal,
|
||||||
const percent = Math.round((event.loaded / event.total) * 100);
|
onUploadProgress: (event) => {
|
||||||
onProgress?.({ percent }, file);
|
if (!event.total) return;
|
||||||
},
|
const percent = Math.round((event.loaded / event.total) * 100);
|
||||||
})
|
onProgress?.({ percent }, file);
|
||||||
.then((res: UploadFileResponse) => {
|
},
|
||||||
onSuccess?.(res, new XMLHttpRequest());
|
|
||||||
if (res?.id) {
|
|
||||||
// 上传成功
|
|
||||||
// fileIds.push(res.id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
onError?.(error as Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 上传成功,移除 AbortController
|
||||||
|
abortControllersRef.current.delete(fileUid);
|
||||||
|
onSuccess?.(res, new XMLHttpRequest());
|
||||||
|
|
||||||
|
if (res?.id) {
|
||||||
|
// 上传成功
|
||||||
|
// fileIds.push(res.id)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
// 移除 AbortController
|
||||||
|
abortControllersRef.current.delete(fileUid);
|
||||||
|
|
||||||
|
// 如果是用户主动取消,不显示错误信息
|
||||||
|
if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') {
|
||||||
|
console.log('上传已取消:', (file as File).name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onError?.(error as Error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
<RbModal
|
<RbModal
|
||||||
title={`${t('knowledgeBase.createA')} ${t('knowledgeBase.imageDataSet')}`}
|
title={`${t('knowledgeBase.createA')} ${t('knowledgeBase.mediaDataSet')}`}
|
||||||
open={visible}
|
open={visible}
|
||||||
onCancel={handleClose}
|
onCancel={handleClose}
|
||||||
okText={t('common.create')}
|
okText={t('common.create')}
|
||||||
onOk={handleSave}
|
onOk={handleSave}
|
||||||
confirmLoading={loading}
|
confirmLoading={loading}
|
||||||
|
maskClosable={false}
|
||||||
|
okButtonProps={{
|
||||||
|
disabled: loading || !hasFiles
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Form form={form} layout="vertical">
|
<Form form={form} layout="vertical">
|
||||||
{/* <Form.Item
|
{/* <Form.Item
|
||||||
@@ -206,11 +253,31 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
|
|||||||
<UploadFiles
|
<UploadFiles
|
||||||
ref={uploadRef}
|
ref={uploadRef}
|
||||||
isCanDrag={true}
|
isCanDrag={true}
|
||||||
fileSize={100}
|
fileSize={50}
|
||||||
multiple={true}
|
multiple={true}
|
||||||
maxCount={99}
|
maxCount={99}
|
||||||
fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'mp3', 'mp4', 'mov', 'wav']}
|
fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'mp3', 'mp4', 'mov', 'wav']}
|
||||||
customRequest={handleUpload}
|
customRequest={handleUpload}
|
||||||
|
onChange={(fileList) => {
|
||||||
|
// 实时更新文件状态
|
||||||
|
setHasFiles(fileList.length > 0);
|
||||||
|
}}
|
||||||
|
onRemove={async (file) => {
|
||||||
|
// 如果文件正在上传,取消上传
|
||||||
|
const fileUid = file.uid;
|
||||||
|
const abortController = abortControllersRef.current.get(fileUid);
|
||||||
|
if (abortController) {
|
||||||
|
abortController.abort();
|
||||||
|
abortControllersRef.current.delete(fileUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果文件已经上传成功,删除服务器上的文件
|
||||||
|
if (file.response?.id) {
|
||||||
|
await handleDeleteFile(file.response.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // 允许移除文件
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import RbModal from '@/components/RbModal'
|
|||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
const { confirm } = Modal
|
const { confirm } = Modal
|
||||||
|
|
||||||
|
// 全局模型数据常量
|
||||||
|
let models: any = null;
|
||||||
|
|
||||||
const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||||
refreshTable
|
refreshTable
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -46,20 +49,26 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchModelLists = async (types: string[]) => {
|
const fetchModelLists = async (types: string[]) => {
|
||||||
// 如果 types 中包含 'llm',也需要获取 'chat' 的数据
|
// 如果还没有获取过全部模型数据,则获取一次
|
||||||
const typesToFetch = types.includes('llm') ? [...types, 'chat'] : types;
|
if (!models) {
|
||||||
|
|
||||||
const entries = await Promise.all(typesToFetch.map(async (tp) => {
|
|
||||||
try {
|
try {
|
||||||
const res = await getModelList(tp === 'image2text' ? 'chat' : tp, { page: 1, pagesize: 100 });
|
models = await getModelList({ page: 1, pagesize: 100 });
|
||||||
const options = (res?.items || []).map((m: any) => ({ label: m.name, value: m.id }));
|
} catch (error) {
|
||||||
return [tp, options] as [string, { label: string; value: string }[]];
|
console.error('Failed to fetch models:', error);
|
||||||
} catch {
|
models = { items: [] };
|
||||||
return [tp, []] as [string, { label: string; value: string }[]];
|
|
||||||
}
|
}
|
||||||
}));
|
}
|
||||||
|
|
||||||
|
// 从全部模型数据中过滤出需要的类型
|
||||||
|
const typesToFetch = types.includes('llm') ? [...types, 'chat'] : types;
|
||||||
const next: Record<string, { label: string; value: string }[]> = {};
|
const next: Record<string, { label: string; value: string }[]> = {};
|
||||||
entries.forEach(([k, v]) => { next[k] = v; });
|
|
||||||
|
typesToFetch.forEach((tp) => {
|
||||||
|
const targetType = tp === 'image2text' ? 'chat' : tp;
|
||||||
|
const filteredModels = (models?.items || []).filter((m: any) => m.type === targetType);
|
||||||
|
next[tp] = filteredModels.map((m: any) => ({ label: m.name, value: m.id }));
|
||||||
|
});
|
||||||
|
|
||||||
setModelOptionsByType(next);
|
setModelOptionsByType(next);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -157,7 +166,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
|||||||
console.log('Validation failed:', err)
|
console.log('Validation failed:', err)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const handleChange = (value: string, tp: string) => {
|
const handleChange = (_value: string, tp: string) => {
|
||||||
// 只在编辑模式且类型为 embedding 时触发提示
|
// 只在编辑模式且类型为 embedding 时触发提示
|
||||||
if (datasets?.id && tp.toLowerCase() === 'embedding') {
|
if (datasets?.id && tp.toLowerCase() === 'embedding') {
|
||||||
const fieldKey = typeToFieldKey(tp);
|
const fieldKey = typeToFieldKey(tp);
|
||||||
|
|||||||
Reference in New Issue
Block a user