@yorha2b-lab/autodev 1.0.25 → 1.0.27
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/README.md +2 -0
- package/README_EN.md +194 -0
- package/bin/autodev.js +38 -11
- package/package.json +1 -1
- package/src/commands/watch-api.js +25 -7
- package/src/commands/watch-page.js +60 -57
- package/src/commands/watch-part.js +26 -5
- package/src/prompts/watch-api.js +2 -1
- package/src/services/llm.js +24 -30
- package/src/utils/utils.js +4 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Auto CRUD Copilot
|
|
2
2
|
|
|
3
|
+
[English](./README_EN.md) | 简体中文
|
|
4
|
+
|
|
3
5
|
[](https://www.npmjs.com/package/@yorha2b-lab/autodev)
|
|
4
6
|
[](https://www.npmjs.com/package/@yorha2b-lab/autodev)
|
|
5
7
|
[](https://github.com/yorha2b-lab/auto-crud-copilot/stargazers)
|
package/README_EN.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Auto CRUD Copilot
|
|
2
|
+
|
|
3
|
+
English | [简体中文](./README.md)
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@yorha2b-lab/autodev)
|
|
6
|
+
[](https://www.npmjs.com/package/@yorha2b-lab/autodev)
|
|
7
|
+
[](https://github.com/yorha2b-lab/auto-crud-copilot/stargazers)
|
|
8
|
+
[](https://github.com/yorha2b-lab/auto-crud-copilot/blob/main/LICENSE)
|
|
9
|
+
|
|
10
|
+
An AI-powered frontend CRUD code generator based on vision models (Umi+Antd) 🚀
|
|
11
|
+
|
|
12
|
+
<video src="https://github.com/user-attachments/assets/6d42fce1-c815-482b-8c6a-4a3b9415ba47.mp4" controls="controls" muted="muted" style="max-width: 100%; display: block; margin: 20px auto;">
|
|
13
|
+
</video>
|
|
14
|
+
|
|
15
|
+
Say goodbye to repetitive work! Why do you need Auto CRUD Copilot?
|
|
16
|
+
Stop writing Tables/Forms manually: Screenshot to code, boost your development efficiency, and say goodbye to tedious copy-paste.
|
|
17
|
+
Eliminate API integration pain: Automatically read Response data, and let AI help you bridge frontend-backend field naming differences.
|
|
18
|
+
Decoupled architecture: Core logic separated from UI frameworks, supporting React/Vue/Angular.
|
|
19
|
+
|
|
20
|
+
## ✨ Features
|
|
21
|
+
|
|
22
|
+
- 🖼️ **Visual Recognition**: Automatically recognize page structures from screenshots and generate corresponding CRUD code
|
|
23
|
+
- 🔧 **Smart Generation**: Support multiple frontend framework templates including React, Vue, and Angular
|
|
24
|
+
- 📊 **Table Components**: Automatically generate editable, sortable, and filterable data tables
|
|
25
|
+
- 📝 **Form Components**: Intelligently generate search forms and modal forms
|
|
26
|
+
- 🔌 **API Alignment**: Automatically align Response interfaces with frontend field mappings
|
|
27
|
+
- 🎨 **UI Components**: Support various UI component types (input, select, date picker, etc.)
|
|
28
|
+
- 📱 **Responsive**: Generated code supports responsive layout
|
|
29
|
+
|
|
30
|
+
## 🚀 Quick Start
|
|
31
|
+
|
|
32
|
+
### Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g @yorha2b-lab/autodev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Environment Setup
|
|
39
|
+
|
|
40
|
+
Quick start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
autodev init
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Create a `.env` file and configure the following environment variables:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# AI Model API Configuration
|
|
50
|
+
API_KEY=your_api_key_here
|
|
51
|
+
BASE_URL=your_api_base_url_here
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
🚗 **Get API Key**: [Alibaba Cloud Bailian Console](https://bailian.console.aliyun.com/cn-beijing?tab=doc#/doc/)
|
|
55
|
+
|
|
56
|
+
### Basic Usage
|
|
57
|
+
|
|
58
|
+
1. **Generate Complete CRUD Pages**
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Put page screenshots in the screenShot directory
|
|
62
|
+
autodev watch:page
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
2. **Generate Partial UI Components**
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Put component screenshots in the screenPart directory
|
|
69
|
+
autodev watch:part
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
3. **Align API Fields**
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
autodev watch:api
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 📖 Detailed Usage Guide
|
|
79
|
+
|
|
80
|
+
### watch:page Command
|
|
81
|
+
|
|
82
|
+
Monitor screenshot files in the `screenShot` directory and automatically generate complete CRUD pages.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
autodev watch:page -t react
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Parameters:
|
|
89
|
+
- `-t, --template <type>`: Specify frontend framework template, default is `react`
|
|
90
|
+
|
|
91
|
+
### watch:part Command
|
|
92
|
+
|
|
93
|
+
Monitor screenshot files in the `screenPart` directory and automatically generate partial UI components.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
autodev watch:part -t vue
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### watch:api Command
|
|
100
|
+
|
|
101
|
+
Monitor JSON files in the `response` directory (named after modules) and automatically align real API fields.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
autodev watch:api
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 📁 Project Structure
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
your-project/
|
|
111
|
+
├── screenShot/ # Page screenshot directory
|
|
112
|
+
├── screenPart/ # Component screenshot directory
|
|
113
|
+
├── response/ # Response directory
|
|
114
|
+
├── mock/ # Generated Mock data
|
|
115
|
+
├── src/
|
|
116
|
+
│ ├── pages/ # Generated page code
|
|
117
|
+
│ ├── components/ # Generated component code
|
|
118
|
+
│ ├── hooks/ # Generated Hook code
|
|
119
|
+
└── config.js # Configuration file
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## ⚙️ Configuration
|
|
123
|
+
|
|
124
|
+
You can configure the following options in `config.js`:
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
module.exports = {
|
|
128
|
+
// Whether to generate Mock data
|
|
129
|
+
needMock: false,
|
|
130
|
+
|
|
131
|
+
// AI model types to use
|
|
132
|
+
visionModel: 'qwen3.5-plus',
|
|
133
|
+
textModel: 'qwen-turbo',
|
|
134
|
+
|
|
135
|
+
// Target project directory paths
|
|
136
|
+
hooksDir: 'src/hooks',
|
|
137
|
+
pagesDir: 'src/pages',
|
|
138
|
+
utilsDir: 'src/utils',
|
|
139
|
+
componentsDir: 'src/components',
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 🎯 Supported UI Components
|
|
144
|
+
|
|
145
|
+
- Input
|
|
146
|
+
- InputNumber
|
|
147
|
+
- Select
|
|
148
|
+
- Radio
|
|
149
|
+
- Checkbox
|
|
150
|
+
- DatePicker
|
|
151
|
+
- RangePicker
|
|
152
|
+
- Cascader
|
|
153
|
+
- AutoComplete
|
|
154
|
+
- TextArea
|
|
155
|
+
- Upload
|
|
156
|
+
|
|
157
|
+
## 🤝 Contributing
|
|
158
|
+
|
|
159
|
+
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) to learn how to participate in project development.
|
|
160
|
+
|
|
161
|
+
## 📄 License
|
|
162
|
+
|
|
163
|
+
This project is licensed under the [MIT](LICENSE) license.
|
|
164
|
+
|
|
165
|
+
## 🙏 Acknowledgments
|
|
166
|
+
|
|
167
|
+
Thanks to the following open-source projects:
|
|
168
|
+
|
|
169
|
+
- [OpenAI](https://openai.com/) - Providing powerful AI capabilities
|
|
170
|
+
- [Ant Design](https://ant.design/) - Excellent enterprise-level UI design language
|
|
171
|
+
- [UmiJS](https://umijs.org/) - Enterprise-level frontend application framework
|
|
172
|
+
|
|
173
|
+
## 📞 Contact
|
|
174
|
+
|
|
175
|
+
If you have any questions or suggestions, feel free to submit an [Issue](https://github.com/yorha2b-lab/auto-crud-copilot/issues).
|
|
176
|
+
|
|
177
|
+
## 🛠️ FAQ
|
|
178
|
+
|
|
179
|
+
**Q: Is this tool free?**
|
|
180
|
+
A: The tool itself is open-source and free, but the AI models it calls (such as GPT-4v, Qwen-VL) may require you to configure your own API Key. We recommend using cost-effective models like Alibaba Cloud's Qwen-VL.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## ⚖️ Disclaimer
|
|
185
|
+
|
|
186
|
+
Auto CRUD Copilot is a **fan-made, non-commercial, open-source tool**.
|
|
187
|
+
|
|
188
|
+
- The themes, names (YoRHa, 2B, etc.), and catchphrases included in this project are inspired by **NieR:Automata**, which is a trademark and copyright of **Square Enix Co., Ltd. / PlatinumGames Inc.**
|
|
189
|
+
- This project is not affiliated with, endorsed by, or representative of Square Enix in any way.
|
|
190
|
+
- Please support the original masterpiece: [NieR:Automata Official Site](https://nierautomata.square-enix-games.com/).
|
|
191
|
+
|
|
192
|
+
**Glory to Mankind.**
|
|
193
|
+
|
|
194
|
+
⭐ If this project helps you, please give it a Star to show your support!
|
package/bin/autodev.js
CHANGED
|
@@ -5,47 +5,74 @@ const chalk = require('chalk')
|
|
|
5
5
|
const figlet = require('figlet')
|
|
6
6
|
const { program } = require('commander')
|
|
7
7
|
const pkg = require(path.join(__dirname, '../package.json'))
|
|
8
|
+
const { language } = require(path.join(__dirname, '../src/utils/utils.js'))
|
|
8
9
|
|
|
9
10
|
console.log(chalk.cyan(figlet.textSync('AutoDev', { horizontalLayout: 'full' })))
|
|
10
11
|
console.log(chalk.gray('Booting System...'))
|
|
12
|
+
console.log(chalk.white(' [System] ') + chalk.green('Locale Detection: ') + chalk.cyan(language('ZH-CN', 'EN-US')))
|
|
11
13
|
console.log(chalk.white(' [System] ') + chalk.green('YoRHa No.2 Type B Unit: ') + chalk.cyan('Online'))
|
|
12
14
|
console.log(chalk.white(' [System] ') + chalk.green('Scanner Type 9S Unit: ') + chalk.cyan('Standby'))
|
|
13
15
|
console.log(chalk.white(' [Mission] ') + chalk.yellow('Frontend Architecture Construction: ') + chalk.cyan('Awaiting Command'))
|
|
14
16
|
console.log(chalk.white(' [Bunker] ') + chalk.magenta('Glory to mankind. (人类荣光永存)'))
|
|
15
17
|
console.log(chalk.gray('--------------------------------------------------\n'))
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
program
|
|
19
|
+
// --- Commander 配置 ---
|
|
20
|
+
program
|
|
21
|
+
.version(pkg.version)
|
|
22
|
+
.description(language('AI驱动的前端CRUD代码生成器', 'AI-powered frontend CRUD code generator'))
|
|
23
|
+
|
|
24
|
+
program.option('-t, --template <type>', language('指定前端框架模板', 'Specify frontend framework template'), 'react')
|
|
19
25
|
|
|
20
26
|
program
|
|
21
27
|
.command('init')
|
|
22
|
-
.description('在当前目录初始化 .env 和 config.js 配置文件')
|
|
28
|
+
.description(language('在当前目录初始化 .env 和 config.js 配置文件', 'Initialize .env and config.js in the current directory'))
|
|
23
29
|
.action(() => {
|
|
24
30
|
const tplConfig = path.resolve(__dirname, '../config.js')
|
|
25
31
|
const tplEnv = path.resolve(__dirname, '../.env.example')
|
|
26
32
|
const targetEnv = path.join(process.cwd(), '.env')
|
|
27
33
|
const targetConfig = path.join(process.cwd(), 'config.js')
|
|
34
|
+
|
|
28
35
|
fs.mkdirSync(path.join(process.cwd(), 'response'), { recursive: true })
|
|
29
36
|
fs.mkdirSync(path.join(process.cwd(), 'screenShot'), { recursive: true })
|
|
30
37
|
fs.mkdirSync(path.join(process.cwd(), 'screenPart'), { recursive: true })
|
|
38
|
+
|
|
39
|
+
// .env 处理
|
|
31
40
|
if (fs.existsSync(targetEnv)) {
|
|
32
|
-
console.log(chalk.yellow(
|
|
41
|
+
console.log(chalk.yellow(language(
|
|
42
|
+
'🤖 Pod 042: [警告] 终端已存在 .env 文件,跳过生成以免覆盖数据。',
|
|
43
|
+
'🤖 Pod 042: [Warning] .env already exists. Skipping to avoid data overwrite.'
|
|
44
|
+
)));
|
|
33
45
|
} else {
|
|
34
46
|
fs.copyFileSync(tplEnv, targetEnv);
|
|
35
|
-
console.log(chalk.green(
|
|
47
|
+
console.log(chalk.green(language(
|
|
48
|
+
'🤖 Pod 042: [报告] .env 存储完毕。',
|
|
49
|
+
'🤖 Pod 042: [Report] .env storage complete.'
|
|
50
|
+
)));
|
|
36
51
|
}
|
|
52
|
+
|
|
53
|
+
// config.js 处理
|
|
37
54
|
if (fs.existsSync(targetConfig)) {
|
|
38
|
-
console.log(chalk.yellow(
|
|
55
|
+
console.log(chalk.yellow(language(
|
|
56
|
+
'🤖 Pod 042: [警告] 终端已存在 config.js 文件,跳过生成以免覆盖数据。',
|
|
57
|
+
'🤖 Pod 042: [Warning] config.js already exists. Skipping to avoid data overwrite.'
|
|
58
|
+
)));
|
|
39
59
|
} else {
|
|
40
60
|
fs.copyFileSync(tplConfig, targetConfig)
|
|
41
|
-
console.log(chalk.green(
|
|
61
|
+
console.log(chalk.green(language(
|
|
62
|
+
'🤖 Pod 042: [报告] config.js 存储完毕。',
|
|
63
|
+
'🤖 Pod 042: [Report] config.js storage complete.'
|
|
64
|
+
)));
|
|
42
65
|
}
|
|
43
|
-
|
|
66
|
+
|
|
67
|
+
console.log(chalk.cyan(language(
|
|
68
|
+
'📡 Operator 6O: 呼叫 2B,环境配置文件已下发至本地终端!',
|
|
69
|
+
'📡 Operator 6O: Calling 2B, environmental config files have been deployed to your local terminal!'
|
|
70
|
+
)))
|
|
44
71
|
})
|
|
45
72
|
|
|
46
73
|
program
|
|
47
74
|
.command('watch:page')
|
|
48
|
-
.description('监听截图,自动生成完整的增删改查页面')
|
|
75
|
+
.description(language('监听截图,自动生成完整的增删改查页面', 'Watch screenshots and auto-generate complete CRUD pages'))
|
|
49
76
|
.action(() => {
|
|
50
77
|
const watchPage = require('../src/commands/watch-page')
|
|
51
78
|
watchPage(program.opts())
|
|
@@ -53,7 +80,7 @@ program
|
|
|
53
80
|
|
|
54
81
|
program
|
|
55
82
|
.command('watch:part')
|
|
56
|
-
.description('监听截图,自动生成局部 UI 组件')
|
|
83
|
+
.description(language('监听截图,自动生成局部 UI 组件', 'Watch screenshots and auto-generate partial UI components'))
|
|
57
84
|
.action(() => {
|
|
58
85
|
const watchPart = require('../src/commands/watch-part')
|
|
59
86
|
watchPart(program.opts())
|
|
@@ -61,7 +88,7 @@ program
|
|
|
61
88
|
|
|
62
89
|
program
|
|
63
90
|
.command('watch:api')
|
|
64
|
-
.description('监听 Response,自动对齐真实接口字段')
|
|
91
|
+
.description(language('监听 Response,自动对齐真实接口字段', 'Watch responses and auto-align real API fields'))
|
|
65
92
|
.action(() => {
|
|
66
93
|
const watchApi = require('../src/commands/watch-api')
|
|
67
94
|
watchApi()
|
package/package.json
CHANGED
|
@@ -5,8 +5,8 @@ const chalk = require('chalk')
|
|
|
5
5
|
const chokidar = require('chokidar')
|
|
6
6
|
|
|
7
7
|
const watchApi = () => {
|
|
8
|
-
|
|
9
|
-
const { getConfig } = require('../utils/utils.js')
|
|
8
|
+
// 导入配置文件和多语言函数
|
|
9
|
+
const { getConfig, language } = require('../utils/utils.js')
|
|
10
10
|
const { alignResponseFields } = require('../services/llm.js')
|
|
11
11
|
|
|
12
12
|
const config = getConfig()
|
|
@@ -23,26 +23,44 @@ const watchApi = () => {
|
|
|
23
23
|
watcher.on('add', async filePath => {
|
|
24
24
|
const startTime = Date.now()
|
|
25
25
|
const fileName = path.basename(filePath, path.extname(filePath))
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
// 使用 language 函数包装 Pod 153 的启动报告
|
|
28
|
+
const spinner = ora(chalk.cyan(language(
|
|
29
|
+
`🤖 Pod 153: [报告] 侦测到未知的 Response 数据源 [${fileName}],开始进行语义对齐...`,
|
|
30
|
+
`🤖 Pod 153: [Report] Detected unknown Response data source [${fileName}]. Commencing semantic alignment...`
|
|
31
|
+
)))
|
|
27
32
|
spinner.start()
|
|
28
33
|
|
|
29
34
|
try {
|
|
30
35
|
const responseStr = fs.readFileSync(filePath, 'utf8')
|
|
31
36
|
let resourceStr = fs.readFileSync(`./${config.pagesDir}/${fileName}/resource.js`, 'utf8')
|
|
37
|
+
|
|
38
|
+
// 调用 AI 服务进行字段对齐
|
|
32
39
|
const result = await alignResponseFields(responseStr, resourceStr)
|
|
40
|
+
|
|
33
41
|
Object.entries(result).forEach(([oldField, newField]) => {
|
|
34
42
|
if (oldField === newField) return
|
|
35
43
|
const regex = new RegExp(`(dataIndex|name)\\s*:\\s*['"]${oldField}['"]`, 'g')
|
|
36
44
|
resourceStr = resourceStr.replace(regex, `$1: '${newField}'`)
|
|
37
45
|
})
|
|
46
|
+
|
|
38
47
|
fs.writeFileSync(path.join(`./${config.pagesDir}/${fileName}/resource.js`), resourceStr)
|
|
39
48
|
const endTime = Date.now()
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
|
|
50
|
+
// 成功报告:带上 9S 的骇客梗
|
|
51
|
+
spinner.succeed(chalk.green(language(
|
|
52
|
+
`🤖 Pod 153: [肯定] 字段对齐协议执行完毕:耗时 ${(endTime - startTime) / 1000} 秒。9S 的骇客任务很顺利。`,
|
|
53
|
+
`🤖 Pod 153: [Affirmative] Field alignment protocol executed. Time: ${(endTime - startTime) / 1000}s. 9S's hacking mission was successful.`
|
|
54
|
+
)))
|
|
55
|
+
|
|
42
56
|
} catch (error) {
|
|
43
|
-
|
|
57
|
+
// 失败报告:提到“防火墙”
|
|
58
|
+
spinner.fail(chalk.red(language(
|
|
59
|
+
`🤖 Pod 153: [警告] 骇入 [${fileName}] 失败。接口数据遭遇强力防火墙:${error}`,
|
|
60
|
+
`🤖 Pod 153: [Warning] Hacking into [${fileName}] failed. Encountered a powerful firewall in API data: ${error}`
|
|
61
|
+
)))
|
|
44
62
|
}
|
|
45
63
|
})
|
|
46
64
|
}
|
|
47
65
|
|
|
48
|
-
module.exports = watchApi
|
|
66
|
+
module.exports = watchApi
|
|
@@ -1,132 +1,135 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
|
-
const ora = require('ora')
|
|
2
|
+
const ora = require('ora')
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const chalk = require('chalk')
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
/**
|
|
8
7
|
* 监听截图目录,自动生成 CRUD 页面
|
|
9
8
|
* @param {Object} options - 命令行选项
|
|
10
|
-
* @param {string} options.template -
|
|
9
|
+
* @param {string} options.template - 前端框架模板
|
|
11
10
|
*/
|
|
12
11
|
const watchPage = options => {
|
|
12
|
+
const chokidar = require('chokidar')
|
|
13
|
+
const Handlebars = require('handlebars')
|
|
14
|
+
const pagePrompt = require('../prompts/watch-page.js')
|
|
15
|
+
const { createTaskQueue } = require('../core/task-queue')
|
|
16
|
+
const stringify = require('json-stringify-pretty-compact')
|
|
17
|
+
const { recognizePage, generateMock } = require('../services/llm.js')
|
|
13
18
|
|
|
14
|
-
const
|
|
15
|
-
const Handlebars = require('handlebars') // 模板引擎
|
|
16
|
-
const pagePrompt = require('../prompts/watch-page.js') // 页面识别提示词
|
|
17
|
-
const { createTaskQueue } = require('../core/task-queue') // 任务队列,控制并发
|
|
18
|
-
const stringify = require('json-stringify-pretty-compact') // 格式化 JSON
|
|
19
|
-
const { recognizePage, generateMock } = require('../services/llm.js') // AI 服务
|
|
20
|
-
const { getConfig, copyTemplateDir, getExistingMenus } = require('../utils/utils.js') // 工具函数
|
|
19
|
+
const { language, getConfig, copyTemplateDir, getExistingMenus } = require('../utils/utils.js')
|
|
21
20
|
|
|
22
21
|
const config = getConfig()
|
|
23
22
|
|
|
24
|
-
// 根据框架加载对应的编译器(react-compiler.js / vue-compiler.js 等)
|
|
25
23
|
const compilerPath = path.join(__dirname, `../core/${options.template}-compiler.js`)
|
|
26
24
|
|
|
27
|
-
//
|
|
25
|
+
// 1. 检查框架支持
|
|
28
26
|
if (!fs.existsSync(compilerPath)) {
|
|
29
|
-
console.error(
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
console.error(chalk.red(language(
|
|
28
|
+
`\n❌ 糟糕!暂不支持 [${options.template}] 框架的自动生成。`,
|
|
29
|
+
`\n❌ Errors! Automatic generation for [${options.template}] is not supported.`
|
|
30
|
+
)))
|
|
31
|
+
console.log(language(
|
|
32
|
+
`💡 提示:目前仅内置了 react 编译器。`,
|
|
33
|
+
`💡 Note: Only the react compiler is currently built-in.`
|
|
34
|
+
))
|
|
35
|
+
console.log(language(
|
|
36
|
+
`🚀 强烈欢迎社区大佬提 PR 补充 ${options.template}-compiler.js !\n`,
|
|
37
|
+
`🚀 PRs for ${options.template}-compiler.js are highly welcomed!\n`
|
|
38
|
+
))
|
|
32
39
|
return
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
// 加载编译器的 index 和 resource 函数
|
|
36
42
|
const { index, resource } = require(compilerPath)
|
|
37
43
|
|
|
38
44
|
try {
|
|
39
|
-
// 首次运行时复制模板到目标项目
|
|
40
45
|
copyTemplateDir(options, 'hooks', config.hooksDir)
|
|
41
46
|
copyTemplateDir(options, 'utils', config.utilsDir)
|
|
42
47
|
copyTemplateDir(options, 'components', config.componentsDir)
|
|
43
48
|
} catch (error) {
|
|
44
|
-
console.error('❌ 程序运行出错:', error)
|
|
49
|
+
console.error(language('❌ 程序运行出错:', '❌ Runtime error:'), error)
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
/**
|
|
48
|
-
* 创建任务队列,限制并发数为 2。
|
|
49
|
-
*
|
|
50
|
-
* 为什么限制并发?
|
|
51
|
-
* 1. 视觉大模型的 API 计费昂贵且通常有严格的 TPM/RPM (每分钟请求数) 限流。
|
|
52
|
-
* 2. 如果用户一次性丢入 5 张截图,全量并发极易触发 HTTP 429 Too Many Requests 报错。
|
|
53
|
-
* 3. 并发设为 2 是实测下来速度与稳定性的最佳平衡点。
|
|
54
|
-
*/
|
|
55
52
|
const queue = createTaskQueue(2)
|
|
56
|
-
|
|
57
|
-
// 获取已有的菜单配置
|
|
58
53
|
const menus = getExistingMenus()
|
|
59
54
|
|
|
60
|
-
// 加载模板文件
|
|
61
55
|
const tplDir = path.join(__dirname, `../../templates/${options.template}`)
|
|
62
56
|
const indexTpl = Handlebars.compile(fs.readFileSync(path.join(tplDir, 'index.hbs'), 'utf-8'))
|
|
63
57
|
const resourceTpl = Handlebars.compile(fs.readFileSync(path.join(tplDir, 'resource.hbs'), 'utf-8'))
|
|
64
58
|
|
|
65
|
-
//
|
|
59
|
+
// 2. 队列空闲时的菜单同步
|
|
66
60
|
queue.onIdle(() => {
|
|
67
|
-
console.log(chalk.green(
|
|
61
|
+
console.log(chalk.green(language(
|
|
62
|
+
'🤖 Pod 042: [报告] 所有排队的视觉数据已解析完毕,正在向 Bunker 同步 Menu 路由配置...',
|
|
63
|
+
'🤖 Pod 042: [Report] Queued visual data processed. Syncing Menu configuration to the Bunker...'
|
|
64
|
+
)))
|
|
68
65
|
const constantDir = path.join(process.cwd(), config.utilsDir)
|
|
69
66
|
if (!fs.existsSync(constantDir)) fs.mkdirSync(constantDir, { recursive: true })
|
|
70
|
-
// 格式化菜单配置为 JS 代码
|
|
71
67
|
fs.writeFileSync(path.join(constantDir, 'constant.js'), `export const menus = ${stringify.default(menus, { indent: 4, maxLength: 50 })}`)
|
|
72
|
-
console.log(chalk.green(
|
|
68
|
+
console.log(chalk.green(language(
|
|
69
|
+
'🤖 Pod 042: [肯定] Menu 配置同步成功。',
|
|
70
|
+
'🤖 Pod 042: [Affirmative] Menu configuration synced successfully.'
|
|
71
|
+
)))
|
|
73
72
|
})
|
|
74
73
|
|
|
75
|
-
// 监听 screenShot 目录
|
|
76
74
|
const watcher = chokidar.watch('./screenShot', {
|
|
77
|
-
persistent: true,
|
|
78
|
-
ignored: /(^|[\/\\])\../,
|
|
79
|
-
awaitWriteFinish: {
|
|
80
|
-
stabilityThreshold: 500, // 稳定性阈值(毫秒)
|
|
81
|
-
pollInterval: 100 // 轮询间隔
|
|
82
|
-
}
|
|
75
|
+
persistent: true,
|
|
76
|
+
ignored: /(^|[\/\\])\../,
|
|
77
|
+
awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 }
|
|
83
78
|
})
|
|
84
79
|
|
|
85
|
-
// 文件添加事件
|
|
80
|
+
// 3. 文件添加事件
|
|
86
81
|
watcher.on('add', filePath => {
|
|
87
82
|
queue.add(async () => {
|
|
88
83
|
const startTime = Date.now()
|
|
89
84
|
const mockDir = path.join(process.cwd(), 'mock')
|
|
90
85
|
const fileName = path.basename(filePath, path.extname(filePath))
|
|
91
86
|
const targetDir = path.join(process.cwd(), config.pagesDir, fileName)
|
|
92
|
-
|
|
87
|
+
|
|
88
|
+
const spinner = ora(chalk.cyan(language(
|
|
89
|
+
`🤖 Pod 042: [报告] 捕获到新的视觉图像 [${fileName}],开始执行构筑程序...`,
|
|
90
|
+
`🤖 Pod 042: [Report] Captured new visual image [${fileName}]. Initializing construction...`
|
|
91
|
+
)))
|
|
93
92
|
spinner.start()
|
|
93
|
+
|
|
94
94
|
try {
|
|
95
|
-
// 创建必要的目录
|
|
96
95
|
if (!fs.existsSync(mockDir)) fs.mkdirSync(mockDir, { recursive: true })
|
|
97
96
|
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true })
|
|
98
97
|
|
|
99
|
-
// 1. 识别页面结构(调用视觉大模型)
|
|
100
98
|
const pageConfig = await recognizePage(pagePrompt, filePath)
|
|
101
|
-
|
|
102
|
-
// 2. 生成 resource.js(表格列、表单配置、字典等)
|
|
103
99
|
fs.writeFileSync(path.join(targetDir, 'resource.js'), resource({ pageConfig, resourceTpl }))
|
|
104
|
-
|
|
105
|
-
// 3. 生成 index.js(页面组件)
|
|
106
100
|
fs.writeFileSync(path.join(targetDir, 'index.js'), index({ fileName, indexTpl, pageConfig }))
|
|
107
101
|
|
|
108
|
-
// 4. 如果是新页面,添加到菜单配置
|
|
109
102
|
if (!menus.find(m => m.key === fileName)) {
|
|
110
103
|
menus.push({ label: fileName, key: fileName })
|
|
111
104
|
}
|
|
112
105
|
|
|
113
|
-
//
|
|
106
|
+
// 4. 生成 Mock 数据
|
|
114
107
|
if (config.needMock) {
|
|
115
|
-
console.log(chalk.yellow(
|
|
108
|
+
console.log(chalk.yellow(language(
|
|
109
|
+
`🧑💻 9S: 交给我吧 2B!正在骇入并伪造 [${fileName}] 的 Mock 数据...`,
|
|
110
|
+
`🧑💻 9S: Leave it to me, 2B! Hacking and forging Mock data for [${fileName}]...`
|
|
111
|
+
)))
|
|
116
112
|
const rawContent = await generateMock(pageConfig.table.columns, fileName)
|
|
117
|
-
// 格式化 Mock 数据
|
|
118
113
|
fs.writeFileSync(path.join(mockDir, `${fileName}.js`), `export default ${stringify.default(rawContent, { indent: 4, maxLength: 200 })}`.replaceAll(`"'`, `"`).replaceAll(`'"`, `"`))
|
|
119
|
-
console.log(chalk.green(
|
|
114
|
+
console.log(chalk.green(language(
|
|
115
|
+
`🤖 Pod 042: [报告] Mock 数据注入完成,耗时: ${(Date.now() - startTime) / 1000} 秒`,
|
|
116
|
+
`🤖 Pod 042: [Report] Mock data injection complete. Elapsed time: ${(Date.now() - startTime) / 1000}s`
|
|
117
|
+
)))
|
|
120
118
|
}
|
|
121
119
|
|
|
122
120
|
const endTime = Date.now()
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
spinner.succeed(chalk.green(language(
|
|
122
|
+
`🤖 Pod 042: [报告] 模块 [${fileName}] 物理装配完成!耗时 ${(endTime - startTime) / 1000} 秒`,
|
|
123
|
+
`🤖 Pod 042: [Report] Module [${fileName}] assembly complete! Elapsed time: ${(endTime - startTime) / 1000}s`
|
|
124
|
+
)))
|
|
125
125
|
} catch (error) {
|
|
126
|
-
spinner.fail(chalk.red(
|
|
126
|
+
spinner.fail(chalk.red(language(
|
|
127
|
+
`🤖 Pod 042: [警告] 模块 [${fileName}] 构筑失败。原因:${error}`,
|
|
128
|
+
`🤖 Pod 042: [Warning] Module [${fileName}] construction failed. Reason: ${error}`
|
|
129
|
+
)))
|
|
127
130
|
}
|
|
128
131
|
})
|
|
129
132
|
})
|
|
130
133
|
}
|
|
131
134
|
|
|
132
|
-
module.exports = watchPage
|
|
135
|
+
module.exports = watchPage
|
|
@@ -5,8 +5,9 @@ const chokidar = require('chokidar')
|
|
|
5
5
|
const stringify = require('json-stringify-pretty-compact')
|
|
6
6
|
|
|
7
7
|
const watchPart = () => {
|
|
8
|
-
|
|
8
|
+
// 导入核心逻辑和多语言函数
|
|
9
9
|
const { recognizePage } = require('../services/llm.js')
|
|
10
|
+
const { language } = require('../utils/utils.js')
|
|
10
11
|
|
|
11
12
|
const watcher = chokidar.watch('./screenPart', {
|
|
12
13
|
persistent: true,
|
|
@@ -18,19 +19,28 @@ const watchPart = () => {
|
|
|
18
19
|
})
|
|
19
20
|
|
|
20
21
|
watcher.on('add', async filePath => {
|
|
21
|
-
|
|
22
|
+
// 使用 language 函数包装 Pod 报告
|
|
23
|
+
const spinner = ora(chalk.cyan(language(
|
|
24
|
+
`🤖 Pod 042: [报告] 捕获到新的视觉图像,开始执行构筑程序...`,
|
|
25
|
+
`🤖 Pod 042: [Report] Captured new visual image. Initializing construction...`
|
|
26
|
+
)))
|
|
22
27
|
spinner.start()
|
|
28
|
+
|
|
23
29
|
try {
|
|
24
30
|
const startTime = Date.now()
|
|
25
31
|
const partPrompt = require('../prompts/watch-part.js')
|
|
26
32
|
const pageConfig = await recognizePage(partPrompt, filePath)
|
|
33
|
+
|
|
27
34
|
const optionDict = pageConfig.optionDict || {}
|
|
28
35
|
delete pageConfig.optionDict
|
|
36
|
+
|
|
37
|
+
// 处理配置字符串映射
|
|
29
38
|
let mainConfigStr = stringify.default(pageConfig, { indent: 4, maxLength: 200 })
|
|
30
39
|
.replace(/"(\w+)":/g, '$1:')
|
|
31
40
|
.replace(/"/g, "'")
|
|
32
41
|
.replace(/['"]_CODE_([\s\S]*?)_CODE_['"]/g, '$1')
|
|
33
42
|
.replace(/_CODE_/g, '')
|
|
43
|
+
|
|
34
44
|
let optionsCodeStr = ''
|
|
35
45
|
Object.keys(optionDict).forEach(key => {
|
|
36
46
|
const varName = key.replace('_CODE_', '')
|
|
@@ -38,11 +48,22 @@ const watchPart = () => {
|
|
|
38
48
|
const arrayItemsStr = optionsArray.map(opt => ` { label: '${opt.label}', value: '${opt.value}' }`).join(',\n')
|
|
39
49
|
optionsCodeStr += `\nexport const ${varName} = [\n${arrayItemsStr}\n]\n`
|
|
40
50
|
})
|
|
51
|
+
|
|
41
52
|
const finalResult = `${mainConfigStr}\n${optionsCodeStr}`
|
|
42
53
|
const endTime = Date.now()
|
|
43
|
-
|
|
54
|
+
|
|
55
|
+
// 成功报告
|
|
56
|
+
spinner.succeed(language(
|
|
57
|
+
`🤖 Pod 042: [报告] 识别程序结束!耗时 ${(endTime - startTime) / 1000} 秒\n================\n\n${finalResult}\n\n================`,
|
|
58
|
+
`🤖 Pod 042: [Report] Recognition complete! Elapsed time: ${(endTime - startTime) / 1000}s\n================\n\n${finalResult}\n\n================`
|
|
59
|
+
))
|
|
60
|
+
|
|
44
61
|
} catch (error) {
|
|
45
|
-
|
|
62
|
+
// 失败报告
|
|
63
|
+
spinner.fail(chalk.red(language(
|
|
64
|
+
`🤖 Pod 042: [警告] 识别程序失败。原因:${error}`,
|
|
65
|
+
`🤖 Pod 042: [Warning] Recognition failed. Reason: ${error}`
|
|
66
|
+
)))
|
|
46
67
|
} finally {
|
|
47
68
|
if (fs.existsSync(filePath)) {
|
|
48
69
|
fs.unlinkSync(filePath)
|
|
@@ -51,4 +72,4 @@ const watchPart = () => {
|
|
|
51
72
|
})
|
|
52
73
|
}
|
|
53
74
|
|
|
54
|
-
module.exports = watchPart
|
|
75
|
+
module.exports = watchPart
|
package/src/prompts/watch-api.js
CHANGED
package/src/services/llm.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const JSON5 = require('json5')
|
|
4
|
-
const sharp = require('sharp')
|
|
4
|
+
const sharp = require('sharp')
|
|
5
5
|
const chalk = require('chalk')
|
|
6
6
|
const OpenAI = require('openai')
|
|
7
|
-
const { getConfig } = require('../utils/utils.js')
|
|
7
|
+
const { getConfig, language } = require('../utils/utils.js') // 导入语言助手
|
|
8
8
|
|
|
9
9
|
let client = null
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* 获取 OpenAI 客户端实例(单例模式)
|
|
13
|
-
* 优先使用项目根目录的 .env 文件,其次使用包内的 .env
|
|
14
13
|
*/
|
|
15
14
|
const getOpenAI = () => {
|
|
16
15
|
if (!client) {
|
|
@@ -26,18 +25,18 @@ const getOpenAI = () => {
|
|
|
26
25
|
|
|
27
26
|
/**
|
|
28
27
|
* 调用 AI 模型并解析响应
|
|
29
|
-
* @param {string} model - 模型名称(如 qwen3.5-plus, qwen-turbo)
|
|
30
|
-
* @param {Array} messages - 对话消息数组
|
|
31
|
-
* @param {number} retryCount - 重试次数
|
|
32
|
-
* @returns {Object} 解析后的 JSON 对象
|
|
33
28
|
*/
|
|
34
29
|
const askAI = async (model, messages, retryCount = 0) => {
|
|
35
|
-
//
|
|
36
|
-
if (retryCount > 3)
|
|
30
|
+
// 最终重试失败:YoRHa 司令部连接中断
|
|
31
|
+
if (retryCount > 3) {
|
|
32
|
+
throw new Error(language(
|
|
33
|
+
'YoRHa 司令部连接中断:请检查网络状态或黑盒共鸣情况。',
|
|
34
|
+
'YoRHa Command Link Severed: Check network status or Black Box resonance.'
|
|
35
|
+
))
|
|
36
|
+
}
|
|
37
37
|
|
|
38
38
|
try {
|
|
39
39
|
const openai = getOpenAI()
|
|
40
|
-
// 调用 AI 模型,强制返回 JSON 格式
|
|
41
40
|
const response = await openai.chat.completions.create({
|
|
42
41
|
model,
|
|
43
42
|
messages,
|
|
@@ -45,21 +44,26 @@ const askAI = async (model, messages, retryCount = 0) => {
|
|
|
45
44
|
})
|
|
46
45
|
|
|
47
46
|
let raw = response.choices[0].message.content.trim()
|
|
48
|
-
|
|
49
|
-
// 清理可能的 Markdown 标记(```json ... ```)
|
|
50
47
|
raw = raw.replace(/^```json\n?/, '').replace(/\n?```$/, '')
|
|
51
|
-
|
|
52
|
-
// 提取 JSON 内容(支持对象和数组)
|
|
53
48
|
const match = raw.match(/[\{\[][\s\S]*[\}\]]/)
|
|
54
|
-
|
|
55
|
-
// 使用 JSON5 解析(支持单引号、注释等非标准 JSON)
|
|
56
49
|
return JSON5.parse(match ? match[0] : raw)
|
|
50
|
+
|
|
57
51
|
} catch (err) {
|
|
58
52
|
const isAuthError = err.message.includes('401') || err.message.includes('402');
|
|
59
53
|
if (isAuthError) {
|
|
60
|
-
|
|
54
|
+
// 权限错误:司令部拒绝访问
|
|
55
|
+
throw new Error(language(
|
|
56
|
+
'[系统警告] YoRHa 司令部拒绝访问:API Key 无效或额度耗尽,请检查!',
|
|
57
|
+
'[SYSTEM ALERT] YoRHa Command Center Access Denied: Invalid API Key or quota exhausted!'
|
|
58
|
+
));
|
|
61
59
|
}
|
|
62
|
-
|
|
60
|
+
|
|
61
|
+
// 重试警告:神经云网络连接不稳
|
|
62
|
+
console.warn(chalk.yellow(language(
|
|
63
|
+
`[系统警告] 神经云网络连接不稳,正在尝试重新链接... (第 ${retryCount + 1} 次重试)`,
|
|
64
|
+
`[SYSTEM ALERT] Neural Network instability detected. Attempting to re-establish link... (Retry #${retryCount + 1})`
|
|
65
|
+
)), err.message)
|
|
66
|
+
|
|
63
67
|
return askAI(model, messages, retryCount + 1)
|
|
64
68
|
}
|
|
65
69
|
}
|
|
@@ -67,9 +71,6 @@ const askAI = async (model, messages, retryCount = 0) => {
|
|
|
67
71
|
module.exports = {
|
|
68
72
|
/**
|
|
69
73
|
* 生成 Mock 数据
|
|
70
|
-
* @param {Array} columns - 表格列配置
|
|
71
|
-
* @param {string} fileName - 文件名
|
|
72
|
-
* @returns {string} 生成的 Mock 数据字符串
|
|
73
74
|
*/
|
|
74
75
|
generateMock: async (columns, fileName) => {
|
|
75
76
|
const config = getConfig()
|
|
@@ -85,16 +86,12 @@ module.exports = {
|
|
|
85
86
|
},
|
|
86
87
|
|
|
87
88
|
/**
|
|
88
|
-
*
|
|
89
|
-
* @param {string} prompt - 提示词
|
|
90
|
-
* @param {string} filePath - 截图文件路径
|
|
91
|
-
* @returns {Object} 识别后的页面配置对象
|
|
89
|
+
* 识别页面截图
|
|
92
90
|
*/
|
|
93
91
|
recognizePage: async (prompt, filePath) => {
|
|
94
92
|
const config = getConfig()
|
|
95
93
|
const { UI_DESIGNER } = require('../prompts/system.js')
|
|
96
94
|
|
|
97
|
-
// 压缩图片到 1280px 宽度(避免超出 token 限制),质量 80%
|
|
98
95
|
const compressedBuffer = await sharp(filePath)
|
|
99
96
|
.resize(1280, null, { withoutEnlargement: true })
|
|
100
97
|
.jpeg({ quality: 80 })
|
|
@@ -117,10 +114,7 @@ module.exports = {
|
|
|
117
114
|
},
|
|
118
115
|
|
|
119
116
|
/**
|
|
120
|
-
* 对齐 Response
|
|
121
|
-
* @param {string} responseStr - 真实接口响应数据
|
|
122
|
-
* @param {string} resourceStr - 前端 resource.js 内容
|
|
123
|
-
* @returns {Object} 字段映射对象 { oldName: newName }
|
|
117
|
+
* 对齐 Response 接口字段
|
|
124
118
|
*/
|
|
125
119
|
alignResponseFields: async (responseStr, resourceStr) => {
|
|
126
120
|
const config = getConfig()
|
package/src/utils/utils.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
|
|
4
|
+
const isCN = Intl.DateTimeFormat().resolvedOptions().locale.includes('zh')
|
|
5
|
+
const language = (zh, en) => (isCN ? zh : en)
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
8
|
* 获取项目配置信息
|
|
6
9
|
* 优先读取项目根目录的 config.js,如果不存在则使用默认配置
|
|
@@ -106,4 +109,4 @@ const copyTemplateDir = (options, templateSubDir, targetSubDir) => {
|
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
// 导出工具函数
|
|
109
|
-
module.exports = { getConfig, cleanCode, getExistingMenus, copyTemplateDir, generateSmartImports }
|
|
112
|
+
module.exports = { language, getConfig, cleanCode, getExistingMenus, copyTemplateDir, generateSmartImports }
|