You've already forked DataMate
feat(annotation): 优化标注模板生成和配置验证
- 添加文本对象类型集合用于模板类型判断 - 将XML生成部分拆分为对象和控件两个独立部分 - 为文本类模板添加响应式布局支持长文本标注 - 修复配置验证器中对象和控件查找逻辑 - 优化标签控件在长文本场景下的显示位置
This commit is contained in:
@@ -36,7 +36,7 @@ class AnnotationTemplateService:
|
|||||||
"""
|
"""
|
||||||
tag_config = LabelStudioTagConfig()
|
tag_config = LabelStudioTagConfig()
|
||||||
control_types = tag_config.get_control_types()
|
control_types = tag_config.get_control_types()
|
||||||
xml_parts = ['<View>']
|
text_object_types = {"Text", "Paragraphs", "ParagraphLabels", "HyperText", "Markdown"}
|
||||||
|
|
||||||
def normalize_control_type(raw: Optional[str]) -> str:
|
def normalize_control_type(raw: Optional[str]) -> str:
|
||||||
if not raw:
|
if not raw:
|
||||||
@@ -49,15 +49,19 @@ class AnnotationTemplateService:
|
|||||||
return ct
|
return ct
|
||||||
return raw
|
return raw
|
||||||
|
|
||||||
|
is_text_template = any((obj.type in text_object_types) for obj in config.objects)
|
||||||
|
|
||||||
# 生成对象定义
|
# 生成对象定义
|
||||||
|
object_parts: List[str] = []
|
||||||
for obj in config.objects:
|
for obj in config.objects:
|
||||||
obj_attrs = [
|
obj_attrs = [
|
||||||
f'name="{obj.name}"',
|
f'name="{obj.name}"',
|
||||||
f'value="{obj.value}"'
|
f'value="{obj.value}"'
|
||||||
]
|
]
|
||||||
xml_parts.append(f' <{obj.type} {" ".join(obj_attrs)}/>')
|
object_parts.append(f' <{obj.type} {" ".join(obj_attrs)}/>')
|
||||||
|
|
||||||
# 生成标签定义
|
# 生成标签定义
|
||||||
|
control_parts: List[str] = []
|
||||||
for label in config.labels:
|
for label in config.labels:
|
||||||
label_attrs = [f'name="{label.from_name}"', f'toName="{label.to_name}"']
|
label_attrs = [f'name="{label.from_name}"', f'toName="{label.to_name}"']
|
||||||
|
|
||||||
@@ -74,7 +78,7 @@ class AnnotationTemplateService:
|
|||||||
# 检查是否需要子元素
|
# 检查是否需要子元素
|
||||||
if label.options or label.labels:
|
if label.options or label.labels:
|
||||||
choices = label.options or label.labels or []
|
choices = label.options or label.labels or []
|
||||||
xml_parts.append(f' <{tag_type} {" ".join(label_attrs)}>')
|
control_parts.append(f' <{tag_type} {" ".join(label_attrs)}>')
|
||||||
|
|
||||||
# 从配置获取子元素标签名
|
# 从配置获取子元素标签名
|
||||||
child_tag = tag_config.get_child_tag(tag_type)
|
child_tag = tag_config.get_child_tag(tag_type)
|
||||||
@@ -83,12 +87,29 @@ class AnnotationTemplateService:
|
|||||||
child_tag = "Label"
|
child_tag = "Label"
|
||||||
|
|
||||||
for choice in choices:
|
for choice in choices:
|
||||||
xml_parts.append(f' <{child_tag} value="{choice}"/>')
|
control_parts.append(f' <{child_tag} value="{choice}"/>')
|
||||||
xml_parts.append(f' </{tag_type}>')
|
control_parts.append(f' </{tag_type}>')
|
||||||
else:
|
else:
|
||||||
# 处理简单标签类型(不需要子元素)
|
# 处理简单标签类型(不需要子元素)
|
||||||
xml_parts.append(f' <{tag_type} {" ".join(label_attrs)}/>')
|
control_parts.append(f' <{tag_type} {" ".join(label_attrs)}/>')
|
||||||
|
|
||||||
|
if is_text_template:
|
||||||
|
# 长文本优化:将标签控件固定在右侧(sticky),避免需要滚动到页面底部才能看到控件
|
||||||
|
xml_parts = [
|
||||||
|
'<View style="display: flex; flex-direction: row; align-items: flex-start; gap: 12px;">',
|
||||||
|
' <View style="flex: 1; min-width: 0;">',
|
||||||
|
*object_parts,
|
||||||
|
' </View>',
|
||||||
|
' <View style="width: 320px; position: sticky; top: 0; align-self: flex-start; max-height: 90vh; overflow: auto; background: #fff; padding: 12px; border: 1px solid #f0f0f0; border-radius: 6px;">',
|
||||||
|
*control_parts,
|
||||||
|
' </View>',
|
||||||
|
'</View>',
|
||||||
|
]
|
||||||
|
return '\n'.join(xml_parts)
|
||||||
|
|
||||||
|
xml_parts = ['<View>']
|
||||||
|
xml_parts.extend(object_parts)
|
||||||
|
xml_parts.extend(control_parts)
|
||||||
xml_parts.append('</View>')
|
xml_parts.append('</View>')
|
||||||
return '\n'.join(xml_parts)
|
return '\n'.join(xml_parts)
|
||||||
|
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ class LabelStudioConfigValidator:
|
|||||||
|
|
||||||
# 检查是否有对象定义
|
# 检查是否有对象定义
|
||||||
object_types = config.get_object_types()
|
object_types = config.get_object_types()
|
||||||
objects = [child for child in root if child.tag in object_types]
|
objects = [elem for elem in root.iter() if elem is not root and elem.tag in object_types]
|
||||||
if not objects:
|
if not objects:
|
||||||
return False, "No data objects (Image, Text, etc.) found"
|
return False, "No data objects (Image, Text, etc.) found"
|
||||||
|
|
||||||
# 检查是否有控件定义
|
# 检查是否有控件定义
|
||||||
control_types = config.get_control_types()
|
control_types = config.get_control_types()
|
||||||
controls = [child for child in root if child.tag in control_types]
|
controls = [elem for elem in root.iter() if elem is not root and elem.tag in control_types]
|
||||||
if not controls:
|
if not controls:
|
||||||
return False, "No annotation controls found"
|
return False, "No annotation controls found"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user