import { useEffect, useMemo, useState } from "react"; import { Button, Input, Select, Form, message, Radio } from "antd"; import type { RadioChangeEvent } from "antd"; import TextArea from "antd/es/input/TextArea"; import { DatabaseOutlined } from "@ant-design/icons"; import { Link, useNavigate } from "react-router"; import { ArrowLeft } from "lucide-react"; import { queryDatasetsUsingGet } from "../../DataManagement/dataset.api"; import { mapDataset } from "@/pages/DataManagement/dataset.const"; import { DatasetType, type Dataset } from "@/pages/DataManagement/dataset.model"; import { createAnnotationTaskUsingPost, queryAnnotationTemplatesUsingGet, } from "../annotation.api"; import { DataType, type AnnotationTemplate } from "../annotation.model"; import TemplateConfigurationTreeEditor from "../components/TemplateConfigurationTreeEditor"; const DEFAULT_SEGMENTATION_ENABLED = true; const SEGMENTATION_OPTIONS = [ { label: "需要切片段", value: true }, { label: "不需要切片段", value: false }, ]; const resolveTemplateDataType = (datasetType?: DatasetType) => { switch (datasetType) { case DatasetType.TEXT: return DataType.TEXT; case DatasetType.IMAGE: return DataType.IMAGE; case DatasetType.AUDIO: return DataType.AUDIO; case DatasetType.VIDEO: return DataType.VIDEO; default: return undefined; } }; const resolveTemplateTimestamp = (template: AnnotationTemplate) => { const timestamp = template.updatedAt || template.createdAt; const parsed = Date.parse(timestamp); return Number.isNaN(parsed) ? 0 : parsed; }; const resolveDefaultTemplate = (items: AnnotationTemplate[]) => items.reduce((latest, current) => { if (!latest) { return current; } return resolveTemplateTimestamp(current) > resolveTemplateTimestamp(latest) ? current : latest; }, undefined); export default function AnnotationTaskCreate() { const navigate = useNavigate(); const [form] = Form.useForm(); const [datasets, setDatasets] = useState([]); const [templates, setTemplates] = useState([]); const [selectedDatasetId, setSelectedDatasetId] = useState(null); const [labelConfig, setLabelConfig] = useState(""); const [configMode, setConfigMode] = useState<"template" | "custom">("template"); const [submitting, setSubmitting] = useState(false); const selectedDataset = useMemo( () => datasets.find((dataset) => dataset.id === selectedDatasetId), [datasets, selectedDatasetId] ); const isTextDataset = selectedDataset?.datasetType === DatasetType.TEXT; const fetchDatasets = async () => { try { const { data } = await queryDatasetsUsingGet({ page: 0, pageSize: 1000 }); const list = data?.content || []; setDatasets(list.map((item) => mapDataset(item)) || []); } catch (error) { console.error("加载数据集失败:", error); message.error("加载数据集失败"); } }; const fetchTemplates = async (dataType?: string) => { if (!dataType) { setTemplates([]); return; } try { const response = await queryAnnotationTemplatesUsingGet({ page: 1, size: 200, dataType, }); if (response.code === 200 && response.data) { setTemplates(response.data.content || []); } else { message.error(response.message || "加载模板失败"); } } catch (error) { console.error("加载模板失败:", error); message.error("加载模板失败"); } }; useEffect(() => { fetchDatasets(); }, []); useEffect(() => { if (!selectedDataset) { setTemplates([]); form.setFieldsValue({ templateId: undefined }); setLabelConfig(""); return; } const dataType = resolveTemplateDataType(selectedDataset.datasetType); fetchTemplates(dataType); }, [form, selectedDataset]); useEffect(() => { if (configMode !== "template" || !selectedDataset) { return; } if (templates.length === 0) { form.setFieldsValue({ templateId: undefined }); setLabelConfig(""); return; } const currentTemplateId = form.getFieldValue("templateId"); const currentTemplate = templates.find((template) => template.id === currentTemplateId); if (currentTemplate) { return; } const defaultTemplate = resolveDefaultTemplate(templates); if (defaultTemplate) { form.setFieldsValue({ templateId: defaultTemplate.id }); setLabelConfig(defaultTemplate.labelConfig || ""); } }, [configMode, form, selectedDataset, templates]); const handleTemplateSelect = (value?: string) => { if (!value) { setLabelConfig(""); return; } const selectedTemplate = templates.find((template) => template.id === value); setLabelConfig(selectedTemplate?.labelConfig || ""); }; const handleConfigModeChange = (e: RadioChangeEvent) => { const mode = e.target.value; setConfigMode(mode); if (mode === "custom") { form.setFieldsValue({ templateId: undefined }); } }; const handleSubmit = async () => { try { const values = await form.validateFields(); if (!labelConfig.trim()) { message.error("请配置标注模板"); return; } setSubmitting(true); const requestData: Record = { name: values.name, description: values.description, datasetId: values.datasetId, templateId: configMode === "template" ? values.templateId : undefined, labelConfig: labelConfig.trim(), }; if (isTextDataset) { requestData.segmentationEnabled = values.segmentationEnabled ?? DEFAULT_SEGMENTATION_ENABLED; } await createAnnotationTaskUsingPost(requestData); message.success("标注任务创建成功"); navigate("/data/annotation"); } catch (error: unknown) { const err = error as { errorFields?: unknown; message?: string; data?: { message?: string } }; if (err?.errorFields) { message.error("请完善必填信息"); } else { const msg = err?.message || err?.data?.message || "创建失败,请稍后重试"; message.error(msg); console.error(error); } } finally { setSubmitting(false); } }; return (

创建标注任务

基本信息