feat(annotation): 添加标注类型显示功能

- 在前端页面中新增标注类型列并使用Tag组件展示
- 添加AnnotationTypeMap常量用于标注类型的映射
- 修改接口定义支持labelingType字段的传递
- 更新后端项目创建和更新逻辑以存储标注类型
- 添加标注类型配置键常量统一管理
- 扩展数据传输对象支持标注类型属性
- 实现模板标注类型的继承逻辑
This commit is contained in:
2026-02-01 19:08:11 +08:00
parent d135a7f336
commit 0bb9abb200
5 changed files with 66 additions and 3 deletions

View File

@@ -1,5 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { Card, Button, Table, message, Modal, Tabs } from "antd"; import { Card, Button, Table, Tag, message, Modal, Tabs } from "antd";
import { import {
PlusOutlined, PlusOutlined,
EditOutlined, EditOutlined,
@@ -15,7 +15,11 @@ import {
deleteAnnotationTaskByIdUsingDelete, deleteAnnotationTaskByIdUsingDelete,
queryAnnotationTasksUsingGet, queryAnnotationTasksUsingGet,
} from "../annotation.api"; } 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 CreateAnnotationTask from "../Create/components/CreateAnnotationTaskDialog";
import ExportAnnotationDialog from "./ExportAnnotationDialog"; import ExportAnnotationDialog from "./ExportAnnotationDialog";
import { ColumnType } from "antd/es/table"; import { ColumnType } from "antd/es/table";
@@ -168,6 +172,21 @@ export default function DataAnnotation() {
key: "datasetName", key: "datasetName",
width: 180, 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 <Tag color="geekblue">{label}</Tag>;
},
},
{ {
title: "数据量", title: "数据量",
dataIndex: "totalCount", dataIndex: "totalCount",

View File

@@ -23,6 +23,12 @@ type AnnotationTaskPayload = {
datasetId?: string; datasetId?: string;
datasetName?: string; datasetName?: string;
dataset_name?: string; dataset_name?: string;
labelingType?: string;
labeling_type?: string;
template?: {
labelingType?: string;
labeling_type?: string;
};
totalCount?: number; totalCount?: number;
total_count?: number; total_count?: number;
annotatedCount?: number; annotatedCount?: number;
@@ -48,6 +54,7 @@ export type AnnotationTaskListItem = {
description?: string; description?: string;
datasetId?: string; datasetId?: string;
datasetName?: string; datasetName?: string;
labelingType?: string;
totalCount?: number; totalCount?: number;
annotatedCount?: number; annotatedCount?: number;
inProgressCount?: 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 labelingProjId = task?.labelingProjId || task?.labelingProjectId || task?.projId || task?.labeling_project_id || "";
const segmentationEnabled = task?.segmentationEnabled ?? task?.segmentation_enabled ?? false; const segmentationEnabled = task?.segmentationEnabled ?? task?.segmentation_enabled ?? false;
const inProgressCount = task?.inProgressCount ?? task?.in_progress_count ?? 0; 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 const statsArray = task?.statistics
? [ ? [
@@ -107,6 +119,7 @@ export function mapAnnotationTask(task: AnnotationTaskPayload): AnnotationTaskLi
projId: labelingProjId, projId: labelingProjId,
segmentationEnabled, segmentationEnabled,
inProgressCount, inProgressCount,
labelingType,
name: task.name, name: task.name,
description: task.description || "", description: task.description || "",
datasetName: task.datasetName || task.dataset_name || "-", datasetName: task.datasetName || task.dataset_name || "-",

View File

@@ -29,6 +29,7 @@ router = APIRouter(
logger = get_logger(__name__) logger = get_logger(__name__)
TEXT_DATASET_TYPE = "TEXT" TEXT_DATASET_TYPE = "TEXT"
SOURCE_DOCUMENT_FILE_TYPES = {"pdf", "doc", "docx", "xls", "xlsx"} SOURCE_DOCUMENT_FILE_TYPES = {"pdf", "doc", "docx", "xls", "xlsx"}
LABELING_TYPE_CONFIG_KEY = "labeling_type"
@router.get("/{mapping_id}/login") @router.get("/{mapping_id}/login")
async def login_label_studio( async def login_label_studio(
@@ -82,6 +83,7 @@ async def create_mapping(
# 如果提供了模板ID,获取模板配置 # 如果提供了模板ID,获取模板配置
label_config = None label_config = None
template_labeling_type = None
if request.template_id: if request.template_id:
logger.info(f"Using template: {request.template_id}") logger.info(f"Using template: {request.template_id}")
template = await template_service.get_template(db, 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}" detail=f"Template not found: {request.template_id}"
) )
label_config = template.label_config label_config = template.label_config
template_labeling_type = getattr(template, "labeling_type", None)
logger.debug(f"Template label config loaded for template: {template.name}") logger.debug(f"Template label config loaded for template: {template.name}")
# 如果直接提供了 label_config (自定义或修改后的),则覆盖模板配置 # 如果直接提供了 label_config (自定义或修改后的),则覆盖模板配置
@@ -109,6 +112,8 @@ async def create_mapping(
project_configuration["description"] = project_description project_configuration["description"] = project_description
if dataset_type == TEXT_DATASET_TYPE and request.segmentation_enabled is not None: if dataset_type == TEXT_DATASET_TYPE and request.segmentation_enabled is not None:
project_configuration["segmentation_enabled"] = bool(request.segmentation_enabled) project_configuration["segmentation_enabled"] = bool(request.segmentation_enabled)
if template_labeling_type:
project_configuration[LABELING_TYPE_CONFIG_KEY] = template_labeling_type
labeling_project = LabelingProject( labeling_project = LabelingProject(
id=str(uuid.uuid4()), # Generate UUID here id=str(uuid.uuid4()), # Generate UUID here
@@ -441,6 +446,18 @@ async def update_mapping(
if request.template_id is not None: if request.template_id is not None:
update_values["template_id"] = request.template_id 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: if not update_values:
# 没有要更新的字段,直接返回当前数据 # 没有要更新的字段,直接返回当前数据

View File

@@ -65,6 +65,7 @@ class DatasetMappingResponse(BaseModel):
name: Optional[str] = Field(None, description="标注项目名称") name: Optional[str] = Field(None, description="标注项目名称")
description: Optional[str] = Field(None, description="标注项目描述") description: Optional[str] = Field(None, description="标注项目描述")
template_id: Optional[str] = Field(None, alias="templateId", description="关联的模板ID") 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="关联的标注模板详情") template: Optional['AnnotationTemplateResponse'] = Field(None, description="关联的标注模板详情")
label_config: Optional[str] = Field(None, alias="labelConfig", description="实际使用的 Label Studio XML 配置") label_config: Optional[str] = Field(None, alias="labelConfig", description="实际使用的 Label Studio XML 配置")
segmentation_enabled: Optional[bool] = Field( segmentation_enabled: Optional[bool] = Field(

View File

@@ -7,7 +7,7 @@ from datetime import datetime
import uuid import uuid
from app.core.logging import get_logger 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.annotation_management import ANNOTATION_STATUS_IN_PROGRESS
from app.db.models.dataset_management import Dataset, DatasetFiles from app.db.models.dataset_management import Dataset, DatasetFiles
from app.module.annotation.schema import ( from app.module.annotation.schema import (
@@ -18,6 +18,7 @@ from app.module.annotation.schema import (
) )
logger = get_logger(__name__) logger = get_logger(__name__)
LABELING_TYPE_CONFIG_KEY = "labeling_type"
class DatasetMappingService: class DatasetMappingService:
"""数据集映射服务""" """数据集映射服务"""
@@ -106,10 +107,12 @@ class DatasetMappingService:
label_config = None label_config = None
description = None description = None
segmentation_enabled = None segmentation_enabled = None
labeling_type = None
if isinstance(configuration, dict): if isinstance(configuration, dict):
label_config = configuration.get('label_config') label_config = configuration.get('label_config')
description = configuration.get('description') description = configuration.get('description')
segmentation_enabled = configuration.get('segmentation_enabled') segmentation_enabled = configuration.get('segmentation_enabled')
labeling_type = configuration.get(LABELING_TYPE_CONFIG_KEY)
# Optionally fetch full template details # Optionally fetch full template details
template_response = None template_response = None
@@ -119,6 +122,9 @@ class DatasetMappingService:
template_response = await template_service.get_template(self.db, template_id) template_response = await template_service.get_template(self.db, template_id)
logger.debug(f"Included template details for template_id: {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( total_count, annotated_count, in_progress_count = await self._get_project_stats(
mapping.id, mapping.dataset_id mapping.id, mapping.dataset_id
@@ -132,6 +138,7 @@ class DatasetMappingService:
"name": mapping.name, "name": mapping.name,
"description": description, "description": description,
"template_id": template_id, "template_id": template_id,
"labeling_type": labeling_type,
"template": template_response, "template": template_response,
"label_config": label_config, "label_config": label_config,
"segmentation_enabled": segmentation_enabled, "segmentation_enabled": segmentation_enabled,
@@ -174,10 +181,12 @@ class DatasetMappingService:
label_config = None label_config = None
description = None description = None
segmentation_enabled = None segmentation_enabled = None
labeling_type = None
if isinstance(configuration, dict): if isinstance(configuration, dict):
label_config = configuration.get('label_config') label_config = configuration.get('label_config')
description = configuration.get('description') description = configuration.get('description')
segmentation_enabled = configuration.get('segmentation_enabled') segmentation_enabled = configuration.get('segmentation_enabled')
labeling_type = configuration.get(LABELING_TYPE_CONFIG_KEY)
# Optionally fetch full template details # Optionally fetch full template details
template_response = None template_response = None
@@ -187,6 +196,9 @@ class DatasetMappingService:
template_response = await template_service.get_template(self.db, template_id) template_response = await template_service.get_template(self.db, template_id)
logger.debug(f"Included template details for template_id: {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 total_count, annotated_count, in_progress_count = 0, 0, 0
if dataset_id: if dataset_id:
@@ -203,6 +215,7 @@ class DatasetMappingService:
"name": mapping.name, "name": mapping.name,
"description": description, "description": description,
"template_id": template_id, "template_id": template_id,
"labeling_type": labeling_type,
"template": template_response, "template": template_response,
"label_config": label_config, "label_config": label_config,
"segmentation_enabled": segmentation_enabled, "segmentation_enabled": segmentation_enabled,