You've already forked DataMate
feat(DataAnnotation): 新增模板配置表单组件
- 实现了数据对象配置区域,支持添加、删除数据对象字段 - 添加了标签控件配置区域,支持多种控件类型的动态配置 - 集成了TagSelector组件用于对象类型和控件类型的选择 - 实现了表单验证规则,包括必填项和值格式校验 - 添加了动态选项渲染功能,根据控件类型显示相应配置项 - 实现了表单联动逻辑,支持对象选择和控件配置的关联 - 添加了用户友好的界面布局和交互提示功能
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Card, Button, Input, Select, Divider, Form, message } from "antd";
|
||||
import { Card, Button, Input, Select, Divider, Form, message, Radio } from "antd";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
import {
|
||||
DatabaseOutlined,
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "@ant-design/icons";
|
||||
import { mockTemplates } from "@/mock/annotation";
|
||||
import CustomTemplateDialog from "./components/CustomTemplateDialog";
|
||||
import TemplateConfigurationForm from "../../components/TemplateConfigurationForm";
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { queryDatasetsUsingGet } from "../../DataManagement/dataset.api";
|
||||
@@ -36,6 +37,7 @@ export default function AnnotationTaskCreate() {
|
||||
const [showCustomTemplateDialog, setShowCustomTemplateDialog] =
|
||||
useState(false);
|
||||
const [selectedCategory, setSelectedCategory] = useState("Computer Vision");
|
||||
const [configMode, setConfigMode] = useState<"template" | "custom">("template");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [datasetFilter, setDatasetFilter] = useState("all");
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(
|
||||
@@ -87,21 +89,67 @@ export default function AnnotationTaskCreate() {
|
||||
setFormValues({ ...formValues, ...allValues });
|
||||
};
|
||||
|
||||
const handleConfigModeChange = (e) => {
|
||||
const mode = e.target.value;
|
||||
setConfigMode(mode);
|
||||
if (mode === "custom") {
|
||||
// Initialize default values for custom configuration
|
||||
form.setFieldsValue({
|
||||
objects: [{ name: "image", type: "Image", value: "$image" }],
|
||||
labels: [],
|
||||
});
|
||||
// Clear template selection
|
||||
setSelectedTemplate(null);
|
||||
setFormValues((prev) => ({ ...prev, templateId: "" }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
const dataset = datasets.find((ds) => ds.id === values.datasetId);
|
||||
const template = mockTemplates.find(
|
||||
(tpl) => tpl.id === values.templateId
|
||||
);
|
||||
|
||||
let template;
|
||||
if (configMode === "template") {
|
||||
template = mockTemplates.find(
|
||||
(tpl) => tpl.id === values.templateId
|
||||
);
|
||||
if (!template) {
|
||||
message.error("请选择标注模板");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Custom configuration
|
||||
const objects = values.objects;
|
||||
const labels = values.labels;
|
||||
if (!objects || objects.length === 0) {
|
||||
message.error("请至少配置一个数据对象");
|
||||
return;
|
||||
}
|
||||
if (!labels || labels.length === 0) {
|
||||
message.error("请至少配置一个标签控件");
|
||||
return;
|
||||
}
|
||||
|
||||
// Construct a temporary custom template object
|
||||
template = {
|
||||
id: `custom-${Date.now()}`,
|
||||
name: "自定义配置",
|
||||
description: "任务特定的自定义配置",
|
||||
type: selectedDataset?.type === DatasetType.PRETRAIN_TEXT ? "text" : "image",
|
||||
isCustom: true,
|
||||
configuration: {
|
||||
objects,
|
||||
labels
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!dataset) {
|
||||
message.error("请选择数据集");
|
||||
return;
|
||||
}
|
||||
if (!template) {
|
||||
message.error("请选择标注模板");
|
||||
return;
|
||||
}
|
||||
|
||||
const taskData = {
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
@@ -109,10 +157,12 @@ export default function AnnotationTaskCreate() {
|
||||
template,
|
||||
};
|
||||
// onCreateTask(taskData); // 实际创建逻辑
|
||||
console.log("Submitting task data:", taskData);
|
||||
message.success("标注任务创建成功");
|
||||
navigate("/data/annotation");
|
||||
} catch (e) {
|
||||
// 校验失败
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -186,144 +236,157 @@ export default function AnnotationTaskCreate() {
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* 模板选择 */}
|
||||
<h2 className="font-medium text-gray-900 text-lg mt-6 mb-2 flex items-center gap-2">
|
||||
模板选择
|
||||
</h2>
|
||||
<Form.Item
|
||||
name="templateId"
|
||||
rules={[{ required: true, message: "请选择标注模板" }]}
|
||||
>
|
||||
<div className="flex">
|
||||
{/* Category Sidebar */}
|
||||
<div className="w-64 pr-6 border-r border-gray-200">
|
||||
<div className="space-y-2">
|
||||
{templateCategories.map((category) => {
|
||||
const isAvailable =
|
||||
selectedDataset?.type === "image"
|
||||
? category === "Computer Vision"
|
||||
: category === "Natural Language Processing";
|
||||
return (
|
||||
<Button
|
||||
key={category}
|
||||
type={
|
||||
selectedCategory === category && isAvailable
|
||||
? "primary"
|
||||
: "default"
|
||||
}
|
||||
block
|
||||
disabled={!isAvailable}
|
||||
onClick={() =>
|
||||
isAvailable && setSelectedCategory(category)
|
||||
}
|
||||
style={{ textAlign: "left", marginBottom: 8 }}
|
||||
>
|
||||
{category}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
<Button
|
||||
type="dashed"
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setShowCustomTemplateDialog(true)}
|
||||
>
|
||||
自定义模板
|
||||
</Button>
|
||||
{/* 模板配置 */}
|
||||
<div className="flex items-center justify-between mt-6 mb-2">
|
||||
<h2 className="font-medium text-gray-900 text-lg flex items-center gap-2">
|
||||
模板配置
|
||||
</h2>
|
||||
<Radio.Group value={configMode} onChange={handleConfigModeChange} buttonStyle="solid">
|
||||
<Radio.Button value="template">选择现有模板</Radio.Button>
|
||||
<Radio.Button value="custom">自定义配置</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
|
||||
{configMode === "template" ? (
|
||||
<Form.Item
|
||||
name="templateId"
|
||||
rules={[{ required: true, message: "请选择标注模板" }]}
|
||||
>
|
||||
<div className="flex">
|
||||
{/* Category Sidebar */}
|
||||
<div className="w-64 pr-6 border-r border-gray-200">
|
||||
<div className="space-y-2">
|
||||
{templateCategories.map((category) => {
|
||||
const isAvailable =
|
||||
selectedDataset?.type === "image"
|
||||
? category === "Computer Vision"
|
||||
: category === "Natural Language Processing";
|
||||
return (
|
||||
<Button
|
||||
key={category}
|
||||
type={
|
||||
selectedCategory === category && isAvailable
|
||||
? "primary"
|
||||
: "default"
|
||||
}
|
||||
block
|
||||
disabled={!isAvailable}
|
||||
onClick={() =>
|
||||
isAvailable && setSelectedCategory(category)
|
||||
}
|
||||
style={{ textAlign: "left", marginBottom: 8 }}
|
||||
>
|
||||
{category}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
<Button
|
||||
type="dashed"
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setShowCustomTemplateDialog(true)}
|
||||
>
|
||||
自定义模板
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Template Grid */}
|
||||
<div className="flex-1 pl-6">
|
||||
<div className="max-h-96 overflow-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{filteredTemplates.map((template) => (
|
||||
<div
|
||||
key={template.id}
|
||||
className={`border rounded-lg cursor-pointer transition-all hover:shadow-md ${
|
||||
formValues.templateId === template.id
|
||||
? "border-blue-500 bg-blue-50"
|
||||
: "border-gray-200"
|
||||
}`}
|
||||
onClick={() => handleTemplateSelect(template)}
|
||||
>
|
||||
{template.preview && (
|
||||
<div className="aspect-video bg-gray-100 rounded-t-lg overflow-hidden">
|
||||
<img
|
||||
src={template.preview || "/placeholder.svg"}
|
||||
alt={template.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{/* Template Grid */}
|
||||
<div className="flex-1 pl-6">
|
||||
<div className="max-h-96 overflow-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{filteredTemplates.map((template) => (
|
||||
<div
|
||||
key={template.id}
|
||||
className={`border rounded-lg cursor-pointer transition-all hover:shadow-md ${
|
||||
formValues.templateId === template.id
|
||||
? "border-blue-500 bg-blue-50"
|
||||
: "border-gray-200"
|
||||
}`}
|
||||
onClick={() => handleTemplateSelect(template)}
|
||||
>
|
||||
{template.preview && (
|
||||
<div className="aspect-video bg-gray-100 rounded-t-lg overflow-hidden">
|
||||
<img
|
||||
src={template.preview || "/placeholder.svg"}
|
||||
alt={template.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-3">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
{template.icon}
|
||||
<span className="font-medium text-sm">
|
||||
{template.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-600">
|
||||
{template.description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{/* Custom Template Option */}
|
||||
<div
|
||||
className={`border-2 border-dashed rounded-lg cursor-pointer transition-all hover:border-gray-400 ${
|
||||
selectedTemplate?.isCustom
|
||||
? "border-blue-500 bg-blue-50"
|
||||
: "border-gray-300"
|
||||
}`}
|
||||
onClick={() => setShowCustomTemplateDialog(true)}
|
||||
>
|
||||
<div className="aspect-video bg-gray-50 rounded-t-lg flex items-center justify-center">
|
||||
<PlusOutlined
|
||||
style={{ fontSize: 32, color: "#bbb" }}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
{template.icon}
|
||||
<PlusOutlined />
|
||||
<span className="font-medium text-sm">
|
||||
{template.name}
|
||||
自定义模板
|
||||
</span>
|
||||
</div>
|
||||
{selectedTemplate?.isCustom && (
|
||||
<CheckOutlined style={{ color: "#1677ff" }} />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-600">
|
||||
{template.description}
|
||||
创建符合特定需求的标注模板
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{/* Custom Template Option */}
|
||||
<div
|
||||
className={`border-2 border-dashed rounded-lg cursor-pointer transition-all hover:border-gray-400 ${
|
||||
selectedTemplate?.isCustom
|
||||
? "border-blue-500 bg-blue-50"
|
||||
: "border-gray-300"
|
||||
}`}
|
||||
onClick={() => setShowCustomTemplateDialog(true)}
|
||||
>
|
||||
<div className="aspect-video bg-gray-50 rounded-t-lg flex items-center justify-center">
|
||||
<PlusOutlined
|
||||
style={{ fontSize: 32, color: "#bbb" }}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<PlusOutlined />
|
||||
<span className="font-medium text-sm">
|
||||
自定义模板
|
||||
</span>
|
||||
</div>
|
||||
{selectedTemplate?.isCustom && (
|
||||
<CheckOutlined style={{ color: "#1677ff" }} />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-600">
|
||||
创建符合特定需求的标注模板
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{selectedTemplate && (
|
||||
<div className="mt-4 p-3 bg-blue-50 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span
|
||||
className="text-sm font-medium"
|
||||
style={{ color: "#1677ff" }}
|
||||
{selectedTemplate && (
|
||||
<div className="mt-4 p-3 bg-blue-50 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span
|
||||
className="text-sm font-medium"
|
||||
style={{ color: "#1677ff" }}
|
||||
>
|
||||
已选择模板
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="text-sm"
|
||||
style={{ color: "#1677ff", marginTop: 4 }}
|
||||
>
|
||||
已选择模板
|
||||
</span>
|
||||
{selectedTemplate.name} - {selectedTemplate.description}
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className="text-sm"
|
||||
style={{ color: "#1677ff", marginTop: 4 }}
|
||||
>
|
||||
{selectedTemplate.name} - {selectedTemplate.description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form.Item>
|
||||
) : (
|
||||
<div className="border border-gray-200 rounded-lg p-6 bg-gray-50">
|
||||
<TemplateConfigurationForm form={form} />
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
<div className="flex gap-2 justify-end border-t border-gray-200 p-6">
|
||||
|
||||
Reference in New Issue
Block a user