You've already forked DataMate
feat(annotation): 添加数据集预览功能
- 引入 previewDatasetUsingGet API 接口用于数据集预览 - 添加数据集预览相关的状态管理(可见性、数据、加载状态等) - 实现 handlePreviewDataset 函数用于获取并展示数据集预览数据 - 在数据集选择区域添加预览按钮,点击可查看数据集内容 - 添加数据集预览弹窗组件,以表格形式展示数据集内容 - 移除原有的 XML 编辑标签页,简化模板配置界面 - 更新表单项标签结构,集成预览按钮到数据集选择区域
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api";
|
import { queryDatasetsUsingGet, previewDatasetUsingGet } from "@/pages/DataManagement/dataset.api";
|
||||||
import { mapDataset } from "@/pages/DataManagement/dataset.const";
|
import { mapDataset } from "@/pages/DataManagement/dataset.const";
|
||||||
import { Button, Form, Input, Modal, Select, message, Tabs, Radio } from "antd";
|
import { Button, Form, Input, Modal, Select, message, Radio, Table } from "antd";
|
||||||
import TextArea from "antd/es/input/TextArea";
|
import TextArea from "antd/es/input/TextArea";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { Eye } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
createAnnotationTaskUsingPost,
|
createAnnotationTaskUsingPost,
|
||||||
queryAnnotationTemplatesUsingGet,
|
queryAnnotationTemplatesUsingGet,
|
||||||
@@ -32,10 +33,15 @@ export default function CreateAnnotationTask({
|
|||||||
const [showPreview, setShowPreview] = useState(false);
|
const [showPreview, setShowPreview] = useState(false);
|
||||||
const [previewTaskData, setPreviewTaskData] = useState<Record<string, any>>({});
|
const [previewTaskData, setPreviewTaskData] = useState<Record<string, any>>({});
|
||||||
const [configMode, setConfigMode] = useState<"template" | "custom">("template");
|
const [configMode, setConfigMode] = useState<"template" | "custom">("template");
|
||||||
const [templateEditTab, setTemplateEditTab] = useState<"visual" | "xml">("visual");
|
|
||||||
// 是否已选择模板(用于启用受限编辑模式)
|
// 是否已选择模板(用于启用受限编辑模式)
|
||||||
const [hasSelectedTemplate, setHasSelectedTemplate] = useState(false);
|
const [hasSelectedTemplate, setHasSelectedTemplate] = useState(false);
|
||||||
|
|
||||||
|
// 数据集预览相关状态
|
||||||
|
const [datasetPreviewVisible, setDatasetPreviewVisible] = useState(false);
|
||||||
|
const [datasetPreviewData, setDatasetPreviewData] = useState<any[]>([]);
|
||||||
|
const [datasetPreviewLoading, setDatasetPreviewLoading] = useState(false);
|
||||||
|
const [selectedDatasetId, setSelectedDatasetId] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return;
|
if (!open) return;
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
@@ -77,11 +83,35 @@ export default function CreateAnnotationTask({
|
|||||||
setShowPreview(false);
|
setShowPreview(false);
|
||||||
setPreviewTaskData({});
|
setPreviewTaskData({});
|
||||||
setConfigMode("template");
|
setConfigMode("template");
|
||||||
setTemplateEditTab("visual");
|
|
||||||
setHasSelectedTemplate(false);
|
setHasSelectedTemplate(false);
|
||||||
|
setSelectedDatasetId(null);
|
||||||
|
setDatasetPreviewData([]);
|
||||||
}
|
}
|
||||||
}, [open, manualForm]);
|
}, [open, manualForm]);
|
||||||
|
|
||||||
|
// 预览数据集
|
||||||
|
const handlePreviewDataset = async () => {
|
||||||
|
if (!selectedDatasetId) {
|
||||||
|
message.warning("请先选择数据集");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setDatasetPreviewLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await previewDatasetUsingGet(selectedDatasetId, { limit: 10 });
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
setDatasetPreviewData(res.data || []);
|
||||||
|
setDatasetPreviewVisible(true);
|
||||||
|
} else {
|
||||||
|
message.error("获取数据集预览失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Preview dataset error:", error);
|
||||||
|
message.error("获取数据集预览失败");
|
||||||
|
} finally {
|
||||||
|
setDatasetPreviewLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const generateXmlFromConfig = (objects: any[], labels: any[]) => {
|
const generateXmlFromConfig = (objects: any[], labels: any[]) => {
|
||||||
let xml = '<View>\n';
|
let xml = '<View>\n';
|
||||||
|
|
||||||
@@ -118,16 +148,6 @@ export default function CreateAnnotationTask({
|
|||||||
return xml;
|
return xml;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 从表单值同步生成 XML
|
|
||||||
const syncFormToXml = () => {
|
|
||||||
const objects = manualForm.getFieldValue("objects");
|
|
||||||
const labels = manualForm.getFieldValue("labels");
|
|
||||||
if (objects && objects.length > 0) {
|
|
||||||
const xml = generateXmlFromConfig(objects, labels || []);
|
|
||||||
setCustomXml(xml);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据 objects 配置生成预览用的示例数据
|
// 根据 objects 配置生成预览用的示例数据
|
||||||
const generateExampleData = (objects: any[]) => {
|
const generateExampleData = (objects: any[]) => {
|
||||||
const exampleUrls: Record<string, string> = {
|
const exampleUrls: Record<string, string> = {
|
||||||
@@ -307,7 +327,22 @@ export default function CreateAnnotationTask({
|
|||||||
{/* 数据集 与 标注工程名称 并排显示(数据集在左) */}
|
{/* 数据集 与 标注工程名称 并排显示(数据集在左) */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="数据集"
|
label={
|
||||||
|
<div className="flex items-center justify-between w-full">
|
||||||
|
<span>数据集</span>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
icon={<Eye size={14} />}
|
||||||
|
disabled={!selectedDatasetId}
|
||||||
|
loading={datasetPreviewLoading}
|
||||||
|
onClick={handlePreviewDataset}
|
||||||
|
className="p-0 h-auto"
|
||||||
|
>
|
||||||
|
预览
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
name="datasetId"
|
name="datasetId"
|
||||||
rules={[{ required: true, message: "请选择数据集" }]}
|
rules={[{ required: true, message: "请选择数据集" }]}
|
||||||
>
|
>
|
||||||
@@ -328,6 +363,7 @@ export default function CreateAnnotationTask({
|
|||||||
};
|
};
|
||||||
})}
|
})}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
setSelectedDatasetId(value);
|
||||||
// 如果用户未手动修改名称,则用数据集名称作为默认任务名
|
// 如果用户未手动修改名称,则用数据集名称作为默认任务名
|
||||||
if (!nameManuallyEdited) {
|
if (!nameManuallyEdited) {
|
||||||
const ds = datasets.find((d) => d.id === value);
|
const ds = datasets.find((d) => d.id === value);
|
||||||
@@ -442,53 +478,12 @@ export default function CreateAnnotationTask({
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Tabs
|
<div style={{ maxHeight: '350px', overflowY: 'auto' }}>
|
||||||
activeKey={templateEditTab}
|
<TemplateConfigurationForm
|
||||||
onChange={(key) => {
|
form={manualForm}
|
||||||
// 切换到 XML 时,从表单同步生成 XML
|
restrictedMode={hasSelectedTemplate}
|
||||||
if (key === "xml") {
|
/>
|
||||||
syncFormToXml();
|
</div>
|
||||||
}
|
|
||||||
setTemplateEditTab(key as "visual" | "xml");
|
|
||||||
}}
|
|
||||||
size="small"
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: "visual",
|
|
||||||
label: "可视化配置",
|
|
||||||
children: (
|
|
||||||
<div style={{ maxHeight: '350px', overflowY: 'auto' }}>
|
|
||||||
<TemplateConfigurationForm
|
|
||||||
form={manualForm}
|
|
||||||
restrictedMode={hasSelectedTemplate}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "xml",
|
|
||||||
label: hasSelectedTemplate ? "XML配置(只读)" : "XML编辑器(高级)",
|
|
||||||
children: (
|
|
||||||
<div>
|
|
||||||
<div className="mb-2 text-xs text-gray-500">
|
|
||||||
{hasSelectedTemplate
|
|
||||||
? "基于模板创建时,XML 配置为只读。如需完全自定义,请切换'自定义配置'模式。"
|
|
||||||
: "直接编辑 Label Studio XML 配置。注意:在此修改后切换回可视化配置可能会丢失部分高级设置。"
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<TextArea
|
|
||||||
rows={10}
|
|
||||||
value={customXml}
|
|
||||||
onChange={(e) => setCustomXml(e.target.value)}
|
|
||||||
placeholder="<View>...</View>"
|
|
||||||
style={{ fontFamily: 'monospace', fontSize: 12 }}
|
|
||||||
disabled={hasSelectedTemplate}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-gray-50 p-4 rounded-md border border-gray-200" style={{ maxHeight: '400px', overflowY: 'auto' }}>
|
<div className="bg-gray-50 p-4 rounded-md border border-gray-200" style={{ maxHeight: '400px', overflowY: 'auto' }}>
|
||||||
@@ -523,6 +518,43 @@ export default function CreateAnnotationTask({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* 数据集预览弹窗 */}
|
||||||
|
<Modal
|
||||||
|
open={datasetPreviewVisible}
|
||||||
|
onCancel={() => setDatasetPreviewVisible(false)}
|
||||||
|
title="数据集预览"
|
||||||
|
width={900}
|
||||||
|
footer={[
|
||||||
|
<Button key="close" onClick={() => setDatasetPreviewVisible(false)}>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
dataSource={datasetPreviewData}
|
||||||
|
columns={
|
||||||
|
datasetPreviewData.length > 0
|
||||||
|
? Object.keys(datasetPreviewData[0]).map((key) => ({
|
||||||
|
title: key,
|
||||||
|
dataIndex: key,
|
||||||
|
key: key,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (value: any) => {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
return JSON.stringify(value);
|
||||||
|
}
|
||||||
|
return String(value ?? '');
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
rowKey={(_, index) => String(index)}
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ x: 'max-content', y: 400 }}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user