@yorha2b-lab/autodev 7.6.0 → 7.7.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.
- package/package.json +5 -6
- package/src/dialogues/EN-US.js +7 -0
- package/src/dialogues/FR-FR.js +7 -0
- package/src/dialogues/JA-JP.js +7 -0
- package/src/dialogues/RU-RU.js +7 -0
- package/src/dialogues/ZH-CN.js +7 -0
- package/src/headquarters/commander.js +23 -21
- package/src/labs/council.js +4 -1
- package/src/labs/scout.js +172 -60
- package/src/workshop/react/blueprint/molds/handleBlock.hbs +1 -0
- package/src/workshop/react/kit/utils.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yorha2b-lab/autodev",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.7.0",
|
|
4
4
|
"description": "基于 AI 视觉与语义协议的前端 (React+Antd) 代码构筑与旧系统重构引擎",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bunker": "bin/bunker.js"
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"homepage": "https://github.com/yorha2b-lab/autodev#readme",
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"ora": "^5.4.1",
|
|
40
|
-
"chalk": "^4.1.2",
|
|
41
40
|
"json5": "^2.2.3",
|
|
41
|
+
"chalk": "^4.1.2",
|
|
42
42
|
"sharp": "^0.34.5",
|
|
43
|
-
"dotenv": "^17.3.1",
|
|
44
|
-
"figlet": "^1.10.0",
|
|
45
43
|
"openai": "^6.24.0",
|
|
44
|
+
"figlet": "^1.10.0",
|
|
45
|
+
"dotenv": "^17.3.1",
|
|
46
46
|
"chokidar": "^5.0.0",
|
|
47
47
|
"segmentit": "^2.0.3",
|
|
48
48
|
"commander": "^14.0.3",
|
|
@@ -50,6 +50,5 @@
|
|
|
50
50
|
"puppeteer": "^25.4.0",
|
|
51
51
|
"http-proxy": "^1.18.1",
|
|
52
52
|
"json-stringify-pretty-compact": "^4.0.0"
|
|
53
|
-
}
|
|
54
|
-
"devDependencies": {}
|
|
53
|
+
}
|
|
55
54
|
}
|
package/src/dialogues/EN-US.js
CHANGED
|
@@ -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: {
|
package/src/dialogues/FR-FR.js
CHANGED
|
@@ -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
|
},
|
package/src/dialogues/JA-JP.js
CHANGED
|
@@ -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
|
},
|
package/src/dialogues/RU-RU.js
CHANGED
|
@@ -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...`,
|
package/src/dialogues/ZH-CN.js
CHANGED
|
@@ -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: {
|
|
@@ -18,27 +18,29 @@ module.exports = bunker => {
|
|
|
18
18
|
|
|
19
19
|
return {
|
|
20
20
|
receive: async mission => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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/council.js
CHANGED
|
@@ -2,6 +2,7 @@ module.exports = async ({ llm, yorha, dialog, logistics }) => {
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
const path = require('path')
|
|
5
|
+
const JSON5 = require('json5')
|
|
5
6
|
|
|
6
7
|
const { apiParser } = llm
|
|
7
8
|
const { pod153, commander } = yorha
|
|
@@ -13,7 +14,9 @@ module.exports = async ({ llm, yorha, dialog, logistics }) => {
|
|
|
13
14
|
|
|
14
15
|
pod153.report(dialog.pod153.autonomousAddressing, 'magenta')
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
+
const apiResponse = await fetch(apiDoc)
|
|
18
|
+
const text = await apiResponse.text()
|
|
19
|
+
const apiData = JSON5.parse(text)
|
|
17
20
|
const refinedApis = Object.entries(apiData.paths).flatMap(([apiUrl, methods]) => {
|
|
18
21
|
return Object.entries(methods).map(([method, info]) => ({
|
|
19
22
|
path: apiUrl,
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
96
|
+
if (!isBrowserAlive) {
|
|
52
97
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
98
|
+
['SingletonLock', 'SingletonSocket', 'SingletonCookie'].forEach(file => {
|
|
99
|
+
try { fs.unlinkSync(path.join(sessionDir, file)) } catch (e) { }
|
|
100
|
+
})
|
|
56
101
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
67
|
-
|
|
111
|
+
const pages = await globalBrowser.pages()
|
|
112
|
+
const page = pages.length > 0 ? pages[0] : await globalBrowser.newPage()
|
|
68
113
|
|
|
69
|
-
|
|
114
|
+
try {
|
|
70
115
|
|
|
71
|
-
|
|
116
|
+
const browserDialog = { capturedToast: dialog.pod042.capturedToast }
|
|
72
117
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
}
|
|
@@ -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
|
|