You've already forked DataMate
feat(annotation): 添加标注任务预览功能
- 新增 previewTaskData 状态管理预览数据 - 实现 generateExampleData 函数根据对象配置生成示例数据 - 支持多种数据类型(图片、音频、视频、文本等)的示例生成 - 优化预览按钮逻辑,自动生成适配的示例数据 - 移除固定示例数据,使用动态生成的数据进行预览 - 调整模板列表组件的分页参数以修复数据获取问题
This commit is contained in:
@@ -116,10 +116,11 @@ export default function CreateAnnotationTask({
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [nameManuallyEdited, setNameManuallyEdited] = useState(false);
|
||||
const [activeMode, setActiveMode] = useState<"manual" | "auto">("manual");
|
||||
|
||||
|
||||
// Custom template state
|
||||
const [customXml, setCustomXml] = useState("");
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
const [previewTaskData, setPreviewTaskData] = useState<Record<string, any>>({});
|
||||
const [configMode, setConfigMode] = useState<"template" | "custom">("template");
|
||||
const [templateEditTab, setTemplateEditTab] = useState<"visual" | "xml">("visual");
|
||||
|
||||
@@ -174,6 +175,7 @@ export default function CreateAnnotationTask({
|
||||
setImageFileCount(0);
|
||||
setCustomXml("");
|
||||
setShowPreview(false);
|
||||
setPreviewTaskData({});
|
||||
setConfigMode("template");
|
||||
setTemplateEditTab("visual");
|
||||
}
|
||||
@@ -190,7 +192,7 @@ export default function CreateAnnotationTask({
|
||||
|
||||
const generateXmlFromConfig = (objects: any[], labels: any[]) => {
|
||||
let xml = '<View>\n';
|
||||
|
||||
|
||||
// Objects
|
||||
if (objects) {
|
||||
objects.forEach((obj: any) => {
|
||||
@@ -203,9 +205,9 @@ export default function CreateAnnotationTask({
|
||||
labels.forEach((lbl: any) => {
|
||||
let attrs = `name="${lbl.fromName}" toName="${lbl.toName}"`;
|
||||
if (lbl.required) attrs += ' required="true"';
|
||||
|
||||
|
||||
xml += ` <${lbl.type} ${attrs}>\n`;
|
||||
|
||||
|
||||
const options = lbl.type === 'Choices' ? lbl.options : lbl.labels;
|
||||
if (options && options.length) {
|
||||
options.forEach((opt: string) => {
|
||||
@@ -234,6 +236,58 @@ export default function CreateAnnotationTask({
|
||||
}
|
||||
};
|
||||
|
||||
// 根据 objects 配置生成预览用的示例数据
|
||||
const generateExampleData = (objects: any[]) => {
|
||||
const exampleUrls: Record<string, string> = {
|
||||
Image: "https://labelstud.io/images/opa-header.png",
|
||||
Audio: "https://labelstud.io/files/sample.wav",
|
||||
Video: "https://labelstud.io/files/sample.mp4",
|
||||
};
|
||||
const exampleTexts: Record<string, string> = {
|
||||
Text: "这是示例文本,用于预览标注界面。",
|
||||
HyperText: "<p>这是示例 HTML 内容</p>",
|
||||
Header: "示例标题",
|
||||
Paragraphs: "段落一\n\n段落二\n\n段落三",
|
||||
};
|
||||
|
||||
const data: Record<string, any> = {};
|
||||
|
||||
if (!objects || objects.length === 0) {
|
||||
// 默认数据
|
||||
return {
|
||||
image: exampleUrls.Image,
|
||||
text: exampleTexts.Text,
|
||||
audio: exampleUrls.Audio,
|
||||
};
|
||||
}
|
||||
|
||||
objects.forEach((obj: any) => {
|
||||
if (!obj?.name || !obj?.value) return;
|
||||
// 变量名从 $varName 中提取
|
||||
const varName = obj.value.startsWith("$") ? obj.value.slice(1) : obj.name;
|
||||
|
||||
if (exampleUrls[obj.type]) {
|
||||
data[varName] = exampleUrls[obj.type];
|
||||
} else if (exampleTexts[obj.type]) {
|
||||
data[varName] = exampleTexts[obj.type];
|
||||
} else {
|
||||
// 未知类型,尝试根据名称猜测
|
||||
const lowerName = varName.toLowerCase();
|
||||
if (lowerName.includes("image") || lowerName.includes("img")) {
|
||||
data[varName] = exampleUrls.Image;
|
||||
} else if (lowerName.includes("audio") || lowerName.includes("sound")) {
|
||||
data[varName] = exampleUrls.Audio;
|
||||
} else if (lowerName.includes("video")) {
|
||||
data[varName] = exampleUrls.Video;
|
||||
} else {
|
||||
data[varName] = exampleTexts.Text;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
// 当选择模板时,加载模板配置到表单
|
||||
const handleTemplateSelect = (value: string, option: any) => {
|
||||
if (option && option.config) {
|
||||
@@ -483,7 +537,7 @@ export default function CreateAnnotationTask({
|
||||
{/* 标注模板选择 */}
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-gray-700 after:content-['*'] after:text-red-500 after:ml-1">标注配置</span>
|
||||
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Radio.Group value={configMode} onChange={handleConfigModeChange} size="small" buttonStyle="solid">
|
||||
<Radio.Button value="template">现有模板</Radio.Button>
|
||||
@@ -494,18 +548,19 @@ export default function CreateAnnotationTask({
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
// 如果在可视化模式,先同步生成 XML
|
||||
if (configMode === 'template' && templateEditTab === 'visual') {
|
||||
syncFormToXml();
|
||||
} else if (configMode === 'custom') {
|
||||
// 自定义模式也从表单生成
|
||||
const objects = manualForm.getFieldValue("objects");
|
||||
const labels = manualForm.getFieldValue("labels");
|
||||
if (objects && objects.length > 0) {
|
||||
const xml = generateXmlFromConfig(objects, labels || []);
|
||||
setCustomXml(xml);
|
||||
}
|
||||
const objects = manualForm.getFieldValue("objects");
|
||||
const labels = manualForm.getFieldValue("labels");
|
||||
|
||||
// 生成 XML
|
||||
if (objects && objects.length > 0) {
|
||||
const xml = generateXmlFromConfig(objects, labels || []);
|
||||
setCustomXml(xml);
|
||||
}
|
||||
|
||||
// 生成适配的示例数据
|
||||
const exampleData = generateExampleData(objects);
|
||||
setPreviewTaskData(exampleData);
|
||||
|
||||
setShowPreview(true);
|
||||
}}
|
||||
>
|
||||
@@ -513,7 +568,7 @@ export default function CreateAnnotationTask({
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{configMode === 'template' ? (
|
||||
<div className="bg-gray-50 p-4 rounded-md border border-gray-200">
|
||||
<Form.Item
|
||||
@@ -713,15 +768,11 @@ export default function CreateAnnotationTask({
|
||||
>
|
||||
<div style={{ height: '600px', overflow: 'hidden' }}>
|
||||
{showPreview && (
|
||||
<LabelStudioEmbed
|
||||
<LabelStudioEmbed
|
||||
config={customXml}
|
||||
task={{
|
||||
id: 1,
|
||||
data: {
|
||||
image: "https://labelstud.io/images/opa-header.png",
|
||||
text: "这是示例文本,用于预览标注界面。",
|
||||
audio: "https://labelstud.io/files/sample.wav"
|
||||
}
|
||||
data: previewTaskData,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -71,7 +71,7 @@ const TemplateList: React.FC = () => {
|
||||
fetchData,
|
||||
handleFiltersChange,
|
||||
handleKeywordChange,
|
||||
} = useFetchData(queryAnnotationTemplatesUsingGet, undefined, undefined, undefined, undefined, 0);
|
||||
} = useFetchData(queryAnnotationTemplatesUsingGet, undefined, undefined, undefined, undefined);
|
||||
|
||||
const handleCreate = () => {
|
||||
setFormMode("create");
|
||||
|
||||
Reference in New Issue
Block a user