From c5ccc56ccaa72cb8ff119cb9b915bfc2c6bb8262 Mon Sep 17 00:00:00 2001 From: Jason Wang <56037774+JasonW404@users.noreply.github.com> Date: Tue, 11 Nov 2025 09:14:14 +0800 Subject: [PATCH] feat: Add labeling template (#72) * feat: Enhance annotation module with template management and validation - Added DatasetMappingCreateRequest and DatasetMappingUpdateRequest schemas to handle dataset mapping requests with camelCase and snake_case support. - Introduced Annotation Template schemas including CreateAnnotationTemplateRequest, UpdateAnnotationTemplateRequest, and AnnotationTemplateResponse for managing annotation templates. - Implemented AnnotationTemplateService for creating, updating, retrieving, and deleting annotation templates, including validation of configurations and XML generation. - Added utility class LabelStudioConfigValidator for validating Label Studio configurations and XML formats. - Updated database schema for annotation templates and labeling projects to include new fields and constraints. - Seeded initial annotation templates for various use cases including image classification, object detection, and text classification. * feat: Enhance TemplateForm with improved validation and dynamic field rendering; update LabelStudio config validation for camelCase support * feat: Update docker-compose.yml to mark datamate dataset volume and network as external --- .../docker/label-studio/docker-compose.yml | 4 +- .../components/CreateAnnotationTaskDialog.tsx | 195 ++++++++ .../components/CreateAnnptationTaskDialog.tsx | 150 +++--- .../DataAnnotation/Home/DataAnnotation.tsx | 174 ++++--- .../Template/TemplateDetail.tsx | 152 ++++++ .../DataAnnotation/Template/TemplateForm.tsx | 454 ++++++++++++++++++ .../DataAnnotation/Template/TemplateList.tsx | 393 +++++++++++++++ .../pages/DataAnnotation/Template/index.ts | 3 + .../pages/DataAnnotation/annotation.api.ts | 10 +- .../pages/DataAnnotation/annotation.const.tsx | 5 +- .../pages/DataAnnotation/annotation.model.ts | 53 +- frontend/src/routes/routes.ts | 27 -- .../app/db/models/annotation_management.py | 35 +- .../annotation/client/labelstudio/client.py | 23 +- .../module/annotation/interface/__init__.py | 4 +- .../module/annotation/interface/project.py | 21 + .../module/annotation/interface/template.py | 137 ++++++ .../app/module/annotation/schema/mapping.py | 16 +- .../app/module/annotation/schema/template.py | 93 ++++ .../app/module/annotation/service/mapping.py | 120 +++-- .../app/module/annotation/service/template.py | 327 +++++++++++++ .../app/module/annotation/utils/__init__.py | 6 + .../annotation/utils/config_validator.py | 263 ++++++++++ scripts/db/data-annotation-init.sql | 382 ++++++++++++++- 24 files changed, 2794 insertions(+), 253 deletions(-) create mode 100644 frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx create mode 100644 frontend/src/pages/DataAnnotation/Template/TemplateDetail.tsx create mode 100644 frontend/src/pages/DataAnnotation/Template/TemplateForm.tsx create mode 100644 frontend/src/pages/DataAnnotation/Template/TemplateList.tsx create mode 100644 frontend/src/pages/DataAnnotation/Template/index.ts create mode 100644 runtime/datamate-python/app/module/annotation/interface/template.py create mode 100644 runtime/datamate-python/app/module/annotation/schema/template.py create mode 100644 runtime/datamate-python/app/module/annotation/service/template.py create mode 100644 runtime/datamate-python/app/module/annotation/utils/__init__.py create mode 100644 runtime/datamate-python/app/module/annotation/utils/config_validator.py diff --git a/deployment/docker/label-studio/docker-compose.yml b/deployment/docker/label-studio/docker-compose.yml index 4026b6a..b45eeaf 100644 --- a/deployment/docker/label-studio/docker-compose.yml +++ b/deployment/docker/label-studio/docker-compose.yml @@ -52,8 +52,10 @@ volumes: label-studio-db: dataset_volume: name: datamate-dataset-volume + external: true networks: datamate: driver: bridge - name: datamate-network \ No newline at end of file + name: datamate-network + external: true \ No newline at end of file diff --git a/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx b/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx new file mode 100644 index 0000000..9f9ddbe --- /dev/null +++ b/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx @@ -0,0 +1,195 @@ +import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api"; +import { mapDataset } from "@/pages/DataManagement/dataset.const"; +import { Button, Form, Input, Modal, Select, message } from "antd"; +import TextArea from "antd/es/input/TextArea"; +import { useEffect, useState } from "react"; +import { createAnnotationTaskUsingPost, queryAnnotationTemplatesUsingGet } from "../../annotation.api"; +import { Dataset } from "@/pages/DataManagement/dataset.model"; +import type { AnnotationTemplate } from "../../annotation.model"; + +export default function CreateAnnotationTask({ + open, + onClose, + onRefresh, +}: { + open: boolean; + onClose: () => void; + onRefresh: () => void; +}) { + const [form] = Form.useForm(); + const [datasets, setDatasets] = useState([]); + const [templates, setTemplates] = useState([]); + const [submitting, setSubmitting] = useState(false); + const [nameManuallyEdited, setNameManuallyEdited] = useState(false); + + useEffect(() => { + if (!open) return; + const fetchData = async () => { + try { + // Fetch datasets + const { data: datasetData } = await queryDatasetsUsingGet({ + page: 0, + size: 1000, + }); + setDatasets(datasetData.content.map(mapDataset) || []); + + // Fetch templates + const templateResponse = await queryAnnotationTemplatesUsingGet({ + page: 1, + size: 100, // Backend max is 100 + }); + + // The API returns: {code, message, data: {content, total, page, ...}} + if (templateResponse.code === 200 && templateResponse.data) { + const fetchedTemplates = templateResponse.data.content || []; + console.log("Fetched templates:", fetchedTemplates); + setTemplates(fetchedTemplates); + } else { + console.error("Failed to fetch templates:", templateResponse); + setTemplates([]); + } + } catch (error) { + console.error("Error fetching data:", error); + setTemplates([]); + } + }; + fetchData(); + }, [open]); + + // Reset form and manual-edit flag when modal opens + useEffect(() => { + if (open) { + form.resetFields(); + setNameManuallyEdited(false); + } + }, [open, form]); + + const handleSubmit = async () => { + try { + const values = await form.validateFields(); + setSubmitting(true); + + // Send templateId instead of labelingConfig + const requestData = { + name: values.name, + description: values.description, + datasetId: values.datasetId, + templateId: values.templateId, + }; + + await createAnnotationTaskUsingPost(requestData); + message?.success?.("创建标注任务成功"); + onClose(); + onRefresh(); + } catch (err: any) { + console.error("Create annotation task failed", err); + const msg = err?.message || err?.data?.message || "创建失败,请稍后重试"; + (message as any)?.error?.(msg); + } finally { + setSubmitting(false); + } + }; + + return ( + + + + + } + width={800} + > +
+ {/* 数据集 与 标注工程名称 并排显示(数据集在左) */} +
+ + setNameManuallyEdited(true)} + /> + +
+ + {/* 描述变为可选 */} + +