@yorha2b-lab/autodev 3.4.4 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/config.js CHANGED
@@ -11,7 +11,7 @@ module.exports = {
11
11
  enableAutoAlignment: false,
12
12
 
13
13
  textModel: 'qwen-turbo',
14
- visionModel: 'qwen3.6-plus',
14
+ visionModel: 'qwen3.7-plus',
15
15
 
16
16
  hooksDir: 'src/hooks',
17
17
  pagesDir: 'src/pages',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yorha2b-lab/autodev",
3
- "version": "3.4.4",
3
+ "version": "3.5.1",
4
4
  "description": "基于视觉大模型的前端(react+Antd)全自动 CRUD 代码生成器",
5
5
  "bin": {
6
6
  "autodev": "bin/autodev.js"
@@ -25,7 +25,7 @@ module.exports = async filePath => {
25
25
  `🤖 Pod 042: Extracting UI metadata from neural cloud network...\n`
26
26
  ))
27
27
 
28
- const pageConfig = await recognizePage(partPrompt, filePath)
28
+ const pageConfig = await recognizePage(partPrompt, filePath, 'part')
29
29
 
30
30
  const { formItems, dictBlocks, processedColumns } = formatFormItemAndColumns({ pageConfig })
31
31
 
@@ -14,6 +14,7 @@ const resource = ({ pageConfig, resourceTpl }) => {
14
14
 
15
15
  const hasTabs = pageConfig.tabs?.length > 0
16
16
  const hasFormItems = pageConfig.formItems?.length > 0
17
+ const optionDict = pageConfig.optionList?.reduce((acc, item) => ({ ...acc, [item.name]: item.options }), {})
17
18
 
18
19
  const columnsData = processedColumns.map(item => {
19
20
  delete item.type
@@ -24,7 +25,7 @@ const resource = ({ pageConfig, resourceTpl }) => {
24
25
  hasTabs,
25
26
  hasFormItems,
26
27
  tabs: pageConfig.tabs,
27
- dictBlocks: dictBlocks.map(item => ({ name: item, data: pageConfig.optionDict[item] ?? [] })),
28
+ dictBlocks: dictBlocks.map(item => ({ name: item, data: optionDict[item] ?? [] })),
28
29
  formItemsData: hasTabs ? Object.fromEntries(pageConfig.tabs.map(tab => [tab.key, formItems])) : formItems,
29
30
  columnsData: hasTabs ? Object.fromEntries(pageConfig.tabs.map(tab => [tab.key, columnsData])) : columnsData,
30
31
  }
@@ -49,8 +50,7 @@ const index = ({ config, fileName, indexTpl, pageConfig }) => {
49
50
  const hasOperate = pageConfig.table?.operation?.length > 0
50
51
  const hasImageColumn = pageConfig.table?.columns?.some(item => item.type === 'image')
51
52
  const pageStruct = pageConfig.pageStruct?.filter(item => item.toLowerCase() !== 'tabs') || []
52
- const functionButtons = pageConfig.functionButton?.filter(item => !['查询', '重置'].includes(item.btn)) || []
53
- const hasFunctionButtons = functionButtons.length > 0
53
+ const functionButtons = pageConfig.functionButton?.filter(item => !['查询', '重置', 'query', 'search', 'reset'].includes(item.btn.toLowerCase().replaceAll(' ', ''))) || []
54
54
  const needRenderAction = hasImageColumn
55
55
 
56
56
  let columnsValue = hasTabs ? 'columns[activeKey]' : 'columns'
@@ -64,6 +64,7 @@ const index = ({ config, fileName, indexTpl, pageConfig }) => {
64
64
  const viewData = {
65
65
  hasTabs,
66
66
  fileName,
67
+ pageStruct,
67
68
  hasOperate,
68
69
  columnsValue,
69
70
  hasFormItems,
@@ -79,7 +80,6 @@ const index = ({ config, fileName, indexTpl, pageConfig }) => {
79
80
  operations: pageConfig.table.operation || [],
80
81
  hasRowSelection: pageConfig.table.rowSelection,
81
82
  formItems: hasFormItems ? (hasTabs ? 'formItems[activeKey]' : 'formItems') : '[]',
82
- pageStruct: hasFunctionButtons ? pageStruct : pageStruct.filter(item => item !== 'FunctionButtonsBlock'),
83
83
  }
84
84
 
85
85
  const bodyCode = indexTpl(viewData)
@@ -10,6 +10,7 @@ module.exports = `
10
10
  ## 2. 零部件构筑规范 (Component Specifications)
11
11
 
12
12
  ### 标签页 (Tabs)
13
+ - **基础格式**: [{ label: '文本', key: 'englishName' }]
13
14
  - **识别条件**: 仅当下部有显著长横线 (Ink Bar) 或呈现包裹感 (Card style) 时判定。
14
15
  - **排除项**: 表格上方的独立按钮必须归类为 [functionButton]。
15
16
 
@@ -20,44 +21,46 @@ module.exports = `
20
21
  - 非默认组件: 仅当类型为 [auto, date, radio, select, upload, checkbox, textarea, daterange] 时才允许添加 type 属性。
21
22
  **标准定义 (FormItem)**:
22
23
  - date: 单日期
23
- - daterange: 日期范围
24
24
  - enum: 枚举类型
25
+ - daterange: 日期范围
25
26
  - text: 普通文本(默认)
26
27
  - 命名规范:
27
- - daterange: name 必须设为 '字段英文名start,字段英文名end'。
28
+ - daterange: name 必须设为 '字段英文名Start,字段英文名End'
29
+ - **⚠️注意⚠️**:不要用_分隔字段名。
28
30
 
29
31
  ### 统计条 (StaticInfo)
30
32
  - **独立存在**: 位于表格外周的汇总信息。输出格式: { has: true, text: '具体内容' }。
31
33
 
32
34
  ### 表格主体 (Table)
35
+ - ⚠️ **操作锁定**: 严禁在 columns 数组中包含“操作”列。
33
36
  - **标准项**: {title:'列名', dataIndex:'列名英文名词',type:''}。
34
37
  - **配置项**: [pagination, expandable, rowSelection] 均为布尔值false,只有页面明确有对应功能时才为true。
35
38
  - **标准定义 (Columns)**:
36
- - ⚠️ **操作锁定**: 严禁在 columns 数组中包含“操作”列。
37
39
  - enum: 枚举列
38
40
  - money: 金额列
39
41
  - index: 序号列
40
42
  - image: 图片列
41
43
  - date: 时间/日期列
42
44
  - text: 普通文本(默认)
43
- - tag: 标签列(有背景色的标签)
45
+ - tag: 标签列(有背景色的文本)
44
46
  - badge: 徽标列(文本前有颜色小圆点)
45
- - **行操作 (Operation)**: [{label:'操作名', action:'动词ByRecord'}]。
47
+ - **行操作 (operation)**: [{label:'操作名', action:'动词ByRecord'}]。
46
48
 
47
49
  ### 全局功能按钮 (FunctionButton)
48
- - **格式**: [{btn:'显示文本', action:'动作名'}]。
49
- - **联动命名**: 若与行操作重复,action 必须加上 'BySelected' 前缀。
50
- - **导出协议**: action 设为 'exportData' 或 'export+模块名'。
50
+ - **格式**: [{btn:'显示文本', action:'操作动词BySelected'}]。
51
+ - **导出数据**: action 设为 'exportData' 或 'export+模块名'。
52
+ - **逻辑锁定**: ⚠️只要页面中出现了非查询、非重置的独立操作按钮,必须识别并存入 [functionButton] 数组。
53
+ - **构筑联动**: ⚠️若 [functionButton] 数组不为空,则在下文的 [pageStruct] 中必须包含 "FunctionButtonsBlock"。
54
+
55
+ ## 3. 布局逻辑编排 (PageStruct)
56
+ - **定义**: 定义从上至下的物理堆叠顺序。
57
+ - **白名单**: [MyTable, AlertInfo, MySearchForm, FunctionButtonsBlock]
58
+ - **注释**: MyTable 是表格主体,AlertInfo 是统计条,MySearchForm 是搜索表单,FunctionButtonsBlock 是全局功能按钮。
51
59
 
52
- ## 3. 布局逻辑编排 (Page Structure)
53
- - **字段**: [pageStruct] 定义从上至下的物理堆叠顺序。
54
- - **白名单**: 只能从 [AlertInfo, MySearchForm, FunctionButtonsBlock, MyTable] 中选择。
55
- - **⚠️ 约束**: 严禁在 pageStruct 中包含 其他未定义组件;并且只有真实存在的组件才能被包含。
56
60
 
57
- ## 4. 输出约束 (Output Format)
58
- - **格式**: 纯 JSON 对象,严禁 Markdown 标签包围。
59
- - **字典映射 (OptionDict)**: 必须包含所有 select 类型所需的 Options 数组,键名格式为 字段英文名+Options ,元素格式为 {label:'', value:''}。
61
+ ## 4. 字典数组 (OptionList)
62
+ - **格式**:[{name:'字段英文名+Options',options:[{label:'', value:''}]}]。
63
+ - **注释**:label:选项文本,value:文本英文。
64
+ - **数据源**:根据对应列的内容去重,生成选项数组。
60
65
 
61
- **JSON 骨架要求**:
62
- { "tabs": [], "table": {}, "formItems": [], "staticInfo": {}, "optionDict": {}, "functionButton": [], "pageStruct": [] }
63
66
  `.trim();
@@ -21,25 +21,26 @@ module.exports = `
21
21
  - image: 图片列
22
22
  - date: 时间/日期列
23
23
  - text: 普通文本(默认)
24
- - tag: 标签列(有背景色的标签)
24
+ - tag: 标签列(有背景色的文本)
25
25
  - badge: 徽标列(文本前有颜色小圆点)
26
26
  - **行操作 (Operation)**: [{label:'操作名', action:'动词ByRecord'}]
27
27
 
28
28
  ## 2. 表单 (Form)
29
29
  - **基础格式**: [{ label: '文本', name: 'englishName',type:''}]
30
30
  - **高级格式**: [{ title: '文本', layoutType:'',childItems:[{ label: '文本', name: 'englishName',type:'' }] }]
31
+ - ***⚠️⚠️⚠️注意⚠️⚠️⚠️***:当存在明显的粗体标题或者有明显的分组式结构时,必须使用高级格式。
31
32
  - 禁止输出:
32
33
  - ***⚠️⚠️⚠️严禁输出columns,只允许输出formItems⚠️⚠️⚠️***
33
34
  - ⚠️ 属性精简规则:
34
35
  - 默认输入框: 仅保留 [label, name] 属性。**严禁出现 type 属性**。
35
36
  - 非默认组件: 仅当类型为 [auto, date, radio, select, upload, checkbox, textarea, daterange] 时才允许添加 type 属性。
36
- - ⚠️ layoutType:
37
+ - ***⚠️⚠️⚠️layoutType⚠️⚠️⚠️***:
37
38
  - section: 段落布局(分段title+childItems项,无明显背景色,纯文本title)
38
39
  - collapse: 折叠布局(折叠展开title+childItems项,有明显背景色,或文本前有展开箭头)
39
40
  **标准定义 (FormItem)**:
40
41
  - date: 单日期
41
- - daterange: 日期范围
42
42
  - enum: 枚举类型
43
+ - daterange: 日期范围
43
44
  - text: 普通文本(默认)
44
45
  - 命名规范:
45
46
  - daterange: name 必须设为 '字段英文名start,字段英文名end'。
@@ -0,0 +1,228 @@
1
+ {
2
+ "strict": true,
3
+ "name": "page_construction_protocol",
4
+ "schema": {
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "tabs",
9
+ "table",
10
+ "formItems",
11
+ "staticInfo",
12
+ "optionList",
13
+ "pageStruct",
14
+ "functionButton"
15
+ ],
16
+ "properties": {
17
+ "tabs": {
18
+ "type": "array",
19
+ "items": {
20
+ "type": "object",
21
+ "required": [
22
+ "label",
23
+ "key"
24
+ ],
25
+ "additionalProperties": false,
26
+ "properties": {
27
+ "label": {
28
+ "type": "string"
29
+ },
30
+ "key": {
31
+ "type": "string"
32
+ }
33
+ }
34
+ }
35
+ },
36
+ "table": {
37
+ "type": "object",
38
+ "additionalProperties": false,
39
+ "required": [
40
+ "columns",
41
+ "operation",
42
+ "pagination",
43
+ "expandable",
44
+ "rowSelection"
45
+ ],
46
+ "properties": {
47
+ "columns": {
48
+ "type": "array",
49
+ "items": {
50
+ "type": "object",
51
+ "additionalProperties": false,
52
+ "required": [
53
+ "title",
54
+ "dataIndex",
55
+ "type"
56
+ ],
57
+ "properties": {
58
+ "title": {
59
+ "type": "string"
60
+ },
61
+ "dataIndex": {
62
+ "type": "string"
63
+ },
64
+ "type": {
65
+ "type": "string",
66
+ "enum": [
67
+ "tag",
68
+ "text",
69
+ "date",
70
+ "enum",
71
+ "badge",
72
+ "money",
73
+ "index",
74
+ "image"
75
+ ]
76
+ }
77
+ }
78
+ }
79
+ },
80
+ "operation": {
81
+ "type": "array",
82
+ "items": {
83
+ "type": "object",
84
+ "required": [
85
+ "label",
86
+ "action"
87
+ ],
88
+ "additionalProperties": false,
89
+ "properties": {
90
+ "label": {
91
+ "type": "string"
92
+ },
93
+ "action": {
94
+ "type": "string"
95
+ }
96
+ }
97
+ }
98
+ },
99
+ "pagination": {
100
+ "type": "boolean"
101
+ },
102
+ "expandable": {
103
+ "type": "boolean"
104
+ },
105
+ "rowSelection": {
106
+ "type": "boolean"
107
+ }
108
+ }
109
+ },
110
+ "formItems": {
111
+ "type": "array",
112
+ "items": {
113
+ "type": "object",
114
+ "required": [
115
+ "label",
116
+ "name",
117
+ "type"
118
+ ],
119
+ "additionalProperties": false,
120
+ "properties": {
121
+ "label": {
122
+ "type": "string"
123
+ },
124
+ "name": {
125
+ "type": "string"
126
+ },
127
+ "type": {
128
+ "type": "string",
129
+ "enum": [
130
+ "text",
131
+ "auto",
132
+ "date",
133
+ "radio",
134
+ "select",
135
+ "upload",
136
+ "checkbox",
137
+ "textarea",
138
+ "daterange"
139
+ ]
140
+ }
141
+ }
142
+ }
143
+ },
144
+ "staticInfo": {
145
+ "type": "object",
146
+ "additionalProperties": false,
147
+ "required": [
148
+ "has",
149
+ "text"
150
+ ],
151
+ "properties": {
152
+ "has": {
153
+ "type": "boolean"
154
+ },
155
+ "text": {
156
+ "type": "string"
157
+ }
158
+ }
159
+ },
160
+ "optionList": {
161
+ "type": "array",
162
+ "items": {
163
+ "type": "object",
164
+ "required": [
165
+ "name",
166
+ "options"
167
+ ],
168
+ "additionalProperties": false,
169
+ "properties": {
170
+ "name": {
171
+ "type": "string"
172
+ },
173
+ "options": {
174
+ "type": "array",
175
+ "items": {
176
+ "type": "object",
177
+ "required": [
178
+ "label",
179
+ "value"
180
+ ],
181
+ "additionalProperties": false,
182
+ "properties": {
183
+ "label": {
184
+ "type": "string"
185
+ },
186
+ "value": {
187
+ "type": "string"
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+ },
195
+ "pageStruct": {
196
+ "type": "array",
197
+ "items": {
198
+ "type": "string",
199
+ "enum": [
200
+ "MyTable",
201
+ "AlertInfo",
202
+ "MySearchForm",
203
+ "FunctionButtonsBlock"
204
+ ]
205
+ }
206
+ },
207
+ "functionButton": {
208
+ "type": "array",
209
+ "items": {
210
+ "type": "object",
211
+ "required": [
212
+ "btn",
213
+ "action"
214
+ ],
215
+ "additionalProperties": false,
216
+ "properties": {
217
+ "btn": {
218
+ "type": "string"
219
+ },
220
+ "action": {
221
+ "type": "string"
222
+ }
223
+ }
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
@@ -18,7 +18,7 @@ const getOpenAI = () => {
18
18
  return client
19
19
  }
20
20
 
21
- const askAI = async (model, messages, retryCount = 0) => {
21
+ const askAI = async ({ model, messages, response_format = { type: 'json_object' }, retryCount = 0 }) => {
22
22
 
23
23
  if (retryCount > 3) {
24
24
  throw new Error(language(
@@ -33,8 +33,8 @@ const askAI = async (model, messages, retryCount = 0) => {
33
33
  model,
34
34
  messages,
35
35
  top_p: 0.1,
36
+ response_format,
36
37
  temperature: 0.01,
37
- response_format: { type: 'json_object' }
38
38
  })
39
39
  let raw = response.choices[0].message.content.trim()
40
40
  raw = raw.replace(/^```json\n?/, '').replace(/\n?```$/, '')
@@ -56,7 +56,7 @@ const askAI = async (model, messages, retryCount = 0) => {
56
56
  `[SYSTEM ALERT] Neural Network instability detected. Attempting to re-establish link... (Retry #${retryCount + 1})`
57
57
  )), err.message)
58
58
 
59
- return askAI(model, messages, retryCount + 1)
59
+ return askAI({ model, messages, response_format, retryCount: retryCount + 1 })
60
60
  }
61
61
  }
62
62
 
@@ -65,15 +65,15 @@ module.exports = {
65
65
  const config = getConfig()
66
66
  const mockPrompt = require('../prompts/mock.js')
67
67
  const { MOCK_DESIGNER } = require('../prompts/system.js')
68
- return askAI(
69
- config.textModel,
70
- [
68
+ return askAI({
69
+ model: config.textModel,
70
+ messages: [
71
71
  { role: 'system', content: MOCK_DESIGNER },
72
72
  { role: 'user', content: mockPrompt(columns, fileName) }
73
73
  ]
74
- )
74
+ })
75
75
  },
76
- recognizePage: async (prompt, filePath) => {
76
+ recognizePage: async (prompt, filePath, taskType = 'page') => {
77
77
  const config = getConfig()
78
78
  const { UI_DESIGNER } = require('../prompts/system.js')
79
79
  const sharp = require('sharp')
@@ -83,9 +83,9 @@ module.exports = {
83
83
  .toBuffer()
84
84
  const base64Image = compressedBuffer.toString('base64')
85
85
 
86
- return askAI(
87
- config.visionModel,
88
- [
86
+ return askAI({
87
+ model: config.visionModel,
88
+ messages: [
89
89
  { role: 'system', content: UI_DESIGNER },
90
90
  {
91
91
  role: 'user',
@@ -94,19 +94,20 @@ module.exports = {
94
94
  { type: 'image_url', image_url: { url: `data:image/jpeg;base64,${base64Image}` } }
95
95
  ]
96
96
  }
97
- ]
98
- )
97
+ ],
98
+ response_format: taskType === 'page' ? { type: 'json_schema', strict: true, json_schema: require('../schema/page.json') } : { type: 'json_object' }
99
+ })
99
100
  },
100
101
  alignResponseFields: async (options, responseStr, resourceStr) => {
101
102
  const config = getConfig()
102
103
  const { API_DESIGNER } = require('../prompts/system.js')
103
104
  const apiPrompt = require(`../prompts/${options.template}/watch-api.js`)
104
- return askAI(
105
- config.textModel,
106
- [
105
+ return askAI({
106
+ model: config.textModel,
107
+ messages: [
107
108
  { role: 'system', content: API_DESIGNER },
108
109
  { role: 'user', content: apiPrompt(responseStr, resourceStr) }
109
110
  ]
110
- )
111
+ })
111
112
  }
112
113
  }
@@ -167,7 +167,7 @@ const matrixEffect = async (duration = 1500) => {
167
167
  '5f 43 4f 44 45 5f' // _CODE_
168
168
  ]
169
169
 
170
- const threshold = 3000
170
+ const threshold = 5000
171
171
  const endTime = Date.now() + duration
172
172
  const width = process.stdout.columns || 80
173
173
  const isLegendary = currentTotal >= threshold
@@ -255,10 +255,17 @@ const formatFormItemAndColumns = ({ pageConfig }) => {
255
255
  const formDicts = pageConfig.formItems?.filter(item => item.type === 'select')?.map(item => `${item.name}Options`) ?? []
256
256
  const dictBlocks = Array.from(new Set([...formDicts, ...tableDicts]))
257
257
 
258
- const formItems = pageConfig.formItems?.map(item => ({
259
- ...item,
260
- ...(item.type === 'select' ? { options: `_CODE_${item.name}Options_CODE_` } : {})
261
- }))
258
+ const formItems = pageConfig.formItems?.map(item => {
259
+
260
+ if (item.type === 'text') {
261
+ delete item.type
262
+ }
263
+
264
+ return {
265
+ ...item,
266
+ ...(item.type === 'select' ? { options: `_CODE_${item.name}Options_CODE_` } : {})
267
+ }
268
+ })
262
269
 
263
270
  const processedColumns = columns?.map(col => {
264
271
  if (['image'].includes(col.type)) {
@@ -4,7 +4,7 @@ export const MyImage = ({ img, setImg }) => {
4
4
  return (
5
5
  <div style={{ display: 'none' }}>
6
6
  <Image.PreviewGroup preview={{ visible: img.visible, onVisibleChange: vis => setImg({ ...img, visible: vis }) }}>
7
- {img.list.map((img, index) => (<Image key={index} src={img} />))}
7
+ {img.list.map((src, index) => (<Image key={index} src={src} />))}
8
8
  </Image.PreviewGroup>
9
9
  </div>
10
10
  )
@@ -27,7 +27,7 @@ import { Button, Tree, Radio, Input, Upload, Select, Cascader, Checkbox, DatePic
27
27
  const AliyunOSSUpload = ({ value, onChange, ...restProps }) => {
28
28
 
29
29
  // 💡 物理剥离:将地堡专用参数与 Antd 原生属性分离
30
- const { oss, url, path, options, ...originUploadProps } = restProps
30
+ const { oss, url, path, options, expireField = 'expire', ...originUploadProps } = restProps
31
31
 
32
32
  const [OSSData, setOSSData] = useState(oss)
33
33
 
@@ -81,7 +81,7 @@ const AliyunOSSUpload = ({ value, onChange, ...restProps }) => {
81
81
  */
82
82
  const beforeUpload = async file => {
83
83
  let currentOSS = OSSData
84
- const expire = Number(OSSData?.expire ?? 0) * 1000
84
+ const expire = Number(OSSData?.[expireField] ?? 0) * 1000
85
85
  if (expire < Date.now()) {
86
86
  currentOSS = await init()
87
87
  }
@@ -21,8 +21,8 @@ export default props => {
21
21
 
22
22
  return (
23
23
  <Card {{#if hasTabs}}tabList={tabs} activeTabKey={activeKey} onTabChange={onTabChange}{{/if}}>
24
- {modal.visible && <MyModalForm {...modal} submit={modalSubmit} setModal={setModal} />}
25
24
  {{#if hasImageColumn}}{img.visible && <MyImage img={img} setImg={setImg}/>}{{/if}}
25
+ {modal.visible && <MyModalForm {...modal} submit={modalSubmit} setModal={setModal} />}
26
26
  {{#each pageStruct as |partialName|}}
27
27
  {{> (partialName) @root }}
28
28
  {{/each}}