diff --git a/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx b/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx index 3cc42da8c..36cdafa02 100644 --- a/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx +++ b/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx @@ -1,968 +1,968 @@ -import { useState, useEffect } from "react"; -import { useNavigate } from "react-router"; -import { Card, Button, Table, message, Modal, Tabs, Tag, Progress, Tooltip, Dropdown } from "antd"; -import { - PlusOutlined, - EditOutlined, - DeleteOutlined, - SyncOutlined, - MoreOutlined, - SettingOutlined, - ExportOutlined, - ImportOutlined, -} from "@ant-design/icons"; -import { SearchControls } from "@/components/SearchControls"; -import CardView from "@/components/CardView"; -import type { AnnotationTask } from "../annotation.model"; -import useFetchData from "@/hooks/useFetchData"; -import { - deleteAnnotationTaskByIdUsingDelete, - queryAnnotationTasksUsingGet, - syncAnnotationTaskUsingPost, - // 自动标注API已屏蔽(保留代码在注释中) - // queryAutoAnnotationTasksUsingGet, - // deleteAutoAnnotationTaskByIdUsingDelete, - // getAutoAnnotationLabelStudioProjectUsingGet, - loginAnnotationUsingGet, - syncManualAnnotationToDatabaseUsingPost, - // syncAutoAnnotationToDatabaseUsingPost, -} from "../annotation.api"; -import { mapAnnotationTask } from "../annotation.const"; -import CreateAnnotationTask from "../Create/components/CreateAnnotationTaskDialog"; -import { ColumnType } from "antd/es/table"; -import { TemplateList } from "../Template"; -// 自动标注组件导入已屏蔽(保留代码在注释中) -// import EditAutoAnnotationDatasetDialog from "../AutoAnnotation/components/EditAutoAnnotationDatasetDialog"; -// import ImportFromLabelStudioDialog from "../AutoAnnotation/components/ImportFromLabelStudioDialog"; -import ManualImportFromLabelStudioDialog from "../ManualImportFromLabelStudioDialog"; -import EditManualAnnotationDatasetDialog from "../EditManualAnnotationDatasetDialog"; -import { useTranslation } from "react-i18next"; -// Note: DevelopmentInProgress intentionally not used here - -export default function DataAnnotation() { - const { t } = useTranslation(); - const navigate = useNavigate(); - // return ; - const [activeTab, setActiveTab] = useState("tasks"); - const [viewMode, setViewMode] = useState<"list" | "card">("list"); - const [showCreateDialog, setShowCreateDialog] = useState(false); - // 自动标注任务state已屏蔽(保留代码在注释中) - // const [autoTasks, setAutoTasks] = useState([]); - - const { - loading, - tableData, - pagination, - searchParams, - fetchData, - handleFiltersChange, - handleKeywordChange, - } = useFetchData(queryAnnotationTasksUsingGet, (item) => mapAnnotationTask(item, t), 30000, true, [], 0); - - const [labelStudioBase, setLabelStudioBase] = useState(null); - const [selectedRowKeys, setSelectedRowKeys] = useState<(string | number)[]>([]); - const [selectedRows, setSelectedRows] = useState([]); - const [datasetProjectMap, setDatasetProjectMap] = useState>({}); - // 自动标注相关state已屏蔽(保留代码在注释中) - // const [editingAutoTask, setEditingAutoTask] = useState(null); - // const [showEditAutoDatasetDialog, setShowEditAutoDatasetDialog] = useState(false); - const [editingManualTask, setEditingManualTask] = useState(null); - const [showEditManualDatasetDialog, setShowEditManualDatasetDialog] = useState(false); - // 自动标注相关state已屏蔽(保留代码在注释中) - // const [importingAutoTask, setImportingAutoTask] = useState(null); - // const [showImportAutoDialog, setShowImportAutoDialog] = useState(false); - const [importingManualTask, setImportingManualTask] = useState(null); - const [showImportManualDialog, setShowImportManualDialog] = useState(false); - - // 自动标注任务拉取函数已屏蔽(保留代码在注释中) - // const refreshAutoTasks = async (silent = false) => { - // try { - // const response = await queryAutoAnnotationTasksUsingGet(); - // const tasks = (response as any)?.data || response || []; - // if (Array.isArray(tasks)) { - // setAutoTasks(tasks); - // } - // } catch (error) { - // console.error("Failed to fetch auto annotation tasks:", error); - // if (!silent) { - // message.error(t('dataAnnotation.home.messages.fetchAutoTasksFailed')); - // } - // } - // }; - - // prefetch config on mount so clicking annotate is fast and we know whether base URL exists - // useEffect ensures this runs once - useEffect(() => { - let mounted = true; - (async () => { - try { - const baseUrl = `http://${window.location.hostname}:${parseInt(window.location.port) + 1}`; - if (mounted) setLabelStudioBase(baseUrl); - } catch (e) { - if (mounted) setLabelStudioBase(null); - } - })(); - return () => { - mounted = false; - }; - }, []); - - // 基于手动标注任务构建 datasetId -> Label Studio project 映射,供自动标注跳转使用 - useEffect(() => { - const map: Record = {}; - (tableData as any[]).forEach((task: any) => { - const datasetId = task.datasetId || task.dataset_id; - const projId = task.labelingProjId || task.projId || task.labeling_project_id; - if (datasetId && projId) { - map[String(datasetId)] = String(projId); - } - }); - setDatasetProjectMap(map); - }, [tableData]); - - // 自动标注任务轮询已屏蔽(保留代码在注释中) - // useEffect(() => { - // refreshAutoTasks(); - // const timer = setInterval(() => refreshAutoTasks(true), 3000); - // return () => { - // clearInterval(timer); - // }; - // }, []); - - const handleAnnotate = (task: AnnotationTask) => { - // Open Label Studio project page in a new tab - (async () => { - try { - // prefer using labeling project id already present on the task - // `mapAnnotationTask` normalizes upstream fields into `labelingProjId`/`projId`, - // so prefer those and fall back to the task id if necessary. - let labelingProjId = (task as any).labelingProjId || (task as any).projId || undefined; - - // no fallback external mapping lookup; rely on normalized fields from mapAnnotationTask - - // use prefetched base if available - const base = labelStudioBase; - - // no debug logging in production - await loginAnnotationUsingGet(labelingProjId) - if (labelingProjId) { - // only open external Label Studio when we have a configured base url - if (base) { - const target = `${base}/projects/${labelingProjId}/data`; - window.open(target, "_blank"); - } else { - // no external Label Studio URL configured — do not perform internal redirect in this version - message.error(t('dataAnnotation.home.messages.cannotJumpNoBase')); - return; - } - } else { - // no labeling project id available — do not attempt internal redirect in this version - message.error(t('dataAnnotation.home.messages.cannotJumpNoMapping')); - return; - } - } catch (error) { - // on error, surface a user-friendly message instead of redirecting - message.error(t('dataAnnotation.home.messages.cannotJumpError')); - return; - } - })(); - }; - - const handleDelete = (task: AnnotationTask) => { - Modal.confirm({ - title: t('dataAnnotation.home.confirm.deleteTaskTitle', { name: task.name }), - content: ( -
-
{t('dataAnnotation.home.confirm.deleteTaskContent1')}
-
{t('dataAnnotation.home.confirm.deleteTaskContent2')}
-
- ), - okText: t('dataAnnotation.home.confirm.deleteOkText'), - okType: "danger", - cancelText: t('dataAnnotation.home.confirm.deleteCancelText'), - onOk: async () => { - try { - await deleteAnnotationTaskByIdUsingDelete(task.id); - message.success(t('dataAnnotation.home.messages.deleteSuccess')); - fetchData(); - // clear selection if deleted item was selected - setSelectedRowKeys((keys) => keys.filter((k) => k !== task.id)); - setSelectedRows((rows) => rows.filter((r) => r.id !== task.id)); - } catch (e) { - console.error(e); - message.error(t('dataAnnotation.home.messages.deleteFailed')); - } - }, - }); - }; - - // 自动标注删除函数已屏蔽(保留代码在注释中) - // const handleDeleteAuto = (task: any) => { - // Modal.confirm({ - // title: t('dataAnnotation.home.confirm.deleteAutoTaskTitle', { name: task.name }), - // content:
{t('dataAnnotation.home.confirm.deleteAutoTaskContent')}
, - // okText: t('dataAnnotation.home.confirm.deleteOkText'), - // okType: "danger", - // cancelText: t('dataAnnotation.home.confirm.deleteCancelText'), - // onOk: async () => { - // try { - // await deleteAutoAnnotationTaskByIdUsingDelete(task.id); - // message.success(t('dataAnnotation.home.messages.autoTaskDeleteSuccess')); - // setAutoTasks((prev) => prev.filter((t: any) => t.id !== task.id)); - // setSelectedRowKeys((keys) => keys.filter((k) => k !== task.id)); - // setSelectedRows((rows) => rows.filter((r) => r.id !== task.id)); - // } catch (e) { - // console.error(e); - // message.error(t('dataAnnotation.home.messages.deleteFailed')); - // } - // }, - // }); - // }; - - // 自动标注编辑数据集函数已屏蔽(保留代码在注释中) - // const handleEditAutoTaskDataset = (row: any) => { - // if (!row?.id) { - // message.error(t('dataAnnotation.home.messages.autoTaskNotFound')); - // return; - // } - // const full = autoTasks.find((t: any) => t.id === row.id); - // if (!full) { - // message.error(t('dataAnnotation.home.messages.autoTaskNotFound') + t('dataAnnotation.home.messages.deleteFailed').split(',')[1]); - // return; - // } - // setEditingAutoTask(full); - // setShowEditAutoDatasetDialog(true); - // }; - - const handleEditManualTaskDataset = (task: AnnotationTask) => { - if (!task?.id) { - message.error(t('dataAnnotation.home.messages.taskNotFound')); - return; - } - setEditingManualTask(task); - setShowEditManualDatasetDialog(true); - }; - - const handleImportManualFromLabelStudio = (task: AnnotationTask) => { - if (!task?.id) { - message.error(t('dataAnnotation.home.messages.taskNotFound')); - return; - } - - setImportingManualTask(task); - setShowImportManualDialog(true); - }; - - const handleSyncManualToDatabase = async (task: AnnotationTask) => { - if (!task?.id) { - message.error(t('dataAnnotation.home.messages.taskNotFound')); - return; - } - - Modal.confirm({ - title: t('dataAnnotation.home.messages.syncToDbTitle', { name: task.name }), - content: ( -
-
{t('dataAnnotation.home.messages.syncToDbContent1')}
-
{t('dataAnnotation.home.messages.syncToDbContent2')}
-
- ), - okText: t('dataAnnotation.home.actions.syncToDb'), - cancelText: t('dataAnnotation.home.confirm.deleteCancelText'), - onOk: async () => { - const hide = message.loading(t('dataAnnotation.dialogs.syncToDb.loading'), 0); - try { - await syncManualAnnotationToDatabaseUsingPost(task.id as any); - hide(); - message.success(t('dataAnnotation.dialogs.syncToDb.success')); - } catch (e) { - console.error(e); - hide(); - message.error(t('dataAnnotation.dialogs.syncToDb.fail')); - } - }, - }); - }; - - // 自动标注导入函数已屏蔽(保留代码在注释中) - // const handleImportAutoFromLabelStudio = (row: any) => { - // if (!row?.id) { - // message.error(t('dataAnnotation.home.messages.autoTaskNotFound')); - // return; - // } - // const full = autoTasks.find((t: any) => t.id === row.id); - // if (!full) { - // message.error(t('dataAnnotation.home.messages.autoTaskNotFound') + t('dataAnnotation.home.messages.deleteFailed').split(',')[1]); - // return; - // } - // setImportingAutoTask(full); - // setShowImportAutoDialog(true); - // }; - - // 自动标注同步到数据库函数已屏蔽(保留代码在注释中) - // const handleSyncAutoToDatabase = (row: any) => { - // if (!row?.id) { - // message.error(t('dataAnnotation.home.messages.autoTaskNotFound')); - // return; - // } - // Modal.confirm({ - // title: t('dataAnnotation.home.messages.syncToDbTitle', { name: row.name }), - // content: ( - //
- //
{t('dataAnnotation.home.messages.syncToDbContent1')}
- //
{t('dataAnnotation.home.messages.syncToDbContent2')}
- //
- // ), - // okText: t('dataAnnotation.home.actions.syncToDb'), - // cancelText: t('dataAnnotation.home.confirm.deleteCancelText'), - // onOk: async () => { - // const hide = message.loading(t('dataAnnotation.dialogs.syncToDb.loading'), 0); - // try { - // await syncAutoAnnotationToDatabaseUsingPost(row.id); - // hide(); - // message.success(t('dataAnnotation.dialogs.syncToDb.success')); - // } catch (e) { - // console.error(e); - // hide(); - // message.error(t('dataAnnotation.dialogs.syncToDb.fail')); - // } - // }, - // }); - // }; - - // 自动标注跳转标注函数已屏蔽(保留代码在注释中) - // const handleAnnotateAuto = (task: any) => { - // (async () => { - // try { - // if (!labelStudioBase) { - // message.error(t('dataAnnotation.home.messages.cannotJumpNoBase')); - // return; - // } - // let projId: string | undefined; - // try { - // const resp = await getAutoAnnotationLabelStudioProjectUsingGet(task.id); - // const data = (resp as any)?.data ?? resp; - // projId = data?.projectId || data?.labeling_project_id; - // } catch (e) { - // console.error("Failed to resolve LS project for auto task", e); - // } - // if (!projId) { - // const datasetId = task.datasetId; - // if (!datasetId) { - // message.error(t('dataAnnotation.home.messages.cannotJumpAutoNoDataset')); - // return; - // } - // projId = datasetProjectMap[String(datasetId)]; - // } - // if (!projId) { - // message.error(t('dataAnnotation.home.messages.cannotJumpAutoNoProject')); - // return; - // } - // const target = `${labelStudioBase}/projects/${projId}/data`; - // window.open(target, "_blank"); - // } catch (error) { - // console.error(error); - // message.error(t('dataAnnotation.home.messages.cannotJumpError')); - // } - // })(); - // }; - - const handleSync = (task: AnnotationTask, batchSize: number = 50) => { - Modal.confirm({ - title: t('dataAnnotation.home.confirm.syncTitle', { name: task.name }), - content: ( -
-
{t('dataAnnotation.home.confirm.syncContent1')}
-
{t('dataAnnotation.home.confirm.syncContent2')}
-
- ), - okText: t('dataAnnotation.home.confirm.syncOkText'), - cancelText: t('dataAnnotation.home.confirm.syncCancelText'), - onOk: async () => { - try { - await syncAnnotationTaskUsingPost({ id: task.id, batchSize }); - message.success(t('dataAnnotation.home.messages.syncRequestSent')); - // optional: refresh list/status - fetchData(); - // clear selection for the task - setSelectedRowKeys((keys) => keys.filter((k) => k !== task.id)); - setSelectedRows((rows) => rows.filter((r) => r.id !== task.id)); - } catch (e) { - console.error(e); - message.error(t('dataAnnotation.home.messages.syncFailed')); - } - }, - }); - }; - - const handleBatchSync = (batchSize: number = 50) => { - if (!selectedRows || selectedRows.length === 0) return; - const manualRows = selectedRows.filter((r) => r._kind !== "auto"); - if (manualRows.length === 0) { - message.warning(t('dataAnnotation.home.messages.batchSyncManualOnly')); - return; - } - Modal.confirm({ - title: t('dataAnnotation.home.confirm.batchSyncTitle', { count: manualRows.length }), - content: ( -
-
{t('dataAnnotation.home.confirm.syncContent1')}
-
{t('dataAnnotation.home.confirm.syncContent2')}
-
- ), - okText: t('dataAnnotation.home.confirm.syncOkText'), - cancelText: t('dataAnnotation.home.confirm.syncCancelText'), - onOk: async () => { - try { - await Promise.all( - manualRows.map((r) => syncAnnotationTaskUsingPost({ id: r.id, batchSize })) - ); - message.success(t('dataAnnotation.home.messages.batchSyncSuccess')); - fetchData(); - setSelectedRowKeys([]); - setSelectedRows([]); - } catch (e) { - console.error(e); - message.error(t('dataAnnotation.home.messages.batchSyncFailed')); - } - }, - }); - }; - - const handleBatchDelete = () => { - if (!selectedRows || selectedRows.length === 0) return; - const manualRows = selectedRows.filter((r) => r._kind !== "auto"); - // 自动标注批量删除已屏蔽 - // const autoRows = selectedRows.filter((r) => r._kind === "auto"); - Modal.confirm({ - title: t('dataAnnotation.home.confirm.batchDeleteTitle', { count: selectedRows.length }), - content: ( -
-
{t('dataAnnotation.home.confirm.deleteTaskContent1')}
-
{t('dataAnnotation.home.confirm.deleteTaskContent2')}
-
- ), - okText: t('dataAnnotation.home.confirm.deleteOkText'), - okType: "danger", - cancelText: t('dataAnnotation.home.confirm.deleteCancelText'), - onOk: async () => { - try { - await Promise.all( - [ - ...manualRows.map((r) => deleteAnnotationTaskByIdUsingDelete(r.id)), - // 自动标注批量删除已屏蔽 - // ...autoRows.map((r) => deleteAutoAnnotationTaskByIdUsingDelete(r.id)), - ] - ); - message.success(t('dataAnnotation.home.messages.batchDeleteSuccess')); - fetchData(); - setSelectedRowKeys([]); - setSelectedRows([]); - } catch (e) { - console.error(e); - message.error(t('dataAnnotation.home.messages.batchDeleteFailed')); - } - }, - }); - }; - - const operations = [ - { - key: "annotate", - label: t('dataAnnotation.home.actions.annotate'), - icon: ( - - ), - onClick: handleAnnotate, - }, - { - key: "sync-db", - label: t('dataAnnotation.home.actions.syncToDb'), - icon: , - onClick: handleSyncManualToDatabase, - }, - { - key: "export-result", - label: t('dataAnnotation.home.actions.exportResult'), - icon: , // 导出/下载类图标 - onClick: handleImportManualFromLabelStudio, - }, - { - key: "delete", - label: t('dataAnnotation.home.actions.delete'), - icon: , - onClick: handleDelete, - }, - ]; - // 合并手动标注任务与自动标注任务 - // 对于由自动标注逻辑内部创建的 Label Studio 映射(名称以 " - 自动标注" 结尾), - // 仅用于 datasetId -> projectId 映射,不在列表中单独展示,避免给人“多了一条手动任务”的感觉。 - const manualVisibleTasks = tableData.filter((task: any) => { - const name = (task as any)?.name; - if (typeof name !== "string") return true; - return !name.endsWith(" - 自动标注"); - }); - - // 自动标注功能已屏蔽:仅显示手动标注任务 - // 自动标注任务合并逻辑已屏蔽(保留代码在注释中,需要恢复时可取消注释) - // ...autoTasks.map((task: any) => { - // const sourceList = Array.isArray(task.sourceDatasets) - // ? task.sourceDatasets - // : task.datasetName - // ? [task.datasetName] - // : []; - // const datasetName = sourceList.length > 0 ? sourceList.join(",") : "-"; - // return { - // id: task.id, - // name: task.name, - // datasetId: task.datasetId || task.dataset_id, - // datasetName, - // createdAt: task.createdAt || "-", - // updatedAt: task.updatedAt || "-", - // _kind: "auto" as const, - // autoStatus: task.status, - // autoProgress: task.progress, - // autoProcessedImages: task.processedImages, - // autoTotalImages: task.totalImages, - // autoDetectedObjects: task.detectedObjects, - // autoConfig: task.config || {}, - // }; - // }), - - const mergedTableData: any[] = manualVisibleTasks.map((task: any) => ({ - ...task, - _kind: "manual" as const, - })); - - const columns: ColumnType[] = [ - { - title: t('dataAnnotation.home.columns.taskName'), - dataIndex: "name", - key: "name", - fixed: "left" as const, - }, - { - title: t('dataAnnotation.home.columns.type'), - key: "kind", - width: 100, - render: (_: any, record: any) => - record._kind === "auto" ? t('dataAnnotation.home.autoAnnotation') : t('dataAnnotation.home.manualAnnotation'), - }, - { - title: t('dataAnnotation.home.columns.taskId'), - dataIndex: "id", - key: "id", - }, - { - title: t('dataAnnotation.home.columns.dataset'), - dataIndex: "datasetName", - key: "datasetName", - width: 180, - render: (text: string, record: any) => { - if (!text || text === "-") return "-"; - const datasetId = record.datasetId || record.dataset_id; - if (!datasetId) return text; - - return ( - { - e.preventDefault(); - e.stopPropagation(); - navigate(`/data/management/detail/${datasetId}`); - }} - style={{ cursor: 'pointer', color: '#1890ff' }} - > - {text} - - ); - }, - }, - // 自动标注相关列已屏蔽(保留代码在注释中) - // { - // title: t('dataAnnotation.home.columns.model'), - // key: "modelSize", - // width: 160, - // render: (_: any, record: any) => { - // if (record._kind !== "auto") return "-"; - // const size = record.autoConfig?.modelSize; - // return t(`dataAnnotation.home.autoModelSizeLabels.${size}`) || size || "-"; - // }, - // }, - // { - // title: t('dataAnnotation.home.columns.confidence'), - // key: "confThreshold", - // width: 120, - // render: (_: any, record: any) => { - // if (record._kind !== "auto") return "-"; - // const threshold = record.autoConfig?.confThreshold; - // if (typeof threshold !== "number") return "-"; - // return `${(threshold * 100).toFixed(0)}%`; - // }, - // }, - // { - // title: t('dataAnnotation.home.columns.targetClasses'), - // key: "targetClasses", - // width: 160, - // render: (_: any, record: any) => { - // if (record._kind !== "auto") return "-"; - // const classes: number[] = record.autoConfig?.targetClasses || []; - // if (!classes.length) return t('dataAnnotation.home.allCategories'); - // const text = classes.join(", "); - // return ( - // - // {t('dataAnnotation.home.categoriesCount', { count: classes.length })} - // - // ); - // }, - // }, - // { - // title: t('dataAnnotation.home.columns.autoStatus'), - // key: "autoStatus", - // width: 130, - // render: (_: any, record: any) => { - // if (record._kind !== "auto") return "-"; - // const status = record.autoStatus as string; - // const label = t(`dataAnnotation.home.autoStatusLabels.${status}`) || status || "-"; - // return {label}; - // }, - // }, - // { - // title: t('dataAnnotation.home.columns.autoProgress'), - // key: "autoProgress", - // width: 200, - // render: (_: any, record: any) => { - // if (record._kind !== "auto") return "-"; - // const progress = typeof record.autoProgress === "number" ? record.autoProgress : 0; - // const processed = record.autoProcessedImages ?? 0; - // const total = record.autoTotalImages ?? 0; - // return ( - //
- // - //
- // {processed} / {total} - //
- //
- // ); - // }, - // }, - // { - // title: t('dataAnnotation.home.columns.detectedObjects'), - // key: "detectedObjects", - // width: 120, - // render: (_: any, record: any) => { - // if (record._kind !== "auto") return "-"; - // const count = record.autoDetectedObjects; - // if (typeof count !== "number") return "-"; - // try { - // return count.toLocaleString(); - // } catch { - // return String(count); - // } - // }, - // }, - { - title: t('dataAnnotation.home.columns.createdAt'), - dataIndex: "createdAt", - key: "createdAt", - width: 180, - }, - { - title: t('dataAnnotation.home.columns.updatedAt'), - dataIndex: "updatedAt", - key: "updatedAt", - width: 180, - }, - { - title: t('dataAnnotation.home.columns.actions'), - key: "actions", - fixed: "right" as const, - width: 260, - dataIndex: "actions", - render: (_: any, task: any) => ( -
- {task._kind === "manual" && ( - <> - - - - , - onClick: () => handleImportManualFromLabelStudio(task), - }, - { - key: "edit-dataset", - label: t('dataAnnotation.home.editDataset'), - icon: , - onClick: () => handleEditManualTaskDataset(task), - }, - { - key: "delete", - label: t('dataAnnotation.home.actions.delete'), - icon: , - danger: true, - onClick: () => handleDelete(task), - }, - ], - }} - trigger={["click"]} - > - - - - - , - onClick: () => handleImportAutoFromLabelStudio(task), - }, - { - key: "edit-dataset", - label: t('dataAnnotation.home.editDataset'), - icon: , - onClick: () => handleEditAutoTaskDataset(task), - }, - { - key: "delete", - label: t('dataAnnotation.home.actions.delete'), - icon: , - danger: true, - onClick: () => handleDeleteAuto(task), - }, - ], - }} - trigger={["click"]} - > -
- ), - }, - ]; - - return ( -
- {/* Header */} -
-

{t('dataAnnotation.home.title')}

-
- - {/* Tabs */} - - {/* Search, Filters and Buttons in one row */} -
- {/* Left side: Search and view controls */} -
- -
- - {/* Right side: All action buttons */} -
- - - -
-
- - {/* Task List/Card */} - {viewMode === "list" ? ( - - { - setSelectedRowKeys(keys as (string | number)[]); - setSelectedRows(rows as any[]); - }, - }} - scroll={{ x: "max-content", y: "calc(100vh - 24rem)" }} - /> - - ) : ( - - )} - - setShowCreateDialog(false)} - onRefresh={() => { - // 手动标注创建成功后刷新标注任务列表 - fetchData(); - // 自动标注创建成功后刷新已屏蔽 - // if (mode === "auto") { - // refreshAutoTasks(true); - // } - }} - /> - - ), - }, - { - key: "templates", - label: t('dataAnnotation.home.tabs.templates'), - children: , - }, - ]} - /> - - {/* {editingAutoTask && ( - { - setShowEditAutoDatasetDialog(false); - setEditingAutoTask(null); - }} - onSuccess={() => { - setShowEditAutoDatasetDialog(false); - setEditingAutoTask(null); - // refreshAutoTasks(); - }} - /> - )} */} - - {editingManualTask && ( - { - setShowEditManualDatasetDialog(false); - setEditingManualTask(null); - }} - onSuccess={() => { - setShowEditManualDatasetDialog(false); - setEditingManualTask(null); - }} - /> - )} - - {importingManualTask && ( - { - setShowImportManualDialog(false); - setImportingManualTask(null); - }} - onSuccess={() => { - setShowImportManualDialog(false); - setImportingManualTask(null); - }} - /> - )} - - {/* 自动标注导入对话框已屏蔽(保留代码在注释中) */} - {/* {importingAutoTask && ( - { - setShowImportAutoDialog(false); - setImportingAutoTask(null); - }} - onSuccess={() => { - setShowImportAutoDialog(false); - setImportingAutoTask(null); - }} - /> - )} */} - - ); -} +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router"; +import { Card, Button, Table, message, Modal, Tabs, Tag, Progress, Tooltip, Dropdown } from "antd"; +import { + PlusOutlined, + EditOutlined, + DeleteOutlined, + SyncOutlined, + MoreOutlined, + SettingOutlined, + ExportOutlined, + ImportOutlined, +} from "@ant-design/icons"; +import { SearchControls } from "@/components/SearchControls"; +import CardView from "@/components/CardView"; +import type { AnnotationTask } from "../annotation.model"; +import useFetchData from "@/hooks/useFetchData"; +import { + deleteAnnotationTaskByIdUsingDelete, + queryAnnotationTasksUsingGet, + syncAnnotationTaskUsingPost, + // 自动标注API已屏蔽(保留代码在注释中) + // queryAutoAnnotationTasksUsingGet, + // deleteAutoAnnotationTaskByIdUsingDelete, + // getAutoAnnotationLabelStudioProjectUsingGet, + loginAnnotationUsingGet, + syncManualAnnotationToDatabaseUsingPost, + // syncAutoAnnotationToDatabaseUsingPost, +} from "../annotation.api"; +import { mapAnnotationTask } from "../annotation.const"; +import CreateAnnotationTask from "../Create/components/CreateAnnotationTaskDialog"; +import { ColumnType } from "antd/es/table"; +import { TemplateList } from "../Template"; +// 自动标注组件导入已屏蔽(保留代码在注释中) +// import EditAutoAnnotationDatasetDialog from "../AutoAnnotation/components/EditAutoAnnotationDatasetDialog"; +// import ImportFromLabelStudioDialog from "../AutoAnnotation/components/ImportFromLabelStudioDialog"; +import ManualImportFromLabelStudioDialog from "../ManualImportFromLabelStudioDialog"; +import EditManualAnnotationDatasetDialog from "../EditManualAnnotationDatasetDialog"; +import { useTranslation } from "react-i18next"; +// Note: DevelopmentInProgress intentionally not used here + +export default function DataAnnotation() { + const { t } = useTranslation(); + const navigate = useNavigate(); + // return ; + const [activeTab, setActiveTab] = useState("tasks"); + const [viewMode, setViewMode] = useState<"list" | "card">("list"); + const [showCreateDialog, setShowCreateDialog] = useState(false); + // 自动标注任务state已屏蔽(保留代码在注释中) + // const [autoTasks, setAutoTasks] = useState([]); + + const { + loading, + tableData, + pagination, + searchParams, + fetchData, + handleFiltersChange, + handleKeywordChange, + } = useFetchData(queryAnnotationTasksUsingGet, (item) => mapAnnotationTask(item, t), 30000, true, [], 0); + + const [labelStudioBase, setLabelStudioBase] = useState(null); + const [selectedRowKeys, setSelectedRowKeys] = useState<(string | number)[]>([]); + const [selectedRows, setSelectedRows] = useState([]); + const [datasetProjectMap, setDatasetProjectMap] = useState>({}); + // 自动标注相关state已屏蔽(保留代码在注释中) + // const [editingAutoTask, setEditingAutoTask] = useState(null); + // const [showEditAutoDatasetDialog, setShowEditAutoDatasetDialog] = useState(false); + const [editingManualTask, setEditingManualTask] = useState(null); + const [showEditManualDatasetDialog, setShowEditManualDatasetDialog] = useState(false); + // 自动标注相关state已屏蔽(保留代码在注释中) + // const [importingAutoTask, setImportingAutoTask] = useState(null); + // const [showImportAutoDialog, setShowImportAutoDialog] = useState(false); + const [importingManualTask, setImportingManualTask] = useState(null); + const [showImportManualDialog, setShowImportManualDialog] = useState(false); + + // 自动标注任务拉取函数已屏蔽(保留代码在注释中) + // const refreshAutoTasks = async (silent = false) => { + // try { + // const response = await queryAutoAnnotationTasksUsingGet(); + // const tasks = (response as any)?.data || response || []; + // if (Array.isArray(tasks)) { + // setAutoTasks(tasks); + // } + // } catch (error) { + // console.error("Failed to fetch auto annotation tasks:", error); + // if (!silent) { + // message.error(t('dataAnnotation.home.messages.fetchAutoTasksFailed')); + // } + // } + // }; + + // prefetch config on mount so clicking annotate is fast and we know whether base URL exists + // useEffect ensures this runs once + useEffect(() => { + let mounted = true; + (async () => { + try { + const baseUrl = `http://${window.location.hostname}:${parseInt(window.location.port) + 1}`; + if (mounted) setLabelStudioBase(baseUrl); + } catch (e) { + if (mounted) setLabelStudioBase(null); + } + })(); + return () => { + mounted = false; + }; + }, []); + + // 基于手动标注任务构建 datasetId -> Label Studio project 映射,供自动标注跳转使用 + useEffect(() => { + const map: Record = {}; + (tableData as any[]).forEach((task: any) => { + const datasetId = task.datasetId || task.dataset_id; + const projId = task.labelingProjId || task.projId || task.labeling_project_id; + if (datasetId && projId) { + map[String(datasetId)] = String(projId); + } + }); + setDatasetProjectMap(map); + }, [tableData]); + + // 自动标注任务轮询已屏蔽(保留代码在注释中) + // useEffect(() => { + // refreshAutoTasks(); + // const timer = setInterval(() => refreshAutoTasks(true), 3000); + // return () => { + // clearInterval(timer); + // }; + // }, []); + + const handleAnnotate = (task: AnnotationTask) => { + // Open Label Studio project page in a new tab + (async () => { + try { + // `labelingProjId` = Label Studio project ID (integer), used for LS URL only + // `mappingId` = mapping UUID, used for backend API calls + let labelingProjId = (task as any).labelingProjId || (task as any).projId || undefined; + let mappingId = (task as any).id || undefined; + + // no fallback external mapping lookup; rely on normalized fields from mapAnnotationTask + + // use prefetched base if available + const base = labelStudioBase; + + // no debug logging in production + await loginAnnotationUsingGet(mappingId) + if (labelingProjId) { + // only open external Label Studio when we have a configured base url + if (base) { + const target = `${base}/projects/${labelingProjId}/data`; + window.open(target, "_blank"); + } else { + // no external Label Studio URL configured — do not perform internal redirect in this version + message.error(t('dataAnnotation.home.messages.cannotJumpNoBase')); + return; + } + } else { + // no labeling project id available — do not attempt internal redirect in this version + message.error(t('dataAnnotation.home.messages.cannotJumpNoMapping')); + return; + } + } catch (error) { + // on error, surface a user-friendly message instead of redirecting + message.error(t('dataAnnotation.home.messages.cannotJumpError')); + return; + } + })(); + }; + + const handleDelete = (task: AnnotationTask) => { + Modal.confirm({ + title: t('dataAnnotation.home.confirm.deleteTaskTitle', { name: task.name }), + content: ( +
+
{t('dataAnnotation.home.confirm.deleteTaskContent1')}
+
{t('dataAnnotation.home.confirm.deleteTaskContent2')}
+
+ ), + okText: t('dataAnnotation.home.confirm.deleteOkText'), + okType: "danger", + cancelText: t('dataAnnotation.home.confirm.deleteCancelText'), + onOk: async () => { + try { + await deleteAnnotationTaskByIdUsingDelete(task.id); + message.success(t('dataAnnotation.home.messages.deleteSuccess')); + fetchData(); + // clear selection if deleted item was selected + setSelectedRowKeys((keys) => keys.filter((k) => k !== task.id)); + setSelectedRows((rows) => rows.filter((r) => r.id !== task.id)); + } catch (e) { + console.error(e); + message.error(t('dataAnnotation.home.messages.deleteFailed')); + } + }, + }); + }; + + // 自动标注删除函数已屏蔽(保留代码在注释中) + // const handleDeleteAuto = (task: any) => { + // Modal.confirm({ + // title: t('dataAnnotation.home.confirm.deleteAutoTaskTitle', { name: task.name }), + // content:
{t('dataAnnotation.home.confirm.deleteAutoTaskContent')}
, + // okText: t('dataAnnotation.home.confirm.deleteOkText'), + // okType: "danger", + // cancelText: t('dataAnnotation.home.confirm.deleteCancelText'), + // onOk: async () => { + // try { + // await deleteAutoAnnotationTaskByIdUsingDelete(task.id); + // message.success(t('dataAnnotation.home.messages.autoTaskDeleteSuccess')); + // setAutoTasks((prev) => prev.filter((t: any) => t.id !== task.id)); + // setSelectedRowKeys((keys) => keys.filter((k) => k !== task.id)); + // setSelectedRows((rows) => rows.filter((r) => r.id !== task.id)); + // } catch (e) { + // console.error(e); + // message.error(t('dataAnnotation.home.messages.deleteFailed')); + // } + // }, + // }); + // }; + + // 自动标注编辑数据集函数已屏蔽(保留代码在注释中) + // const handleEditAutoTaskDataset = (row: any) => { + // if (!row?.id) { + // message.error(t('dataAnnotation.home.messages.autoTaskNotFound')); + // return; + // } + // const full = autoTasks.find((t: any) => t.id === row.id); + // if (!full) { + // message.error(t('dataAnnotation.home.messages.autoTaskNotFound') + t('dataAnnotation.home.messages.deleteFailed').split(',')[1]); + // return; + // } + // setEditingAutoTask(full); + // setShowEditAutoDatasetDialog(true); + // }; + + const handleEditManualTaskDataset = (task: AnnotationTask) => { + if (!task?.id) { + message.error(t('dataAnnotation.home.messages.taskNotFound')); + return; + } + setEditingManualTask(task); + setShowEditManualDatasetDialog(true); + }; + + const handleImportManualFromLabelStudio = (task: AnnotationTask) => { + if (!task?.id) { + message.error(t('dataAnnotation.home.messages.taskNotFound')); + return; + } + + setImportingManualTask(task); + setShowImportManualDialog(true); + }; + + const handleSyncManualToDatabase = async (task: AnnotationTask) => { + if (!task?.id) { + message.error(t('dataAnnotation.home.messages.taskNotFound')); + return; + } + + Modal.confirm({ + title: t('dataAnnotation.home.messages.syncToDbTitle', { name: task.name }), + content: ( +
+
{t('dataAnnotation.home.messages.syncToDbContent1')}
+
{t('dataAnnotation.home.messages.syncToDbContent2')}
+
+ ), + okText: t('dataAnnotation.home.actions.syncToDb'), + cancelText: t('dataAnnotation.home.confirm.deleteCancelText'), + onOk: async () => { + const hide = message.loading(t('dataAnnotation.dialogs.syncToDb.loading'), 0); + try { + await syncManualAnnotationToDatabaseUsingPost(task.id as any); + hide(); + message.success(t('dataAnnotation.dialogs.syncToDb.success')); + } catch (e) { + console.error(e); + hide(); + message.error(t('dataAnnotation.dialogs.syncToDb.fail')); + } + }, + }); + }; + + // 自动标注导入函数已屏蔽(保留代码在注释中) + // const handleImportAutoFromLabelStudio = (row: any) => { + // if (!row?.id) { + // message.error(t('dataAnnotation.home.messages.autoTaskNotFound')); + // return; + // } + // const full = autoTasks.find((t: any) => t.id === row.id); + // if (!full) { + // message.error(t('dataAnnotation.home.messages.autoTaskNotFound') + t('dataAnnotation.home.messages.deleteFailed').split(',')[1]); + // return; + // } + // setImportingAutoTask(full); + // setShowImportAutoDialog(true); + // }; + + // 自动标注同步到数据库函数已屏蔽(保留代码在注释中) + // const handleSyncAutoToDatabase = (row: any) => { + // if (!row?.id) { + // message.error(t('dataAnnotation.home.messages.autoTaskNotFound')); + // return; + // } + // Modal.confirm({ + // title: t('dataAnnotation.home.messages.syncToDbTitle', { name: row.name }), + // content: ( + //
+ //
{t('dataAnnotation.home.messages.syncToDbContent1')}
+ //
{t('dataAnnotation.home.messages.syncToDbContent2')}
+ //
+ // ), + // okText: t('dataAnnotation.home.actions.syncToDb'), + // cancelText: t('dataAnnotation.home.confirm.deleteCancelText'), + // onOk: async () => { + // const hide = message.loading(t('dataAnnotation.dialogs.syncToDb.loading'), 0); + // try { + // await syncAutoAnnotationToDatabaseUsingPost(row.id); + // hide(); + // message.success(t('dataAnnotation.dialogs.syncToDb.success')); + // } catch (e) { + // console.error(e); + // hide(); + // message.error(t('dataAnnotation.dialogs.syncToDb.fail')); + // } + // }, + // }); + // }; + + // 自动标注跳转标注函数已屏蔽(保留代码在注释中) + // const handleAnnotateAuto = (task: any) => { + // (async () => { + // try { + // if (!labelStudioBase) { + // message.error(t('dataAnnotation.home.messages.cannotJumpNoBase')); + // return; + // } + // let projId: string | undefined; + // try { + // const resp = await getAutoAnnotationLabelStudioProjectUsingGet(task.id); + // const data = (resp as any)?.data ?? resp; + // projId = data?.projectId || data?.labeling_project_id; + // } catch (e) { + // console.error("Failed to resolve LS project for auto task", e); + // } + // if (!projId) { + // const datasetId = task.datasetId; + // if (!datasetId) { + // message.error(t('dataAnnotation.home.messages.cannotJumpAutoNoDataset')); + // return; + // } + // projId = datasetProjectMap[String(datasetId)]; + // } + // if (!projId) { + // message.error(t('dataAnnotation.home.messages.cannotJumpAutoNoProject')); + // return; + // } + // const target = `${labelStudioBase}/projects/${projId}/data`; + // window.open(target, "_blank"); + // } catch (error) { + // console.error(error); + // message.error(t('dataAnnotation.home.messages.cannotJumpError')); + // } + // })(); + // }; + + const handleSync = (task: AnnotationTask, batchSize: number = 50) => { + Modal.confirm({ + title: t('dataAnnotation.home.confirm.syncTitle', { name: task.name }), + content: ( +
+
{t('dataAnnotation.home.confirm.syncContent1')}
+
{t('dataAnnotation.home.confirm.syncContent2')}
+
+ ), + okText: t('dataAnnotation.home.confirm.syncOkText'), + cancelText: t('dataAnnotation.home.confirm.syncCancelText'), + onOk: async () => { + try { + await syncAnnotationTaskUsingPost({ id: task.id, batchSize }); + message.success(t('dataAnnotation.home.messages.syncRequestSent')); + // optional: refresh list/status + fetchData(); + // clear selection for the task + setSelectedRowKeys((keys) => keys.filter((k) => k !== task.id)); + setSelectedRows((rows) => rows.filter((r) => r.id !== task.id)); + } catch (e) { + console.error(e); + message.error(t('dataAnnotation.home.messages.syncFailed')); + } + }, + }); + }; + + const handleBatchSync = (batchSize: number = 50) => { + if (!selectedRows || selectedRows.length === 0) return; + const manualRows = selectedRows.filter((r) => r._kind !== "auto"); + if (manualRows.length === 0) { + message.warning(t('dataAnnotation.home.messages.batchSyncManualOnly')); + return; + } + Modal.confirm({ + title: t('dataAnnotation.home.confirm.batchSyncTitle', { count: manualRows.length }), + content: ( +
+
{t('dataAnnotation.home.confirm.syncContent1')}
+
{t('dataAnnotation.home.confirm.syncContent2')}
+
+ ), + okText: t('dataAnnotation.home.confirm.syncOkText'), + cancelText: t('dataAnnotation.home.confirm.syncCancelText'), + onOk: async () => { + try { + await Promise.all( + manualRows.map((r) => syncAnnotationTaskUsingPost({ id: r.id, batchSize })) + ); + message.success(t('dataAnnotation.home.messages.batchSyncSuccess')); + fetchData(); + setSelectedRowKeys([]); + setSelectedRows([]); + } catch (e) { + console.error(e); + message.error(t('dataAnnotation.home.messages.batchSyncFailed')); + } + }, + }); + }; + + const handleBatchDelete = () => { + if (!selectedRows || selectedRows.length === 0) return; + const manualRows = selectedRows.filter((r) => r._kind !== "auto"); + // 自动标注批量删除已屏蔽 + // const autoRows = selectedRows.filter((r) => r._kind === "auto"); + Modal.confirm({ + title: t('dataAnnotation.home.confirm.batchDeleteTitle', { count: selectedRows.length }), + content: ( +
+
{t('dataAnnotation.home.confirm.deleteTaskContent1')}
+
{t('dataAnnotation.home.confirm.deleteTaskContent2')}
+
+ ), + okText: t('dataAnnotation.home.confirm.deleteOkText'), + okType: "danger", + cancelText: t('dataAnnotation.home.confirm.deleteCancelText'), + onOk: async () => { + try { + await Promise.all( + [ + ...manualRows.map((r) => deleteAnnotationTaskByIdUsingDelete(r.id)), + // 自动标注批量删除已屏蔽 + // ...autoRows.map((r) => deleteAutoAnnotationTaskByIdUsingDelete(r.id)), + ] + ); + message.success(t('dataAnnotation.home.messages.batchDeleteSuccess')); + fetchData(); + setSelectedRowKeys([]); + setSelectedRows([]); + } catch (e) { + console.error(e); + message.error(t('dataAnnotation.home.messages.batchDeleteFailed')); + } + }, + }); + }; + + const operations = [ + { + key: "annotate", + label: t('dataAnnotation.home.actions.annotate'), + icon: ( + + ), + onClick: handleAnnotate, + }, + { + key: "sync-db", + label: t('dataAnnotation.home.actions.syncToDb'), + icon: , + onClick: handleSyncManualToDatabase, + }, + { + key: "export-result", + label: t('dataAnnotation.home.actions.exportResult'), + icon: , // 导出/下载类图标 + onClick: handleImportManualFromLabelStudio, + }, + { + key: "delete", + label: t('dataAnnotation.home.actions.delete'), + icon: , + onClick: handleDelete, + }, + ]; + // 合并手动标注任务与自动标注任务 + // 对于由自动标注逻辑内部创建的 Label Studio 映射(名称以 " - 自动标注" 结尾), + // 仅用于 datasetId -> projectId 映射,不在列表中单独展示,避免给人“多了一条手动任务”的感觉。 + const manualVisibleTasks = tableData.filter((task: any) => { + const name = (task as any)?.name; + if (typeof name !== "string") return true; + return !name.endsWith(" - 自动标注"); + }); + + // 自动标注功能已屏蔽:仅显示手动标注任务 + // 自动标注任务合并逻辑已屏蔽(保留代码在注释中,需要恢复时可取消注释) + // ...autoTasks.map((task: any) => { + // const sourceList = Array.isArray(task.sourceDatasets) + // ? task.sourceDatasets + // : task.datasetName + // ? [task.datasetName] + // : []; + // const datasetName = sourceList.length > 0 ? sourceList.join(",") : "-"; + // return { + // id: task.id, + // name: task.name, + // datasetId: task.datasetId || task.dataset_id, + // datasetName, + // createdAt: task.createdAt || "-", + // updatedAt: task.updatedAt || "-", + // _kind: "auto" as const, + // autoStatus: task.status, + // autoProgress: task.progress, + // autoProcessedImages: task.processedImages, + // autoTotalImages: task.totalImages, + // autoDetectedObjects: task.detectedObjects, + // autoConfig: task.config || {}, + // }; + // }), + + const mergedTableData: any[] = manualVisibleTasks.map((task: any) => ({ + ...task, + _kind: "manual" as const, + })); + + const columns: ColumnType[] = [ + { + title: t('dataAnnotation.home.columns.taskName'), + dataIndex: "name", + key: "name", + fixed: "left" as const, + }, + { + title: t('dataAnnotation.home.columns.type'), + key: "kind", + width: 100, + render: (_: any, record: any) => + record._kind === "auto" ? t('dataAnnotation.home.autoAnnotation') : t('dataAnnotation.home.manualAnnotation'), + }, + { + title: t('dataAnnotation.home.columns.taskId'), + dataIndex: "id", + key: "id", + }, + { + title: t('dataAnnotation.home.columns.dataset'), + dataIndex: "datasetName", + key: "datasetName", + width: 180, + render: (text: string, record: any) => { + if (!text || text === "-") return "-"; + const datasetId = record.datasetId || record.dataset_id; + if (!datasetId) return text; + + return ( + { + e.preventDefault(); + e.stopPropagation(); + navigate(`/data/management/detail/${datasetId}`); + }} + style={{ cursor: 'pointer', color: '#1890ff' }} + > + {text} + + ); + }, + }, + // 自动标注相关列已屏蔽(保留代码在注释中) + // { + // title: t('dataAnnotation.home.columns.model'), + // key: "modelSize", + // width: 160, + // render: (_: any, record: any) => { + // if (record._kind !== "auto") return "-"; + // const size = record.autoConfig?.modelSize; + // return t(`dataAnnotation.home.autoModelSizeLabels.${size}`) || size || "-"; + // }, + // }, + // { + // title: t('dataAnnotation.home.columns.confidence'), + // key: "confThreshold", + // width: 120, + // render: (_: any, record: any) => { + // if (record._kind !== "auto") return "-"; + // const threshold = record.autoConfig?.confThreshold; + // if (typeof threshold !== "number") return "-"; + // return `${(threshold * 100).toFixed(0)}%`; + // }, + // }, + // { + // title: t('dataAnnotation.home.columns.targetClasses'), + // key: "targetClasses", + // width: 160, + // render: (_: any, record: any) => { + // if (record._kind !== "auto") return "-"; + // const classes: number[] = record.autoConfig?.targetClasses || []; + // if (!classes.length) return t('dataAnnotation.home.allCategories'); + // const text = classes.join(", "); + // return ( + // + // {t('dataAnnotation.home.categoriesCount', { count: classes.length })} + // + // ); + // }, + // }, + // { + // title: t('dataAnnotation.home.columns.autoStatus'), + // key: "autoStatus", + // width: 130, + // render: (_: any, record: any) => { + // if (record._kind !== "auto") return "-"; + // const status = record.autoStatus as string; + // const label = t(`dataAnnotation.home.autoStatusLabels.${status}`) || status || "-"; + // return {label}; + // }, + // }, + // { + // title: t('dataAnnotation.home.columns.autoProgress'), + // key: "autoProgress", + // width: 200, + // render: (_: any, record: any) => { + // if (record._kind !== "auto") return "-"; + // const progress = typeof record.autoProgress === "number" ? record.autoProgress : 0; + // const processed = record.autoProcessedImages ?? 0; + // const total = record.autoTotalImages ?? 0; + // return ( + //
+ // + //
+ // {processed} / {total} + //
+ //
+ // ); + // }, + // }, + // { + // title: t('dataAnnotation.home.columns.detectedObjects'), + // key: "detectedObjects", + // width: 120, + // render: (_: any, record: any) => { + // if (record._kind !== "auto") return "-"; + // const count = record.autoDetectedObjects; + // if (typeof count !== "number") return "-"; + // try { + // return count.toLocaleString(); + // } catch { + // return String(count); + // } + // }, + // }, + { + title: t('dataAnnotation.home.columns.createdAt'), + dataIndex: "createdAt", + key: "createdAt", + width: 180, + }, + { + title: t('dataAnnotation.home.columns.updatedAt'), + dataIndex: "updatedAt", + key: "updatedAt", + width: 180, + }, + { + title: t('dataAnnotation.home.columns.actions'), + key: "actions", + fixed: "right" as const, + width: 260, + dataIndex: "actions", + render: (_: any, task: any) => ( +
+ {task._kind === "manual" && ( + <> + + + + , + onClick: () => handleImportManualFromLabelStudio(task), + }, + { + key: "edit-dataset", + label: t('dataAnnotation.home.editDataset'), + icon: , + onClick: () => handleEditManualTaskDataset(task), + }, + { + key: "delete", + label: t('dataAnnotation.home.actions.delete'), + icon: , + danger: true, + onClick: () => handleDelete(task), + }, + ], + }} + trigger={["click"]} + > + + + + + , + onClick: () => handleImportAutoFromLabelStudio(task), + }, + { + key: "edit-dataset", + label: t('dataAnnotation.home.editDataset'), + icon: , + onClick: () => handleEditAutoTaskDataset(task), + }, + { + key: "delete", + label: t('dataAnnotation.home.actions.delete'), + icon: , + danger: true, + onClick: () => handleDeleteAuto(task), + }, + ], + }} + trigger={["click"]} + > +
+ ), + }, + ]; + + return ( +
+ {/* Header */} +
+

{t('dataAnnotation.home.title')}

+
+ + {/* Tabs */} + + {/* Search, Filters and Buttons in one row */} +
+ {/* Left side: Search and view controls */} +
+ +
+ + {/* Right side: All action buttons */} +
+ + + +
+
+ + {/* Task List/Card */} + {viewMode === "list" ? ( + +
{ + setSelectedRowKeys(keys as (string | number)[]); + setSelectedRows(rows as any[]); + }, + }} + scroll={{ x: "max-content", y: "calc(100vh - 24rem)" }} + /> + + ) : ( + + )} + + setShowCreateDialog(false)} + onRefresh={() => { + // 手动标注创建成功后刷新标注任务列表 + fetchData(); + // 自动标注创建成功后刷新已屏蔽 + // if (mode === "auto") { + // refreshAutoTasks(true); + // } + }} + /> + + ), + }, + { + key: "templates", + label: t('dataAnnotation.home.tabs.templates'), + children: , + }, + ]} + /> + + {/* {editingAutoTask && ( + { + setShowEditAutoDatasetDialog(false); + setEditingAutoTask(null); + }} + onSuccess={() => { + setShowEditAutoDatasetDialog(false); + setEditingAutoTask(null); + // refreshAutoTasks(); + }} + /> + )} */} + + {editingManualTask && ( + { + setShowEditManualDatasetDialog(false); + setEditingManualTask(null); + }} + onSuccess={() => { + setShowEditManualDatasetDialog(false); + setEditingManualTask(null); + }} + /> + )} + + {importingManualTask && ( + { + setShowImportManualDialog(false); + setImportingManualTask(null); + }} + onSuccess={() => { + setShowImportManualDialog(false); + setImportingManualTask(null); + }} + /> + )} + + {/* 自动标注导入对话框已屏蔽(保留代码在注释中) */} + {/* {importingAutoTask && ( + { + setShowImportAutoDialog(false); + setImportingAutoTask(null); + }} + onSuccess={() => { + setShowImportAutoDialog(false); + setImportingAutoTask(null); + }} + /> + )} */} + + ); +}