diff --git a/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx b/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx index 3003ee4..e9f2b1e 100644 --- a/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx +++ b/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx @@ -121,24 +121,13 @@ export default function CreateAnnotationTask({ }); setSelectedDatasetId(taskDetail.datasetId); - // 配置可能在 template.configuration 或 taskDetail.configuration - const configuration = taskDetail.template?.configuration || taskDetail.configuration; - const labelConfig = taskDetail.template?.labelConfig || taskDetail.labelConfig; + // 获取实际的 labelConfig(优先使用任务自身的配置,回退到模板配置) + const labelConfig = taskDetail.labelConfig || taskDetail.template?.labelConfig; // 设置 XML 配置用于预览 if (labelConfig) { setCustomXml(labelConfig); - } - - // 填充模板配置:优先使用 configuration,否则从 labelConfig 解析 - if (configuration && configuration.objects?.length > 0) { - const { objects, labels } = configuration; - manualForm.setFieldsValue({ - objects: objects || [], - labels: labels || [], - }); - } else if (labelConfig) { - // 从 XML 解析配置 + // 始终从 XML 解析配置,确保数据一致性 const parsed = parseXmlToConfig(labelConfig); manualForm.setFieldsValue({ objects: parsed.objects, diff --git a/runtime/datamate-python/app/module/annotation/schema/mapping.py b/runtime/datamate-python/app/module/annotation/schema/mapping.py index 3e2cec3..87e3a9b 100644 --- a/runtime/datamate-python/app/module/annotation/schema/mapping.py +++ b/runtime/datamate-python/app/module/annotation/schema/mapping.py @@ -48,10 +48,11 @@ class DatasetMappingResponse(BaseModel): description: Optional[str] = Field(None, description="标注项目描述") template_id: Optional[str] = Field(None, alias="templateId", description="关联的模板ID") template: Optional['AnnotationTemplateResponse'] = Field(None, description="关联的标注模板详情") + label_config: Optional[str] = Field(None, alias="labelConfig", description="实际使用的 Label Studio XML 配置") created_at: datetime = Field(..., alias="createdAt", description="创建时间") updated_at: Optional[datetime] = Field(None, alias="updatedAt", description="更新时间") deleted_at: Optional[datetime] = Field(None, alias="deletedAt", description="删除时间") - + class Config: from_attributes = True populate_by_name = True diff --git a/runtime/datamate-python/app/module/annotation/service/mapping.py b/runtime/datamate-python/app/module/annotation/service/mapping.py index 43a627c..18b788a 100644 --- a/runtime/datamate-python/app/module/annotation/service/mapping.py +++ b/runtime/datamate-python/app/module/annotation/service/mapping.py @@ -35,23 +35,28 @@ class DatasetMappingService: ) async def _to_response_from_row( - self, - row, + self, + row, include_template: bool = False ) -> DatasetMappingResponse: """ Convert query row (mapping + dataset_name) to response - + Args: row: Query result row containing (LabelingProject, dataset_name) include_template: If True, fetch and include full template details """ mapping = row[0] # LabelingProject object dataset_name = row[1] # dataset_name from join - + # Get template_id from mapping template_id = getattr(mapping, 'template_id', None) - + + # 从 configuration JSON 字段中提取 label_config 和 description + configuration = getattr(mapping, 'configuration', None) or {} + label_config = configuration.get('label_config') if isinstance(configuration, dict) else None + description = configuration.get('description') if isinstance(configuration, dict) else None + # Optionally fetch full template details template_response = None if include_template and template_id: @@ -59,31 +64,32 @@ class DatasetMappingService: template_service = AnnotationTemplateService() template_response = await template_service.get_template(self.db, template_id) logger.debug(f"Included template details for template_id: {template_id}") - + response_data = { "id": mapping.id, "dataset_id": mapping.dataset_id, "dataset_name": dataset_name, "labeling_project_id": mapping.labeling_project_id, "name": mapping.name, - "description": getattr(mapping, 'description', None), + "description": description, "template_id": template_id, "template": template_response, + "label_config": label_config, "created_at": mapping.created_at, "updated_at": mapping.updated_at, "deleted_at": mapping.deleted_at, } - + return DatasetMappingResponse(**response_data) async def _to_response( - self, - mapping: LabelingProject, + self, + mapping: LabelingProject, include_template: bool = False ) -> DatasetMappingResponse: """ Convert ORM model to response with dataset name (for single entity operations) - + Args: mapping: LabelingProject ORM object include_template: If True, fetch and include full template details @@ -96,10 +102,15 @@ class DatasetMappingService: select(Dataset.name).where(Dataset.id == dataset_id) ) dataset_name = dataset_result.scalar_one_or_none() - + # Get template_id from mapping template_id = getattr(mapping, 'template_id', None) - + + # 从 configuration JSON 字段中提取 label_config 和 description + configuration = getattr(mapping, 'configuration', None) or {} + label_config = configuration.get('label_config') if isinstance(configuration, dict) else None + description = configuration.get('description') if isinstance(configuration, dict) else None + # Optionally fetch full template details template_response = None if include_template and template_id: @@ -107,7 +118,7 @@ class DatasetMappingService: template_service = AnnotationTemplateService() template_response = await template_service.get_template(self.db, template_id) logger.debug(f"Included template details for template_id: {template_id}") - + # Create response dict with all fields response_data = { "id": mapping.id, @@ -115,14 +126,15 @@ class DatasetMappingService: "dataset_name": dataset_name, "labeling_project_id": mapping.labeling_project_id, "name": mapping.name, - "description": getattr(mapping, 'description', None), + "description": description, "template_id": template_id, "template": template_response, + "label_config": label_config, "created_at": mapping.created_at, "updated_at": mapping.updated_at, "deleted_at": mapping.deleted_at, } - + return DatasetMappingResponse(**response_data) async def create_mapping(