@yorha2b-lab/autodev 1.0.26 → 1.0.28
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/bin/autodev.js +38 -11
- package/config.js +3 -0
- package/package.json +1 -1
- package/src/commands/watch-api.js +25 -7
- package/src/commands/watch-page.js +61 -58
- package/src/commands/watch-part.js +26 -5
- package/src/core/react-compiler.js +2 -2
- package/src/services/llm.js +24 -30
- package/src/utils/utils.js +4 -1
- package/templates/react/index.hbs +6 -2
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/config.js
CHANGED
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 }))
|
|
100
|
+
fs.writeFileSync(path.join(targetDir, 'index.js'), index({ config, fileName, indexTpl, pageConfig }))
|
|
104
101
|
|
|
105
|
-
// 3. 生成 index.js(页面组件)
|
|
106
|
-
fs.writeFileSync(path.join(targetDir, 'index.js'), index({ fileName, indexTpl, pageConfig }))
|
|
107
|
-
|
|
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
|
|
@@ -44,7 +44,7 @@ const resource = ({ pageConfig, resourceTpl }) => {
|
|
|
44
44
|
* @param {Object} param0.pageConfig - 页面配置对象
|
|
45
45
|
* @returns {string} 生成的 index.js 代码
|
|
46
46
|
*/
|
|
47
|
-
const index = ({ fileName, indexTpl, pageConfig }) => {
|
|
47
|
+
const index = ({ config, fileName, indexTpl, pageConfig }) => {
|
|
48
48
|
const hasTabs = pageConfig.tabs?.length > 0
|
|
49
49
|
const hasOperate = pageConfig.table.operation?.length > 0
|
|
50
50
|
|
|
@@ -72,7 +72,7 @@ const index = ({ fileName, indexTpl, pageConfig }) => {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// 渲染模板
|
|
75
|
-
const bodyCode = indexTpl(viewData)
|
|
75
|
+
const bodyCode = indexTpl(viewData).replaceAll('_responseSuccess_', config.responseSuccess)
|
|
76
76
|
// 生成智能导入语句
|
|
77
77
|
const importsStr = generateSmartImports(bodyCode, hasTabs)
|
|
78
78
|
// 清理代码并返回
|
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 }
|
|
@@ -25,7 +25,9 @@ export default props => {
|
|
|
25
25
|
{{#each operations}}
|
|
26
26
|
const {{this.action}} = async record => {
|
|
27
27
|
const response = await request('/api')
|
|
28
|
-
|
|
28
|
+
if(_responseSuccess_){
|
|
29
|
+
refresh()
|
|
30
|
+
}
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
{{/each}}
|
|
@@ -38,7 +40,9 @@ export default props => {
|
|
|
38
40
|
content: '确认{{this.btn}}吗?',
|
|
39
41
|
onOk: async () => {
|
|
40
42
|
const response = await request('/api')
|
|
41
|
-
|
|
43
|
+
if(_responseSuccess_){
|
|
44
|
+
refresh()
|
|
45
|
+
}
|
|
42
46
|
}
|
|
43
47
|
})
|
|
44
48
|
}
|