Skip to content

Commit ba0c690

Browse files
authored
feat: data annotation page adaptation to backend API. Improve labeling project creation module.
* feat: data annotation page adaptation to the backend API. * feat: Implement labeling configuration editor and enhance annotation task creation form
1 parent 452d279 commit ba0c690

File tree

20 files changed

+737
-194
lines changed

20 files changed

+737
-194
lines changed

frontend/src/pages/DataAnnotation/Create/components/CreateAnnptationTaskDialog.tsx

Lines changed: 148 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api";
2-
import {
3-
datasetTypeMap,
4-
mapDataset,
5-
} from "@/pages/DataManagement/dataset.const";
6-
import { Button, Form, Input, Modal, Select } from "antd";
2+
import { mapDataset } from "@/pages/DataManagement/dataset.const";
3+
import { Button, Form, Input, Modal, Select, message } from "antd";
74
import TextArea from "antd/es/input/TextArea";
8-
import { Database } from "lucide-react";
95
import { useEffect, useState } from "react";
106
import { createAnnotationTaskUsingPost } from "../../annotation.api";
117
import { Dataset } from "@/pages/DataManagement/dataset.model";
8+
import LabelingConfigEditor from "./LabelingConfigEditor";
9+
import { useRef } from "react";
1210

1311
export default function CreateAnnotationTask({
1412
open,
@@ -21,6 +19,10 @@ export default function CreateAnnotationTask({
2119
}) {
2220
const [form] = Form.useForm();
2321
const [datasets, setDatasets] = useState<Dataset[]>([]);
22+
const [submitting, setSubmitting] = useState(false);
23+
const [nameManuallyEdited, setNameManuallyEdited] = useState(false);
24+
const editorRef = useRef<any>(null);
25+
const EDITOR_LIST_HEIGHT = 420;
2426

2527
useEffect(() => {
2628
if (!open) return;
@@ -34,11 +36,36 @@ export default function CreateAnnotationTask({
3436
fetchDatasets();
3537
}, [open]);
3638

39+
// Reset form and manual-edit flag when modal opens
40+
useEffect(() => {
41+
if (open) {
42+
form.resetFields();
43+
setNameManuallyEdited(false);
44+
}
45+
}, [open, form]);
46+
3747
const handleSubmit = async () => {
38-
const values = await form.validateFields();
39-
await createAnnotationTaskUsingPost(values);
40-
onClose();
41-
onRefresh();
48+
try {
49+
const values = await form.validateFields();
50+
setSubmitting(true);
51+
await createAnnotationTaskUsingPost(values);
52+
message?.success?.("创建标注任务成功");
53+
onClose();
54+
onRefresh();
55+
} catch (err: any) {
56+
console.error("Create annotation task failed", err);
57+
const msg = err?.message || err?.data?.message || "创建失败,请稍后重试";
58+
// show a user friendly message
59+
(message as any)?.error?.(msg);
60+
} finally {
61+
setSubmitting(false);
62+
}
63+
};
64+
65+
// Placeholder function: generates labeling interface from config
66+
// For now it simply returns the parsed config (per requirement)
67+
const generateLabelingInterface = (config: any) => {
68+
return config;
4269
};
4370

4471
return (
@@ -48,51 +75,124 @@ export default function CreateAnnotationTask({
4875
title="创建标注任务"
4976
footer={
5077
<>
51-
<Button onClick={onClose}>取消</Button>
52-
<Button type="primary" onClick={handleSubmit}>
78+
<Button onClick={onClose} disabled={submitting}>
79+
取消
80+
</Button>
81+
<Button type="primary" onClick={handleSubmit} loading={submitting}>
5382
确定
5483
</Button>
5584
</>
5685
}
86+
width={1200}
5787
>
58-
<Form layout="vertical">
59-
<Form.Item
60-
label="名称"
61-
name="name"
62-
rules={[{ required: true, message: "请输入任务名称" }]}
63-
>
64-
<Input placeholder="输入任务名称" />
65-
</Form.Item>
66-
<Form.Item
67-
label="描述"
68-
name="description"
69-
rules={[{ required: true, message: "请输入任务描述" }]}
70-
>
71-
<TextArea placeholder="详细描述标注任务的要求和目标" rows={3} />
72-
</Form.Item>
73-
<Form.Item
74-
label="数据集"
75-
name="datasetId"
76-
rules={[{ required: true, message: "请选择数据集" }]}
77-
>
78-
<Select
79-
placeholder="请选择数据集"
80-
options={datasets.map((dataset) => {
81-
return {
82-
label: (
83-
<div className="flex items-center justify-between gap-3 py-2">
84-
<div className="flex items-center font-sm text-gray-900">
85-
<span className="mr-2">{dataset.icon}</span>
86-
<span>{dataset.name}</span>
88+
<Form form={form} layout="vertical">
89+
{/* 数据集 与 标注工程名称 并排显示(数据集在左) */}
90+
<div className="grid grid-cols-2 gap-4">
91+
<Form.Item
92+
label="数据集"
93+
name="datasetId"
94+
rules={[{ required: true, message: "请选择数据集" }]}
95+
>
96+
<Select
97+
placeholder="请选择数据集"
98+
options={datasets.map((dataset) => {
99+
return {
100+
label: (
101+
<div className="flex items-center justify-between gap-3 py-2">
102+
<div className="flex items-center font-sm text-gray-900">
103+
<span className="mr-2">{(dataset as any).icon}</span>
104+
<span>{dataset.name}</span>
105+
</div>
106+
<div className="text-xs text-gray-500">{dataset.size}</div>
87107
</div>
88-
<div className="text-xs text-gray-500">{dataset.size}</div>
89-
</div>
90-
),
91-
value: dataset.id,
92-
};
93-
})}
94-
/>
108+
),
109+
value: dataset.id,
110+
};
111+
})}
112+
onChange={(value) => {
113+
// 如果用户未手动修改名称,则用数据集名称作为默认任务名
114+
if (!nameManuallyEdited) {
115+
const ds = datasets.find((d) => d.id === value);
116+
if (ds) {
117+
form.setFieldsValue({ name: ds.name });
118+
}
119+
}
120+
}}
121+
/>
122+
</Form.Item>
123+
124+
<Form.Item
125+
label="标注工程名称"
126+
name="name"
127+
rules={[{ required: true, message: "请输入任务名称" }]}
128+
>
129+
<Input
130+
placeholder="输入标注工程名称"
131+
onChange={() => setNameManuallyEdited(true)}
132+
/>
133+
</Form.Item>
134+
</div>
135+
{/* 描述变为可选 */}
136+
<Form.Item label="描述" name="description">
137+
<TextArea placeholder="(可选)详细描述标注任务的要求和目标" rows={3} />
95138
</Form.Item>
139+
140+
{/* 标注页面设计 模块:左侧为配置编辑,右侧为预览(作为表单的一部分,与其他字段同级) */}
141+
<div style={{ marginTop: 8 }}>
142+
<label className="block font-medium mb-2">标注页面设计</label>
143+
<div style={{ display: "grid", gridTemplateColumns: "minmax(360px, 1fr) 1fr", gridTemplateRows: "auto 1fr", gap: 16 }}>
144+
{/* Row 1: buttons on the left, spacer on the right so preview aligns with editor below */}
145+
<div style={{ gridColumn: 1, gridRow: 1, display: 'flex', gap: 8 }}>
146+
<Button onClick={() => editorRef.current?.addLabel?.()}>添加标签</Button>
147+
<Button type="primary" onClick={() => editorRef.current?.generate?.()}>生成标注页面配置</Button>
148+
</div>
149+
150+
{/* empty spacer to occupy top-right cell so preview starts on the second row */}
151+
<div style={{ gridColumn: 2, gridRow: 1 }} />
152+
153+
{/* Row 2, Col 1: 编辑列表(固定高度) */}
154+
<div style={{ gridColumn: 1, gridRow: 2, height: EDITOR_LIST_HEIGHT, overflowY: 'auto', paddingRight: 8, border: '1px solid #e6e6e6', borderRadius: 6, padding: 12 }}>
155+
<LabelingConfigEditor
156+
ref={editorRef}
157+
hideFooter={true}
158+
initial={undefined}
159+
onGenerate={(config: any) => {
160+
form.setFieldsValue({ labelingConfig: JSON.stringify(config, null, 2), labelingInterface: JSON.stringify(generateLabelingInterface(config), null, 2) });
161+
}}
162+
/>
163+
<Form.Item
164+
name="labelingConfig"
165+
rules={[
166+
{
167+
validator: async (_, value) => {
168+
if (!value || value === "") return Promise.resolve();
169+
try {
170+
JSON.parse(value);
171+
return Promise.resolve();
172+
} catch (e) {
173+
return Promise.reject(new Error("请输入有效的 JSON"));
174+
}
175+
},
176+
},
177+
]}
178+
style={{ display: "none" }}
179+
>
180+
<Input />
181+
</Form.Item>
182+
</div>
183+
184+
{/* Row 2, Col 2: 预览,与编辑列表在同一行,保持一致高度 */}
185+
<div style={{ gridColumn: 2, gridRow: 2, display: 'flex', flexDirection: 'column' }}>
186+
<Form.Item name="labelingInterface" style={{ flex: 1 }}>
187+
<TextArea
188+
placeholder="标注页面设计(只读,由标注配置生成)"
189+
disabled
190+
style={{ height: EDITOR_LIST_HEIGHT, resize: 'none' }}
191+
/>
192+
</Form.Item>
193+
</div>
194+
</div>
195+
</div>
96196
</Form>
97197
</Modal>
98198
);

0 commit comments

Comments
 (0)