11import { 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" ;
74import TextArea from "antd/es/input/TextArea" ;
8- import { Database } from "lucide-react" ;
95import { useEffect , useState } from "react" ;
106import { createAnnotationTaskUsingPost } from "../../annotation.api" ;
117import { Dataset } from "@/pages/DataManagement/dataset.model" ;
8+ import LabelingConfigEditor from "./LabelingConfigEditor" ;
9+ import { useRef } from "react" ;
1210
1311export 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