diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetApplicationService.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetApplicationService.java index ae5c9b742..f9e3a55c8 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetApplicationService.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetApplicationService.java @@ -111,8 +111,10 @@ private void addDatasetToGraph(Dataset dataset, CollectionTaskDetailResponse col collectionEdge.setDescription(dataset.getDescription()); collectionEdge.setFromNodeId(collectionNode.getId()); collectionEdge.setToNodeId(datasetNode.getId()); + lineageService.generateGraph(collectionNode, collectionEdge, datasetNode); + } else { + lineageService.generateGraph(datasetNode, null, null); } - lineageService.generateGraph(collectionNode, collectionEdge, datasetNode); } public DatasetLineage getDatasetLineage(String datasetId) { diff --git a/frontend/src/components/AddTagPopover.tsx b/frontend/src/components/AddTagPopover.tsx index 92d53047f..8ce7370c5 100644 --- a/frontend/src/components/AddTagPopover.tsx +++ b/frontend/src/components/AddTagPopover.tsx @@ -1,6 +1,7 @@ import { Button, Input, Popover, theme, Tag, Empty } from "antd"; import { PlusOutlined } from "@ant-design/icons"; import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; interface Tag { id: number; @@ -22,6 +23,7 @@ export default function AddTagPopover({ onCreateAndTag, }: AddTagPopoverProps) { const { token } = theme.useToken(); + const { t } = useTranslation(); const [showPopover, setShowPopover] = useState(false); const [newTag, setNewTag] = useState(""); @@ -68,12 +70,12 @@ export default function AddTagPopover({ content={

- 添加标签 + {t("tagManagement.addTag")}

{/* Available Tags */} {availableTags?.length ? (
-
选择现有标签
+
{t("tagManagement.selectExistingTags")}
{availableTags.map((tag) => (
) : ( - + )} {/* Create New Tag */}
-
创建新标签
+
{t("tagManagement.createNewTag")}
setNewTag(e.target.value)} className="h-8 text-sm" @@ -109,13 +111,13 @@ export default function AddTagPopover({ disabled={!newTag.trim()} type="primary" > - 添加 + {t("tagManagement.createTag")}
} @@ -126,7 +128,7 @@ export default function AddTagPopover({ className="cursor-pointer" onClick={() => setShowPopover(true)} > - 添加标签 + {t("tagManagement.addTag")} diff --git a/frontend/src/components/CardView.tsx b/frontend/src/components/CardView.tsx index ec74f957b..84a29f6c9 100644 --- a/frontend/src/components/CardView.tsx +++ b/frontend/src/components/CardView.tsx @@ -7,6 +7,7 @@ import ActionDropdown from "./ActionDropdown"; import { Database } from "lucide-react"; interface BadgeItem { + name: string; label: string; color?: string; background?: string; @@ -84,7 +85,7 @@ const TagsRenderer = ({ tags }: { tags?: Array }) => { if (typeof tag === "string") { tagElement.textContent = tag; } else { - tagElement.textContent = tag.label; + tagElement.textContent = tag.label ? tag.label : tag.name; } tempDiv.appendChild(tagElement); tagElements.push(tagElement); @@ -145,7 +146,7 @@ const TagsRenderer = ({ tags }: { tags?: Array }) => { : { background: tag.background, color: tag.color } } > - {typeof tag === "string" ? tag : tag.label} + {typeof tag === "string" ? tag : (tag.label ? tag.label : tag.name)} ))}
@@ -164,7 +165,7 @@ const TagsRenderer = ({ tags }: { tags?: Array }) => { : { background: tag.background, color: tag.color } } > - {typeof tag === "string" ? tag : tag.label} + {typeof tag === "string" ? tag : (tag.label ? tag.label : tag.name)} ))} {hiddenTags.length > 0 && ( @@ -255,7 +256,7 @@ function CardView(props: CardViewProps) { color: item.tags[0].color, }} > - {item.tags[0].label} + {item.tags[0].label ? item.tags[0].label : item.tags[0].name} )} {item?.status && ( diff --git a/frontend/src/components/business/TagManagement.tsx b/frontend/src/components/business/TagManagement.tsx index 42913b372..51c55bcb1 100644 --- a/frontend/src/components/business/TagManagement.tsx +++ b/frontend/src/components/business/TagManagement.tsx @@ -3,6 +3,7 @@ import { Drawer, Input, Button, App } from "antd"; import { PlusOutlined } from "@ant-design/icons"; import { Edit, Save, TagIcon, X, Trash } from "lucide-react"; import { TagItem } from "@/pages/DataManagement/dataset.model"; +import { useTranslation } from "react-i18next"; interface CustomTagProps { isEditable?: boolean; @@ -105,6 +106,7 @@ const TagManager: React.FC = ({ }) => { const [showTagManager, setShowTagManager] = useState(false); const { message } = App.useApp(); + const { t } = useTranslation(); const [tags, setTags] = useState<{ id: number; name: string }[]>([]); const [newTag, setNewTag] = useState(""); const [editingTag, setEditingTag] = useState(null); @@ -117,7 +119,7 @@ const TagManager: React.FC = ({ const { data } = await onFetch?.(); setTags(data || []); } catch (e) { - message.error("获取标签失败"); + message.error(t("tagManagement.messages.fetchFailed")); } }; @@ -129,9 +131,9 @@ const TagManager: React.FC = ({ }); fetchTags(); setNewTag(""); - message.success("标签添加成功"); + message.success(t("tagManagement.messages.addSuccess")); } catch (error) { - message.error("添加标签失败"); + message.error(t("tagManagement.messages.addFailed")); } }; @@ -140,9 +142,9 @@ const TagManager: React.FC = ({ try { await onDelete?.(tag.id); fetchTags(); - message.success("标签删除成功"); + message.success(t("tagManagement.messages.deleteSuccess")); } catch (error) { - message.error("删除标签失败"); + message.error(t("tagManagement.messages.deleteFailed")); } }; @@ -150,9 +152,9 @@ const TagManager: React.FC = ({ try { await onUpdate?.({ ...oldTag, name: newTag }); fetchTags(); - message.success("标签更新成功"); + message.success(t("tagManagement.messages.updateSuccess")); } catch (error) { - message.error("更新标签失败"); + message.error(t("tagManagement.messages.updateFailed")); } }; @@ -192,19 +194,19 @@ const TagManager: React.FC = ({ icon={} onClick={() => setShowTagManager(true)} > - 标签管理 + {t("tagManagement.manageTags")} setShowTagManager(false)} - title="标签管理" + title={t("tagManagement.manageTags")} width={500} >
{/* Add New Tag */}
setNewTag(e.target.value)} @@ -220,7 +222,7 @@ const TagManager: React.FC = ({ disabled={!newTag.trim()} icon={} > - 添加 + {t("tagManagement.createTag")}
diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json index 791fc98ce..33ddca7bf 100644 --- a/frontend/src/i18n/locales/en/common.json +++ b/frontend/src/i18n/locales/en/common.json @@ -206,17 +206,17 @@ "messages": { "loadTemplatesFailed": "Failed to load collection templates", "createSuccess": "Task created successfully", - "errorWithDetail": "{message}: {detail}", + "errorWithDetail": "{{message}}: {{detail}}", "jsonObjectRequired": "Must be a JSON object", "jsonObjectInvalid": "Please enter a valid JSON object", "jsonFormatError": "Invalid JSON format", - "jsonFormatErrorWithMessage": "Invalid JSON format: {message}" + "jsonFormatErrorWithMessage": "Invalid JSON format: {{message}}" }, "placeholders": { "enter": "Please enter", "select": "Please select", - "enterWithLabel": "Please enter {label}", - "selectWithLabel": "Please select {label}" + "enterWithLabel": "Please enter {{label}}", + "selectWithLabel": "Please select {{label}}" } }, "scheduler": { @@ -254,7 +254,329 @@ } } }, - "common": { + "dataManagement": { + "title": "Data Management", + "actions": { + "createDataset": "Create Dataset", + "edit": "Edit", + "import": "Import", + "importData": "Import Data", + "download": "Download", + "delete": "Delete", + "refresh": "Refresh", + "export": "Export", + "batchExport": "Batch Export", + "batchDelete": "Batch Delete", + "rename": "Rename", + "createFolder": "New Folder", + "cancel": "Cancel", + "confirm": "Confirm" + }, + "stats": { + "totalDatasets": "Total Datasets", + "totalFiles": "Total Files", + "totalSize": "Total Size", + "text": "Text", + "image": "Image", + "audio": "Audio", + "video": "Video" + }, + "filters": { + "type": "Type", + "status": "Status", + "tags": "Tags" + }, + "search": { + "placeholder": "Search dataset name or description" + }, + "columns": { + "name": "Name", + "type": "Type", + "status": "Status", + "size": "Size", + "fileCount": "File Count", + "storagePath": "Storage Path", + "createdAt": "Created At", + "updatedAt": "Updated At", + "actions": "Actions", + "fileName": "File Name", + "fileSize": "Size", + "filesInFolder": "Files in Folder", + "uploadTime": "Upload Time", + "tags": "Tags", + "tagsUpdatedAt": "Tags Updated At" + }, + "formLabels": { + "name": "Name", + "description": "Description", + "type": "Type", + "tags": "Tags", + "collectionTask": "Collection Task", + "dataSource": "Data Source", + "autoExtract": "Auto-extract uploaded archives", + "uploadFiles": "Upload Files / Folder", + "databaseType": "Database Type", + "tableName": "Table Name", + "connectionString": "Connection String", + "obsEndpoint": "Endpoint", + "obsBucket": "Bucket", + "obsAccessKey": "Access Key", + "obsSecretKey": "Secret Key" + }, + "placeholders": { + "datasetName": "Enter dataset name", + "datasetDescription": "Describe dataset purpose and content", + "selectTags": "Select tags", + "selectCollectionTask": "Select collection task", + "selectDataSource": "Select data source", + "selectType": "Select dataset type", + "obsEndpoint": "obs.cn-north-4.myhuaweicloud.com", + "obsBucket": "my-bucket", + "databaseTable": "dataset_table", + "databaseConnection": "Database connection string", + "fileName": "Enter file name", + "folderName": "Enter folder name" + }, + "validation": { + "nameRequired": "Please enter dataset name", + "typeRequired": "Please select dataset type", + "filesRequired": "Please upload file or folder" + }, + "messages": { + "createSuccess": "Dataset created successfully", + "createFailed": "Dataset creation failed, please try again", + "updateSuccess": "Dataset updated successfully", + "updateFailed": "Dataset update failed, please try again", + "deleteSuccess": "Dataset deleted", + "downloadSuccess": "Dataset downloaded successfully", + "refreshSuccess": "Data refreshed", + "fileDownloadSuccess": "File downloaded successfully" + }, + "confirm": { + "deleteDatasetTitle": "Confirm delete this dataset?", + "deleteDatasetDesc": "This dataset cannot be recovered after deletion. Proceed with caution.", + "deleteFolderTitle": "Confirm delete folder?", + "deleteFolderDesc": "Deleting folder \"{{name}}\" will remove all files and subfolders. This action cannot be undone.", + "deleteConfirm": "Delete", + "deleteCancel": "Cancel" + }, + "detail": { + "breadcrumb": "Data Management", + "title": "Dataset Detail", + "tabOverview": "Overview", + "tabLineage": "Data Lineage", + "tabQuality": "Data Quality", + "sectionBasicInfo": "Basic Information", + "sectionFileList": "File List", + "selectedFiles": "Selected {{count}} files", + "goUp": "Go up", + "currentPath": "Current path: {{path}}", + "totalItems": "Total {{total}} items", + "previewTitle": "File Preview: {{name}}", + "previewEmpty": "No preview available or unsupported file type.", + "previewInfoTitle": "File Info", + "editTitle": "Edit Dataset - {{name}}" + }, + "labels": { + "id": "ID", + "name": "Name", + "fileCount": "File Count", + "dataSize": "Data Size", + "type": "Type", + "status": "Status", + "creator": "Creator", + "storagePath": "Storage Path", + "storageName": "Storage Name", + "createdAt": "Created At", + "updatedAt": "Updated At", + "description": "Description", + "fileName": "File Name", + "originalName": "Original Name", + "fileType": "File Type", + "fileSize": "File Size", + "tags": "Tags", + "tagsUpdatedAt": "Tags Updated At", + "uploadTime": "Upload Time", + "uploadedBy": "Uploaded By", + "filePath": "File Path", + "annotation": "Annotation" + }, + "defaults": { + "unknown": "Unknown", + "none": "None", + "empty": "-", + "unnamedFile": "Unnamed File" + }, + "lineage": { + "title": "Data Lineage", + "stats": "{{nodes}} nodes · {{edges}} edges", + "legendDatasource": "Data Source", + "legendDataset": "Dataset", + "legendModel": "Model", + "legendKnowledge": "Knowledge Base", + "emptyTitle": "No lineage data", + "emptyDesc": "Lineage will appear after collection or workflow linkage", + "edgeTypeCollection": "Data Collection", + "edgeTypeCleaning": "Data Processing", + "edgeTypeLabeling": "Data Annotation", + "edgeTypeSynthesis": "Data Synthesis", + "edgeTypeRatio": "Data Ratio", + "edgeTypeDefault": "Processing Flow", + "edgeTypeDefaultLabel": "Processing Flow", + "detailBasicInfo": "Basic Information", + "detailId": "ID:", + "detailName": "Name:", + "detailStatus": "Status:", + "detailFileCount": "File Count:", + "detailDataSize": "Data Size:", + "detailUpdateTime": "Updated At:", + "detailDescription": "Description", + "detailUpstream": "Upstream", + "detailDownstream": "Downstream", + "detailNoUpstream": "No upstream dependencies", + "detailNoDownstream": "No downstream impacts", + "processDetail": "Process Detail", + "processId": "Process ID:", + "relationships": "Upstream and Downstream", + "processUpstream": "Upstream:", + "processDownstream": "Downstream:", + "processDescription": "Description", + "processEmpty": "No process description", + "nodeTypeDatasource": "Data Source", + "nodeTypeDataset": "Dataset", + "nodeTypeModel": "Model", + "nodeTypeKnowledge": "Knowledge Base", + "filesCount": "{{count}} files · {{size}}", + "statusRunning": "Running", + "statusStopped": "Stopped", + "statusActive": "Active", + "statusInactive": "Inactive", + "statusDraft": "Draft" + }, + "quality": { + "titleDistribution": "Quality Distribution", + "titleIntegrity": "Data Integrity", + "metricImageClarity": "Image Clarity", + "metricColorConsistency": "Color Consistency", + "metricAnnotationCompleteness": "Annotation Completeness", + "metricTokenQuality": "Token Quality", + "metricLabelConsistency": "Label Consistency", + "metricMetadataCompleteness": "Metadata Completeness", + "metricMissingValueControl": "Missing Value Control", + "metricTypeConsistency": "Type Consistency", + "metricUniqueness": "Uniqueness/Dedup", + "metricFileIntegrity": "File Integrity", + "metricFieldIntegrity": "Field Integrity", + "metricColumnIntegrity": "Column Integrity", + "metricDuplicateRate": "Duplicate Rate", + "recommendationTitle": "Quality Improvement Suggestions", + "recommendationReviewLowQuality": "Review or recollect {{count}} low-quality samples", + "recommendationSupplementMetadata": "Check and supplement missing metadata fields (missing: {{missing}})", + "recommendationBalanceDistribution": "Consider adding more underrepresented samples to balance distribution", + "imageClarity": "Image Clarity", + "colorConsistency": "Color Consistency", + "annotationCompleteness": "Annotation Completeness", + "tokenQuality": "Token Quality", + "labelConsistency": "Label Consistency", + "metadataCompleteness": "Metadata Completeness", + "missingValueControl": "Missing Value Control", + "typeConsistency": "Type Consistency", + "uniqueness": "Uniqueness/Dedup", + "fileIntegrity": "File Integrity", + "fieldIntegrity": "Field Integrity", + "columnIntegrity": "Column Integrity", + "duplicateRate": "Duplicate Rate", + "labelDistribution": { + "title": "Label Distribution Details", + "category": "Category", + "labelName": "Label Name", + "count": "Count", + "percentage": "Percentage", + "noData": "No label distribution data", + "noDataSubtitle": "No label distribution data available", + "statisticsTitle": "Dataset Label Statistics", + "statisticsSummary": "{{categoryCount}} categories, {{totalLabels}} label samples", + "detailsCardTitle": "Label Distribution Details", + "totalLabels": "Total: {{count}} labels", + "moreLabels": "And {{count}} more labels..." + } + }, + "import": { + "title": "Import Data", + "uploadFileTitle": "Local File Upload", + "uploadFileHint": "Drag files here or click to select files", + "uploadFolderTitle": "Local Folder Upload", + "uploadFolderHint": "Drag folder here or click to select folder", + "fileLabel": "File:", + "folderLabel": "Folder:", + "warningTypeMismatch": "Dataset type is {{type}}. Some selected files may not match; please confirm.", + "warningUseFolderUpload": "To upload a folder, use the \"Local Folder Upload\" area on the right.", + "warningUseFileUpload": "To upload a single file, use the \"Local File Upload\" area on the left.", + "obsEndpoint": "Endpoint", + "obsBucket": "Bucket", + "obsAccessKey": "Access Key", + "obsSecretKey": "Secret Key", + "dbType": "Database Type", + "dbTableName": "Table Name", + "dbConnectionString": "Connection String", + "dbTypeMysql": "MySQL", + "dbTypePostgres": "PostgreSQL", + "dbTypeMongo": "MongoDB", + "obsAccessKeyPlaceholder": "Access Key", + "obsSecretKeyPlaceholder": "Secret Key", + "validation": { + "selectDataSource": "Please select data source" + }, + "autoExtract": "Auto-extract uploaded archives", + "uploadFiles": "Upload Files / Folder", + "unnamedFile": "Unnamed File" + }, + "datasetTypes": { + "text": "Text", + "image": "Image", + "audio": "Audio", + "video": "Video" + }, + "datasetTypeDesc": { + "text": "Dataset for processing and analyzing text data", + "image": "Dataset for processing and analyzing image data", + "audio": "Dataset for processing and analyzing audio data", + "video": "Dataset for processing and analyzing video data" + }, + "datasetSubTypes": { + "textDocument": "Document", + "textWeb": "Web", + "textDialog": "Dialog", + "imageImage": "Image", + "imageCaption": "Image + Caption", + "audioAudio": "Audio", + "audioJsonl": "Audio + JSONL", + "videoVideo": "Video", + "videoJsonl": "Video + JSONL" + }, + "datasetSubTypeDesc": { + "textDocument": "Dataset for storing and processing document text", + "textWeb": "Dataset for storing and processing web data", + "textDialog": "Dataset for storing and processing dialog data", + "imageImage": "Dataset for large-scale image pretraining", + "imageCaption": "Dataset for image captioning", + "audioAudio": "Dataset for large-scale audio pretraining", + "audioJsonl": "Dataset for large-scale audio pretraining", + "videoVideo": "Dataset for large-scale video pretraining", + "videoJsonl": "Dataset for large-scale video pretraining" + }, + "datasetStatus": { + "active": "Active", + "processing": "Processing", + "inactive": "Inactive", + "draft": "Draft" + }, + "dataSources": { + "upload": "Local Upload", + "collection": "Collection Task Import" + } + }, + "common": { "actions": { "createTask": "Create Task" }, @@ -271,5 +593,27 @@ "pending": "Ready" } } + }, + "tagManagement": { + "manageTags": "Manage Tags", + "addTag": "Add Tag", + "tagName": "Tag Name", + "tagNamePlaceholder": "Enter tag name...", + "createTag": "Create", + "selectExistingTags": "Select existing tags", + "createNewTag": "Create new tag", + "newTagNamePlaceholder": "Enter new tag name...", + "noAvailableTags": "No available tags. Please create a tag first.", + "cancel": "Cancel", + "messages": { + "fetchFailed": "Failed to fetch tags", + "addSuccess": "Tag added successfully", + "addFailed": "Failed to add tag", + "deleteSuccess": "Tag deleted successfully", + "deleteFailed": "Failed to delete tag", + "updateSuccess": "Tag updated successfully", + "updateFailed": "Failed to update tag" + }, + "uncategorized": "Uncategorized" } } diff --git a/frontend/src/i18n/locales/zh/common.json b/frontend/src/i18n/locales/zh/common.json index a091f84b6..72f890bd2 100644 --- a/frontend/src/i18n/locales/zh/common.json +++ b/frontend/src/i18n/locales/zh/common.json @@ -206,17 +206,17 @@ "messages": { "loadTemplatesFailed": "加载归集模板失败", "createSuccess": "任务创建成功", - "errorWithDetail": "{message}:{detail}", + "errorWithDetail": "{{message}}:{{detail}}", "jsonObjectRequired": "必须是JSON对象", "jsonObjectInvalid": "请输入合法的JSON对象", "jsonFormatError": "JSON格式错误", - "jsonFormatErrorWithMessage": "JSON格式错误:{message}" + "jsonFormatErrorWithMessage": "JSON格式错误:{{message}}" }, "placeholders": { "enter": "请输入", "select": "请选择", - "enterWithLabel": "请输入{label}", - "selectWithLabel": "请选择{label}" + "enterWithLabel": "请输入{{label}}", + "selectWithLabel": "请选择{{label}}" } }, "scheduler": { @@ -254,7 +254,328 @@ } } }, - "common": { + "dataManagement": { + "title": "数据管理", + "actions": { + "createDataset": "创建数据集", + "edit": "编辑", + "import": "导入", + "importData": "导入数据", + "download": "下载", + "delete": "删除", + "refresh": "刷新", + "export": "导出", + "batchExport": "批量导出", + "batchDelete": "批量删除", + "rename": "重命名", + "createFolder": "新建文件夹", + "cancel": "取消", + "confirm": "确定" + }, + "stats": { + "totalDatasets": "数据集总数", + "totalFiles": "文件总数", + "totalSize": "总大小", + "text": "文本", + "image": "图像", + "audio": "音频", + "video": "视频" + }, + "filters": { + "type": "类型", + "status": "状态", + "tags": "标签" + }, + "search": { + "placeholder": "搜索数据集名称、描述" + }, + "columns": { + "name": "名称", + "type": "类型", + "status": "状态", + "size": "大小", + "fileCount": "文件数", + "storagePath": "存储路径", + "createdAt": "创建时间", + "updatedAt": "更新时间", + "actions": "操作", + "fileName": "文件名", + "fileSize": "大小", + "filesInFolder": "包含文件数", + "uploadTime": "上传时间", + "tags": "标签", + "tagsUpdatedAt": "标签更新时间" + }, + "formLabels": { + "name": "名称", + "description": "描述", + "type": "类型", + "tags": "标签", + "collectionTask": "关联归集任务", + "dataSource": "数据源", + "autoExtract": "自动解压上传的压缩包", + "uploadFiles": "上传文件 / 文件夹", + "databaseType": "数据库类型", + "tableName": "表名", + "connectionString": "连接字符串", + "obsEndpoint": "Endpoint", + "obsBucket": "Bucket", + "obsAccessKey": "Access Key", + "obsSecretKey": "Secret Key" + }, + "placeholders": { + "datasetName": "输入数据集名称", + "datasetDescription": "描述数据集的用途和内容", + "selectTags": "请选择标签", + "selectCollectionTask": "请选择归集任务", + "selectDataSource": "请选择数据源", + "selectType": "请选择数据集类型", + "obsEndpoint": "obs.cn-north-4.myhuaweicloud.com", + "obsBucket": "my-bucket", + "databaseTable": "dataset_table", + "databaseConnection": "数据库连接字符串", + "fileName": "请输入文件名称", + "folderName": "请输入文件夹名称" + }, + "validation": { + "nameRequired": "请输入数据集名称", + "typeRequired": "请选择数据集类型", + "filesRequired": "请上传文件或文件夹" + }, + "messages": { + "createSuccess": "数据集创建成功", + "createFailed": "数据集创建失败,请重试", + "updateSuccess": "数据集更新成功", + "updateFailed": "数据集更新失败,请重试", + "deleteSuccess": "数据删除成功", + "downloadSuccess": "数据集下载成功", + "refreshSuccess": "数据已刷新", + "fileDownloadSuccess": "文件下载成功" + }, + "confirm": { + "deleteDatasetTitle": "确认删除该数据集?", + "deleteDatasetDesc": "删除后该数据集将无法恢复,请谨慎操作。", + "deleteFolderTitle": "确认删除文件夹?", + "deleteFolderDesc": "删除文件夹 \"{{name}}\" 将同时删除其中的所有文件和子文件夹,此操作不可恢复。", + "deleteConfirm": "删除", + "deleteCancel": "取消" + }, + "detail": { + "breadcrumb": "数据管理", + "title": "数据集详情", + "tabOverview": "概览", + "tabLineage": "数据血缘", + "tabQuality": "数据质量", + "sectionBasicInfo": "基本信息", + "sectionFileList": "文件列表", + "selectedFiles": "已选择 {{count}} 个文件", + "goUp": "返回上一级", + "currentPath": "当前路径: {{path}}", + "totalItems": "共 {{total}} 条", + "previewTitle": "文件预览:{{name}}", + "previewEmpty": "暂无预览内容,或当前文件类型暂不支持预览。", + "previewInfoTitle": "文件信息", + "editTitle": "编辑数据集 - {{name}}" + }, + "labels": { + "id": "ID", + "name": "名称", + "fileCount": "文件数", + "dataSize": "数据大小", + "type": "类型", + "status": "状态", + "creator": "创建者", + "storagePath": "存储路径", + "storageName": "存储名称", + "createdAt": "创建时间", + "updatedAt": "更新时间", + "description": "描述", + "fileName": "文件名", + "originalName": "原始文件名", + "fileType": "文件类型", + "fileSize": "文件大小", + "tags": "标签", + "tagsUpdatedAt": "标签更新时间", + "uploadTime": "上传时间", + "uploadedBy": "上传者", + "filePath": "文件路径", + "annotation": "标注信息" + }, + "defaults": { + "unknown": "未知", + "none": "无", + "empty": "-", + "unnamedFile": "未命名文件" + }, + "lineage": { + "title": "数据血缘图", + "stats": "{{nodes}} 个节点 · {{edges}} 条边", + "legendDatasource": "数据源", + "legendDataset": "数据集", + "legendModel": "模型", + "legendKnowledge": "知识库", + "emptyTitle": "暂无血缘数据", + "emptyDesc": "完成归集或关联流程后将自动生成血缘图", + "edgeTypeCollection": "数据归集", + "edgeTypeCleaning": "数据处理", + "edgeTypeLabeling": "数据标注", + "edgeTypeSynthesis": "数据合成", + "edgeTypeRatio": "数据配比", + "edgeTypeDefault": "处理流程", + "detailBasicInfo": "基本信息", + "detailId": "ID:", + "detailName": "名称:", + "detailStatus": "状态:", + "detailFileCount": "文件数:", + "detailDataSize": "数据大小:", + "detailUpdateTime": "更新时间:", + "detailDescription": "描述", + "detailUpstream": "上游依赖", + "detailDownstream": "下游影响", + "detailNoUpstream": "无上游依赖", + "detailNoDownstream": "无下游影响", + "processDetail": "流程详情", + "processId": "流程ID:", + "relationships": "上下游关系", + "processUpstream": "上游:", + "processDownstream": "下游:", + "processDescription": "描述", + "processEmpty": "无流程描述", + "nodeTypeDatasource": "数据源", + "nodeTypeDataset": "数据集", + "nodeTypeModel": "模型", + "nodeTypeKnowledge": "知识库", + "filesCount": "{{count}} 个文件 · {{size}}", + "statusRunning": "运行中", + "statusStopped": "已停止", + "statusActive": "活跃", + "statusInactive": "未激活", + "statusDraft": "草稿" + }, + "quality": { + "titleDistribution": "质量分布", + "titleIntegrity": "数据完整性", + "metricImageClarity": "图像清晰度", + "metricColorConsistency": "色彩一致性", + "metricAnnotationCompleteness": "标注完整性", + "metricTokenQuality": "分词/Token质量", + "metricLabelConsistency": "标签一致性", + "metricMetadataCompleteness": "元数据完整性", + "metricMissingValueControl": "缺失值比例控制", + "metricTypeConsistency": "类型一致性", + "metricUniqueness": "唯一性/去重", + "metricFileIntegrity": "文件完整性", + "metricFieldIntegrity": "字段完整性", + "metricColumnIntegrity": "列完整性", + "metricDuplicateRate": "重复率", + "recommendationTitle": "质量改进建议", + "recommendationReviewLowQuality": "建议对{{count}}项低质量样本进行复查或重新采集", + "recommendationSupplementMetadata": "检查并补充缺失的元数据字段(现有缺失:{{missing}})", + "recommendationBalanceDistribution": "考虑增加更多低代表性样本以平衡数据分布", + "imageClarity": "图像清晰度", + "colorConsistency": "色彩一致性", + "annotationCompleteness": "标注完整性", + "tokenQuality": "分词/Token质量", + "labelConsistency": "标签一致性", + "metadataCompleteness": "元数据完整性", + "missingValueControl": "缺失值比例控制", + "typeConsistency": "类型一致性", + "uniqueness": "唯一性/去重", + "fileIntegrity": "文件完整性", + "fieldIntegrity": "字段完整性", + "columnIntegrity": "列完整性", + "duplicateRate": "重复率", + "labelDistribution": { + "title": "标签分布明细", + "category": "类别", + "labelName": "标签名称", + "count": "数量", + "percentage": "占比", + "noData": "暂无标签分布数据", + "noDataSubtitle": "暂无标签分布数据可用", + "statisticsTitle": "数据集标签统计", + "statisticsSummary": "共 {{categoryCount}} 个类别,{{totalLabels}} 个标签样本", + "detailsCardTitle": "标签分布明细", + "totalLabels": "总计: {{count}} 个标签", + "moreLabels": "还有 {{count}} 个标签..." + } + }, + "import": { + "title": "导入数据", + "uploadFileTitle": "本地文件上传", + "uploadFileHint": "拖拽文件到此处或点击选择文件", + "uploadFolderTitle": "本地文件夹上传", + "uploadFolderHint": "拖拽文件夹到此处或点击选择文件夹", + "fileLabel": "文件:", + "folderLabel": "文件夹:", + "warningTypeMismatch": "当前数据集类型为 {{type}},本次选择的部分文件格式与该类型不太匹配,建议确认是否为预期数据。", + "warningUseFolderUpload": "如需上传文件夹,请使用右侧\"本地文件夹上传\"区域。", + "warningUseFileUpload": "如需上传单个文件,请使用左侧\"本地文件上传\"区域。", + "obsEndpoint": "Endpoint", + "obsBucket": "Bucket", + "obsAccessKey": "Access Key", + "obsSecretKey": "Secret Key", + "dbType": "数据库类型", + "dbTableName": "表名", + "dbConnectionString": "连接字符串", + "dbTypeMysql": "MySQL", + "dbTypePostgres": "PostgreSQL", + "dbTypeMongo": "MongoDB", + "obsAccessKeyPlaceholder": "Access Key", + "obsSecretKeyPlaceholder": "Secret Key", + "validation": { + "selectDataSource": "请选择数据源" + }, + "autoExtract": "自动解压上传的压缩包", + "uploadFiles": "上传文件 / 文件夹", + "unnamedFile": "未命名文件" + }, + "datasetTypes": { + "text": "文本", + "image": "图像", + "audio": "音频", + "video": "视频" + }, + "datasetTypeDesc": { + "text": "用于处理和分析文本数据的数据集", + "image": "用于处理和分析图像数据的数据集", + "audio": "用于处理和分析音频数据的数据集", + "video": "用于处理和分析视频数据的数据集" + }, + "datasetSubTypes": { + "textDocument": "文档", + "textWeb": "网页", + "textDialog": "对话", + "imageImage": "图像", + "imageCaption": "图像+caption", + "audioAudio": "音频", + "audioJsonl": "音频+JSONL", + "videoVideo": "视频", + "videoJsonl": "视频+JSONL" + }, + "datasetSubTypeDesc": { + "textDocument": "用于存储和处理各种文档格式的文本数据集", + "textWeb": "用于存储和处理网页数据集", + "textDialog": "用于存储和处理对话数据的数据集", + "imageImage": "用于大规模图像预训练模型的数据集", + "imageCaption": "用于图像标题生成的数据集", + "audioAudio": "用于大规模音频预训练模型的数据集", + "audioJsonl": "用于大规模音频预训练模型的数据集", + "videoVideo": "用于大规模视频预训练模型的数据集", + "videoJsonl": "用于大规模视频预训练模型的数据集" + }, + "datasetStatus": { + "active": "活跃", + "processing": "处理中", + "inactive": "未激活", + "draft": "草稿" + }, + "dataSources": { + "upload": "本地上传", + "collection": "归集任务导入 " + } + }, + "common": { "actions": { "createTask": "创建任务" }, @@ -271,5 +592,27 @@ "pending": "就绪" } } + }, + "tagManagement": { + "manageTags": "标签管理", + "addTag": "添加标签", + "tagName": "标签名称", + "tagNamePlaceholder": "输入标签名称...", + "createTag": "添加", + "selectExistingTags": "选择现有标签", + "createNewTag": "创建新标签", + "newTagNamePlaceholder": "输入新标签名称...", + "noAvailableTags": "没有可用标签,请先创建标签。", + "cancel": "取消", + "messages": { + "fetchFailed": "获取标签失败", + "addSuccess": "标签添加成功", + "addFailed": "添加标签失败", + "deleteSuccess": "标签删除成功", + "deleteFailed": "删除标签失败", + "updateSuccess": "标签更新成功", + "updateFailed": "更新标签失败" + }, + "uncategorized": "未分类" } } diff --git a/frontend/src/pages/DataCleansing/Create/components/CreateTaskStepOne.tsx b/frontend/src/pages/DataCleansing/Create/components/CreateTaskStepOne.tsx index 5cd7929fc..2c570d033 100644 --- a/frontend/src/pages/DataCleansing/Create/components/CreateTaskStepOne.tsx +++ b/frontend/src/pages/DataCleansing/Create/components/CreateTaskStepOne.tsx @@ -1,6 +1,6 @@ import RadioCard from "@/components/RadioCard"; import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api"; -import { datasetTypes, mapDataset } from "@/pages/DataManagement/dataset.const"; +import { getDatasetTypeMap, mapDataset } from "@/pages/DataManagement/dataset.const"; import { Dataset, DatasetSubType, @@ -9,6 +9,7 @@ import { import { Input, Select, Form, AutoComplete } from "antd"; import TextArea from "antd/es/input/TextArea"; import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; export default function CreateTaskStepOne({ form, @@ -26,7 +27,9 @@ export default function CreateTaskStepOne({ }; setTaskConfig: (config: any) => void; }) { + const { t } = useTranslation(); const [datasets, setDatasets] = useState([]); + const datasetTypes = getDatasetTypeMap(t); const fetchDatasets = async () => { const { data } = await queryDatasetsUsingGet({ page: 1, size: 1000 }); diff --git a/frontend/src/pages/DataManagement/Create/CreateDataset.tsx b/frontend/src/pages/DataManagement/Create/CreateDataset.tsx index cd0109d91..2416dede7 100644 --- a/frontend/src/pages/DataManagement/Create/CreateDataset.tsx +++ b/frontend/src/pages/DataManagement/Create/CreateDataset.tsx @@ -6,11 +6,13 @@ import { Link, useNavigate } from "react-router"; import { createDatasetUsingPost } from "../dataset.api"; import { DatasetType } from "../dataset.model"; import BasicInformation from "./components/BasicInformation"; +import { useTranslation } from "react-i18next"; export default function DatasetCreate() { const navigate = useNavigate(); const { message } = App.useApp(); const [form] = Form.useForm(); + const { t } = useTranslation(); const [newDataset, setNewDataset] = useState({ name: "", @@ -28,11 +30,11 @@ export default function DatasetCreate() { }; try { const { data } = await createDatasetUsingPost(params); - message.success(`数据集创建成功`); + message.success(t("dataManagement.messages.createSuccess")); navigate("/data/management/detail/" + data.id); } catch (error) { console.error(error); - message.error("数据集创建失败,请重试"); + message.error(t("dataManagement.messages.createFailed")); return; } }; @@ -51,7 +53,9 @@ export default function DatasetCreate() { -

创建数据集

+

+ {t("dataManagement.actions.createDataset")} +

@@ -68,13 +72,15 @@ export default function DatasetCreate() {
- +
diff --git a/frontend/src/pages/DataManagement/Create/EditDataset.tsx b/frontend/src/pages/DataManagement/Create/EditDataset.tsx index 843f8e528..8d6eeb3ab 100644 --- a/frontend/src/pages/DataManagement/Create/EditDataset.tsx +++ b/frontend/src/pages/DataManagement/Create/EditDataset.tsx @@ -6,6 +6,7 @@ import { import { useEffect, useState } from "react"; import { Dataset, DatasetType } from "../dataset.model"; import { App, Button, Form, Modal } from "antd"; +import { useTranslation } from "react-i18next"; export default function EditDataset({ open, @@ -20,6 +21,7 @@ export default function EditDataset({ }) { const [form] = Form.useForm(); const { message } = App.useApp(); + const { t } = useTranslation(); const [newDataset, setNewDataset] = useState({ name: "", @@ -60,27 +62,27 @@ export default function EditDataset({ try { await updateDatasetByIdUsingPut(data?.id, params); onClose(); - message.success("数据集更新成功"); + message.success(t("dataManagement.messages.updateSuccess")); onRefresh?.(false); } catch (error) { console.error(error); - message.error("数据集更新失败,请重试"); + message.error(t("dataManagement.messages.updateFailed")); return; } }; return ( - + } diff --git a/frontend/src/pages/DataManagement/Create/components/BasicInformation.tsx b/frontend/src/pages/DataManagement/Create/components/BasicInformation.tsx index 363aa49cd..b283694fd 100644 --- a/frontend/src/pages/DataManagement/Create/components/BasicInformation.tsx +++ b/frontend/src/pages/DataManagement/Create/components/BasicInformation.tsx @@ -1,9 +1,10 @@ import RadioCard from "@/components/RadioCard"; import { Input, Select, Form } from "antd"; -import { datasetTypes } from "../../dataset.const"; +import { getDatasetTypes } from "../../dataset.const"; import { useEffect, useState } from "react"; import { queryDatasetTagsUsingGet } from "../../dataset.api"; import {queryTasksUsingGet} from "@/pages/DataCollection/collection.apis.ts"; +import { useTranslation } from "react-i18next"; export default function BasicInformation({ data, @@ -14,6 +15,8 @@ export default function BasicInformation({ setData: any; hidden?: string[]; }) { + const { t } = useTranslation(); + const datasetTypes = getDatasetTypes(t); const [tagOptions, setTagOptions] = useState< { label: JSX.Element; @@ -59,24 +62,27 @@ export default function BasicInformation({ return ( <> - + {!hidden.includes("description") && ( - - + + )} {/* 数据集类型选择 - 使用卡片形式 */} {!hidden.includes("datasetType") && ( )} {!hidden.includes("tags") && ( - + + + + + - + - + @@ -481,7 +485,7 @@ export default function ImportConfiguration({ {importConfig?.source === DataSource.UPLOAD && ( <> @@ -510,8 +514,8 @@ export default function ImportConfiguration({

-

本地文件上传

-

拖拽文件到此处或点击选择文件

+

{t("dataManagement.import.uploadFileTitle")}

+

{t("dataManagement.import.uploadFileHint")}

@@ -527,8 +531,8 @@ export default function ImportConfiguration({

-

本地文件夹上传

-

拖拽文件夹到此处或点击选择文件夹

+

{t("dataManagement.import.uploadFolderTitle")}

+

{t("dataManagement.import.uploadFolderHint")}

@@ -538,9 +542,9 @@ export default function ImportConfiguration({
{pureFileList.length > 0 && (
- 文件: + {t("dataManagement.import.fileLabel")} {pureFileList.map((file) => { - const name = file.name || "未命名文件"; + const name = file.name || t("dataManagement.defaults.unnamedFile"); const displayName = name.length > 20 ? `${name.slice(0, 20)}...` : name; return ( - 文件夹: + {t("dataManagement.import.folderLabel")} {folderNames.map((name) => { const displayName = name.length > 20 ? `${name.slice(0, 20)}...` : name; return ( @@ -594,32 +598,32 @@ export default function ImportConfiguration({ - +
diff --git a/frontend/src/pages/DataManagement/Detail/components/Overview.tsx b/frontend/src/pages/DataManagement/Detail/components/Overview.tsx index 8ed179e2b..cc71bca26 100644 --- a/frontend/src/pages/DataManagement/Detail/components/Overview.tsx +++ b/frontend/src/pages/DataManagement/Detail/components/Overview.tsx @@ -1,10 +1,13 @@ import { App, Button, Descriptions, DescriptionsProps, Modal, Table, Input, Spin } from "antd"; import { formatBytes, formatDateTime } from "@/utils/unit"; import { Download, Trash2, Folder, File } from "lucide-react"; -import { datasetTypeMap } from "../../dataset.const"; +import { getDatasetTypeMap } from "../../dataset.const"; +import { useTranslation } from "react-i18next"; export default function Overview({ dataset, filesOperation, fetchDataset }) { const { modal, message } = App.useApp(); + const { t } = useTranslation(); + const datasetTypeMap = getDatasetTypeMap(t); const { fileList, pagination, @@ -44,71 +47,71 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { const items: DescriptionsProps["items"] = [ { key: "id", - label: "ID", + label: t("dataManagement.labels.id"), children: dataset.id, }, { key: "name", - label: "名称", + label: t("dataManagement.labels.name"), children: dataset.name, }, { key: "fileCount", - label: "文件数", + label: t("dataManagement.labels.fileCount"), children: dataset.fileCount || 0, }, { key: "size", - label: "数据大小", + label: t("dataManagement.labels.dataSize"), children: dataset.size || "0 B", }, { key: "datasetType", - label: "类型", - children: datasetTypeMap[dataset?.datasetType]?.label || "未知", + label: t("dataManagement.labels.type"), + children: datasetTypeMap[dataset?.datasetType]?.label || t("dataManagement.defaults.unknown"), }, { key: "status", - label: "状态", - children: dataset?.status?.label || "未知", + label: t("dataManagement.labels.status"), + children: dataset?.status?.label || t("dataManagement.defaults.unknown"), }, { key: "createdBy", - label: "创建者", - children: dataset.createdBy || "未知", + label: t("dataManagement.labels.creator"), + children: dataset.createdBy || t("dataManagement.defaults.unknown"), }, { key: "targetLocation", - label: "存储路径", - children: dataset.targetLocation || "未知", + label: t("dataManagement.labels.storagePath"), + children: dataset.targetLocation || t("dataManagement.defaults.unknown"), }, { key: "pvcName", - label: "存储名称", - children: dataset.pvcName || "未知", + label: t("dataManagement.labels.storageName"), + children: dataset.pvcName || t("dataManagement.defaults.unknown"), }, { key: "createdAt", - label: "创建时间", + label: t("dataManagement.labels.createdAt"), children: dataset.createdAt, }, { key: "updatedAt", - label: "更新时间", + label: t("dataManagement.labels.updatedAt"), children: dataset.updatedAt, }, { key: "description", - label: "描述", - children: dataset.description || "无", + label: t("dataManagement.labels.description"), + children: dataset.description || t("dataManagement.defaults.none"), }, ]; // 文件列表列定义 const columns = [ { - title: "文件名", + title: t("dataManagement.columns.fileName"), dataIndex: "fileName", key: "fileName", fixed: "left", @@ -157,7 +160,7 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { }, }, { - title: "大小", + title: t("dataManagement.columns.fileSize"), dataIndex: "fileSize", key: "fileSize", width: 150, @@ -170,36 +173,36 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { }, }, { - title: "包含文件数", + title: t("dataManagement.columns.filesInFolder"), dataIndex: "fileCount", key: "fileCount", width: 120, render: (text: number, record: any) => { const isDirectory = record.id.startsWith('directory-'); if (!isDirectory) { - return "-"; + return t("dataManagement.defaults.empty"); } return record.fileCount ?? 0; }, }, { - title: "上传时间", + title: t("dataManagement.columns.uploadTime"), dataIndex: "uploadTime", key: "uploadTime", width: 200, render: (text) => formatDateTime(text), }, { - title: "标签", + title: t("dataManagement.columns.tags"), dataIndex: "tags", key: "tags", width: 220, render: (value: any, record: any) => { const isDirectory = typeof record.id === "string" && record.id.startsWith("directory-"); - if (isDirectory) return "-"; + if (isDirectory) return t("dataManagement.defaults.empty"); let raw = value; - if (!raw) return "-"; + if (!raw) return t("dataManagement.defaults.empty"); // 后端目前将 tags 作为 JSON 字符串存储在 t_dm_dataset_files.tags 中 // 这里尝试解析为 FileTag 数组结构 [{ type, from_name, values: { [type]: [...] } }] @@ -212,7 +215,7 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { } } - if (!Array.isArray(raw) || raw.length === 0) return "-"; + if (!Array.isArray(raw) || raw.length === 0) return t("dataManagement.defaults.empty"); const labels: string[] = []; raw.forEach((tag: any) => { @@ -231,24 +234,24 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { } }); - if (!labels.length) return "-"; + if (!labels.length) return t("dataManagement.defaults.empty"); return labels.join(", "); }, }, { - title: "标签更新时间", + title: t("dataManagement.columns.tagsUpdatedAt"), dataIndex: "tagsUpdatedAt", key: "tagsUpdatedAt", width: 200, render: (text: any, record: any) => { const isDirectory = typeof record.id === "string" && record.id.startsWith("directory-"); - if (isDirectory) return "-"; - if (!text) return "-"; + if (isDirectory) return t("dataManagement.defaults.empty"); + if (!text) return t("dataManagement.defaults.empty"); return formatDateTime(text); }, }, { - title: "操作", + title: t("dataManagement.columns.actions"), key: "action", width: 180, fixed: "right", @@ -266,7 +269,7 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { type="link" onClick={() => handleDownloadDirectory(fullPath, record.fileName)} > - 下载 + {t("dataManagement.actions.download")}
); @@ -329,7 +332,7 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { type="link" onClick={() => handleDownloadFile(record)} > - 下载 + {t("dataManagement.actions.download")} )}, @@ -391,7 +394,7 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) {
{/* 基本信息 */} -

文件列表

+

+ {t("dataManagement.detail.sectionFileList")} +

{selectedFiles.length > 0 && (
- 已选择 {selectedFiles.length} 个文件 + {t("dataManagement.detail.selectedFiles", { count: selectedFiles.length })}
)} @@ -487,12 +492,16 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { d="M10 19l-7-7m0 0l7-7m-7 7h18" /> - 返回上一级 + {t("dataManagement.detail.goUp")} )} {filesOperation.pagination.prefix && ( - 当前路径: {filesOperation.pagination.prefix} + + {t("dataManagement.detail.currentPath", { + path: filesOperation.pagination.prefix, + })} + )} `共 ${total} 条`, + showTotal: (total) => t("dataManagement.detail.totalItems", { total }), onChange: (page, pageSize) => { filesOperation.fetchFiles(filesOperation.pagination.prefix, page, pageSize); } @@ -514,7 +523,7 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { {/* 文件预览弹窗 */} setPreviewVisible(false)} footer={null} @@ -549,50 +558,64 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { ) : ( - 暂无预览内容,或当前文件类型暂不支持预览。 + {t("dataManagement.detail.previewEmpty")} )} {/* 右侧文件信息(来自 t_dm_dataset_files) */}
-

文件信息

+

+ {t("dataManagement.detail.previewInfoTitle")} +

- 文件名: + + {t("dataManagement.labels.fileName")}: + {previewFileDetail?.fileName || previewFileName}
{previewFileDetail?.originalName && (
- 原始文件名: + + {t("dataManagement.labels.originalName")}: + {previewFileDetail.originalName}
)} {previewFileDetail?.fileType && (
- 文件类型: + + {t("dataManagement.labels.fileType")}: + {previewFileDetail.fileType}
)} {typeof previewFileDetail?.fileSize === "number" && (
- 文件大小: + + {t("dataManagement.labels.fileSize")}: + {formatBytes(previewFileDetail.fileSize || 0)}
)} {previewFileDetail?.status && (
- 状态: + + {t("dataManagement.labels.status")}: + {previewFileDetail.status}
)} {previewFileDetail?.tags && (
- 标签: + + {t("dataManagement.labels.tags")}: + {(() => { let raw = previewFileDetail.tags as any; - if (!raw) return "-"; + if (!raw) return t("dataManagement.defaults.empty"); if (typeof raw === "string") { try { @@ -602,7 +625,9 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { } } - if (!Array.isArray(raw) || raw.length === 0) return "-"; + if (!Array.isArray(raw) || raw.length === 0) { + return t("dataManagement.defaults.empty"); + } const labels: string[] = []; raw.forEach((tag: any) => { @@ -621,7 +646,7 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { } }); - if (!labels.length) return "-"; + if (!labels.length) return t("dataManagement.defaults.empty"); return labels.join(", "); })()} @@ -629,36 +654,48 @@ export default function Overview({ dataset, filesOperation, fetchDataset }) { )} {previewFileDetail?.tagsUpdatedAt && (
- 标签更新时间: + + {t("dataManagement.labels.tagsUpdatedAt")}: + {formatDateTime(previewFileDetail.tagsUpdatedAt)}
)} {previewFileDetail?.uploadTime && (
- 上传时间: + + {t("dataManagement.labels.uploadTime")}: + {formatDateTime(previewFileDetail.uploadTime)}
)} {previewFileDetail?.uploadedBy && (
- 上传者: + + {t("dataManagement.labels.uploadedBy")}: + {previewFileDetail.uploadedBy}
)} {previewFileDetail?.filePath && (
- 文件路径: + + {t("dataManagement.labels.filePath")}: + {previewFileDetail.filePath}
)} {previewFileDetail?.description && (
- 描述: + + {t("dataManagement.labels.description")}: + {previewFileDetail.description}
)}
- 标注信息: + + {t("dataManagement.labels.annotation")}: + {(() => { const raw = (previewFileDetail?.annotation diff --git a/frontend/src/pages/DataManagement/Home/DataManagement.tsx b/frontend/src/pages/DataManagement/Home/DataManagement.tsx index 61c037f54..1b47b80c5 100644 --- a/frontend/src/pages/DataManagement/Home/DataManagement.tsx +++ b/frontend/src/pages/DataManagement/Home/DataManagement.tsx @@ -12,7 +12,7 @@ import { useEffect, useMemo, useState } from "react"; import { SearchControls } from "@/components/SearchControls"; import CardView from "@/components/CardView"; import type { Dataset } from "@/pages/DataManagement/dataset.model"; -import { datasetStatusMap, datasetTypeMap, mapDataset } from "../dataset.const"; +import { getDatasetStatusMap, getDatasetTypeMap, mapDataset } from "../dataset.const"; import useFetchData from "@/hooks/useFetchData"; import { downloadDatasetUsingGet, @@ -27,10 +27,14 @@ import { import { formatBytes } from "@/utils/unit"; import EditDataset from "../Create/EditDataset"; import ImportConfiguration from "../Detail/components/ImportConfiguration"; +import { useTranslation } from "react-i18next"; export default function DatasetManagementPage() { const navigate = useNavigate(); const { message } = App.useApp(); + const { t } = useTranslation(); + const datasetStatusMap = getDatasetStatusMap(t); + const datasetTypeMap = getDatasetTypeMap(t); const [viewMode, setViewMode] = useState<"card" | "list">("card"); const [editDatasetOpen, setEditDatasetOpen] = useState(false); const [currentDataset, setCurrentDataset] = useState(null); @@ -46,33 +50,33 @@ export default function DatasetManagementPage() { const statistics = { size: [ { - title: "数据集总数", + title: t("dataManagement.stats.totalDatasets"), value: data?.totalDatasets || 0, }, { - title: "文件总数", + title: t("dataManagement.stats.totalFiles"), value: data?.totalFiles || 0, }, { - title: "总大小", + title: t("dataManagement.stats.totalSize"), value: formatBytes(data?.totalSize) || '0 B', }, ], count: [ { - title: "文本", + title: t("dataManagement.stats.text"), value: data?.count?.text || 0, }, { - title: "图像", + title: t("dataManagement.stats.image"), value: data?.count?.image || 0, }, { - title: "音频", + title: t("dataManagement.stats.audio"), value: data?.count?.audio || 0, }, { - title: "视频", + title: t("dataManagement.stats.video"), value: data?.count?.video || 0, }, ], @@ -82,34 +86,26 @@ export default function DatasetManagementPage() { const [tags, setTags] = useState([]); - useEffect(() => { - const fetchTags = async () => { - const { data } = await queryDatasetTagsUsingGet(); - setTags(data.map((tag) => tag.name)); - }; - fetchTags(); - }, []); - const filterOptions = useMemo( () => [ { key: "type", - label: "类型", + label: t("dataManagement.filters.type"), options: [...Object.values(datasetTypeMap)], }, { key: "status", - label: "状态", + label: t("dataManagement.filters.status"), options: [...Object.values(datasetStatusMap)], }, { key: "tags", - label: "标签", + label: t("dataManagement.filters.tags"), mode: "multiple", options: tags.map((tag) => ({ label: tag, value: tag })), }, ], - [tags] + [tags, datasetStatusMap, datasetTypeMap, t] ); const { @@ -123,23 +119,33 @@ export default function DatasetManagementPage() { handleKeywordChange, } = useFetchData( queryDatasetsUsingGet, - mapDataset, + (dataset) => mapDataset(dataset, t), 30000, // 30秒轮询间隔 true, // 自动刷新 [fetchStatistics], // 额外的轮询函数 0 ); + useEffect(() => { + const fetchTags = async () => { + const { data } = await queryDatasetTagsUsingGet(); + setTags(data.map((tag) => tag.name)); + }; + fetchTags(); + fetchData(); + fetchStatistics(); + }, [t]); + const handleDownloadDataset = async (dataset: Dataset) => { - await downloadDatasetUsingGet(dataset.id, dataset.name); - message.success("数据集下载成功"); + await downloadDatasetUsingGet(dataset.id); + message.success(t("dataManagement.messages.downloadSuccess")); }; const handleDeleteDataset = async (id: number) => { if (!id) return; await deleteDatasetByIdUsingDelete(id); fetchData({ pageOffset: 0 }); - message.success("数据删除成功"); + message.success(t("dataManagement.messages.deleteSuccess")); }; const handleImportData = (dataset: Dataset) => { @@ -150,14 +156,14 @@ export default function DatasetManagementPage() { const handleRefresh = async (showMessage = true) => { await fetchData({ pageOffset: 0 }); if (showMessage) { - message.success("数据已刷新"); + message.success(t("dataManagement.messages.refreshSuccess")); } }; const operations = [ { key: "edit", - label: "编辑", + label: t("dataManagement.actions.edit"), icon: , onClick: (item: Dataset) => { setCurrentDataset(item); @@ -166,7 +172,7 @@ export default function DatasetManagementPage() { }, { key: "import", - label: "导入", + label: t("dataManagement.actions.import"), icon: , onClick: (item: Dataset) => { handleImportData(item); @@ -174,7 +180,7 @@ export default function DatasetManagementPage() { }, { key: "download", - label: "下载", + label: t("dataManagement.actions.download"), icon: , onClick: (item: Dataset) => { if (!item.id) return; @@ -183,13 +189,13 @@ export default function DatasetManagementPage() { }, { key: "delete", - label: "删除", + label: t("dataManagement.actions.delete"), danger: true, confirm: { - title: "确认删除该数据集?", - description: "删除后该数据集将无法恢复,请谨慎操作。", - okText: "删除", - cancelText: "取消", + title: t("dataManagement.confirm.deleteDatasetTitle"), + description: t("dataManagement.confirm.deleteDatasetDesc"), + okText: t("dataManagement.confirm.deleteConfirm"), + cancelText: t("dataManagement.confirm.deleteCancel"), okType: "danger", }, icon: , @@ -199,7 +205,7 @@ export default function DatasetManagementPage() { const columns = [ { - title: "名称", + title: t("dataManagement.columns.name"), dataIndex: "name", key: "name", fixed: "left", @@ -213,13 +219,13 @@ export default function DatasetManagementPage() { ), }, { - title: "类型", + title: t("dataManagement.columns.type"), dataIndex: "type", key: "type", width: 100, }, { - title: "状态", + title: t("dataManagement.columns.status"), dataIndex: "status", key: "status", render: (status: any) => { @@ -232,44 +238,38 @@ export default function DatasetManagementPage() { width: 120, }, { - title: "大小", + title: t("dataManagement.columns.size"), dataIndex: "size", key: "size", width: 120, }, { - title: "文件数", + title: t("dataManagement.columns.fileCount"), dataIndex: "fileCount", key: "fileCount", width: 100, }, - // { - // title: "创建者", - // dataIndex: "createdBy", - // key: "createdBy", - // width: 120, - // }, { - title: "存储路径", + title: t("dataManagement.columns.storagePath"), dataIndex: "targetLocation", key: "targetLocation", width: 200, ellipsis: true, }, { - title: "创建时间", + title: t("dataManagement.columns.createdAt"), dataIndex: "createdAt", key: "createdAt", width: 180, }, { - title: "更新时间", + title: t("dataManagement.columns.updatedAt"), dataIndex: "updatedAt", key: "updatedAt", width: 180, }, { - title: "操作", + title: t("dataManagement.columns.actions"), key: "actions", width: 200, fixed: "right", @@ -327,7 +327,7 @@ export default function DatasetManagementPage() {
{/* Header */}
-

数据管理

+

{t("dataManagement.title")}

{/* tasks */} } > - 创建数据集 + {t("dataManagement.actions.createDataset")}
@@ -364,7 +364,7 @@ export default function DatasetManagementPage() { setSearchParams({ ...searchParams, filter: {} })} diff --git a/frontend/src/pages/DataManagement/dataset.const.tsx b/frontend/src/pages/DataManagement/dataset.const.tsx index d5b70901e..ae7debbe2 100644 --- a/frontend/src/pages/DataManagement/dataset.const.tsx +++ b/frontend/src/pages/DataManagement/dataset.const.tsx @@ -29,7 +29,7 @@ import { ScanText, } from "lucide-react"; -export const datasetTypeMap: Record< +export function getDatasetTypeMap(t: (key: string) => string): Record< string, { value: DatasetType; @@ -40,50 +40,52 @@ export const datasetTypeMap: Record< iconColor?: string; children: DatasetSubType[]; } -> = { - [DatasetType.TEXT]: { - value: DatasetType.TEXT, - label: "文本", - order: 1, - icon: ScanText, - iconColor: "#A78BFA", - children: [ - DatasetSubType.TEXT_DOCUMENT, - DatasetSubType.TEXT_WEB, - DatasetSubType.TEXT_DIALOG, - ], - description: "用于处理和分析文本数据的数据集", - }, - [DatasetType.IMAGE]: { - value: DatasetType.IMAGE, - label: "图像", - order: 2, - icon: Image, - iconColor: "#38BDF8", - children: [DatasetSubType.IMAGE_IMAGE, DatasetSubType.IMAGE_CAPTION], - description: "用于处理和分析图像数据的数据集", - }, - [DatasetType.AUDIO]: { - value: DatasetType.AUDIO, - label: "音频", - order: 3, - icon: Music, - iconColor: "#F59E0B", - children: [DatasetSubType.AUDIO_AUDIO, DatasetSubType.AUDIO_JSONL], - description: "用于处理和分析音频数据的数据集", - }, - [DatasetType.VIDEO]: { - value: DatasetType.VIDEO, - label: "视频", - order: 3, - icon: Film, - iconColor: "#22D3EE", - children: [DatasetSubType.VIDEO_VIDEO, DatasetSubType.VIDEO_JSONL], - description: "用于处理和分析视频数据的数据集", - }, -}; +> { + return { + [DatasetType.TEXT]: { + value: DatasetType.TEXT, + label: t("dataManagement.datasetTypes.text"), + order: 1, + icon: ScanText, + iconColor: "#A78BFA", + children: [ + DatasetSubType.TEXT_DOCUMENT, + DatasetSubType.TEXT_WEB, + DatasetSubType.TEXT_DIALOG, + ], + description: t("dataManagement.datasetTypeDesc.text"), + }, + [DatasetType.IMAGE]: { + value: DatasetType.IMAGE, + label: t("dataManagement.datasetTypes.image"), + order: 2, + icon: Image, + iconColor: "#38BDF8", + children: [DatasetSubType.IMAGE_IMAGE, DatasetSubType.IMAGE_CAPTION], + description: t("dataManagement.datasetTypeDesc.image"), + }, + [DatasetType.AUDIO]: { + value: DatasetType.AUDIO, + label: t("dataManagement.datasetTypes.audio"), + order: 3, + icon: Music, + iconColor: "#F59E0B", + children: [DatasetSubType.AUDIO_AUDIO, DatasetSubType.AUDIO_JSONL], + description: t("dataManagement.datasetTypeDesc.audio"), + }, + [DatasetType.VIDEO]: { + value: DatasetType.VIDEO, + label: t("dataManagement.datasetTypes.video"), + order: 3, + icon: Film, + iconColor: "#22D3EE", + children: [DatasetSubType.VIDEO_VIDEO, DatasetSubType.VIDEO_JSONL], + description: t("dataManagement.datasetTypeDesc.video"), + }, + }; +} -export const datasetSubTypeMap: Record< +export function getDatasetSubTypeMap(t: (key: string) => string): Record< string, { value: DatasetSubType; @@ -93,133 +95,153 @@ export const datasetSubTypeMap: Record< icon?: any; color?: string; } -> = { - [DatasetSubType.TEXT_DOCUMENT]: { - value: DatasetSubType.TEXT_DOCUMENT, - label: "文档", - color: "blue", - icon: FileText, - description: "用于存储和处理各种文档格式的文本数据集", - }, - [DatasetSubType.TEXT_WEB]: { - value: DatasetSubType.TEXT_WEB, - label: "网页", - color: "cyan", - icon: FileCode, - description: "用于存储和处理网页数据集", - }, - [DatasetSubType.TEXT_DIALOG]: { - value: DatasetSubType.TEXT_DIALOG, - label: "对话", - color: "teal", - icon: MessageCircleMore, - description: "用于存储和处理对话数据的数据集", - }, - [DatasetSubType.IMAGE_IMAGE]: { - value: DatasetSubType.IMAGE_IMAGE, - label: "图像", - color: "green", - icon: FileImage, - description: "用于大规模图像预训练模型的数据集", - }, - [DatasetSubType.IMAGE_CAPTION]: { - value: DatasetSubType.IMAGE_CAPTION, - label: "图像+caption", - color: "lightgreen", - icon: ImagePlus, - description: "用于图像标题生成的数据集", - }, - [DatasetSubType.AUDIO_AUDIO]: { - value: DatasetSubType.AUDIO_AUDIO, - label: "音频", - color: "purple", - icon: Music, - description: "用于大规模音频预训练模型的数据集", - }, - [DatasetSubType.AUDIO_JSONL]: { - value: DatasetSubType.AUDIO_JSONL, - label: "音频+JSONL", - color: "purple", - icon: FileMusic, - description: "用于大规模音频预训练模型的数据集", - }, - [DatasetSubType.VIDEO_VIDEO]: { - value: DatasetSubType.VIDEO_VIDEO, - label: "视频", - color: "orange", - icon: Video, - description: "用于大规模视频预训练模型的数据集", - }, - [DatasetSubType.VIDEO_JSONL]: { - value: DatasetSubType.VIDEO_JSONL, - label: "视频+JSONL", - color: "orange", - icon: Videotape, - description: "用于大规模视频预训练模型的数据集", - }, -}; +> { + return { + [DatasetSubType.TEXT_DOCUMENT]: { + value: DatasetSubType.TEXT_DOCUMENT, + label: t("dataManagement.datasetSubTypes.textDocument"), + color: "blue", + icon: FileText, + description: t("dataManagement.datasetSubTypeDesc.textDocument"), + }, + [DatasetSubType.TEXT_WEB]: { + value: DatasetSubType.TEXT_WEB, + label: t("dataManagement.datasetSubTypes.textWeb"), + color: "cyan", + icon: FileCode, + description: t("dataManagement.datasetSubTypeDesc.textWeb"), + }, + [DatasetSubType.TEXT_DIALOG]: { + value: DatasetSubType.TEXT_DIALOG, + label: t("dataManagement.datasetSubTypes.textDialog"), + color: "teal", + icon: MessageCircleMore, + description: t("dataManagement.datasetSubTypeDesc.textDialog"), + }, + [DatasetSubType.IMAGE_IMAGE]: { + value: DatasetSubType.IMAGE_IMAGE, + label: t("dataManagement.datasetSubTypes.imageImage"), + color: "green", + icon: FileImage, + description: t("dataManagement.datasetSubTypeDesc.imageImage"), + }, + [DatasetSubType.IMAGE_CAPTION]: { + value: DatasetSubType.IMAGE_CAPTION, + label: t("dataManagement.datasetSubTypes.imageCaption"), + color: "lightgreen", + icon: ImagePlus, + description: t("dataManagement.datasetSubTypeDesc.imageCaption"), + }, + [DatasetSubType.AUDIO_AUDIO]: { + value: DatasetSubType.AUDIO_AUDIO, + label: t("dataManagement.datasetSubTypes.audioAudio"), + color: "purple", + icon: Music, + description: t("dataManagement.datasetSubTypeDesc.audioAudio"), + }, + [DatasetSubType.AUDIO_JSONL]: { + value: DatasetSubType.AUDIO_JSONL, + label: t("dataManagement.datasetSubTypes.audioJsonl"), + color: "purple", + icon: FileMusic, + description: t("dataManagement.datasetSubTypeDesc.audioJsonl"), + }, + [DatasetSubType.VIDEO_VIDEO]: { + value: DatasetSubType.VIDEO_VIDEO, + label: t("dataManagement.datasetSubTypes.videoVideo"), + color: "orange", + icon: Video, + description: t("dataManagement.datasetSubTypeDesc.videoVideo"), + }, + [DatasetSubType.VIDEO_JSONL]: { + value: DatasetSubType.VIDEO_JSONL, + label: t("dataManagement.datasetSubTypes.videoJsonl"), + color: "orange", + icon: Videotape, + description: t("dataManagement.datasetSubTypeDesc.videoJsonl"), + }, + }; +} -export const datasetStatusMap = { - [DatasetStatus.ACTIVE]: { - label: "活跃", - value: DatasetStatus.ACTIVE, - color: "#409f17ff", - icon: , - }, - [DatasetStatus.PROCESSING]: { - label: "处理中", - value: DatasetStatus.PROCESSING, - color: "#2673e5", - icon: , - }, - [DatasetStatus.INACTIVE]: { - label: "未激活", - value: DatasetStatus.INACTIVE, - color: "#4f4444ff", - icon: , - }, - [DatasetStatus.DRAFT]: { - label: "草稿", - value: DatasetStatus.DRAFT, - color: "#a1a1a1ff", - icon: , - }, -}; +export function getDatasetStatusMap(t: (key: string) => string) { + return { + [DatasetStatus.ACTIVE]: { + label: t("dataManagement.datasetStatus.active"), + value: DatasetStatus.ACTIVE, + color: "#409f17ff", + icon: , + }, + [DatasetStatus.PROCESSING]: { + label: t("dataManagement.datasetStatus.processing"), + value: DatasetStatus.PROCESSING, + color: "#2673e5", + icon: , + }, + [DatasetStatus.INACTIVE]: { + label: t("dataManagement.datasetStatus.inactive"), + value: DatasetStatus.INACTIVE, + color: "#4f4444ff", + icon: , + }, + [DatasetStatus.DRAFT]: { + label: t("dataManagement.datasetStatus.draft"), + value: DatasetStatus.DRAFT, + color: "#a1a1a1ff", + icon: , + }, + }; +} -export const dataSourceMap: Record = { - [DataSource.UPLOAD]: { label: "本地上传", value: DataSource.UPLOAD }, - [DataSource.COLLECTION]: { label: "归集任务导入 ", value: DataSource.COLLECTION }, - // [DataSource.DATABASE]: { label: "数据库导入", value: DataSource.DATABASE }, - // [DataSource.NAS]: { label: "NAS导入", value: DataSource.NAS }, - // [DataSource.OBS]: { label: "OBS导入", value: DataSource.OBS }, -}; +export function getDataSourceMap(t: (key: string) => string): Record< + string, + { label: string; value: string } +> { + return { + [DataSource.UPLOAD]: { + label: t("dataManagement.dataSources.upload"), + value: DataSource.UPLOAD, + }, + [DataSource.COLLECTION]: { + label: t("dataManagement.dataSources.collection"), + value: DataSource.COLLECTION, + }, + }; +} -export const dataSourceOptions = Object.values(dataSourceMap); +export function getDataSourceOptions(t: (key: string) => string) { + return Object.values(getDataSourceMap(t)); +} -export function mapDataset(dataset: AnyObject): Dataset { +export function mapDataset(dataset: AnyObject, t: (key: string) => string): Dataset { + const datasetTypeMap = getDatasetTypeMap(t); + const datasetStatusMap = getDatasetStatusMap(t); const { icon: IconComponent, iconColor } = datasetTypeMap[dataset?.datasetType] || {}; return { ...dataset, key: dataset.id, - type: datasetTypeMap[dataset.datasetType]?.label || "未知", + type: datasetTypeMap[dataset.datasetType]?.label || t("dataManagement.defaults.unknown"), size: formatBytes(dataset.totalSize || 0), - createdAt: formatDateTime(dataset.createdAt) || "--", - updatedAt: formatDateTime(dataset?.updatedAt) || "--", + createdAt: formatDateTime(dataset.createdAt) || t("dataManagement.defaults.empty"), + updatedAt: formatDateTime(dataset?.updatedAt) || t("dataManagement.defaults.empty"), icon: IconComponent ? : , iconColor: iconColor, status: datasetStatusMap[dataset.status], statistics: [ - { label: "文件数", value: dataset.fileCount || 0 }, - { label: "大小", value: formatBytes(dataset.totalSize || 0) }, + { label: t("dataManagement.labels.fileCount"), value: dataset.fileCount || 0 }, + { label: t("dataManagement.labels.dataSize"), value: formatBytes(dataset.totalSize || 0) }, ], lastModified: dataset.updatedAt, }; } -export const datasetTypes = Object.values(datasetTypeMap).map((type) => ({ - ...type, - options: type.children?.map( - (subType) => datasetSubTypeMap[subType as keyof typeof datasetSubTypeMap] - ), -})); +export function getDatasetTypes(t: (key: string) => string) { + const datasetTypeMap = getDatasetTypeMap(t); + const datasetSubTypeMap = getDatasetSubTypeMap(t); + return Object.values(datasetTypeMap).map((type) => ({ + ...type, + options: type.children?.map( + (subType) => datasetSubTypeMap[subType as keyof typeof datasetSubTypeMap] + ), + })); +}