@yorha2b-lab/autodev 4.1.0 → 4.2.0

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.
Files changed (33) hide show
  1. package/.github/scripts/analyze/bot_detect.py +5 -5
  2. package/.github/scripts/analyze/cluster.py +5 -5
  3. package/.github/scripts/collect/traffic.py +11 -11
  4. package/.github/scripts/deploy/publish.py +15 -15
  5. package/.github/scripts/render/dashboard.py +9 -9
  6. package/.github/workflows/bunker-intelligence.yml +4 -4
  7. package/bin/autodev.js +6 -5
  8. package/config.js +1 -1
  9. package/package.json +1 -1
  10. package/src/commands/handlers/api-handler.js +15 -10
  11. package/src/commands/handlers/page-handler.js +21 -19
  12. package/src/commands/handlers/part-handler.js +14 -11
  13. package/src/commands/watch.js +20 -18
  14. package/src/core/context.js +39 -52
  15. package/src/core/react-compiler.js +69 -85
  16. package/src/prompts/mock.js +1 -1
  17. package/src/prompts/react/api-linker.js +1 -1
  18. package/src/prompts/react/watch-api.js +1 -1
  19. package/src/prompts/react/watch-page.js +1 -0
  20. package/src/prompts/react/watch-part.js +1 -0
  21. package/src/services/api-linker.js +21 -59
  22. package/src/services/llm.js +64 -81
  23. package/src/services/proxy-tower.js +17 -12
  24. package/src/utils/{utils.js → core.js} +41 -165
  25. package/src/utils/infrastructure.js +102 -0
  26. package/src/utils/ux.js +115 -0
  27. package/templates/react/components/MyModalForm.js +1 -1
  28. package/templates/react/components/MyModalTable.js +1 -1
  29. package/templates/react/components/index.js +2 -2
  30. package/templates/react/handlebars/handleBlock.hbs +3 -2
  31. package/templates/react/handlebars/hookBlock.hbs +1 -0
  32. package/src/core/task-queue.js +0 -38
  33. /package/src/schema/{page.json → react/page.json} +0 -0
@@ -1,42 +1,45 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const chalk = require('chalk')
4
+ const OpenAI = require('openai')
4
5
  const Handlebars = require('handlebars')
5
6
  const stringify = require('json-stringify-pretty-compact')
6
7
 
7
- Handlebars.registerHelper('raw', options => options.fn())
8
- Handlebars.registerHelper('stringify', (context, maxLength = 200) => context ? new Handlebars.SafeString(stringify.default(context, { indent: 4, maxLength })) : '[]')
8
+ const contextStringify = ({ context, indent = 4, maxLength = 200 }) => stringify.default(context, { indent, maxLength })
9
9
 
10
- let instance = null // 💡 物理封存的全局实例
10
+ Handlebars.registerHelper('raw', opt => opt.fn())
11
+ Handlebars.registerHelper('stringify', (context, maxLength = 200) => context ? new Handlebars.SafeString(contextStringify({ context, maxLength })) : '[]')
12
+
13
+ let instance = null
11
14
 
12
15
  module.exports = {
13
- /**
14
- * @description 执行中枢神经元初始化
15
- */
16
+
16
17
  init: options => {
17
18
 
18
- const { apiLinker, recognizePage, generateMock, alignResponseFields } = require('../services/llm')
19
- const { language, getConfig, unwrapSignal, isQuerySignal, getExistingMenus } = require('../utils/utils')
19
+ const template = options.template
20
+ const uxCtx = require('../utils/ux')
21
+ const coreCtx = require('../utils/core')
22
+ const infrastructureCtx = require('../utils/infrastructure')
23
+
24
+ const { language } = uxCtx
25
+ const { getConfig, getExistingMenus } = infrastructureCtx
20
26
 
21
27
  const config = getConfig()
28
+ const { hbsDir } = config
29
+
22
30
  const menus = getExistingMenus()
23
- const template = options.template
24
31
  const apiHandler = require('../commands/handlers/api-handler')
25
32
  const pageHandler = require('../commands/handlers/page-handler')
26
33
  const partHandler = require('../commands/handlers/part-handler')
27
- const tplDir = config.hbsDir !== '' ? path.join(process.cwd(), config.hbsDir) : path.join(__dirname, `../../templates/${template}`)
34
+ const tplDir = hbsDir !== '' ? path.join(process.cwd(), hbsDir) : path.join(__dirname, `../../templates/${template}`)
28
35
 
29
36
  const compilerPath = path.join(__dirname, `./${template}-compiler.js`)
30
37
  if (!fs.existsSync(compilerPath)) {
31
38
  console.error(chalk.red(language(`❌ 暂不支持 [${template}] 框架。欢迎提交 Pull Request 贡献。`, `❌ [${template}] framework not supported. Welcome to contribute a Pull Request to help.`)))
32
39
  return
33
40
  }
34
- const { index, resource } = require(compilerPath)
35
41
 
36
- // 💡 物理扫描:把目录下所有的 .hbs 文件全部识别并自动注册
37
- const partialsDir = config.hbsDir !== ''
38
- ? path.join(process.cwd(), config.hbsDir, 'handlebars')
39
- : path.join(__dirname, `../../templates/${template}/handlebars`)
42
+ const partialsDir = hbsDir !== '' ? path.join(process.cwd(), hbsDir, 'handlebars') : path.join(__dirname, `../../templates/${template}/handlebars`)
40
43
  fs.readdirSync(partialsDir).forEach(file => {
41
44
  if (file.endsWith('.hbs')) {
42
45
  const name = path.basename(file, '.hbs') // 去掉后缀拿名字
@@ -45,62 +48,46 @@ module.exports = {
45
48
  }
46
49
  })
47
50
 
48
- const cliCtx = {
49
- options,
50
- template,
51
- }
52
-
53
- const llmCtx = {
54
- apiLinker,
55
- generateMock,
56
- recognizePage,
57
- alignResponseFields
58
- }
59
-
60
- const utilsCtx = {
61
- menus,
62
- config,
63
- language,
64
- unwrapSignal,
65
- isQuerySignal,
66
- }
67
-
68
- const handlerCtx = {
69
- apiHandler,
70
- pageHandler,
71
- partHandler
72
- }
73
-
74
- const compileCtx = {
75
- index,
76
- resource
77
- }
78
-
79
51
  const promptCtx = {
52
+ mockPrompt: require(`../prompts/mock.js`),
53
+ sysPrompt: require(`../prompts/system.js`),
54
+ apiPrompt: require(`../prompts/${template}/watch-api.js`),
80
55
  pagePrompt: require(`../prompts/${template}/watch-page.js`),
81
56
  partPrompt: require(`../prompts/${template}/watch-part.js`),
57
+ linkerPrompt: require(`../prompts/${template}/api-linker.js`),
82
58
  }
83
59
 
84
- const templateCtx = {
60
+ const hbsTplCtx = {
85
61
  indexTpl: Handlebars.compile(fs.readFileSync(path.join(tplDir, 'index.hbs'), 'utf-8')),
86
62
  resourceTpl: Handlebars.compile(fs.readFileSync(path.join(tplDir, 'resource.hbs'), 'utf-8')),
87
63
  }
88
64
 
65
+ const pkgEnv = path.resolve(__dirname, '../.env')
66
+ const localEnv = path.resolve(process.cwd(), '.env')
67
+ require('dotenv').config({ path: fs.existsSync(localEnv) ? localEnv : pkgEnv })
68
+ const openAI = new OpenAI({ apiKey: process.env.API_KEY, baseURL: process.env.BASE_URL })
69
+
70
+ const cliCtx = { options }
71
+ const handlerCtx = { apiHandler, pageHandler, partHandler }
72
+ const finalInfrastructureCtx = { menus, config, ...infrastructureCtx }
73
+ const llmCtx = require('../services/llm')({ config, openAI, template, ...promptCtx })
74
+ const compileCtx = require(compilerPath)({ config, contextStringify, ...coreCtx, ...hbsTplCtx })
89
75
 
90
76
  instance = {
77
+ openAI,
78
+ ...uxCtx,
91
79
  ...cliCtx,
92
80
  ...llmCtx,
93
- ...utilsCtx,
81
+ ...coreCtx,
94
82
  ...promptCtx,
83
+ ...hbsTplCtx,
95
84
  ...handlerCtx,
96
85
  ...compileCtx,
97
- ...templateCtx,
86
+ contextStringify,
87
+ ...finalInfrastructureCtx,
98
88
  }
99
89
  return instance
100
90
  },
101
91
 
102
- /**
103
- * @description 全频道信号接入:获取全局上下文
104
- */
105
92
  get: () => instance
106
93
  }
@@ -1,98 +1,82 @@
1
- const stringify = require('json-stringify-pretty-compact')
2
- const { cleanCode, generateSmartImports, formatFormItemAndColumns } = require('../utils/utils.js')
1
+ module.exports = ({ config, contextStringify, indexTpl, resourceTpl, formatFormItemAndColumns, cleanCode, generateSmartImports }) => {
3
2
 
4
- /**
5
- * 生成 resource.js 文件(表格列配置、表单配置、字典等)
6
- * @param {Object} param0 - 函数参数
7
- * @param {Object} param0.pageConfig - 页面配置对象(包含 tabs、formItems、table 等)
8
- * @param {Object} param0.resourceTpl - Handlebars 模板函数
9
- * @returns {string} 生成的 resource.js 代码
10
- */
11
- const resource = ({ pageConfig, resourceTpl }) => {
3
+ const { needMock, responseSuccess } = config
12
4
 
13
- const { formItems, processedColumns, dictBlocks } = formatFormItemAndColumns({ pageConfig })
5
+ return {
6
+ index: ({ fileName, pageConfig }) => {
7
+ const hasTabs = pageConfig.tabs?.length > 0
8
+ const hasPagination = pageConfig.table.pagination
9
+ const hasFormItems = pageConfig.formItems?.length > 0
10
+ const hasOperate = pageConfig.table?.operation?.length > 0
11
+ const hasImageColumn = pageConfig.table?.columns?.some(item => item.type === 'image')
12
+ const functionButtons = pageConfig.functionButton?.filter(item => !['查询', '重置', 'query', 'search', 'reset'].includes(item.btn.toLowerCase().replaceAll(' ', ''))) || []
13
+ const needRenderAction = hasImageColumn
14
14
 
15
- const hasTabs = pageConfig.tabs?.length > 0
16
- const hasFormItems = pageConfig.formItems?.length > 0
17
- const tabKeys = pageConfig.tabs.map(tab => tab.key).sort((a, b) => a.length - b.length)
18
- const optionDict = pageConfig.optionList?.reduce((acc, item) => ({ ...acc, [item.name]: item.options }), {})
15
+ const pageStruct = ['stateBlock', 'handleBlock', 'hookBlock', hasOperate ? 'operateBlock' : '', 'renderBlock'].filter(Boolean)
19
16
 
20
- const columnsData = processedColumns.map(item => {
21
- delete item.type
22
- return item
23
- })
17
+ let columnsValue = hasTabs ? 'columns[activeKey]' : 'columns'
18
+ if (hasOperate) {
19
+ columnsValue = `${columnsValue}.concat(operate)`
20
+ }
24
21
 
25
- const viewData = {
26
- hasTabs,
27
- columnsData,
28
- hasFormItems,
29
- tabs: pageConfig.tabs,
30
- dictBlocks: dictBlocks.map(item => ({ name: item, data: optionDict[item] ?? [] })),
31
- tabColumns: stringify.default(Object.fromEntries(tabKeys.map(tab => [tab, '_CODE_commonColumns_CODE_'])), { indent: 4, maxLength: 100 }),
32
- formItemsData: !hasTabs ?
33
- `export const formItems = ${stringify.default(formItems, { indent: 4, maxLength: 120 })}` :
34
- `const searchItems = ${stringify.default(formItems, { indent: 4, maxLength: 120 })}\n\nexport const formItems = ${stringify.default(Object.fromEntries(tabKeys.map(tab => [tab, '_CODE_searchItems_CODE_'])), { indent: 4, maxLength: 100 })}`,
35
- }
22
+ const imgAction = Object.fromEntries(pageConfig.table?.columns?.filter(item => item.type === 'image')?.map(item => [item.dataIndex, `_CODE_(_, record) => <a onClick={() => showImg(record, '${item.dataIndex}')}>查看图片</a>_CODE_`]))
23
+ const renderAction = `{${contextStringify({ context: imgAction, maxLength: 120 })}}`.replace(/\n/g, '\n ')
36
24
 
37
- const bodyCode = resourceTpl(viewData)
38
- const importsStr = generateSmartImports({ module: 'resource', hasTabs, bodyCode, hasFormItems })
39
- return cleanCode(`${importsStr}\n\n${bodyCode}`)
40
- }
25
+ const viewData = {
26
+ hasTabs,
27
+ pageStruct,
28
+ columnsValue,
29
+ hasFormItems,
30
+ renderAction,
31
+ hasPagination,
32
+ hasImageColumn,
33
+ functionButtons,
34
+ responseSuccess,
35
+ needRenderAction,
36
+ tabs: pageConfig.tabs,
37
+ renderTree: pageConfig.renderTree,
38
+ hasExpandable: pageConfig.table.expandable,
39
+ staticInfoText: pageConfig.staticInfo?.text,
40
+ operations: pageConfig.table.operation || [],
41
+ hasRowSelection: pageConfig.table.rowSelection,
42
+ uri: needMock ? fileName : 'BUNKER_API_ANCHOR_pages',
43
+ formItems: hasFormItems ? (hasTabs ? 'formItems[activeKey]' : 'formItems') : '[]',
44
+ initParams: `{ ${hasTabs ? 'type: tabs[0].key ,' : ''}${hasPagination ? 'pageNo: 1 , pageSize: 10' : ''}}`,
45
+ }
41
46
 
42
- /**
43
- * 生成 index.js 文件(页面组件)
44
- * @param {Object} param0 - 函数参数
45
- * @param {string} param0.fileName - 页面文件名
46
- * @param {Object} param0.indexTpl - Handlebars 模板函数
47
- * @param {Object} param0.pageConfig - 页面配置对象
48
- * @returns {string} 生成的 index.js 代码
49
- */
50
- const index = ({ config, fileName, indexTpl, pageConfig }) => {
47
+ const bodyCode = indexTpl(viewData)
48
+ const importsStr = generateSmartImports({ module: 'index', hasTabs, bodyCode, hasFormItems })
49
+ return cleanCode(`${importsStr}\n\n${bodyCode}`)
50
+ },
51
+ resource: ({ pageConfig }) => {
51
52
 
52
- const hasTabs = pageConfig.tabs?.length > 0
53
- const hasPagination = pageConfig.table.pagination
54
- const hasFormItems = pageConfig.formItems?.length > 0
55
- const hasOperate = pageConfig.table?.operation?.length > 0
56
- const hasImageColumn = pageConfig.table?.columns?.some(item => item.type === 'image')
57
- const renderTree = pageConfig.renderTree?.filter(item => item.toLowerCase() !== 'tabs') || []
58
- const functionButtons = pageConfig.functionButton?.filter(item => !['查询', '重置', 'query', 'search', 'reset'].includes(item.btn.toLowerCase().replaceAll(' ', ''))) || []
59
- const needRenderAction = hasImageColumn
53
+ const { formItems, processedColumns, dictBlocks } = formatFormItemAndColumns({ pageConfig })
60
54
 
61
- const pageStruct = ['stateBlock', 'handleBlock', 'hookBlock', hasOperate ? 'operateBlock' : '', 'renderBlock'].filter(Boolean)
55
+ const hasTabs = pageConfig.tabs?.length > 0
56
+ const hasFormItems = pageConfig.formItems?.length > 0
57
+ const tabKeys = pageConfig.tabs.map(tab => tab.key).sort((a, b) => a.length - b.length)
58
+ const optionDict = pageConfig.optionList?.reduce((acc, item) => ({ ...acc, [item.name]: item.options }), {})
62
59
 
63
- let columnsValue = hasTabs ? 'columns[activeKey]' : 'columns'
64
- if (hasOperate) {
65
- columnsValue = `${columnsValue}.concat(operate)`
66
- }
60
+ const columnsData = processedColumns.map(item => {
61
+ delete item.type
62
+ return item
63
+ })
67
64
 
68
- const imgAction = Object.fromEntries(pageConfig.table?.columns?.filter(item => item.type === 'image')?.map(item => [item.dataIndex, `_CODE_(_, record) => <a onClick={() => showImg(record, '${item.dataIndex}')}>查看图片</a>_CODE_`]))
69
- const renderAction = `{${stringify.default({ ...imgAction }, { indent: 4, maxLength: 120 })}}`.replace(/\n/g, '\n ')
65
+ const viewData = {
66
+ hasTabs,
67
+ columnsData,
68
+ hasFormItems,
69
+ tabs: pageConfig.tabs,
70
+ dictBlocks: dictBlocks.map(item => ({ name: item, data: optionDict[item] ?? [] })),
71
+ tabColumns: contextStringify({ context: Object.fromEntries(tabKeys.map(tab => [tab, '_CODE_commonColumns_CODE_'])), maxLength: 100 }),
72
+ formItemsData: !hasTabs ?
73
+ `export const formItems = ${contextStringify({ context: formItems, maxLength: 120 })}` :
74
+ `const searchItems = ${contextStringify({ context: formItems, maxLength: 120 })}\n\nexport const formItems = ${contextStringify({ context: Object.fromEntries(tabKeys.map(tab => [tab, '_CODE_searchItems_CODE_'])), maxLength: 100 })}`,
75
+ }
70
76
 
71
- const viewData = {
72
- hasTabs,
73
- renderTree,
74
- pageStruct,
75
- columnsValue,
76
- hasFormItems,
77
- renderAction,
78
- hasPagination,
79
- hasImageColumn,
80
- functionButtons,
81
- needRenderAction,
82
- tabs: pageConfig.tabs,
83
- responseSuccess: config.responseSuccess,
84
- hasExpandable: pageConfig.table.expandable,
85
- staticInfoText: pageConfig.staticInfo?.text,
86
- operations: pageConfig.table.operation || [],
87
- hasRowSelection: pageConfig.table.rowSelection,
88
- uri: config.needMock ? fileName : 'BUNKER_API_ANCHOR_pages',
89
- formItems: hasFormItems ? (hasTabs ? 'formItems[activeKey]' : 'formItems') : '[]',
90
- initParams: `{ ${hasTabs ? 'type: tabs[0].key ,' : ''}${hasPagination ? 'pageNo: 1 , pageSize: 10' : ''}}`,
77
+ const bodyCode = resourceTpl(viewData)
78
+ const importsStr = generateSmartImports({ module: 'resource', hasTabs, bodyCode, hasFormItems })
79
+ return cleanCode(`${importsStr}\n\n${bodyCode}`)
80
+ }
91
81
  }
92
-
93
- const bodyCode = indexTpl(viewData)
94
- const importsStr = generateSmartImports({ module: 'index', hasTabs, bodyCode, hasFormItems })
95
- return cleanCode(`${importsStr}\n\n${bodyCode}`)
96
- }
97
-
98
- module.exports = { index, resource }
82
+ }
@@ -1,4 +1,4 @@
1
- module.exports = (columns, fileName) => `
1
+ module.exports = ({ columns, fileName }) => `
2
2
  根据columns的dataIndex字段生成一个简单的接口mock数据: ${JSON.stringify(columns)},
3
3
  格式为{POST /api/${fileName}:{data:2条真实mock数据]}}
4
4
  `
@@ -1,4 +1,4 @@
1
- module.exports = (bunkerAnchors, realApis) => `
1
+ module.exports = ({ bunkerAnchors, realApis }) => `
2
2
  # MISSION: Multi-Point API Alignment
3
3
  目标:为地堡生成的多个作战动作匹配真实的后端接口。
4
4
 
@@ -1,4 +1,4 @@
1
- module.exports = (responseStr, resourceStr) => `
1
+ module.exports = ({ responseStr, resourceStr }) => `
2
2
  目标:执行【前端虚拟字段】与【后端真实响应】的物理重组与对账。
3
3
 
4
4
  ## 1. 核心输入 (Signal Capture)
@@ -34,6 +34,7 @@ module.exports = `
34
34
  ### 表格主体 (Table)
35
35
  - ⚠️ **操作锁定**: 严禁在 columns 数组中包含“操作”列。
36
36
  - **标准项**: {title:'列名', dataIndex:'列名英文名词',type:''}。
37
+ - **注意**: type为text时必须省略type属性。
37
38
  - **配置项**: [pagination, expandable, rowSelection] 均为布尔值false,只有页面明确有对应功能时才为true。
38
39
  - **标准定义 (Columns)**:
39
40
  - enum: 枚举列
@@ -10,6 +10,7 @@ module.exports = `
10
10
  ## 1. 表格 (Table)
11
11
  - 结构: { columns: [{ title: '列名', dataIndex: '列名英文名词', type: '' }] }
12
12
  - ⚠️ 字段白名单: [title, dataIndex, sorter, render, filters, onFilter]
13
+ - **注意**: type为text时必须省略type属性。
13
14
  - 禁止输出:
14
15
  - 严禁在 columns 中包含“操作”列。
15
16
  - ***⚠️⚠️⚠️严禁输出formItems,只允许输出columns⚠️⚠️⚠️***
@@ -1,65 +1,27 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
- const chalk = require('chalk')
4
- const { Segment, useDefault } = require('segmentit')
5
- const segmentit = useDefault(new Segment())
6
-
7
- /**
8
- * @function getSemanticKeywords
9
- * @description [语义精炼厂 2.0] 使用 segmentit 引擎进行物理切片。
10
- */
11
- const getSemanticKeywords = text => {
12
- // 💡 物理脱水:确保输入为纯净字符串
13
- const content = Array.isArray(text) ? text.join(' ') : (text || '')
14
- // 💡 执行分词
15
- const result = segmentit.doSegment(content, {
16
- simple: true, // 开启简易模式,提升吞吐效率
17
- stripPunctuation: true // 物理过滤掉所有的标点符号
18
- })
19
- // 💡 提取词条并执行“基因筛选”
20
- return Array.from(new Set(result.filter(word => word.length >= 2 && !/^[0-9]+$/.test(word))))
21
- }
22
-
23
- /**
24
- * @function getLocalScore
25
- * @description [战力评估协议] 计算语义重合度。
26
- */
27
- const getLocalScore = (api, pageKeywords, moduleName) => {
28
- let score = 0
29
- const path = api.path.toLowerCase()
30
- const desc = api.desc.toLowerCase()
31
- const mod = moduleName.toLowerCase()
32
-
33
- // 💡 权重 A:路径直接包含模块名 (暴击加分 +50)
34
- if (path.includes(mod)) score += 50
35
-
36
- // 💡 权重 B:描述包含模块核心词 (中量加分 +10)
37
- pageKeywords.forEach(word => {
38
- if (desc.includes(word.toLowerCase())) score += 10
39
- if (path.includes(word.toLowerCase())) score += 5
40
- })
41
-
42
- return score
43
- }
44
-
45
- /**
46
- * @module apiLinker
47
- * @description [地堡 4.0 核心] 全频道自动寻址协议。
48
- */
49
1
  module.exports = async () => {
50
2
 
51
- const { get } = require('../core/context')
52
- const { config, options, language, apiLinker } = get()
53
- if (!config.apiDoc) return
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const chalk = require('chalk')
6
+
7
+ const {
8
+ config,
9
+ language,
10
+ apiLinker,
11
+ getLocalScore, getSemanticKeywords,
12
+ } = require('../core/context').get()
13
+
14
+ const { apiDoc, pagesDir } = config
15
+ if (!apiDoc) return
54
16
 
55
17
  console.log(chalk.cyan(language(
56
18
  `\n🤖 Pod 153: 启动‘全频道自动寻址协议 [语义扫描版]’...`,
57
- `\n🤖 Pod 153: Initiating "Full-Channel Autonomous Addressing Protocol [Semantic Scan]"...`
19
+ `\n🤖 Pod 153: Initiating 'Full-Channel Autonomous Addressing Protocol [Semantic Scan]'...`
58
20
  )))
59
21
 
60
22
  try {
61
23
  // 1. 抓取情报
62
- const apiData = await fetch(config.apiDoc).then(res => res.json())
24
+ const apiData = await fetch(apiDoc).then(res => res.json())
63
25
  const refinedApis = Object.entries(apiData.paths).flatMap(([apiUrl, methods]) => {
64
26
  // 💡 适配多方法接口:有的路径下可能既有 GET 也有 POST
65
27
  return Object.entries(methods).map(([method, info]) => ({
@@ -70,12 +32,12 @@ module.exports = async () => {
70
32
  })
71
33
 
72
34
  // 2. 战场侦察
73
- const pagesDir = path.join(process.cwd(), config.pagesDir)
74
- const files = fs.readdirSync(pagesDir)
35
+ const pages = path.join(process.cwd(), pagesDir)
36
+ const files = fs.readdirSync(pages)
75
37
 
76
38
  for (const fileName of files) {
77
- const indexPath = path.join(pagesDir, fileName, 'index.js')
78
- const resourcePath = path.join(pagesDir, fileName, 'resource.js')
39
+ const indexPath = path.join(pages, fileName, 'index.js')
40
+ const resourcePath = path.join(pages, fileName, 'resource.js')
79
41
 
80
42
  if (fs.existsSync(indexPath) && fs.existsSync(resourcePath)) {
81
43
  let indexCode = fs.readFileSync(indexPath, 'utf-8')
@@ -83,7 +45,7 @@ module.exports = async () => {
83
45
  // 💡 锁定待通电锚点
84
46
  if (indexCode.includes('BUNKER_API_ANCHOR')) {
85
47
  const resourceCode = fs.readFileSync(resourcePath, 'utf-8')
86
- const bunkerAnchors = indexCode.match(/BUNKER_API_ANCHOR_\w+/g).join('\n')
48
+ const bunkerAnchors = indexCode.match(/BUNKER_API_ANCHOR_\w+/g)?.join('\n') ?? ''
87
49
 
88
50
  // 💡 执行语义提取流程
89
51
  const returnMatch = indexCode.match(/return\s*\(([\s\S]*?)\)\s*}/)
@@ -109,7 +71,7 @@ module.exports = async () => {
109
71
  )))
110
72
 
111
73
  // 3. 驱动 9S 最终裁决
112
- const result = await apiLinker(options, bunkerAnchors, finalCandidates)
74
+ const result = await apiLinker({ bunkerAnchors, realApis: finalCandidates })
113
75
 
114
76
  if (result) {
115
77
  // 💡 执行物理更替
@@ -1,24 +1,8 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
- const chalk = require('chalk')
4
- const { language, getConfig } = require('../utils/utils.js')
5
-
6
- let client = null
7
1
 
8
- const getOpenAI = () => {
9
- if (!client) {
10
- const OpenAI = require('openai')
11
- const localEnv = path.resolve(process.cwd(), '.env')
12
- const pkgEnv = path.resolve(__dirname, '../.env')
13
- require("dotenv").config({
14
- path: fs.existsSync(localEnv) ? localEnv : pkgEnv
15
- })
16
- client = new OpenAI({ apiKey: process.env.API_KEY, baseURL: process.env.BASE_URL })
17
- }
18
- return client
19
- }
2
+ const chalk = require('chalk')
3
+ const { language } = require('../utils/ux')
20
4
 
21
- const askAI = async ({ model, messages, response_format = { type: 'json_object' }, retryCount = 0 }) => {
5
+ const askAI = async ({ model, openAI, messages, response_format = { type: 'json_object' }, retryCount = 0 }) => {
22
6
 
23
7
  if (retryCount > 3) {
24
8
  throw new Error(language(
@@ -28,8 +12,7 @@ const askAI = async ({ model, messages, response_format = { type: 'json_object'
28
12
  }
29
13
 
30
14
  try {
31
- const openai = getOpenAI()
32
- const response = await openai.chat.completions.create({
15
+ const response = await openAI.chat.completions.create({
33
16
  model,
34
17
  messages,
35
18
  top_p: 0.1,
@@ -60,66 +43,66 @@ const askAI = async ({ model, messages, response_format = { type: 'json_object'
60
43
  }
61
44
  }
62
45
 
63
- module.exports = {
64
- generateMock: async (columns, fileName) => {
65
- const config = getConfig()
66
- const mockPrompt = require('../prompts/mock.js')
67
- const { MOCK_DESIGNER } = require('../prompts/system.js')
68
- return askAI({
69
- model: config.textModel,
70
- messages: [
71
- { role: 'system', content: MOCK_DESIGNER },
72
- { role: 'user', content: mockPrompt(columns, fileName) }
73
- ]
74
- })
75
- },
76
- apiLinker: async (options, bunkerAnchors, realApis) => {
77
- const config = getConfig()
78
- const { API_DESIGNER } = require('../prompts/system.js')
79
- const apiPrompt = require(`../prompts/${options.template}/api-linker.js`)
80
- return askAI({
81
- model: config.textModel,
82
- messages: [
83
- { role: 'system', content: API_DESIGNER },
84
- { role: 'user', content: apiPrompt(bunkerAnchors, realApis) }
85
- ]
86
- })
87
- },
88
- recognizePage: async (prompt, filePath, taskType = 'page') => {
89
- const config = getConfig()
90
- const { UI_DESIGNER } = require('../prompts/system.js')
91
- const sharp = require('sharp')
92
- const compressedBuffer = await sharp(filePath)
93
- .resize(1280, null, { withoutEnlargement: true })
94
- .jpeg({ quality: 80 })
95
- .toBuffer()
96
- const base64Image = compressedBuffer.toString('base64')
46
+ module.exports = ({ config, openAI, template, sysPrompt, apiPrompt, mockPrompt, linkerPrompt }) => {
97
47
 
98
- return askAI({
99
- model: config.visionModel,
100
- messages: [
101
- { role: 'system', content: UI_DESIGNER },
102
- {
103
- role: 'user',
104
- content: [
105
- { type: 'text', text: prompt },
106
- { type: 'image_url', image_url: { url: `data:image/jpeg;base64,${base64Image}` } }
107
- ]
108
- }
109
- ],
110
- response_format: taskType === 'page' ? { type: 'json_schema', strict: true, json_schema: require('../schema/page.json') } : { type: 'json_object' }
111
- })
112
- },
113
- alignResponseFields: async (options, responseStr, resourceStr) => {
114
- const config = getConfig()
115
- const { API_DESIGNER } = require('../prompts/system.js')
116
- const apiPrompt = require(`../prompts/${options.template}/watch-api.js`)
117
- return askAI({
118
- model: config.textModel,
119
- messages: [
120
- { role: 'system', content: API_DESIGNER },
121
- { role: 'user', content: apiPrompt(responseStr, resourceStr) }
122
- ]
123
- })
48
+ const sharp = require('sharp')
49
+
50
+ const { textModel, visionModel } = config
51
+ const { UI_DESIGNER, API_DESIGNER, MOCK_DESIGNER } = sysPrompt
52
+
53
+ return {
54
+ generateMock: async ({ columns, fileName }) => {
55
+ return askAI({
56
+ openAI,
57
+ model: textModel,
58
+ messages: [
59
+ { role: 'system', content: MOCK_DESIGNER },
60
+ { role: 'user', content: mockPrompt({ columns, fileName }) }
61
+ ]
62
+ })
63
+ },
64
+ apiLinker: async ({ bunkerAnchors, realApis }) => {
65
+ return askAI({
66
+ openAI,
67
+ model: textModel,
68
+ messages: [
69
+ { role: 'system', content: API_DESIGNER },
70
+ { role: 'user', content: linkerPrompt({ bunkerAnchors, realApis }) }
71
+ ]
72
+ })
73
+ },
74
+ alignResponseFields: async ({ responseStr, resourceStr }) => {
75
+ return askAI({
76
+ openAI,
77
+ model: textModel,
78
+ messages: [
79
+ { role: 'system', content: API_DESIGNER },
80
+ { role: 'user', content: apiPrompt({ responseStr, resourceStr }) }
81
+ ]
82
+ })
83
+ },
84
+ recognizePage: async ({ prompt, filePath, taskType = 'page' }) => {
85
+ const compressedBuffer = await sharp(filePath)
86
+ .resize(1280, null, { withoutEnlargement: true })
87
+ .jpeg({ quality: 80 })
88
+ .toBuffer()
89
+ const base64Image = compressedBuffer.toString('base64')
90
+
91
+ return askAI({
92
+ openAI,
93
+ model: visionModel,
94
+ messages: [
95
+ { role: 'system', content: UI_DESIGNER },
96
+ {
97
+ role: 'user',
98
+ content: [
99
+ { type: 'text', text: prompt },
100
+ { type: 'image_url', image_url: { url: `data:image/jpeg;base64,${base64Image}` } }
101
+ ]
102
+ }
103
+ ],
104
+ response_format: taskType === 'page' ? { type: 'json_schema', strict: true, json_schema: require(`../schema/${template}/page.json`) } : { type: 'json_object' }
105
+ })
106
+ },
124
107
  }
125
108
  }