diff --git a/web/src/api/knowledgeBase.ts b/web/src/api/knowledgeBase.ts index 3c1433a1..c8e388a0 100644 --- a/web/src/api/knowledgeBase.ts +++ b/web/src/api/knowledgeBase.ts @@ -64,8 +64,8 @@ export const getModelTypeList = async () => { return response as any[]; }; // 获取模型列表 -export const getModelList = async (type: string | string[], pageInfo: PageRequest) => { - const response = await request.get(`${apiPrefix}/models`, { type, ...pageInfo }); +export const getModelList = async (pageInfo: PageRequest) => { + const response = await request.get(`${apiPrefix}/models`, pageInfo); return response as any; }; //获取模型提供者 @@ -135,16 +135,18 @@ interface UploadFileOptions { kb_id?: string; parent_id?: string; onUploadProgress?: (event: AxiosProgressEvent) => void; + signal?: AbortSignal; } // 上传文件 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 = {}; if (kb_id) params.kb_id = kb_id; if (parent_id) params.parent_id = parent_id; const response = await request.uploadFile(`${apiPrefix}/files/file`, data, { params, onUploadProgress, + signal, }); return response as UploadFileResponse; }; diff --git a/web/src/components/Upload/UploadFiles.tsx b/web/src/components/Upload/UploadFiles.tsx index aedd0e17..a725278c 100644 --- a/web/src/components/Upload/UploadFiles.tsx +++ b/web/src/components/Upload/UploadFiles.tsx @@ -38,6 +38,8 @@ interface UploadFilesProps extends Omit { maxCount?: number; /** 是否支持拖拽上传,默认为false */ isCanDrag?: boolean; + /** 自定义移除文件回调 */ + onRemove?: (file: UploadFile) => boolean | void | Promise; } const ALL_FILE_TYPE: { [key: string]: string; @@ -77,6 +79,7 @@ const UploadFiles = forwardRef(({ isAutoUpload = true, maxCount = 1, isCanDrag = false, + onRemove: customOnRemove, ...props }, ref) => { const { t } = useTranslation(); @@ -86,11 +89,20 @@ const UploadFiles = forwardRef(({ // 处理文件移除 const handleRemove = (file: UploadFile) => { + // 如果有自定义的 onRemove 回调,先执行它 + if (customOnRemove) { + const result = customOnRemove(file); + // 如果返回 false,阻止移除 + if (result === false) { + return false; + } + } + confirm({ - title: '确定要删除此文件吗?', - okText: '确定', + title: `${t('common.confirmRemoveFile')}`, + okText: `${t('common.confirm')}`, okType: 'danger', - cancelText: '取消', + cancelText: `${t('common.cancel')}`, onOk: () => { const newFileList = fileList.filter((item) => item.uid !== file.uid); setFileList(newFileList); @@ -236,7 +248,7 @@ const UploadFiles = forwardRef(({
{file.name} - actions?.remove()}>Cancel + actions?.remove()}>Cancel
diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index f8f2e40e..1dfb4bf9 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -296,7 +296,9 @@ export const en = { add: 'Add', addOption: 'Add Option', viewDetail: 'View Detail', + confirmRemoveFile: 'Are you sure you want to remove this file?', deleteSuccess: 'Delete successfully', + deleteFailed: 'Delete Failure', foldUp: 'Collapse', expanded: 'Expand', clickUploadIcon: 'click on the upload icon', @@ -473,7 +475,7 @@ export const en = { knowledgeBase: 'Knowledge Base', selectDataSource: 'Select Source', 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', 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', @@ -481,6 +483,7 @@ export const en = { readStaticWebPage: 'Read static web page content as dataset', customText: 'Custom Text', customContent: 'Custom Content', + createContentError: 'Failed to create custom content', manuallyInputText: 'Manually input a text as dataset', importTemplate: 'Template Import', importBackup: 'Backup Import', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 5131a791..b39a599b 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -94,7 +94,7 @@ export const zh = { operation: '操作', selectDataSource: '选择来源', localFile: '本地文件', - uploadFileTypes: '上传 PDF、TXT、DOCX 等格式的文件', + uploadFileTypes: '上传 PDF、TXT、DOCX, IMAGE, MEDIA 等格式的文件', webLink: '网页链接', webLinkPlaceholder: '请输入', webLinkDesc: '仅支持静态链接。如果上传的数据显示为空,则该链接可能无法读取。每行一个,一次最多{{count}}个链接', @@ -102,6 +102,7 @@ export const zh = { readStaticWebPage: '读取静态网页内容作为数据集', customText: '自定义文本', customContent: '自定义内容', + createContentError: '创建自定义文件失败', manuallyInputText: '手动输入一段文本作为数据集', openKnowledgeBase: '打开知识库', searchPlaceholder: '搜索', @@ -796,7 +797,9 @@ export const zh = { add: '添加', addOption: '添加选项', viewDetail: '查看详情', + confirmRemoveFile: '确定要移除此文件吗?', deleteSuccess: '删除成功', + deleteFailed: '删除失败', foldUp: '收起', expanded: '展开', clickUploadIcon: '点击上传图标', diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx index 7ce3881d..872a58ac 100644 --- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx +++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx @@ -1,14 +1,15 @@ 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 { useLocation, useNavigate, useParams } from 'react-router-dom'; import Table, { type TableRef } from '@/components/Table' import type { AnyObject } from 'antd/es/_util/type'; import type { UploadFileResponse,KnowledgeBaseDocumentData } from '@/views/KnowledgeBase/types'; import type { ColumnsType } from 'antd/es/table'; +import type { UploadFile } from 'antd'; import UploadFiles from '@/components/Upload/UploadFiles'; 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 SliderInput from '@/components/SliderInput'; @@ -56,7 +57,10 @@ interface CreateDatasetLocationState { fileId?: string | string[]; fileIds?: string | string[]; } - +interface ContentFormData { + title: string; + content: string; +} const CreateDataset = () => { const { t } = useTranslation(); const navigate = useNavigate(); @@ -75,7 +79,7 @@ const CreateDataset = () => { const [current, setCurrent] = useState(stepIndexMap[initialStepKey]); const tableRef = useRef(null); - + const [form] = Form.useForm(); const [data, setData] = useState([]); const [rechunkFileIds, setRechunkFileIds] = useState(initialFileIds); @@ -98,12 +102,15 @@ const CreateDataset = () => { ], [t], ); - - const handleNext = () => { + // 存储每个文件的 AbortController,用于取消上传 + const abortControllersRef = useRef>(new Map()); + const uploadRef = useRef<{ fileList: UploadFile[]; clearFiles: () => void }>(null); + console.log('上传文件',uploadRef.current?.fileList.length) + const handleNext = async () => { // 暂时隐藏第三步:调整步骤索引(0->1->2 对应 选择文件->参数设置->确认上传) let nextStep = current + 1; - if(nextStep === 1) { + if(nextStep === 1 && source === 'local') { // 检查是否有文件已上传 if (rechunkFileIds.length === 0) { // 如果没有文件,提示用户先上传文件 @@ -113,6 +120,27 @@ const CreateDataset = () => { }); 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 = () => { URL.revokeObjectURL(url); - reject(new Error('无法读取媒体文件')); + reject(new Error(`${t('knowledgeBase.unableReadFile')}`)); }; media.src = url; @@ -273,18 +301,24 @@ const CreateDataset = () => { const handleUpload = async (options: UploadRequestOption) => { 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 mediaExtensions = ['mp3', 'mp4', 'mov', 'wav']; // 如果是媒体文件,进行大小和时长检查 if (fileExtension && mediaExtensions.includes(fileExtension)) { - const fileSizeInMB = (file as File).size / (1024 * 1024); + const fileSizeInMB = (file as File).size / (100 * 1024); - // 检查文件大小(256MB限制) - if (fileSizeInMB > 256) { + // 检查文件大小(50MB限制) + if (fileSizeInMB > 100) { messageApi.error(`${t('knowledgeBase.sizeLimitError')}:${fileSizeInMB.toFixed(2)}MB`); onError?.(new Error(`${t('knowledgeBase.fileSizeExceeds')}`)); + abortControllersRef.current.delete(fileUid); return; } @@ -294,11 +328,13 @@ const CreateDataset = () => { if (duration > 150) { messageApi.error(`${t('knowledgeBase.fileDurationLimitError')}:${Math.round(duration)}秒`); onError?.(new Error(`${t('knowledgeBase.fileDurationExceeds')}`)); + abortControllersRef.current.delete(fileUid); return; } } catch (error) { messageApi.error(`${t('knowledgeBase.unableReadFile')}`); onError?.(error as Error); + abortControllersRef.current.delete(fileUid); return; } } @@ -315,6 +351,7 @@ const CreateDataset = () => { uploadFile(formData, { kb_id: knowledgeBaseId, parent_id: parentId, + signal: abortController.signal, onUploadProgress: (event) => { if (!event.total) return; const percent = Math.round((event.loaded / event.total) * 100); @@ -332,6 +369,14 @@ const CreateDataset = () => { } }) .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); }); }; @@ -419,23 +464,33 @@ const CreateDataset = () => { setBlockSize(value); } } - - // 当从其他页面跳转过来且带有 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); - }); + // 删除已上传的文件 + const handleDeleteFile = async (fileId: string) => { + try { + await deleteDocument(fileId); + // 删除成功,从 rechunkFileIds 中移除该 id + setRechunkFileIds((prev) => prev.filter((id) => id !== fileId)); + console.log(`${t('common.deleteSuccess')}`); + } catch (error) { + messageApi.error(`${t('common.deleteFailed')}`); } - }, []); + }; + // 当从其他页面跳转过来且带有 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 状态 useEffect(() => { @@ -480,7 +535,40 @@ const CreateDataset = () => { {current === 0 && (
{source && source === 'local' && ( - + { + 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' && (
@@ -500,15 +588,36 @@ const CreateDataset = () => { )} {source && source === 'text' && (
+
+ + + -
+ + + + + {/*
{t('knowledgeBase.customText')}
{t('knowledgeBase.customContent')}
-