You've already forked DataMate
feat(annotation): 添加模板示例数据配置功能
- 在模板配置表单中新增示例数据输入区域 - 实现不同数据类型的示例输入框(文本、图片、音频、视频等) - 添加图片类型示例的实时预览功能 - 在模板详情页增加示例数据预览卡片 - 支持多种媒体类型的示例展示(图片、音频、视频、文本) - 更新前后端数据模型以支持exampleData字段 - 添加示例数据的placeholder提示文案
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Modal, Descriptions, Tag, Space, Divider, Card, Typography } from "antd";
|
||||
import { Modal, Descriptions, Tag, Space, Divider, Card, Typography, Image, Empty } from "antd";
|
||||
import type { AnnotationTemplate } from "../annotation.model";
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
@@ -81,6 +81,69 @@ const TemplateDetail: React.FC<TemplateDetailProps> = ({
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* 示例数据预览 */}
|
||||
<Card title="示例数据预览" size="small" style={{ marginBottom: 16 }}>
|
||||
{template.configuration.exampleData &&
|
||||
Object.keys(template.configuration.exampleData).length > 0 ? (
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="middle">
|
||||
{template.configuration.objects.map((obj, index) => {
|
||||
const varName = obj.value?.replace(/^\$/, "") || obj.name;
|
||||
const exampleValue = template.configuration.exampleData?.[varName];
|
||||
|
||||
if (!exampleValue) return null;
|
||||
|
||||
return (
|
||||
<Card key={index} size="small" type="inner">
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
<div>
|
||||
<Text strong>{obj.name}</Text>
|
||||
<Text type="secondary" style={{ marginLeft: 8 }}>
|
||||
({obj.type})
|
||||
</Text>
|
||||
</div>
|
||||
{/* 根据类型渲染不同的预览 */}
|
||||
{obj.type === "Image" ? (
|
||||
<Image
|
||||
src={exampleValue}
|
||||
alt={`示例: ${obj.name}`}
|
||||
style={{ maxHeight: 200, maxWidth: "100%" }}
|
||||
fallback="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgesAAN4TeleOJA8AAAA="
|
||||
/>
|
||||
) : obj.type === "Audio" ? (
|
||||
<audio
|
||||
src={exampleValue}
|
||||
controls
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
) : obj.type === "Video" ? (
|
||||
<video
|
||||
src={exampleValue}
|
||||
controls
|
||||
style={{ maxHeight: 200, maxWidth: "100%" }}
|
||||
/>
|
||||
) : (
|
||||
<Paragraph
|
||||
style={{
|
||||
background: "#f5f5f5",
|
||||
padding: 12,
|
||||
borderRadius: 4,
|
||||
margin: 0,
|
||||
whiteSpace: "pre-wrap",
|
||||
}}
|
||||
>
|
||||
{exampleValue}
|
||||
</Paragraph>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
) : (
|
||||
<Empty description="暂无示例数据" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Card title="标注控件" size="small" style={{ marginBottom: 16 }}>
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="middle">
|
||||
{template.configuration.labels.map((label, index) => (
|
||||
|
||||
@@ -53,6 +53,12 @@ export interface TemplateConfiguration {
|
||||
labels: LabelDefinition[];
|
||||
objects: ObjectDefinition[];
|
||||
metadata?: Record<string, any>;
|
||||
/**
|
||||
* 示例数据,用于模板预览
|
||||
* key: 变量名(不带$前缀,如 "image"、"text")
|
||||
* value: 示例内容(URL 或文本)
|
||||
*/
|
||||
exampleData?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface AnnotationTemplate {
|
||||
|
||||
@@ -8,11 +8,15 @@ import {
|
||||
Divider,
|
||||
Card,
|
||||
Checkbox,
|
||||
Typography,
|
||||
Image,
|
||||
} from "antd";
|
||||
import { PlusOutlined, MinusCircleOutlined } from "@ant-design/icons";
|
||||
import TagSelector from "../Template/components/TagSelector";
|
||||
|
||||
const { Option } = Select;
|
||||
const { TextArea } = Input;
|
||||
const { Text } = Typography;
|
||||
|
||||
interface TemplateConfigurationFormProps {
|
||||
form: any;
|
||||
@@ -27,6 +31,31 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
/** 判断对象类型是否为文本类 */
|
||||
const isTextType = (type: string) => {
|
||||
return ["Text", "HyperText", "Table", "List"].includes(type);
|
||||
};
|
||||
|
||||
/** 判断对象类型是否为图片类 */
|
||||
const isImageType = (type: string) => {
|
||||
return ["Image"].includes(type);
|
||||
};
|
||||
|
||||
/** 获取示例数据输入提示 */
|
||||
const getExamplePlaceholder = (type: string) => {
|
||||
const map: Record<string, string> = {
|
||||
Text: "输入示例文本内容...",
|
||||
HyperText: "输入示例 HTML 内容...",
|
||||
Image: "输入图片 URL,如 https://example.com/image.jpg",
|
||||
Audio: "输入音频 URL,如 https://example.com/audio.mp3",
|
||||
Video: "输入视频 URL,如 https://example.com/video.mp4",
|
||||
Table: "输入示例表格数据(JSON 格式)...",
|
||||
List: "输入示例列表数据(JSON 格式)...",
|
||||
TimeSeries: "输入时间序列数据 URL...",
|
||||
};
|
||||
return map[type] || "输入示例数据...";
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider orientation="left">数据对象</Divider>
|
||||
@@ -287,6 +316,78 @@ const TemplateConfigurationForm: React.FC<TemplateConfigurationFormProps> = ({
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
|
||||
<Divider orientation="left">示例数据(可选)</Divider>
|
||||
<Card size="small" style={{ marginBottom: 16 }}>
|
||||
<Text type="secondary" style={{ display: "block", marginBottom: 16 }}>
|
||||
为数据对象提供示例数据,帮助用户在模板预览时直观了解标注效果
|
||||
</Text>
|
||||
<Form.Item noStyle shouldUpdate={(prev, curr) => prev.objects !== curr.objects}>
|
||||
{({ getFieldValue }) => {
|
||||
const objects = getFieldValue("objects") || [];
|
||||
if (objects.length === 0) {
|
||||
return (
|
||||
<Text type="secondary">请先添加数据对象</Text>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="middle">
|
||||
{objects.map((obj: any, index: number) => {
|
||||
if (!obj?.name) return null;
|
||||
const varName = obj.value?.replace(/^\$/, "") || obj.name;
|
||||
const objType = obj.type || "Text";
|
||||
|
||||
return (
|
||||
<Card key={index} size="small" type="inner">
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
<div>
|
||||
<Text strong>{obj.name}</Text>
|
||||
<Text type="secondary" style={{ marginLeft: 8 }}>
|
||||
({objType})
|
||||
</Text>
|
||||
</div>
|
||||
<Form.Item
|
||||
name={["exampleData", varName]}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
{isTextType(objType) ? (
|
||||
<TextArea
|
||||
rows={3}
|
||||
placeholder={getExamplePlaceholder(objType)}
|
||||
/>
|
||||
) : (
|
||||
<Input placeholder={getExamplePlaceholder(objType)} />
|
||||
)}
|
||||
</Form.Item>
|
||||
{/* 图片类型显示预览 */}
|
||||
<Form.Item noStyle shouldUpdate>
|
||||
{({ getFieldValue: getVal }) => {
|
||||
const url = getVal(["exampleData", varName]);
|
||||
if (isImageType(objType) && url) {
|
||||
return (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>预览:</Text>
|
||||
<Image
|
||||
src={url}
|
||||
alt="示例图片预览"
|
||||
style={{ maxHeight: 150, maxWidth: "100%", marginTop: 4 }}
|
||||
fallback="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgesAAN4TeleOJA8AAAA="
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,7 +35,12 @@ class TemplateConfiguration(BaseModel):
|
||||
labels: List[LabelDefinition] = Field(description="标签定义列表")
|
||||
objects: List[ObjectDefinition] = Field(description="对象定义列表")
|
||||
metadata: Optional[Dict[str, Any]] = Field(None, description="额外元数据")
|
||||
|
||||
example_data: Optional[Dict[str, str]] = Field(
|
||||
None,
|
||||
alias="exampleData",
|
||||
description="示例数据,key 为变量名(不带$),value 为示例内容"
|
||||
)
|
||||
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user