From 0bb9abb2001adece7e5943b85a84165aba05c0c8 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Sun, 1 Feb 2026 19:08:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(annotation):=20=E6=B7=BB=E5=8A=A0=E6=A0=87?= =?UTF-8?q?=E6=B3=A8=E7=B1=BB=E5=9E=8B=E6=98=BE=E7=A4=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在前端页面中新增标注类型列并使用Tag组件展示 - 添加AnnotationTypeMap常量用于标注类型的映射 - 修改接口定义支持labelingType字段的传递 - 更新后端项目创建和更新逻辑以存储标注类型 - 添加标注类型配置键常量统一管理 - 扩展数据传输对象支持标注类型属性 - 实现模板标注类型的继承逻辑 --- .../DataAnnotation/Home/DataAnnotation.tsx | 23 +++++++++++++++++-- .../pages/DataAnnotation/annotation.const.tsx | 13 +++++++++++ .../module/annotation/interface/project.py | 17 ++++++++++++++ .../app/module/annotation/schema/mapping.py | 1 + .../app/module/annotation/service/mapping.py | 15 +++++++++++- 5 files changed, 66 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx b/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx index 3cbdc45..0749986 100644 --- a/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx +++ b/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { Card, Button, Table, message, Modal, Tabs } from "antd"; +import { Card, Button, Table, Tag, message, Modal, Tabs } from "antd"; import { PlusOutlined, EditOutlined, @@ -15,7 +15,11 @@ import { deleteAnnotationTaskByIdUsingDelete, queryAnnotationTasksUsingGet, } from "../annotation.api"; -import { mapAnnotationTask, type AnnotationTaskListItem } from "../annotation.const"; +import { + AnnotationTypeMap, + mapAnnotationTask, + type AnnotationTaskListItem, +} from "../annotation.const"; import CreateAnnotationTask from "../Create/components/CreateAnnotationTaskDialog"; import ExportAnnotationDialog from "./ExportAnnotationDialog"; import { ColumnType } from "antd/es/table"; @@ -168,6 +172,21 @@ export default function DataAnnotation() { key: "datasetName", width: 180, }, + { + title: "标注类型", + dataIndex: "labelingType", + key: "labelingType", + width: 160, + render: (value?: string) => { + if (!value) { + return "-"; + } + const label = + AnnotationTypeMap[value as keyof typeof AnnotationTypeMap]?.label || + value; + return {label}; + }, + }, { title: "数据量", dataIndex: "totalCount", diff --git a/frontend/src/pages/DataAnnotation/annotation.const.tsx b/frontend/src/pages/DataAnnotation/annotation.const.tsx index 77b5ba9..d375f23 100644 --- a/frontend/src/pages/DataAnnotation/annotation.const.tsx +++ b/frontend/src/pages/DataAnnotation/annotation.const.tsx @@ -23,6 +23,12 @@ type AnnotationTaskPayload = { datasetId?: string; datasetName?: string; dataset_name?: string; + labelingType?: string; + labeling_type?: string; + template?: { + labelingType?: string; + labeling_type?: string; + }; totalCount?: number; total_count?: number; annotatedCount?: number; @@ -48,6 +54,7 @@ export type AnnotationTaskListItem = { description?: string; datasetId?: string; datasetName?: string; + labelingType?: string; totalCount?: number; annotatedCount?: number; inProgressCount?: number; @@ -90,6 +97,11 @@ export function mapAnnotationTask(task: AnnotationTaskPayload): AnnotationTaskLi const labelingProjId = task?.labelingProjId || task?.labelingProjectId || task?.projId || task?.labeling_project_id || ""; const segmentationEnabled = task?.segmentationEnabled ?? task?.segmentation_enabled ?? false; const inProgressCount = task?.inProgressCount ?? task?.in_progress_count ?? 0; + const labelingType = + task?.labelingType || + task?.labeling_type || + task?.template?.labelingType || + task?.template?.labeling_type; const statsArray = task?.statistics ? [ @@ -107,6 +119,7 @@ export function mapAnnotationTask(task: AnnotationTaskPayload): AnnotationTaskLi projId: labelingProjId, segmentationEnabled, inProgressCount, + labelingType, name: task.name, description: task.description || "", datasetName: task.datasetName || task.dataset_name || "-", diff --git a/runtime/datamate-python/app/module/annotation/interface/project.py b/runtime/datamate-python/app/module/annotation/interface/project.py index 4ad3779..6a08499 100644 --- a/runtime/datamate-python/app/module/annotation/interface/project.py +++ b/runtime/datamate-python/app/module/annotation/interface/project.py @@ -29,6 +29,7 @@ router = APIRouter( logger = get_logger(__name__) TEXT_DATASET_TYPE = "TEXT" SOURCE_DOCUMENT_FILE_TYPES = {"pdf", "doc", "docx", "xls", "xlsx"} +LABELING_TYPE_CONFIG_KEY = "labeling_type" @router.get("/{mapping_id}/login") async def login_label_studio( @@ -82,6 +83,7 @@ async def create_mapping( # 如果提供了模板ID,获取模板配置 label_config = None + template_labeling_type = None if request.template_id: logger.info(f"Using template: {request.template_id}") template = await template_service.get_template(db, request.template_id) @@ -91,6 +93,7 @@ async def create_mapping( detail=f"Template not found: {request.template_id}" ) label_config = template.label_config + template_labeling_type = getattr(template, "labeling_type", None) logger.debug(f"Template label config loaded for template: {template.name}") # 如果直接提供了 label_config (自定义或修改后的),则覆盖模板配置 @@ -109,6 +112,8 @@ async def create_mapping( project_configuration["description"] = project_description if dataset_type == TEXT_DATASET_TYPE and request.segmentation_enabled is not None: project_configuration["segmentation_enabled"] = bool(request.segmentation_enabled) + if template_labeling_type: + project_configuration[LABELING_TYPE_CONFIG_KEY] = template_labeling_type labeling_project = LabelingProject( id=str(uuid.uuid4()), # Generate UUID here @@ -441,6 +446,18 @@ async def update_mapping( if request.template_id is not None: update_values["template_id"] = request.template_id + template_labeling_type = None + if request.template_id: + template_service = AnnotationTemplateService() + template = await template_service.get_template(db, request.template_id) + if not template: + raise HTTPException( + status_code=404, + detail=f"Template not found: {request.template_id}" + ) + template_labeling_type = getattr(template, "labeling_type", None) + if template_labeling_type: + configuration[LABELING_TYPE_CONFIG_KEY] = template_labeling_type if not update_values: # 没有要更新的字段,直接返回当前数据 diff --git a/runtime/datamate-python/app/module/annotation/schema/mapping.py b/runtime/datamate-python/app/module/annotation/schema/mapping.py index e510477..2416c8b 100644 --- a/runtime/datamate-python/app/module/annotation/schema/mapping.py +++ b/runtime/datamate-python/app/module/annotation/schema/mapping.py @@ -65,6 +65,7 @@ class DatasetMappingResponse(BaseModel): name: Optional[str] = Field(None, description="标注项目名称") description: Optional[str] = Field(None, description="标注项目描述") template_id: Optional[str] = Field(None, alias="templateId", description="关联的模板ID") + labeling_type: Optional[str] = Field(None, alias="labelingType", description="标注类型") template: Optional['AnnotationTemplateResponse'] = Field(None, description="关联的标注模板详情") label_config: Optional[str] = Field(None, alias="labelConfig", description="实际使用的 Label Studio XML 配置") segmentation_enabled: Optional[bool] = Field( diff --git a/runtime/datamate-python/app/module/annotation/service/mapping.py b/runtime/datamate-python/app/module/annotation/service/mapping.py index 9bf51e8..33d70b7 100644 --- a/runtime/datamate-python/app/module/annotation/service/mapping.py +++ b/runtime/datamate-python/app/module/annotation/service/mapping.py @@ -7,7 +7,7 @@ from datetime import datetime import uuid from app.core.logging import get_logger -from app.db.models import LabelingProject, AnnotationTemplate, AnnotationResult, LabelingProjectFile +from app.db.models import LabelingProject, AnnotationResult, LabelingProjectFile from app.db.models.annotation_management import ANNOTATION_STATUS_IN_PROGRESS from app.db.models.dataset_management import Dataset, DatasetFiles from app.module.annotation.schema import ( @@ -18,6 +18,7 @@ from app.module.annotation.schema import ( ) logger = get_logger(__name__) +LABELING_TYPE_CONFIG_KEY = "labeling_type" class DatasetMappingService: """数据集映射服务""" @@ -106,10 +107,12 @@ class DatasetMappingService: label_config = None description = None segmentation_enabled = None + labeling_type = None if isinstance(configuration, dict): label_config = configuration.get('label_config') description = configuration.get('description') segmentation_enabled = configuration.get('segmentation_enabled') + labeling_type = configuration.get(LABELING_TYPE_CONFIG_KEY) # Optionally fetch full template details template_response = None @@ -119,6 +122,9 @@ class DatasetMappingService: template_response = await template_service.get_template(self.db, template_id) logger.debug(f"Included template details for template_id: {template_id}") + if not labeling_type and template_response: + labeling_type = getattr(template_response, "labeling_type", None) + # 获取统计数据 total_count, annotated_count, in_progress_count = await self._get_project_stats( mapping.id, mapping.dataset_id @@ -132,6 +138,7 @@ class DatasetMappingService: "name": mapping.name, "description": description, "template_id": template_id, + "labeling_type": labeling_type, "template": template_response, "label_config": label_config, "segmentation_enabled": segmentation_enabled, @@ -174,10 +181,12 @@ class DatasetMappingService: label_config = None description = None segmentation_enabled = None + labeling_type = None if isinstance(configuration, dict): label_config = configuration.get('label_config') description = configuration.get('description') segmentation_enabled = configuration.get('segmentation_enabled') + labeling_type = configuration.get(LABELING_TYPE_CONFIG_KEY) # Optionally fetch full template details template_response = None @@ -187,6 +196,9 @@ class DatasetMappingService: template_response = await template_service.get_template(self.db, template_id) logger.debug(f"Included template details for template_id: {template_id}") + if not labeling_type and template_response: + labeling_type = getattr(template_response, "labeling_type", None) + # 获取统计数据 total_count, annotated_count, in_progress_count = 0, 0, 0 if dataset_id: @@ -203,6 +215,7 @@ class DatasetMappingService: "name": mapping.name, "description": description, "template_id": template_id, + "labeling_type": labeling_type, "template": template_response, "label_config": label_config, "segmentation_enabled": segmentation_enabled,