Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -402,13 +402,23 @@ private void addFileToDataset(String datasetId, List<FileUploadResult> unpacked)
for (FileUploadResult file : unpacked) {
File savedFile = file.getSavedFile();
LocalDateTime currentTime = LocalDateTime.now();
// 统一 fileName:无论是否通过文件夹/压缩包上传,都只保留纯文件名
String originalFileName = file.getFileName();
String baseFileName = originalFileName;
if (originalFileName != null) {
String normalized = originalFileName.replace("\\", "/");
int lastSlash = normalized.lastIndexOf('/');
if (lastSlash >= 0 && lastSlash + 1 < normalized.length()) {
baseFileName = normalized.substring(lastSlash + 1);
}
}
DatasetFile datasetFile = DatasetFile.builder()
.id(UUID.randomUUID().toString())
.datasetId(datasetId)
.fileSize(savedFile.length())
.uploadTime(currentTime)
.lastAccessTime(currentTime)
.fileName(file.getFileName())
.fileName(baseFileName)
.filePath(savedFile.getPath())
.fileType(AnalyzerUtils.getExtension(file.getFileName()))
.build();
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/components/business/DatasetFileTransfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ interface DatasetFileTransferProps
onSelectedFilesChange: (filesMap: { [key: string]: DatasetFile }) => void;
onDatasetSelect?: (dataset: Dataset | null) => void;
datasetTypeFilter?: DatasetType;
/**
* 锁定的文件ID集合:
* - 在左侧文件列表中,这些文件的勾选框会变成灰色且不可交互;
* - 点击整行也不会改变其选中状态;
* - 主要用于“编辑任务数据集”场景下锁死任务初始文件。
*/
lockedFileIds?: string[];
}

const fileCols = [
Expand Down Expand Up @@ -52,6 +59,7 @@ const DatasetFileTransfer: React.FC<DatasetFileTransferProps> = ({
onSelectedFilesChange,
onDatasetSelect,
datasetTypeFilter,
lockedFileIds,
...props
}) => {
const [datasets, setDatasets] = React.useState<Dataset[]>([]);
Expand Down Expand Up @@ -79,6 +87,10 @@ const DatasetFileTransfer: React.FC<DatasetFileTransferProps> = ({
);
const [selectingAll, setSelectingAll] = React.useState<boolean>(false);

const lockedIdSet = React.useMemo(() => {
return new Set((lockedFileIds || []).map((id) => String(id)));
}, [lockedFileIds]);

const fetchDatasets = async () => {
const { data } = await queryDatasetsUsingGet({
// Ant Design Table pagination.current is 1-based; ensure backend also receives 1-based value
Expand Down Expand Up @@ -230,6 +242,10 @@ const DatasetFileTransfer: React.FC<DatasetFileTransferProps> = ({
}, [selectedDataset, selectedFilesMap, onSelectedFilesChange]);

const toggleSelectFile = (record: DatasetFile) => {
// 被锁定的文件不允许在此组件中被增删
if (lockedIdSet.has(String(record.id))) {
return;
}
if (!selectedFilesMap[record.id]) {
onSelectedFilesChange({
...selectedFilesMap,
Expand Down Expand Up @@ -421,6 +437,7 @@ const DatasetFileTransfer: React.FC<DatasetFileTransferProps> = ({

getCheckboxProps: (record: DatasetFile) => ({
name: record.fileName,
disabled: lockedIdSet.has(String(record.id)),
}),
}}
/>
Expand Down
145 changes: 115 additions & 30 deletions frontend/src/pages/DataAnnotation/AutoAnnotation/AutoAnnotation.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { useState, useEffect } from "react";
import { Card, Button, Table, message, Modal, Tag, Progress, Space, Tooltip } from "antd";
import { Card, Button, Table, message, Modal, Tag, Progress, Space, Tooltip, Dropdown } from "antd";
import {
PlusOutlined,
DeleteOutlined,
DownloadOutlined,
ReloadOutlined,
EyeOutlined,
SyncOutlined,
EditOutlined,
MoreOutlined,
SettingOutlined,
ExportOutlined,
ImportOutlined,
} from "@ant-design/icons";
import type { ColumnType } from "antd/es/table";
import type { AutoAnnotationTask, AutoAnnotationStatus } from "../annotation.model";
Expand All @@ -19,6 +22,8 @@ import {
syncAutoAnnotationTaskToLabelStudioUsingPost,
} from "../annotation.api";
import CreateAutoAnnotationDialog from "./components/CreateAutoAnnotationDialog";
import EditAutoAnnotationDatasetDialog from "./components/EditAutoAnnotationDatasetDialog";
import ImportFromLabelStudioDialog from "./components/ImportFromLabelStudioDialog";

const STATUS_COLORS: Record<AutoAnnotationStatus, string> = {
pending: "default",
Expand Down Expand Up @@ -51,6 +56,10 @@ export default function AutoAnnotation() {
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
const [labelStudioBase, setLabelStudioBase] = useState<string | null>(null);
const [datasetProjectMap, setDatasetProjectMap] = useState<Record<string, string>>({});
const [editingTask, setEditingTask] = useState<AutoAnnotationTask | null>(null);
const [showEditDatasetDialog, setShowEditDatasetDialog] = useState(false);
const [importingTask, setImportingTask] = useState<AutoAnnotationTask | null>(null);
const [showImportDialog, setShowImportDialog] = useState(false);

useEffect(() => {
fetchTasks();
Expand Down Expand Up @@ -106,6 +115,16 @@ export default function AutoAnnotation() {
}
};

const handleEditTaskDataset = (task: AutoAnnotationTask) => {
setEditingTask(task);
setShowEditDatasetDialog(true);
};

const handleImportFromLabelStudio = (task: AutoAnnotationTask) => {
setImportingTask(task);
setShowImportDialog(true);
};

const handleDelete = (task: AutoAnnotationTask) => {
Modal.confirm({
title: `确认删除自动标注任务「${task.name}」吗?`,
Expand Down Expand Up @@ -303,55 +322,90 @@ export default function AutoAnnotation() {
{
title: "操作",
key: "actions",
width: 260,
width: 320,
fixed: "right",
render: (_: any, record: AutoAnnotationTask) => (
<Space size="small">
{/* 一级功能菜单:前向同步 + 编辑(跳转 Label Studio) */}
<Tooltip title="将 YOLO 预测结果前向同步到 Label Studio">
<Button
type="link"
size="small"
icon={<ExportOutlined />}
onClick={() => handleSyncToLabelStudio(record)}
>
前向同步
</Button>
</Tooltip>
<Tooltip title="在 Label Studio 中手动标注">
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => handleAnnotate(record)}
>
编辑
</Button>
</Tooltip>
<Tooltip title="从 Label Studio 导回标注结果到数据集">
<Button
type="link"
size="small"
icon={<ImportOutlined />}
onClick={() => handleImportFromLabelStudio(record)}
>
后向同步
</Button>
</Tooltip>

{/* 已完成任务的查看/下载结果仍保留 */}
{record.status === "completed" && (
<>
<Tooltip title="查看结果">
<Tooltip title="查看结果信息">
<Button
type="link"
size="small"
icon={<EyeOutlined />}
onClick={() => handleViewResult(record)}
/>
</Tooltip>
<Tooltip title="下载结果">
<Tooltip title="下载标注结果 ZIP">
<Button
type="link"
size="small"
icon={<DownloadOutlined />}
onClick={() => handleDownload(record)}
/>
</Tooltip>
<Tooltip title="同步到 Label Studio">
<Button
type="link"
size="small"
icon={<SyncOutlined />}
onClick={() => handleSyncToLabelStudio(record)}
/>
</Tooltip>
<Tooltip title="在 Label Studio 中标注">
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => handleAnnotate(record)}
/>
</Tooltip>
</>
)}
<Tooltip title="删除任务记录">
<Button
type="link"
size="small"
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(record)}
/>
</Tooltip>

{/* 二级功能菜单:折叠的删除任务 + 编辑任务数据集 */}
<Dropdown
menu={{
items: [
{
key: "edit-dataset",
label: "编辑任务数据集",
icon: <SettingOutlined />,
onClick: () => handleEditTaskDataset(record),
},
{
key: "delete",
label: "删除任务",
icon: <DeleteOutlined />,
danger: true,
onClick: () => handleDelete(record),
},
],
}}
trigger={["click"]}
>
<Button type="link" size="small" icon={<MoreOutlined />}
>
更多
</Button>
</Dropdown>
</Space>
),
},
Expand Down Expand Up @@ -402,6 +456,37 @@ export default function AutoAnnotation() {
fetchTasks();
}}
/>

{editingTask && (
<EditAutoAnnotationDatasetDialog
visible={showEditDatasetDialog}
task={editingTask}
onCancel={() => {
setShowEditDatasetDialog(false);
setEditingTask(null);
}}
onSuccess={() => {
setShowEditDatasetDialog(false);
setEditingTask(null);
fetchTasks();
}}
/>
)}

{importingTask && (
<ImportFromLabelStudioDialog
visible={showImportDialog}
task={importingTask}
onCancel={() => {
setShowImportDialog(false);
setImportingTask(null);
}}
onSuccess={() => {
setShowImportDialog(false);
setImportingTask(null);
}}
/>
)}
</div>
);
}
Loading
Loading