diff --git a/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx b/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx index 8ae25dc..3003ee4 100644 --- a/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx +++ b/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx @@ -121,18 +121,29 @@ export default function CreateAnnotationTask({ }); setSelectedDatasetId(taskDetail.datasetId); - // 填充模板配置 - if (taskDetail.configuration) { - const { objects, labels } = taskDetail.configuration; + // 配置可能在 template.configuration 或 taskDetail.configuration + const configuration = taskDetail.template?.configuration || taskDetail.configuration; + const labelConfig = taskDetail.template?.labelConfig || taskDetail.labelConfig; + + // 设置 XML 配置用于预览 + if (labelConfig) { + setCustomXml(labelConfig); + } + + // 填充模板配置:优先使用 configuration,否则从 labelConfig 解析 + if (configuration && configuration.objects?.length > 0) { + const { objects, labels } = configuration; manualForm.setFieldsValue({ objects: objects || [], labels: labels || [], }); - } - - // 设置 XML 配置用于预览 - if (taskDetail.labelConfig) { - setCustomXml(taskDetail.labelConfig); + } else if (labelConfig) { + // 从 XML 解析配置 + const parsed = parseXmlToConfig(labelConfig); + manualForm.setFieldsValue({ + objects: parsed.objects, + labels: parsed.labels, + }); } // 编辑模式始终使用 custom 配置模式(不改变结构,只改标签) @@ -243,6 +254,80 @@ export default function CreateAnnotationTask({ } }; + // 从 Label Studio XML 配置解析出 objects 和 labels + const parseXmlToConfig = (xml: string): { objects: any[], labels: any[] } => { + const objects: any[] = []; + const labels: any[] = []; + + try { + const parser = new DOMParser(); + const doc = parser.parseFromString(xml, "text/xml"); + + // 数据对象类型列表 + const objectTypes = ["Image", "Text", "Audio", "Video", "HyperText", "Header", "Paragraphs", "TimeSeries", "TimeSeriesChannel"]; + // 标签控件类型列表 + const controlTypes = ["Choices", "Labels", "RectangleLabels", "PolygonLabels", "EllipseLabels", "KeyPointLabels", "BrushLabels", "TextArea", "Number", "DateTime", "Rating", "Taxonomy"]; + + // 解析数据对象 + objectTypes.forEach(type => { + const elements = doc.getElementsByTagName(type); + for (let i = 0; i < elements.length; i++) { + const el = elements[i]; + const name = el.getAttribute("name") || ""; + const value = el.getAttribute("value") || ""; + if (name) { + objects.push({ name, type, value }); + } + } + }); + + // 解析标签控件 + controlTypes.forEach(type => { + const elements = doc.getElementsByTagName(type); + for (let i = 0; i < elements.length; i++) { + const el = elements[i]; + const fromName = el.getAttribute("name") || ""; + const toName = el.getAttribute("toName") || ""; + const required = el.getAttribute("required") === "true"; + + if (fromName) { + const label: any = { + fromName, + toName, + type, + required, + }; + + // 解析选项/标签值 + if (type === "Choices") { + const choices: string[] = []; + const choiceElements = el.getElementsByTagName("Choice"); + for (let j = 0; j < choiceElements.length; j++) { + const value = choiceElements[j].getAttribute("value"); + if (value) choices.push(value); + } + label.options = choices; + } else if (["Labels", "RectangleLabels", "PolygonLabels", "EllipseLabels", "KeyPointLabels", "BrushLabels"].includes(type)) { + const labelValues: string[] = []; + const labelElements = el.getElementsByTagName("Label"); + for (let j = 0; j < labelElements.length; j++) { + const value = labelElements[j].getAttribute("value"); + if (value) labelValues.push(value); + } + label.labels = labelValues; + } + + labels.push(label); + } + } + }); + } catch (e) { + console.error("Failed to parse XML config:", e); + } + + return { objects, labels }; + }; + const generateXmlFromConfig = (objects: any[], labels: any[]) => { let xml = '\n';