@yorha2b-lab/autodev 7.6.1 → 7.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yorha2b-lab/autodev",
3
- "version": "7.6.1",
3
+ "version": "7.7.1",
4
4
  "description": "基于 AI 视觉与语义协议的前端 (React+Antd) 代码构筑与旧系统重构引擎",
5
5
  "bin": {
6
6
  "bunker": "bin/bunker.js"
@@ -13,12 +13,19 @@ module.exports = {
13
13
  partialConstruction: time => `Report: Partial construction achieved. Elapsed: ${time}s`,
14
14
  capturedPage: pageName => `Page captured [${pageName}] -> Transmitted to mission directory`,
15
15
  constructionAborted: virus => `Construction aborted. Logic virus or timeout detected: ${virus}`,
16
+ captureFailed: (name, virus) => `Warning: Optical reconnaissance failure on [${name}]: ${virus}`,
16
17
  visualPartCaptured: `Partial visual sample detected. Executing 'Fragment Construction Protocol'...`,
18
+ noAvailablePages: `Report: No viable HTML constructs detected within ruins. Scan sequence aborted.`,
17
19
  scoutReady: '[Right-Click Snapshot Ready] Right-click anywhere on the webpage to capture instantly.',
18
20
  intercept: fileName => `Module [${fileName}] already exists. Construction skipped to prevent overwrite.`,
21
+ relicAccessFailed: entry => `Warning: Failed to identify relic entrance at [${entry}]. Coordinates invalid.`,
19
22
  partialRecommendation: '│ Recommendation: Please manually assemble the above code block into your target file',
20
23
  partialConstructionAborted: virus => `[Warning] Fragment construction failed. Logic interference detected: ${virus}`,
24
+ swarmScanning: entry => `Local prototype ruins detected at [${entry}]. Drone swarm reconnaissance protocol engaged.`,
25
+ captureProgress: (curr, total, name) => `Optical survey in progress [${curr}/${total}]: Target acquired -> ${name}.png`,
26
+ swarmVictory: count => `Strategic Report: Drone swarm survey triumphant. Airdropped ${count} blueprints into mission bay!`,
21
27
  visualCaptured: fileName => `Visual sensor captured: [${fileName}]. Allocating Bunker resources for physical construction...`,
28
+ deployRelayServer: (count, port) => `Identified ${count} tactical blueprints. Deploying field relay terminal on port ${port}...`,
22
29
  assemblyComplete: (fileName, time) => `Protocol complete. Physical assembly of [${fileName}] achieved. Elapsed: ${time}s, Syncing Menu configuration to Bunker...`,
23
30
  },
24
31
  pod153: {
@@ -11,13 +11,20 @@ module.exports = {
11
11
  partialConstruction: time => `Rapport : Construction partielle terminée. Durée : ${time}s`,
12
12
  simulate: 'Requête réelle détectée. Déploiement du paquet de simulation standard : example.json',
13
13
  capturedPage: pageName => `Page capturée [${pageName}] -> Transférée vers le répertoire mission`,
14
+ captureFailed: (name, virus) => `Alerte : Défaillance de reconnaissance sur [${name}] : ${virus}`,
15
+ noAvailablePages: `Rapport : Aucune structure HTML viable détectée dans les ruines. Séquence abandonnée.`,
16
+ captureProgress: (curr, total, name) => `Topographie optique [${curr}/${total}] : Verrouillage sur ${name}.png`,
17
+ swarmScanning: entry => `Ruines de prototype local détectées à [${entry}]. Protocole d'essaim de drones engagé.`,
14
18
  scoutReady: '[Capture par Clic Droit Prête] Effectuez un clic droit n’importe où sur la page web pour capturer.',
15
19
  visualPartCaptured: 'Fragment visuel local détecté. Activation du « protocole de construction fragmentaire »...',
16
20
  uploadVisualMetadata: 'Transmission des métadonnées visuelles au quartier général. Analyse sémantique en cours...',
21
+ relicAccessFailed: entry => `Alerte : Échec d'identification de l'accès aux ruines [${entry}]. Coordonnées invalides.`,
17
22
  constructionAborted: virus => `Construction interrompue. Virus logique ou délai de connexion dépassé détecté : ${virus}`,
18
23
  partialRecommendation: '│ Recommandation : veuillez intégrer manuellement le bloc de code ci-dessus dans le fichier cible',
24
+ deployRelayServer: (count, port) => `${count} pages tactiques identifiées. Déploiement du relais de données sur le port ${port}...`,
19
25
  partialConstructionAborted: virus => `[Avertissement] Échec de la construction fragmentaire. Interférence logique détectée : ${virus}`,
20
26
  intercept: fileName => `Le module [${fileName}] existe déjà dans la base de données du Bunker. Protocole interrompu pour éviter l’écrasement`,
27
+ swarmVictory: count => `Rapport tactique : Victoire de l'essaim de reconnaissance. ${count} plans d'opération parachutés dans la soute mission !`,
21
28
  visualCaptured: fileName => `Capture visuelle terminée : [${fileName}]. Déploiement des ressources du Bunker et lancement de la construction physique...`,
22
29
  assemblyComplete: (fileName, time) => `Protocole de construction terminé. Module [${fileName}] assemblé avec succès. Durée : ${time}s, Synchronisation de la configuration des routes Menu avec le Bunker...`,
23
30
  },
@@ -8,16 +8,23 @@ module.exports = {
8
8
  startScout: '【右クリック即時スナップショット】モードを起動中...',
9
9
  capturedToast: '視覚データをキャプチャしました。送信を開始します...',
10
10
  partialConstruction: time => `報告:局所構築完了。処理時間: ${time}s`,
11
+ noAvailablePages: `報告:遺跡内部に有効なHTML構造体を感知できず。作戦中断。`,
11
12
  visualPartCaptured: '局所視覚サンプルを検知。「断片構築プロトコル」を開始します',
12
13
  uploadVisualMetadata: '視覚メタデータを司令部へ送信。セマンティック解析を開始...',
13
14
  extractingUiMetadata: 'ニューラルクラウドネットワークよりUIメタデータを抽出中...',
14
15
  partialRecommendation: '│ 推奨:上記コードブロックを対象ファイルへ手動実装してください',
15
16
  partialConstructionAborted: virus => `[警告] 断片構築失敗。論理干渉を検出:${virus}`,
17
+ captureFailed: (name, virus) => `警告:[${name}] の観測中に論理エラー発生:${virus}`,
16
18
  simulate: '実弾リクエストを検知。標準シミュレーションパッケージ example.json を投下します',
19
+ relicAccessFailed: entry => `警告:目標遺跡 [${entry}] の侵入経路を特定できず。座標無効。`,
17
20
  constructionAborted: virus => `構築中断。論理ウイルスまたは接続タイムアウトを検出:${virus}`,
18
21
  capturedPage: pageName => `ページデータを取得【${pageName}】-> mission ディレクトリへ送信完了`,
22
+ swarmScanning: entry => `ローカルプロトタイプ遺跡 [${entry}] を探知。ドローン群索敵プロトコル起動。`,
19
23
  scoutReady: '【スナップショット準備完了】ウェブページ上の任意の位置で【右クリック】すると即座に撮影されます。',
24
+ deployRelayServer: (count, port) => `計${count}個の戦術ページを補足。ポート${port}にて臨時中継局を展開中...`,
25
+ captureProgress: (curr, total, name) => `視覚マッピング進行中 [${curr}/${total}]:${name}.png を捕捉完了。`,
20
26
  visualCaptured: fileName => `視覚センサーによる捕捉完了: [${fileName}]。Bunkerリソースを展開し物理構築を開始します`,
27
+ swarmVictory: count => `戦況報告:ドローン群による測量完了。${count}件の作戦設計図をミッションポッドへ投下。人類に栄光あれ。`,
21
28
  intercept: fileName => `モジュール [${fileName}] は既にBunkerデータベースに存在。データ上書き防止のため構築プロトコルを中断`,
22
29
  assemblyComplete: (fileName, time) => `構築プロトコル完了。モジュール [${fileName}] の実装を確認。処理時間: ${time}s, BunkerへMenuルーティング設定を同期中...`,
23
30
  },
@@ -7,16 +7,23 @@ module.exports = {
7
7
  pod042: {
8
8
  capturedToast: 'Визуальные разведданные захвачены. Начиная передачу...',
9
9
  startScout: 'Активация режима [Тактический снимок правой кнопкой мыши]...',
10
+ captureFailed: (name, virus) => `Внимание: Сбой захвата [${name}]: ${virus}`,
10
11
  extractingUiMetadata: 'Извлечение метаданных UI из нейронной облачной сети...',
11
12
  capturedPage: pageName => `Страница захвачена [${pageName}] -> Передано в каталог mission`,
13
+ noAvailablePages: `Доклад: Внутри руин не обнаружено структур HTML. Сканирование прервано.`,
12
14
  partialConstruction: time => `Доклад: частичная сборка завершена. Время обработки: ${time}s`,
13
15
  uploadVisualMetadata: 'Передача визуальных метаданных в штаб. Запуск семантического анализа...',
14
16
  partialRecommendation: '│ Рекомендация: вручную интегрируйте указанный блок кода в целевой файл',
15
17
  simulate: 'Обнаружен реальный запрос. Развёртывание стандартного симуляционного пакета: example.json',
16
18
  visualPartCaptured: 'Обнаружен локальный визуальный фрагмент. Запуск «протокола фрагментарной сборки»...',
17
19
  scoutReady: '[Режим снимка правой кнопкой готов] Щелкните правой кнопкой мыши в любом месте веб-страницы.',
20
+ swarmScanning: entry => `Обнаружены локальные руины прототипа [${entry}]. Протокол роя дронов активирован.`,
21
+ captureProgress: (curr, total, name) => `Визуальное сканирование [${curr}/${total}]: Зафиксирован ${name}.png`,
22
+ relicAccessFailed: entry => `Внимание: Ошибка опознавания входа в руины [${entry}]. Координаты недействительны.`,
18
23
  constructionAborted: virus => `Сборка прервана. Обнаружен логический вирус или превышение времени соединения: ${virus}`,
24
+ deployRelayServer: (count, port) => `Обнаружено ${count} тактических страниц. Развёртывание полевого узла на порту ${port}...`,
19
25
  partialConstructionAborted: virus => `[Предупреждение] Ошибка фрагментарной сборки. Обнаружено логическое вмешательство: ${virus}`,
26
+ swarmVictory: count => `Боевой доклад: Операция роя дронов успешна! ${count} чертежей сброшено в отсек mission. Слава Человечеству!`,
20
27
  intercept: fileName => `Модуль [${fileName}] уже существует в базе данных Bunker. Протокол сборки остановлен во избежание перезаписи`,
21
28
  visualCaptured: fileName => `Визуальное сканирование завершено: [${fileName}]. Развёртывание ресурсов Bunker и запуск физической сборки...`,
22
29
  assemblyComplete: (fileName, time) => `Протокол сборки завершён. Модуль [${fileName}] успешно установлен. Время: ${time}s, Синхронизация конфигурации маршрутов Menu с Bunker...`,
@@ -13,12 +13,19 @@ module.exports = {
13
13
  visualPartCaptured: `侦测到局部视觉样本。正在执行“碎片构筑协议”...`,
14
14
  partialConstruction: time => `报告:局部构筑达成。耗时: ${time}s`,
15
15
  partialRecommendation: '│ 建议:请手动将上述代码块物理装配至您的目标文件中',
16
+ noAvailablePages: `报告:废墟内部未探查到可用的 HTML 构造体,扫描任务中止。`,
16
17
  scoutReady: '【右键快照模式就绪】在浏览器页面任意位置点击【鼠标右键】即可秒截屏',
17
18
  constructionAborted: virus => `构筑中断。检测到逻辑病毒或连接超时:${virus}`,
18
19
  capturedPage: pageName => `捕获页面【${pageName}】-> 已投递至 mission 目录`,
20
+ swarmScanning: entry => `探测到本地原型遗迹 [${entry}]。无人机蜂群测绘协议已激活。`,
21
+ captureFailed: (name, virus) => `警告:机体测绘 [${name}] 发生逻辑溢出:${virus}`,
22
+ relicAccessFailed: entry => `警告:目标废墟 [${entry}] 入口识别失败,物理坐标无效。`,
19
23
  partialConstructionAborted: virus => `[警告] 碎片构筑失败。系统检测到逻辑干扰:${virus}`,
20
24
  visualCaptured: fileName => `视觉传感器捕捉完成: [${fileName}]。正在调配地堡资源进行物理构筑...`,
21
25
  intercept: fileName => `模块 [${fileName}] 已存在于地堡数据库中。为了防止数据覆盖,构筑协议已跳过`,
26
+ swarmVictory: count => `战况汇报:无人机蜂群测绘大捷。已将 ${count} 份作战蓝图空投至 mission 舱!`,
27
+ captureProgress: (curr, total, name) => `视觉测绘推进中 [${curr}/${total}]:已锁定 ${name}.png`,
28
+ deployRelayServer: (count, port) => `探测到 ${count} 个战术页面,正在部署 ${port} 临时数据中继站...`,
22
29
  assemblyComplete: (fileName, time) => `构筑协议执行完毕。模块 [${fileName}] 物理装配达成。耗时: ${time}s,开始同步 Menu 路由配置...`,
23
30
  },
24
31
  pod153: {
@@ -12,33 +12,35 @@ module.exports = bunker => {
12
12
 
13
13
  const acp = bunker.get()
14
14
  const units = Object.values(acp.units)
15
- const dispatcher = acp.headquarters.dispatcher(acp, 2)
15
+ const dispatcher = acp.headquarters.dispatcher(acp)
16
16
 
17
17
  dispatcher.onIdle(() => acp.yorha.commander.report(acp.dialog.bunker.systemStandby, 'gray'))
18
18
 
19
19
  return {
20
20
  receive: async mission => {
21
- let unit
22
- let spinner
23
- try {
24
- if (acp.logistics.supporter.getConfig().useDemo) {
25
- unit = units.find(unit => unit.meta.name === 'striker')
26
- } else {
27
- spinner = acp.yorha.commander.start(acp.dialog.bunker.detectedEnemy)
28
- const result = await acp.llm.recognizePage({ filePath: mission.input, prompt: acp.constitution.commander(units) })
29
- acp.yorha.commander.success(spinner, acp.dialog.bunker.confirmEnemy)
30
- unit = units.map(unit => ({ unit, score: unit.meta.capabilities.filter(cap => result.capabilities.includes(cap)).length }))
31
- .filter(item => item.score > 0)
32
- .sort((a, b) => b.score - a.score)[0]?.unit
21
+ dispatcher.add(async () => {
22
+ let unit
23
+ let spinner
24
+ try {
25
+ if (acp.logistics.supporter.getConfig().useDemo) {
26
+ unit = units.find(unit => unit.meta.name === 'striker')
27
+ } else {
28
+ spinner = acp.yorha.commander.start(acp.dialog.bunker.detectedEnemy)
29
+ const result = await acp.llm.recognizePage({ filePath: mission.input, prompt: acp.constitution.commander(units) })
30
+ acp.yorha.commander.success(spinner, acp.dialog.bunker.confirmEnemy)
31
+ unit = units.map(unit => ({ unit, score: unit.meta.capabilities.filter(cap => result.capabilities.includes(cap)).length }))
32
+ .filter(item => item.score > 0)
33
+ .sort((a, b) => b.score - a.score)[0]?.unit
34
+ }
35
+ if (!unit) {
36
+ acp.yorha.commander.fail(spinner, acp.dialog.bunker.missionFailed)
37
+ return
38
+ }
39
+ await unit.execute({ acp, mission })
40
+ } catch (error) {
41
+ acp.yorha.commander.fail(spinner, acp.dialog.bunker.missionUnknown)
33
42
  }
34
- if (!unit) {
35
- acp.yorha.commander.fail(spinner, acp.dialog.bunker.missionFailed)
36
- return
37
- }
38
- dispatcher.add(() => unit.execute({ acp, mission }))
39
- } catch (error) {
40
- acp.yorha.commander.fail(spinner, acp.dialog.bunker.missionUnknown)
41
- }
42
- },
43
+ })
44
+ }
43
45
  }
44
46
  }
package/src/labs/scout.js CHANGED
@@ -4,8 +4,10 @@ module.exports = ({ yorha, dialog }) => {
4
4
 
5
5
  const fs = require('fs')
6
6
  const path = require('path')
7
+ const http = require('http')
7
8
 
8
9
  const { pod042 } = yorha
10
+ const relayServerPort = 15342
9
11
 
10
12
  const isHttpURL = str => {
11
13
  try {
@@ -25,70 +27,113 @@ module.exports = ({ yorha, dialog }) => {
25
27
  return parts.at(-1) ?? 'capturedPage'
26
28
  }
27
29
 
28
- return {
29
- archeology: async targetUrl => {
30
-
31
- if (!isHttpURL(targetUrl)) {
32
- return
30
+ const scanHtmlFiles = (dir, rootDir = dir) => {
31
+ let results = []
32
+ const list = fs.readdirSync(dir)
33
+ for (const file of list) {
34
+ const fullPath = path.join(dir, file)
35
+ const stat = fs.statSync(fullPath)
36
+ if (stat.isDirectory()) {
37
+ // 排除常见的非业务页面文件夹
38
+ if (!['resources', 'plugins', 'images', 'files'].includes(file)) {
39
+ results = results.concat(scanHtmlFiles(fullPath, rootDir))
40
+ }
41
+ } else if (file.endsWith('.html')) {
42
+ if (!['start.html', 'reload.html'].includes(file)) {
43
+ results.push({
44
+ fullPath,
45
+ name: path.basename(file, '.html'),
46
+ relativeUrl: path.relative(rootDir, fullPath).replace(/\\/g, '/'),
47
+ })
48
+ }
33
49
  }
50
+ }
51
+ return results
52
+ }
34
53
 
35
- pod042.report(dialog.pod042.startScout, 'green')
54
+ const startStaticServer = staticDir => {
55
+ const mimeTypes = {
56
+ '.html': 'text/html',
57
+ '.js': 'text/javascript',
58
+ '.css': 'text/css',
59
+ '.png': 'image/png',
60
+ '.jpg': 'image/jpeg',
61
+ '.svg': 'image/svg+xml'
62
+ }
63
+ const server = http.createServer((req, res) => {
64
+ const reqPath = decodeURIComponent(req.url.split('?')[0])
65
+ const filePath = path.join(staticDir, reqPath === '/' ? 'index.html' : reqPath)
66
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
67
+ const ext = path.extname(filePath).toLowerCase()
68
+ res.writeHead(200, { 'Content-Type': mimeTypes[ext] || 'application/octet-stream' })
69
+ fs.createReadStream(filePath).pipe(res)
70
+ } else {
71
+ res.writeHead(404)
72
+ res.end('Not Found')
73
+ }
74
+ })
75
+ return new Promise(resolve => server.listen(relayServerPort, () => resolve({ server })))
76
+ }
36
77
 
78
+ return {
79
+ archeology: async targetUrl => {
37
80
  const puppeteer = require('puppeteer')
38
81
  const missionDir = path.join(process.cwd(), 'bunker', 'mission')
39
- const sessionDir = path.join(process.cwd(), 'bunker', '.chrome_session')
40
82
 
41
- let isBrowserAlive = false
42
- if (globalBrowser) {
43
- try {
44
- await globalBrowser.pages()
45
- isBrowserAlive = true
46
- } catch (e) {
47
- globalBrowser = null
83
+ if (isHttpURL(targetUrl)) {
84
+ pod042.report(dialog.pod042.startScout, 'green')
85
+ const sessionDir = path.join(process.cwd(), 'bunker', '.chrome_session')
86
+ let isBrowserAlive = false
87
+ if (globalBrowser) {
88
+ try {
89
+ await globalBrowser.pages()
90
+ isBrowserAlive = true
91
+ } catch (e) {
92
+ globalBrowser = null
93
+ }
48
94
  }
49
- }
50
95
 
51
- if (!isBrowserAlive) {
96
+ if (!isBrowserAlive) {
52
97
 
53
- ['SingletonLock', 'SingletonSocket', 'SingletonCookie'].forEach(file => {
54
- try { fs.unlinkSync(path.join(sessionDir, file)) } catch (e) { }
55
- })
98
+ ['SingletonLock', 'SingletonSocket', 'SingletonCookie'].forEach(file => {
99
+ try { fs.unlinkSync(path.join(sessionDir, file)) } catch (e) { }
100
+ })
56
101
 
57
- globalBrowser = await puppeteer.launch({
58
- headless: false,
59
- handleSIGINT: false,
60
- defaultViewport: null,
61
- userDataDir: sessionDir,
62
- args: ['--start-maximized']
63
- })
64
- }
102
+ globalBrowser = await puppeteer.launch({
103
+ headless: false,
104
+ handleSIGINT: false,
105
+ defaultViewport: null,
106
+ userDataDir: sessionDir,
107
+ args: ['--start-maximized']
108
+ })
109
+ }
65
110
 
66
- const pages = await globalBrowser.pages()
67
- const page = pages.length > 0 ? pages[0] : await globalBrowser.newPage()
111
+ const pages = await globalBrowser.pages()
112
+ const page = pages.length > 0 ? pages[0] : await globalBrowser.newPage()
68
113
 
69
- try {
114
+ try {
70
115
 
71
- const browserDialog = { capturedToast: dialog.pod042.capturedToast }
116
+ const browserDialog = { capturedToast: dialog.pod042.capturedToast }
72
117
 
73
- await page.exposeFunction('nodeTakeScreenshot', async () => {
74
- const currentUrl = await page.url()
75
- const pageName = derivePageNameFromUrl(currentUrl)
76
- const savePath = path.join(missionDir, `${pageName}.png`)
77
- await page.screenshot({ path: savePath, fullPage: true })
78
- pod042.report(dialog.pod042.capturedPage(pageName), 'green')
79
- })
118
+ await page.exposeFunction('nodeTakeScreenshot', async () => {
119
+ const currentUrl = await page.url()
120
+ const pageName = derivePageNameFromUrl(currentUrl)
121
+ const savePath = path.join(missionDir, `${pageName}.png`)
122
+ await page.screenshot({ path: savePath, fullPage: true })
123
+ pod042.report(dialog.pod042.capturedPage(pageName), 'green')
124
+ })
80
125
 
81
- await page.evaluateOnNewDocument(({ capturedToast }) => {
82
- if (window.__hasBunkerListener__) return
83
- window.__hasBunkerListener__ = true
84
- window.addEventListener('contextmenu', (e) => {
85
- e.preventDefault()
86
- if (window.nodeTakeScreenshot) {
87
- window.nodeTakeScreenshot()
88
- }
89
- const toast = document.createElement('div')
90
- toast.innerText = capturedToast
91
- toast.style.cssText = `
126
+ await page.evaluateOnNewDocument(({ capturedToast }) => {
127
+ if (window.__hasBunkerListener__) return
128
+ window.__hasBunkerListener__ = true
129
+ window.addEventListener('contextmenu', (e) => {
130
+ e.preventDefault()
131
+ if (window.nodeTakeScreenshot) {
132
+ window.nodeTakeScreenshot()
133
+ }
134
+ const toast = document.createElement('div')
135
+ toast.innerText = capturedToast
136
+ toast.style.cssText = `
92
137
  position: fixed; top: 20px; right: 20px; z-index: 999999;
93
138
  background: rgba(0, 0, 0, 0.85); color: #00ffcc;
94
139
  border: 1px solid #00ffcc; padding: 12px 24px;
@@ -96,18 +141,85 @@ module.exports = ({ yorha, dialog }) => {
96
141
  box-shadow: 0 0 15px rgba(0, 255, 204, 0.5);
97
142
  pointer-events: none; transition: opacity 0.4s ease;
98
143
  `
99
- document.body.appendChild(toast)
100
- setTimeout(() => {
101
- toast.style.opacity = '0'
102
- setTimeout(() => toast.remove(), 400)
103
- }, 1200)
104
- })
105
- }, browserDialog)
106
- } catch (error) { }
144
+ document.body.appendChild(toast)
145
+ setTimeout(() => {
146
+ toast.style.opacity = '0'
147
+ setTimeout(() => toast.remove(), 400)
148
+ }, 1200)
149
+ })
150
+ }, browserDialog)
151
+ } catch (error) { }
152
+
153
+ await page.goto(targetUrl)
154
+ pod042.report(dialog.pod042.scoutReady, 'green')
155
+ return globalBrowser
156
+ }
107
157
 
108
- await page.goto(targetUrl)
109
- pod042.report(dialog.pod042.scoutReady, 'green')
110
- return globalBrowser
158
+ const localDir = path.resolve(targetUrl)
159
+ if (!fs.existsSync(localDir) || !fs.statSync(localDir).isDirectory()) {
160
+ pod042.report(dialog.pod042.relicAccessFailed(localDir), 'red')
161
+ return
162
+ }
163
+ pod042.report(dialog.pod042.swarmScanning(localDir), 'green')
164
+ const htmlPages = scanHtmlFiles(localDir)
165
+ if (!htmlPages.length) {
166
+ pod042.report(dialog.pod042.noAvailablePages, 'yellow')
167
+ return
168
+ }
169
+ pod042.report(dialog.pod042.deployRelayServer(htmlPages.length, relayServerPort), 'cyan')
170
+ const { server } = await startStaticServer(localDir)
171
+ let browser
172
+ try {
173
+ // 💡 锁死标准宽屏分辨率,防止变形折叠
174
+ browser = await puppeteer.launch({
175
+ headless: 'new',
176
+ defaultViewport: { width: 1440, height: 900 }
177
+ })
178
+ const page = await browser.newPage()
179
+ let capturedCount = 0
180
+
181
+ for (const item of htmlPages) {
182
+ try {
183
+ await page.setViewport({ width: 1440, height: 900 })
184
+ const pageUrl = `http://localhost:${relayServerPort}/${encodeURI(item.relativeUrl)}`
185
+ await page.goto(pageUrl, { waitUntil: 'networkidle0', timeout: 15000 })
186
+ // 稍作等待,确保渲染完毕
187
+ await new Promise(r => setTimeout(r, 600))
188
+ const contentWidth = await page.evaluate(() => {
189
+ let maxW = 1440
190
+ document.querySelectorAll('*').forEach(el => {
191
+ const style = window.getComputedStyle(el)
192
+ if (['auto', 'scroll'].includes(style.overflowX) || el.scrollWidth > el.clientWidth) {
193
+ el.style.overflowX = 'visible'
194
+ el.style.width = 'max-content' // 强行把宽度撑到真实列宽!
195
+ }
196
+ // 记录全页面最宽的物理尺寸
197
+ if (el.scrollWidth > maxW) {
198
+ maxW = el.scrollWidth
199
+ }
200
+ })
201
+ return maxW
202
+ })
203
+ await page.setViewport({
204
+ width: Math.min(contentWidth + 80, 3840),
205
+ height: 900
206
+ })
207
+ // 稍等 200ms 让重绘完成
208
+ await new Promise(r => setTimeout(r, 200))
209
+ const savePath = path.join(missionDir, `${item.name}.png`)
210
+ await page.screenshot({ path: savePath, fullPage: true })
211
+ capturedCount++
212
+ pod042.report(dialog.pod042.captureProgress(capturedCount, htmlPages.length, item.name), 'green')
213
+ } catch (err) {
214
+ pod042.report(dialog.pod042.captureFailed(item.name, err.message), 'yellow')
215
+ }
216
+ }
217
+ pod042.report(dialog.pod042.swarmVictory(capturedCount), 'magenta')
218
+ } finally {
219
+ // 💡 绝对保证释放资源,绝不霸占 15342 端口
220
+ await browser?.close()
221
+ server?.close()
222
+ }
111
223
  }
112
224
  }
113
225
  }
@@ -8,23 +8,23 @@ const yorha = () => {
8
8
 
9
9
  const createMember = ({ name }) => ({
10
10
  start(text) {
11
- const spinner = ora({ text: chalk.cyan(`${name} ${text}\n`) }).start()
11
+ const spinner = ora({ text: chalk.cyan(`${name} ${text}`) }).start()
12
12
  return spinner
13
13
  },
14
14
  fail(spinner, text) {
15
- spinner.fail(chalk.red(`${name} ${text}\n`))
15
+ spinner.fail(chalk.red(`${name} ${text}`))
16
16
  },
17
17
  report(text, color = 'yellow') {
18
- console.log(chalk[color](`${name} ${text}\n`))
18
+ console.log(chalk[color](`${name} ${text}`))
19
19
  },
20
20
  update(spinner, text) {
21
- spinner.text = chalk.cyan(`${name} ${text}\n`)
21
+ spinner.text = chalk.cyan(`${name} ${text}`)
22
22
  },
23
23
  warning(spinner, text) {
24
- spinner.warn(chalk.yellow(`${name} ${text}\n`))
24
+ spinner.warn(chalk.yellow(`${name} ${text}`))
25
25
  },
26
26
  success(spinner, text) {
27
- spinner.succeed(chalk.green(`${name} ${text}\n`))
27
+ spinner.succeed(chalk.green(`${name} ${text}`))
28
28
  },
29
29
  })
30
30
 
@@ -80,6 +80,7 @@ const modalSubmit = async params => {
80
80
  if (fetching) return
81
81
  fetching = true
82
82
  const response = await requestMap[modal.type]?.()
83
+ fetching = false
83
84
  if({{{responseSuccess}}}){
84
85
  setModal({ visible: false })
85
86
  refresh()
@@ -135,7 +135,7 @@ export const exportDataToExcel = async ({ url, options, columns, fileName, forma
135
135
  * @param {Object} [params.headers] - 自定义请求头
136
136
  * @param {string} [fileName='下载'] - 预设文件名(支持从 Content-Disposition 自动对齐)
137
137
  */
138
- export const streamDownload = async ({ url, options, headers = {} }, fileName = '下载') => {
138
+ export const streamDownload = async ({ url, options, headers = { 'Content-Type': 'application/json' } }, fileName = '下载') => {
139
139
 
140
140
  const response = await fetch(url, { ...options, headers })
141
141