diff --git a/frontend/src/pages/KnowledgeBase/components/AddDataDialog.tsx b/frontend/src/pages/KnowledgeBase/components/AddDataDialog.tsx index b137b7b..542a216 100644 --- a/frontend/src/pages/KnowledgeBase/components/AddDataDialog.tsx +++ b/frontend/src/pages/KnowledgeBase/components/AddDataDialog.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useState } from "react"; import { Button, App, @@ -7,31 +7,13 @@ import { Form, Modal, Steps, - Empty, - Checkbox, - Pagination, - Space, + Descriptions, } from "antd"; -import { SearchOutlined, PlusOutlined } from "@ant-design/icons"; -import { KnowledgeBaseItem } from "../knowledge-base.model"; -import { - queryDatasetFilesUsingGet, - queryDatasetsUsingGet, -} from "@/pages/DataManagement/dataset.api"; -import { datasetTypeMap } from "@/pages/DataManagement/dataset.const"; +import { PlusOutlined } from "@ant-design/icons"; import { addKnowledgeBaseFilesUsingPost } from "../knowledge-base.api"; -import { DatasetType } from "@/pages/DataManagement/dataset.model"; - -// 定义简单的FileInfo接口,确保与API兼容 -interface FileInfo { - id: string; - name?: string; - fileName?: string; - size?: string; - createdAt?: string; -} - -const { Step } = Steps; +import DatasetFileTransfer from "./DatasetFileTransfer"; +import { DescriptionsItemType } from "antd/es/descriptions"; +import { DatasetFile } from "@/pages/DataManagement/dataset.model"; const sliceOptions = [ { label: "默认分块", value: "DEFAULT_CHUNK" }, @@ -42,259 +24,79 @@ const sliceOptions = [ ]; export default function AddDataDialog({ knowledgeBase, onDataAdded }) { - const [isOpen, setIsOpen] = useState(false); + const [open, setOpen] = useState(false); const { message } = App.useApp(); const [form] = Form.useForm(); const [currentStep, setCurrentStep] = useState(0); - // 数据集相关状态 - const [datasets, setDatasets] = useState([]); - const [datasetsTotal, setDatasetsTotal] = useState(0); - const [datasetPage, setDatasetPage] = useState(1); - const [datasetSearch, setDatasetSearch] = useState(''); - const [datasetsLoading, setDatasetsLoading] = useState(false); - // 文件相关状态 - const [datasetFiles, setDatasetFiles] = useState([]); - const [filesTotal, setFilesTotal] = useState(0); - const [filesPage, setFilesPage] = useState(0); - const [fileSearch, setFileSearch] = useState('knowledge-base/detail/'); - const [filesLoading, setFilesLoading] = useState(false); - // 已选择的文件,格式:{datasetId: [fileIds]} - const [selectedFilesMap, setSelectedFilesMap] = useState>({}); - // 当前正在查看的数据集 - const [activeDataset, setActiveDataset] = useState(null); + + const [selectedMap, setSelectedMap] = useState>( + {} + ); // 定义分块选项 const sliceOptions = [ - { label: '默认分块', value: 'DEFAULT_CHUNK' }, - { label: '按章节分块', value: 'CHAPTER_CHUNK' }, - { label: '按段落分块', value: 'PARAGRAPH_CHUNK' }, - { label: '固定长度分块', value: 'FIXED_LENGTH_CHUNK' }, - { label: '自定义分隔符分块', value: 'CUSTOM_SEPARATOR_CHUNK' }, + { label: "默认分块", value: "DEFAULT_CHUNK" }, + { label: "按章节分块", value: "CHAPTER_CHUNK" }, + { label: "按段落分块", value: "PARAGRAPH_CHUNK" }, + { label: "固定长度分块", value: "FIXED_LENGTH_CHUNK" }, + { label: "自定义分隔符分块", value: "CUSTOM_SEPARATOR_CHUNK" }, ]; - + // 定义初始状态 const [newKB, setNewKB] = useState({ processType: "DEFAULT_CHUNK", chunkSize: 500, overlapSize: 50, - delimiter: '', + delimiter: "", }); const steps = [ { - title: '选择数据集文件', - description: '从多个数据集中选择文件', + title: "选择数据集文件", + description: "从多个数据集中选择文件", }, { - title: '配置参数', - description: '设置数据处理参数', + title: "配置参数", + description: "设置数据处理参数", }, { - title: '确认上传', - description: '确认信息并上传', + title: "确认上传", + description: "确认信息并上传", }, ]; - // 数据集列表每页大小 - const DATASET_PAGE_SIZE = 6; - - // 获取数据集列表(支持分页和搜索) - const fetchDatasets = async (page = 1, search = '') => { - setDatasetsLoading(true); - try { - const { data } = await queryDatasetsUsingGet({ - page: page, - size: DATASET_PAGE_SIZE, // 每页大小通过接口传递 - type: DatasetType.TEXT, - keyword: search || undefined, // 搜索参数 - }); - setDatasets(data.content || []); - setDatasetsTotal(data.totalElements || 0); - } catch (error) { - message.error('获取数据集失败'); - console.error('获取数据集列表失败:', error); - } finally { - setDatasetsLoading(false); - } - }; - - // 文件列表每页大小 - const FILES_PAGE_SIZE = 8; - - // 获取数据集文件列表(支持分页和搜索) - const fetchDatasetFiles = async (datasetId, page = 0, search = '') => { - if (!datasetId) return; - - setFilesLoading(true); - try { - const { data } = await queryDatasetFilesUsingGet(datasetId, { - page: page, // 后端使用0-based页码 - size: FILES_PAGE_SIZE, // 每页最多8条数据 - keyword: search || undefined, // 搜索参数 - }); - - // 确保数据格式正确 - if (data && Array.isArray(data.content)) { - setDatasetFiles(data.content || []); - setFilesTotal(data.totalElements || 0); - } else { - setDatasetFiles([]); - setFilesTotal(0); - } - } catch (error) { - message.error('获取数据集文件失败'); - console.error('获取文件列表失败:', error); - } finally { - setFilesLoading(false); - } - }; - - // 初始化时加载数据集 - useEffect(() => { - if (isOpen && currentStep === 0) { - fetchDatasets(datasetPage, datasetSearch); - } - }, [isOpen, currentStep, datasetPage, datasetSearch]); - - // 切换数据集时加载对应文件(重置页码为0) - useEffect(() => { - if (activeDataset) { - setFilesPage(0); // 重置页码 - fetchDatasetFiles(activeDataset.id, 0, fileSearch); - } - }, [activeDataset, fileSearch]); - - // 确保在文件搜索文本变化时重新加载文件 - useEffect(() => { - if (activeDataset && fileSearch !== undefined) { - setFilesPage(0); - fetchDatasetFiles(activeDataset.id, 0, fileSearch); - } - }, [fileSearch, activeDataset]); - - // 当文件页码变化时重新加载文件 - useEffect(() => { - if (activeDataset && filesPage >= 1) { - fetchDatasetFiles(activeDataset.id, filesPage, fileSearch); - } - }, [filesPage]); - - // 处理数据集搜索 - const handleDatasetSearch = () => { - setDatasetPage(1); - fetchDatasets(1, datasetSearch); - }; - - // 处理文件搜索 - const handleFileSearch = (value) => { - setFileSearch(value); - setFilesPage(0); - if (activeDataset) { - fetchDatasetFiles(activeDataset.id, 0, value); - } - }; - - // 处理数据集分页变化 - const handleDatasetPageChange = (page) => { - setDatasetPage(page); - fetchDatasets(page, datasetSearch); - }; - - // 处理文件分页变化 - const handleFilesPageChange = (page) => { - setFilesPage(page); - if (activeDataset) { - fetchDatasetFiles(activeDataset.id, page, fileSearch); - } - }; - - // 切换活动数据集 - const handleDatasetClick = (dataset) => { - setActiveDataset(dataset); - }; - - // 已经在后面定义了handleFileSelect函数,删除重复定义 - - // 处理全选/取消全选 - const handleSelectAll = (e) => { - if (!activeDataset) return; - - const newSelectedFiles = e.target.checked - ? datasetFiles.map(file => file.id) - : []; - - setSelectedFilesMap(prev => ({ - ...prev, - [activeDataset.id]: newSelectedFiles - })); - }; - - // 检查文件是否已选择 - const isFileSelected = (fileId) => { - if (!activeDataset) return false; - return selectedFilesMap[activeDataset.id]?.includes(fileId) || false; - }; - - // 检查当前数据集是否全选 - const isAllSelected = () => { - if (!activeDataset || datasetFiles.length === 0) return false; - const selectedCount = selectedFilesMap[activeDataset.id]?.length || 0; - return selectedCount === datasetFiles.length; - }; - - // 检查是否部分选择 - const isIndeterminate = () => { - if (!activeDataset) return false; - const selectedCount = selectedFilesMap[activeDataset.id]?.length || 0; - return selectedCount > 0 && selectedCount < datasetFiles.length; - }; - // 获取已选择文件总数 const getSelectedFilesCount = () => { - return Object.values(selectedFilesMap).reduce((total, ids) => total + ids.length, 0); - }; - - // 获取所有已选择的文件信息 - const getAllSelectedFiles = () => { - const allFiles = []; - for (const [datasetId, fileIds] of Object.entries(selectedFilesMap)) { - // 找到对应的数据集 - const dataset = datasets.find(d => d.id === datasetId); - // 获取文件详情并添加到数组 - if (dataset) { - allFiles.push(...fileIds.map(fileId => ({ - id: fileId, - name: `${dataset.name}/file_${fileId}` // 使用数据集名称作为前缀 - }))); - } - } - return allFiles; + return Object.values(selectedMap).reduce( + (total, files) => total + files.length, + 0 + ); }; const handleNext = () => { // 验证当前步骤 if (currentStep === 0) { if (getSelectedFilesCount() === 0) { - message.warning('请至少选择一个文件'); + message.warning("请至少选择一个文件"); return; } } if (currentStep === 1) { // 验证切片参数 if (!newKB.processType) { - message.warning('请选择分块方式'); + message.warning("请选择分块方式"); return; } if (!newKB.chunkSize || Number(newKB.chunkSize) <= 0) { - message.warning('请输入有效的分块大小'); + message.warning("请输入有效的分块大小"); return; } if (!newKB.overlapSize || Number(newKB.overlapSize) < 0) { - message.warning('请输入有效的重叠长度'); + message.warning("请输入有效的重叠长度"); return; } - if (newKB.processType === 'CUSTOM_SEPARATOR_CHUNK' && !newKB.delimiter) { - message.warning('请输入分隔符'); + if (newKB.processType === "CUSTOM_SEPARATOR_CHUNK" && !newKB.delimiter) { + message.warning("请输入分隔符"); return; } } @@ -308,497 +110,234 @@ export default function AddDataDialog({ knowledgeBase, onDataAdded }) { // 重置所有状态 const handleReset = () => { setCurrentStep(0); - setSelectedFilesMap({}); setNewKB({ - processType: 'DEFAULT_CHUNK', + processType: "DEFAULT_CHUNK", chunkSize: 500, overlapSize: 50, - delimiter: '', + delimiter: "", }); - setDatasets([]); - setDatasetPage(1); - setDatasetSearch(''); - setDatasetFiles([]); - setFilesPage(1); - setFileSearch(''); - setActiveDataset(null); - setLoadedFilesCache({}); // 清除文件缓存 form.resetFields(); }; - // 用于缓存已加载过的文件信息,避免重复请求 - const [loadedFilesCache, setLoadedFilesCache] = useState>>({}); - - // 优化处理文件选择(确保选择状态在分页切换后保持) - const handleFileSelect = (checkedValues) => { - if (!activeDataset) return; - - // 更新选择的文件 - setSelectedFilesMap(prev => ({ - ...prev, - [activeDataset.id]: checkedValues - })); - - // 缓存当前页面的文件信息 - if (datasetFiles.length > 0) { - setLoadedFilesCache(prev => { - const datasetCache = prev[activeDataset.id] || {}; - // 更新缓存中的文件信息 - datasetFiles.forEach(file => { - datasetCache[file.id] = file; - }); - return { - ...prev, - [activeDataset.id]: datasetCache - }; - }); - } - }; - - // 当数据集文件加载完成后,缓存文件信息 - useEffect(() => { - if (activeDataset && datasetFiles.length > 0) { - setLoadedFilesCache(prev => { - const datasetCache = prev[activeDataset.id] || {}; - datasetFiles.forEach(file => { - datasetCache[file.id] = file; - }); - return { - ...prev, - [activeDataset.id]: datasetCache - }; - }); - } - }, [activeDataset, datasetFiles]); - const handleAddData = async () => { - const selectedFiles = []; + const files = []; - Object.entries(selectedFilesMap).forEach(([datasetId, fileIds]) => { - fileIds.forEach(fileId => { - // 查找文件信息以获取文件名 - const fileInfo = datasetFiles.find(file => file.id === fileId); - // 根据API定义,需要id和name字段 - selectedFiles.push({ - id: fileId, - name: fileInfo?.name || fileInfo?.fileName || `文件_${fileId}` - }); - }); - }); - - if (selectedFiles.length === 0) { - message.warning('请至少选择一个文件'); + Object.entries(selectedMap).forEach(([datasetId, fileList]) => { + files.push( + ...fileList.map((file) => ({ + ...file, + id: file.id, + name: file.fileName, + datasetId, + })) + ); + }); + + if (files.length === 0) { + message.warning("请至少选择一个文件"); return; } - + try { // 构造符合API要求的请求数据 const requestData = { - files: selectedFiles, + files, processType: newKB.processType, chunkSize: Number(newKB.chunkSize), // 确保是数字类型 overlapSize: Number(newKB.overlapSize), // 确保是数字类型 delimiter: newKB.delimiter, }; - + await addKnowledgeBaseFilesUsingPost(knowledgeBase.id, requestData); - + // 先通知父组件刷新数据(确保刷新发生在重置前) onDataAdded?.(); - - message.success('数据添加成功'); + + message.success("数据添加成功"); // 重置状态 handleReset(); - setIsOpen(false); + setOpen(false); } catch (error) { - message.error('数据添加失败,请重试'); - console.error('添加文件失败:', error); + message.error("数据添加失败,请重试"); + console.error("添加文件失败:", error); } }; - // handleReset函数已在前面定义,删除重复定义 - const handleModalCancel = () => { handleReset(); - setIsOpen(false); + setOpen(false); }; - const renderStepContent = () => { - switch (currentStep) { - case 0: - return ( -
-
选择数据集文件
-
-
- 请从左侧选择数据集,然后在右侧选择需要导入的文件。支持从多个不同数据集中交叉选择文件。 -
- - {getSelectedFilesCount() > 0 && ( -
- 已选择 {getSelectedFilesCount()} 个文件(来自 {Object.keys(selectedFilesMap).length} 个数据集) -
- )} - -
- {/* 左侧数据集列表(带搜索和分页) */} -
-
-
数据集列表
-
- setDatasetSearch(e.target.value)} - onPressEnter={handleDatasetSearch} - prefix={} - suffix={ - - } - /> -
-
- -
- {datasetsLoading ? ( -
加载中...
- ) : datasets.length === 0 ? ( - - ) : ( -
- {datasets.map(dataset => { - const isSelected = selectedFilesMap[dataset.id]?.length > 0; - return ( -
handleDatasetClick(dataset)} - > -
-
{dataset.name}
-
- 类型: {datasetTypeMap[dataset.datasetType]?.label || '未知'} -
-
- 文件数: {dataset.fileCount} -
-
- {isSelected && ( -
- 已选择 {selectedFilesMap[dataset.id].length} 个文件 -
- )} -
- ); - })} -
- )} -
- - {/* 数据集分页 */} -
- `共 ${total} 个数据集`} - size="small" - /> -
-
- - {/* 右侧文件列表(带搜索和分页) */} -
-
-
- {activeDataset ? `${activeDataset.name} 的文件` : '文件列表'} -
- {activeDataset && ( -
- setFileSearch(e.target.value)} - onPressEnter={handleFileSearch} - prefix={} - suffix={ - - } - /> -
- )} -
- -
- {!activeDataset ? ( - - ) : filesLoading ? ( -
加载中...
- ) : datasetFiles.length === 0 ? ( - - ) : ( -
-
- - 每页显示 {Math.min(datasetFiles.length, FILES_PAGE_SIZE)} 条,共 {filesTotal} 条 - - - 全选 - -
- - -
- {datasetFiles.map(file => ( -
- -
{file.name || file.fileName}
-
- 大小: {file.size || 'N/A'} | 创建时间: {file.createdAt ? new Date(file.createdAt).toLocaleString() : 'N/A'} -
-
-
- ))} -
-
-
- )} -
- - {/* 文件分页 */} - {activeDataset && ( -
- handleFilesPageChange(page - 1)} // 转换为0-based页码 - showSizeChanger={false} - showQuickJumper - showTotal={(total) => `共 ${total} 个文件`} - size="small" - /> -
- )} -
-
-
-
- ); - - case 1: - return ( -
- setNewKB({ ...newKB, processType: value })} - > - - - setNewKB({ ...newKB, overlapSize: value })} - > - - -
- - {newKB.processType === "CUSTOM_SEPARATOR_CHUNK" && ( - setNewKB({ ...newKB, delimiter: value })} - > - - - )} - - ); - - case 2: - return ( -
-
-
上传信息确认
- -
-
-
数据来源:
-
数据集
-
- -
-
选择的数据集数:
-
{Object.keys(selectedFilesMap).length}
-
- -
-
文件总数:
-
{getSelectedFilesCount()}
-
- -
-
分块方式:
-
- {sliceOptions.find(opt => opt.value === newKB.processType)?.label} -
-
- -
-
分块大小:
-
{newKB.chunkSize}
-
- -
-
重叠长度:
-
{newKB.overlapSize}
-
- - {newKB.processType === "CUSTOM_SEPARATOR_CHUNK" && newKB.delimiter && ( -
-
分隔符:
-
{newKB.delimiter}
-
- )} - - {/* 显示每个数据集选择的文件数 */} - {Object.keys(selectedFilesMap).length > 0 && ( -
-
数据集文件明细:
-
- {datasets.map(dataset => { - const selectedCount = selectedFilesMap[dataset.id]?.length || 0; - if (selectedCount === 0) return null; - return ( -
- {dataset.name}: - {selectedCount} 个文件 -
- ); - })} -
-
- )} -
-
- -
- 提示:上传后系统将自动处理文件,请耐心等待 -
-
- ); - - default: - return null; - } - }; + const descItems: DescriptionsItemType[] = [ + { + label: "知识库名称", + key: "knowledgeBaseName", + children: knowledgeBase?.name, + }, + { + label: "数据来源", + key: "dataSource", + children: "数据集", + }, + { + label: "选择的数据集数", + key: "selectedDatasetCount", + children: Object.keys(selectedMap).length, + }, + { + label: "文件总数", + key: "totalFileCount", + children: getSelectedFilesCount(), + }, + { + label: "分块方式", + key: "chunkingMethod", + children: + sliceOptions.find((opt) => opt.value === newKB.processType)?.label || + "", + }, + { + label: "分块大小", + key: "chunkSize", + children: newKB.chunkSize, + }, + { + label: "重叠长度", + key: "overlapSize", + children: newKB.overlapSize, + }, + ...(newKB.processType === "CUSTOM_SEPARATOR_CHUNK" && newKB.delimiter + ? [ + { + label: "分隔符", + children: {newKB.delimiter}, + }, + ] + : []), + ]; return ( <> + {currentStep > 0 && ( + + )} + {currentStep < steps.length - 1 ? ( + + ) : ( + + )} + + } width={1000} >
{/* 步骤导航 */} - setCurrentStep(step)} - className="mb-6" - items={steps} - /> - + + {/* 步骤内容 */} -
-
setNewKB(allValues)} - > - {renderStepContent()} -
-
- - {/* 底部按钮 */} -
- {currentStep > 0 && ( - - )} - {currentStep < steps.length - 1 ? ( - - ) : ( - +
+ + +
); -} \ No newline at end of file +} diff --git a/frontend/src/pages/KnowledgeBase/components/DatasetFileTransfer.tsx b/frontend/src/pages/KnowledgeBase/components/DatasetFileTransfer.tsx new file mode 100644 index 0000000..0b556fa --- /dev/null +++ b/frontend/src/pages/KnowledgeBase/components/DatasetFileTransfer.tsx @@ -0,0 +1,253 @@ +import React, { useEffect } from "react"; +import { Input, Table } from "antd"; +import { RightOutlined } from "@ant-design/icons"; +import { mapDataset } from "@/pages/DataManagement/dataset.const"; +import { + Dataset, + DatasetFile, + DatasetType, +} from "@/pages/DataManagement/dataset.model"; +import { + queryDatasetFilesUsingGet, + queryDatasetsUsingGet, +} from "@/pages/DataManagement/dataset.api"; +import { formatBytes } from "@/utils/unit"; + +interface DatasetFileTransferProps + extends React.HTMLAttributes { + open: boolean; + selectedMap: Record; + onSelectedChange: (filesMap: Record) => void; +} + +// Customize Table Transfer +const DatasetFileTransfer: React.FC = ({ + open, + selectedMap, + onSelectedChange, + ...props +}) => { + const [datasets, setDatasets] = React.useState([]); + const [datasetSearch, setDatasetSearch] = React.useState(""); + const [datasetPagination, setDatasetPagination] = React.useState<{ + current: number; + pageSize: number; + total: number; + }>({ current: 1, pageSize: 1000, total: 0 }); + + const [expandedRowKeys, setExpandedRowKeys] = React.useState([]); + + const [loadedFiles, setLoadedFiles] = React.useState< + Record + >({}); + const [filesSearch, setFilesSearch] = React.useState(""); + const [filesPagination, setFilesPagination] = React.useState<{ + current: number; + pageSize: number; + total: number; + }>({ current: 1, pageSize: 10, total: 0 }); + + const selectedFiles = React.useMemo(() => { + const files: DatasetFile[] = []; + Object.values(selectedMap).forEach((fileList) => { + files.push(...fileList); + }); + return files; + }, [selectedMap]); + + const fetchDatasets = async () => { + const { data } = await queryDatasetsUsingGet({ + page: datasetPagination.current - 1, + size: datasetPagination.pageSize, + keyword: datasetSearch, + type: DatasetType.TEXT, + }); + setDatasets(data.content.map(mapDataset) || []); + setDatasetPagination((prev) => ({ + ...prev, + total: data.totalElements, + })); + }; + + useEffect(() => { + if (open) { + fetchDatasets(); + } + }, [open]); + + const fetchFiles = async (dataset: Dataset) => { + if (!dataset || loadedFiles[dataset.id]) return; + const { data } = await queryDatasetFilesUsingGet(dataset.id, { + page: filesPagination.current - 1, + size: 1000, + keyword: filesSearch, + }); + setLoadedFiles((prev) => ({ + ...prev, + [dataset.id]: data.content, + })); + setFilesPagination((prev) => ({ + ...prev, + total: data.totalElements, + })); + return data.content; + }; + + const onExpand = (expanded: boolean, record: Dataset) => { + if (expanded) { + fetchFiles(record); + setExpandedRowKeys([...expandedRowKeys, record.id]); + } else { + setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.id)); + } + }; + + const toggleSelectFile = (dataset: Dataset, record: DatasetFile) => { + const datasetFiles = selectedMap[dataset.id] || []; + const hasSelected = datasetFiles.find((file) => file.id === record.id); + let files = [...datasetFiles]; + if (!hasSelected) { + files.push(record); + } else { + files = datasetFiles.filter((file) => file.id !== record.id); + } + + const newMap = { ...selectedMap, [dataset.id]: files }; + if (files.length === 0) { + delete newMap[dataset.id]; + } + onSelectedChange(newMap); + }; + + const datasetCols = [ + { + title: "数据集名称", + dataIndex: "name", + key: "name", + ellipsis: true, + }, + { + title: "文件数", + dataIndex: "fileCount", + key: "fileCount", + ellipsis: true, + }, + { + title: "大小", + dataIndex: "totalSize", + key: "totalSize", + ellipsis: true, + render: formatBytes, + }, + ]; + + const fileCols = [ + { + title: "文件名", + dataIndex: "fileName", + key: "fileName", + ellipsis: true, + }, + { + title: "大小", + dataIndex: "size", + key: "size", + ellipsis: true, + render: formatBytes, + }, + ]; + return ( +
+
+
选择数据集文件
+
+ setDatasetSearch(e.target.value)} + /> +
+ ({ + onClick: () => { + const isExpanded = expandedRowKeys.includes(record.id); + onExpand(!isExpanded, record); + }, + })} + dataSource={datasets} + columns={datasetCols} + pagination={datasetPagination} + rowSelection={{ + type: "checkbox", + selectedRowKeys: Object.keys(selectedMap), + onSelect: async (record, isSelected) => { + let files = []; + if (!loadedFiles[record.id]) { + files = await fetchFiles(record); + } else { + files = loadedFiles[record.id]; + } + + const newMap = { ...selectedMap }; + if (isSelected) { + newMap[record.id] = files; + } else { + delete newMap[record.id]; + } + onSelectedChange(newMap); + }, + }} + expandable={{ + expandedRowKeys, + onExpand, + expandedRowRender: (dataset) => ( +
({ + onClick: () => toggleSelectFile(dataset, record), + })} + rowSelection={{ + type: "checkbox", + selectedRowKeys: Object.values( + selectedMap[dataset.id] || {} + ).map((file) => file.id), + onSelect: (record) => toggleSelectFile(dataset, record), + }} + /> + ), + }} + /> + + +
+
+ 已选文件({selectedFiles.length}) +
+
+ setFilesSearch(e.target.value)} + /> +
+
+ + + ); +}; + +export default DatasetFileTransfer; diff --git a/frontend/src/pages/KnowledgeBase/components/TableTransfer.tsx b/frontend/src/pages/KnowledgeBase/components/TableTransfer.tsx deleted file mode 100644 index 76a2666..0000000 --- a/frontend/src/pages/KnowledgeBase/components/TableTransfer.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from "react"; -import { Table, Transfer } from "antd"; -import type { - GetProp, - TableColumnsType, - TableProps, - TransferProps, -} from "antd"; - -type TransferItem = GetProp[number]; -type TableRowSelection = TableProps["rowSelection"]; - -interface DataType { - key: string; - title: string; - description: string; -} - -interface TableTransferProps extends TransferProps { - dataSource: DataType[]; - leftColumns: TableColumnsType; - rightColumns: TableColumnsType; -} - -// Customize Table Transfer -const TableTransfer: React.FC = (props) => { - const { leftColumns, rightColumns, ...restProps } = props; - return ( - - {({ - direction, - filteredItems, - onItemSelect, - onItemSelectAll, - selectedKeys: listSelectedKeys, - disabled: listDisabled, - }) => { - const columns = direction === "left" ? leftColumns : rightColumns; - const rowSelection: TableRowSelection = { - getCheckboxProps: () => ({ disabled: listDisabled }), - onChange(selectedRowKeys) { - onItemSelectAll(selectedRowKeys, "replace"); - }, - selectedRowKeys: listSelectedKeys, - selections: [ - Table.SELECTION_ALL, - Table.SELECTION_INVERT, - Table.SELECTION_NONE, - ], - }; - - return ( -
({ - onClick: () => { - if (itemDisabled || listDisabled) { - return; - } - onItemSelect(key, !listSelectedKeys.includes(key)); - }, - })} - /> - ); - }} - - ); -}; - -export default TableTransfer; diff --git a/frontend/src/pages/SynthesisTask/CreateTask.tsx b/frontend/src/pages/SynthesisTask/CreateTask.tsx index 6eaf791..6b4e9fe 100644 --- a/frontend/src/pages/SynthesisTask/CreateTask.tsx +++ b/frontend/src/pages/SynthesisTask/CreateTask.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import type { Dataset } from "@/pages/DataManagement/dataset.model"; import { Steps, @@ -36,18 +36,21 @@ import { Brain, } from "lucide-react"; import { Link, useNavigate } from "react-router"; -import DevelopmentInProgress from "@/components/DevelopmentInProgress"; +import { queryDatasetsUsingGet } from "../DataManagement/dataset.api"; +import { formatBytes } from "@/utils/unit"; +import DatasetFileTransfer from "../KnowledgeBase/components/DatasetFileTransfer"; const { TextArea } = Input; export default function SynthesisTaskCreate() { - return ; const navigate = useNavigate(); const [form] = Form.useForm(); const [searchQuery, setSearchQuery] = useState(""); const [createStep, setCreateStep] = useState(1); const [selectedFiles, setSelectedFiles] = useState([]); - const [datasets] = useState([]); + const [selectedMap, setSelectedMap] = useState>( + {} + ); const [files] = useState([]); const [selectedSynthesisTypes, setSelectedSynthesisTypes] = useState< string[] @@ -59,6 +62,15 @@ export default function SynthesisTaskCreate() { "distillation", ]); + const fetchDatasets = async () => { + const { data } = await queryDatasetsUsingGet({ page: 1, size: 1000 }); + setDatasets(data.content || []); + }; + + useEffect(() => { + fetchDatasets(); + }, []); + // 表单数据 const [formValues, setFormValues] = useState({ name: "", @@ -270,7 +282,7 @@ export default function SynthesisTaskCreate() { const renderCreateTaskPage = () => { if (createStep === 1) { return ( - +
- - setSearchQuery(e.target.value)} - /> -
- - -
- {files - .filter((file) => - file.name - .toLowerCase() - .includes(searchQuery.toLowerCase()) - ) - .map((file) => ( -
- { - if (e.target.checked) { - setSelectedFiles([ - ...selectedFiles, - file.id, - ]); - } else { - setSelectedFiles( - selectedFiles.filter( - (id) => id !== file.id - ) - ); - } - }} - /> -
-

- {file.name} -

-

- {file.size} • {file.type} -

-
-
- ))} -
- -
- {/* 已选文件列表 */} - -
- 已选文件 - -
-
- {selectedFiles.length === 0 ? ( -
- 暂未选择文件 -
- ) : ( - selectedFiles.map((fileId) => { - const file = files.find((f) => f.id === fileId); - if (!file) return null; - return ( -
-
-

- {file.name} -

-

- {file.size} • {file.type} -

-
- -
- ); - }) - )} -
-
- - - )} - +

任务配置

@@ -514,32 +385,8 @@ export default function SynthesisTaskCreate() { - -
- - -
- + ); } @@ -1224,26 +1071,47 @@ export default function SynthesisTaskCreate() { }; return ( -
-
- {/* Header */} -
-
- - - -

创建合成任务

-
- +
+ {/* Header */} +
+
+ + + +

创建合成任务

+ +
+
{renderCreateTaskPage()} +
+ + +
); diff --git a/frontend/src/pages/SynthesisTask/CreateTemplate.tsx b/frontend/src/pages/SynthesisTask/CreateTemplate.tsx index f196a23..ca24bda 100644 --- a/frontend/src/pages/SynthesisTask/CreateTemplate.tsx +++ b/frontend/src/pages/SynthesisTask/CreateTemplate.tsx @@ -12,12 +12,10 @@ import { import { Plus, ArrowLeft, Play, Save, RefreshCw, Code, X } from "lucide-react"; import { useNavigate } from "react-router"; import { mockTemplates } from "@/mock/annotation"; -import DevelopmentInProgress from "@/components/DevelopmentInProgress"; const { TextArea } = Input; export default function InstructionTemplateCreate() { - return ; const navigate = useNavigate(); const [selectedTemplate, setSelectedTemplate] = useState