sloth-d2c-mcp 1.0.4-beta74 → 1.0.4-beta76
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/dist/build/core/prompt-builder.js +45 -23
- package/dist/build/core/sampling.js +5 -23
- package/dist/build/index.js +159 -26
- package/dist/build/server.js +218 -131
- package/dist/build/socket-client.js +163 -0
- package/dist/build/socket-server.js +233 -0
- package/dist/build/utils/extract.js +11 -9
- package/dist/build/utils/file-manager.js +80 -11
- package/dist/build/utils/tj.js +139 -0
- package/dist/build/utils/utils.js +5 -5
- package/dist/interceptor-web/dist/build-report.json +7 -7
- package/dist/interceptor-web/dist/detail.html +1 -1
- package/dist/interceptor-web/dist/index.html +1 -1
- package/package.json +5 -4
- package/dist/build/component-mapping/adapter-manager.js +0 -45
- package/dist/build/component-mapping/adapters/base-adapter.js +0 -137
- package/dist/build/component-mapping/adapters/ios-adapter.js +0 -697
- package/dist/build/component-mapping/adapters/web-adapter.js +0 -536
- package/dist/build/component-mapping/index.js +0 -32
- package/dist/build/component-mapping/storage.js +0 -142
- package/dist/build/component-mapping/types.js +0 -4
package/dist/build/server.js
CHANGED
|
@@ -10,15 +10,20 @@ import * as fs from 'fs';
|
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
12
|
import { v4 as uuidv4 } from 'uuid';
|
|
13
|
+
import { nanoid } from 'nanoid';
|
|
13
14
|
import open from 'open';
|
|
14
15
|
import { fileManager } from './index.js';
|
|
15
16
|
import * as flatted from 'flatted';
|
|
16
|
-
import { ComponentMappingSystem } from './component-mapping/index.js';
|
|
17
17
|
import multer from 'multer';
|
|
18
18
|
import { ImageMatcher } from './utils/image-matcher.js';
|
|
19
19
|
import { initOpenCV } from './utils/opencv-loader.js';
|
|
20
20
|
import { extractJson } from './utils/extract.js';
|
|
21
21
|
import { componentAnalysisPrompt, componentAnalysisPromptVue } from 'sloth-d2c-node/convert';
|
|
22
|
+
import { SocketServer } from './socket-server.js';
|
|
23
|
+
/**
|
|
24
|
+
* 生成组件 ID(4位随机字符串)
|
|
25
|
+
*/
|
|
26
|
+
export const generateComponentId = () => `comp_${nanoid(4)}`;
|
|
22
27
|
// 配置 multer 用于文件上传
|
|
23
28
|
const storage = multer.memoryStorage();
|
|
24
29
|
const upload = multer({
|
|
@@ -29,6 +34,8 @@ const upload = multer({
|
|
|
29
34
|
import { chunkOptimizeCodePrompt, aggregationOptimizeCodePrompt, finalOptimizeCodePrompt, chunkOptimizeCodePromptVue, aggregationOptimizeCodePromptVue, finalOptimizeCodePromptVue, } from 'sloth-d2c-node/convert';
|
|
30
35
|
// 保存 HTTP 服务器实例
|
|
31
36
|
let httpServer = null;
|
|
37
|
+
// 保存 Socket 服务器实例
|
|
38
|
+
let socketServer = null;
|
|
32
39
|
// 管理所有活跃的传输对象,按 sessionId 分类
|
|
33
40
|
const transports = {
|
|
34
41
|
streamable: {}, // 流式 HTTP 传输
|
|
@@ -38,11 +45,14 @@ const transports = {
|
|
|
38
45
|
const pendingRequests = new Map(); // 等待中的认证请求
|
|
39
46
|
let configStorage = {}; // 配置缓存
|
|
40
47
|
let configManager = null; // 配置管理器实例
|
|
41
|
-
let mappingSystem = null; // 组件映射系统实例
|
|
42
48
|
const __filename = fileURLToPath(import.meta.url);
|
|
43
49
|
const __dirname = path.dirname(__filename);
|
|
44
50
|
// 获取端口号
|
|
45
51
|
let PORT = 0;
|
|
52
|
+
// 导出获取端口号的函数
|
|
53
|
+
export function getPort() {
|
|
54
|
+
return PORT;
|
|
55
|
+
}
|
|
46
56
|
export async function loadConfig(mcpServer, configManagerInstance) {
|
|
47
57
|
if (configManagerInstance) {
|
|
48
58
|
configManager = configManagerInstance;
|
|
@@ -112,14 +122,13 @@ export async function loadConfig(mcpServer, configManagerInstance) {
|
|
|
112
122
|
}
|
|
113
123
|
else {
|
|
114
124
|
const vueConfig = await configManager.loadFrameworkConfig('vue');
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
125
|
+
await configManager.saveFrameworkConfig('vue', {
|
|
126
|
+
chunkOptimizePrompt: chunkOptimizeCodePromptVue,
|
|
127
|
+
aggregationOptimizePrompt: aggregationOptimizeCodePromptVue,
|
|
128
|
+
finalOptimizePrompt: finalOptimizeCodePromptVue,
|
|
129
|
+
componentAnalysisPrompt: componentAnalysisPromptVue,
|
|
130
|
+
...(vueConfig || {}),
|
|
131
|
+
});
|
|
123
132
|
}
|
|
124
133
|
}
|
|
125
134
|
catch (error) {
|
|
@@ -326,11 +335,29 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
326
335
|
// const fileManager = new FileManager('d2c-mcp')
|
|
327
336
|
const groupsData = await fileManager.loadGroupsData(fileKey, nodeId);
|
|
328
337
|
const savedPromptSetting = await fileManager.loadPromptSetting(fileKey, nodeId);
|
|
338
|
+
let curFramework = fileConfig?.convertSetting?.framework;
|
|
339
|
+
// 获取框架列表
|
|
340
|
+
const frameworks = await configManager.getFrameworks();
|
|
341
|
+
if (!curFramework || !frameworks.find((fw) => fw.value === curFramework)) {
|
|
342
|
+
curFramework = globalConfig.defaultFramework || 'react';
|
|
343
|
+
}
|
|
344
|
+
const curFrameworkDefaultConfig = (await configManager.loadFrameworkConfig(curFramework)) ||
|
|
345
|
+
(curFramework !== 'vue'
|
|
346
|
+
? {
|
|
347
|
+
chunkOptimizePrompt: chunkOptimizeCodePrompt,
|
|
348
|
+
aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
|
|
349
|
+
finalOptimizePrompt: finalOptimizeCodePrompt,
|
|
350
|
+
}
|
|
351
|
+
: {
|
|
352
|
+
chunkOptimizePrompt: chunkOptimizeCodePromptVue,
|
|
353
|
+
aggregationOptimizePrompt: aggregationOptimizeCodePromptVue,
|
|
354
|
+
finalOptimizePrompt: finalOptimizeCodePromptVue,
|
|
355
|
+
});
|
|
329
356
|
// 如果指定了框架,加载框架配置的提示词
|
|
330
357
|
let promptSetting = {
|
|
331
|
-
chunkOptimizePrompt: savedPromptSetting?.chunkOptimizePrompt ||
|
|
332
|
-
aggregationOptimizePrompt: savedPromptSetting?.aggregationOptimizePrompt ||
|
|
333
|
-
finalOptimizePrompt: savedPromptSetting?.finalOptimizePrompt ||
|
|
358
|
+
chunkOptimizePrompt: savedPromptSetting?.chunkOptimizePrompt || curFrameworkDefaultConfig.chunkOptimizePrompt,
|
|
359
|
+
aggregationOptimizePrompt: savedPromptSetting?.aggregationOptimizePrompt || curFrameworkDefaultConfig.aggregationOptimizePrompt,
|
|
360
|
+
finalOptimizePrompt: savedPromptSetting?.finalOptimizePrompt || curFrameworkDefaultConfig.finalOptimizePrompt,
|
|
334
361
|
};
|
|
335
362
|
if (framework) {
|
|
336
363
|
// 加载框架配置
|
|
@@ -344,8 +371,6 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
344
371
|
};
|
|
345
372
|
}
|
|
346
373
|
}
|
|
347
|
-
// 获取框架列表
|
|
348
|
-
const frameworks = await configManager.getFrameworks();
|
|
349
374
|
res.json({
|
|
350
375
|
success: true,
|
|
351
376
|
data: {
|
|
@@ -428,7 +453,15 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
428
453
|
let promptSetting = {};
|
|
429
454
|
const fw = frameworks.find((f) => f.value === framework) ? framework : defaultFramework;
|
|
430
455
|
const frameworkConfig = await configManager.loadFrameworkConfig(fw);
|
|
431
|
-
promptSetting[fw] =
|
|
456
|
+
promptSetting[fw] = {
|
|
457
|
+
...frameworkConfig,
|
|
458
|
+
enableFrameworkGuide: frameworkConfig.enableFrameworkGuide || false,
|
|
459
|
+
frameworkPrompt: frameworkConfig.frameworkPrompt || '',
|
|
460
|
+
chunkOptimizePrompt: frameworkConfig.chunkOptimizePrompt || chunkOptimizeCodePrompt,
|
|
461
|
+
aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt || aggregationOptimizeCodePrompt,
|
|
462
|
+
finalOptimizePrompt: frameworkConfig.finalOptimizePrompt || finalOptimizeCodePrompt,
|
|
463
|
+
componentAnalysisPrompt: frameworkConfig.componentAnalysisPrompt || componentAnalysisPrompt,
|
|
464
|
+
};
|
|
432
465
|
res.json({
|
|
433
466
|
success: true,
|
|
434
467
|
data: {
|
|
@@ -494,14 +527,16 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
494
527
|
chunkOptimizePrompt: chunkOptimizeCodePrompt,
|
|
495
528
|
aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
|
|
496
529
|
finalOptimizePrompt: finalOptimizeCodePrompt,
|
|
530
|
+
componentAnalysisPrompt: componentAnalysisPrompt,
|
|
497
531
|
};
|
|
498
532
|
const frameworkConfig = await configManager.loadFrameworkConfig(framework);
|
|
499
533
|
if (frameworkConfig && Object.keys(frameworkConfig).length > 0) {
|
|
500
534
|
// 框架配置优先级更高
|
|
501
535
|
promptSetting = {
|
|
502
|
-
chunkOptimizePrompt: frameworkConfig.chunkOptimizePrompt,
|
|
503
|
-
aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt,
|
|
504
|
-
finalOptimizePrompt: frameworkConfig.finalOptimizePrompt,
|
|
536
|
+
chunkOptimizePrompt: frameworkConfig.chunkOptimizePrompt || promptSetting.chunkOptimizePrompt,
|
|
537
|
+
aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt || promptSetting.aggregationOptimizePrompt,
|
|
538
|
+
finalOptimizePrompt: frameworkConfig.finalOptimizePrompt || promptSetting.finalOptimizePrompt,
|
|
539
|
+
componentAnalysisPrompt: frameworkConfig.componentAnalysisPrompt || promptSetting.componentAnalysisPrompt,
|
|
505
540
|
};
|
|
506
541
|
}
|
|
507
542
|
res.json({
|
|
@@ -607,14 +642,19 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
607
642
|
await configManager.save(globalConfig);
|
|
608
643
|
Logger.log('已更新全局 MCP 配置');
|
|
609
644
|
}
|
|
645
|
+
// 首先尝试主进程的 pendingRequests(主进程场景),查询对应token的Promise,判断是否是主进程提交的
|
|
610
646
|
const request = pendingRequests.get(token);
|
|
647
|
+
Logger.log('submit pendingRequests', [...pendingRequests.keys()]);
|
|
611
648
|
if (request && request.resolve) {
|
|
612
649
|
request.resolve(JSON.stringify(value));
|
|
613
650
|
res.send('提交成功!您可以关闭此窗口。');
|
|
651
|
+
return;
|
|
614
652
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
653
|
+
// 如果主进程查不到pendingRequests,则判断是子进程提交的,通过 Socket 发送提交数据给子进程
|
|
654
|
+
socketServer?.sendSubmitResponse(token, value);
|
|
655
|
+
Logger.log(`已通过 Socket 发送认证响应: token=${token}`);
|
|
656
|
+
res.send('提交成功!您可以关闭此窗口。');
|
|
657
|
+
return;
|
|
618
658
|
}
|
|
619
659
|
catch (error) {
|
|
620
660
|
Logger.error('保存配置失败:', error);
|
|
@@ -869,7 +909,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
869
909
|
const analysisResult = extractResult.value;
|
|
870
910
|
// 构建组件信息
|
|
871
911
|
const component = {
|
|
872
|
-
id:
|
|
912
|
+
id: generateComponentId(),
|
|
873
913
|
name: analysisResult.componentName || path.basename(filePath, path.extname(filePath)),
|
|
874
914
|
type: 'custom',
|
|
875
915
|
path: filePath,
|
|
@@ -890,7 +930,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
890
930
|
results.push({
|
|
891
931
|
success: true,
|
|
892
932
|
component: {
|
|
893
|
-
id:
|
|
933
|
+
id: generateComponentId(),
|
|
894
934
|
name: path.basename(filePath, path.extname(filePath)),
|
|
895
935
|
type: 'custom',
|
|
896
936
|
path: filePath,
|
|
@@ -908,7 +948,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
908
948
|
results.push({
|
|
909
949
|
success: true,
|
|
910
950
|
component: {
|
|
911
|
-
id:
|
|
951
|
+
id: generateComponentId(),
|
|
912
952
|
name: path.basename(filePath, path.extname(filePath)),
|
|
913
953
|
type: 'custom',
|
|
914
954
|
path: filePath,
|
|
@@ -926,7 +966,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
926
966
|
results.push({
|
|
927
967
|
success: true,
|
|
928
968
|
component: {
|
|
929
|
-
id:
|
|
969
|
+
id: generateComponentId(),
|
|
930
970
|
name: path.basename(filePath, path.extname(filePath)),
|
|
931
971
|
type: 'custom',
|
|
932
972
|
path: filePath,
|
|
@@ -976,6 +1016,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
976
1016
|
});
|
|
977
1017
|
/**
|
|
978
1018
|
* 保存项目中的组件到components.json
|
|
1019
|
+
* 根据组件 id 自动判断是新增还是更新
|
|
979
1020
|
*/
|
|
980
1021
|
app.post('/saveComponents', async (req, res) => {
|
|
981
1022
|
try {
|
|
@@ -990,19 +1031,35 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
990
1031
|
Logger.log(`准备保存 ${components.length} 个组件`);
|
|
991
1032
|
// 读取现有组件
|
|
992
1033
|
const existingComponents = await fileManager.loadComponentsDatabase();
|
|
993
|
-
|
|
994
|
-
|
|
1034
|
+
const existingMap = new Map(existingComponents.map((c) => [c.id, c]));
|
|
1035
|
+
let addedCount = 0;
|
|
1036
|
+
let updatedCount = 0;
|
|
1037
|
+
// 根据 id 判断新增或更新
|
|
1038
|
+
for (const comp of components) {
|
|
1039
|
+
if (existingMap.has(comp.id)) {
|
|
1040
|
+
// 更新现有组件
|
|
1041
|
+
existingMap.set(comp.id, { ...existingMap.get(comp.id), ...comp });
|
|
1042
|
+
updatedCount++;
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
// 新增组件
|
|
1046
|
+
existingMap.set(comp.id, comp);
|
|
1047
|
+
addedCount++;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
const allComponents = Array.from(existingMap.values());
|
|
995
1051
|
// 保存到文件(带备份)
|
|
996
1052
|
await fileManager.saveComponentsDatabase(allComponents);
|
|
997
|
-
Logger.log(`✅
|
|
1053
|
+
Logger.log(`✅ 成功保存:新增 ${addedCount} 个,更新 ${updatedCount} 个,共 ${allComponents.length} 个组件`);
|
|
998
1054
|
res.json({
|
|
999
1055
|
success: true,
|
|
1000
1056
|
data: {
|
|
1001
|
-
added:
|
|
1057
|
+
added: addedCount,
|
|
1058
|
+
updated: updatedCount,
|
|
1002
1059
|
total: allComponents.length,
|
|
1003
1060
|
components: allComponents,
|
|
1004
1061
|
},
|
|
1005
|
-
message: '
|
|
1062
|
+
message: '组件保存成功',
|
|
1006
1063
|
});
|
|
1007
1064
|
}
|
|
1008
1065
|
catch (error) {
|
|
@@ -1014,31 +1071,12 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1014
1071
|
});
|
|
1015
1072
|
}
|
|
1016
1073
|
});
|
|
1017
|
-
|
|
1018
|
-
* 获取或创建映射系统实例
|
|
1019
|
-
*/
|
|
1020
|
-
async function getMappingSystem() {
|
|
1021
|
-
if (!mappingSystem) {
|
|
1022
|
-
const projectPath = getProjectRoot();
|
|
1023
|
-
mappingSystem = new ComponentMappingSystem(projectPath);
|
|
1024
|
-
await mappingSystem.initialize();
|
|
1025
|
-
Logger.log('组件映射系统已初始化,项目路径:', projectPath);
|
|
1026
|
-
}
|
|
1027
|
-
return mappingSystem;
|
|
1028
|
-
}
|
|
1029
|
-
// 扫描项目组件接口(同时返回项目组件和外部依赖组件)
|
|
1074
|
+
// 扫描项目组件接口
|
|
1030
1075
|
app.get('/scanComponents', async (req, res) => {
|
|
1031
1076
|
try {
|
|
1032
|
-
const {
|
|
1033
|
-
if (!platform) {
|
|
1034
|
-
res.status(400).json({
|
|
1035
|
-
success: false,
|
|
1036
|
-
message: '缺少必要参数: platform',
|
|
1037
|
-
});
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1077
|
+
const { includePaths, excludePaths } = req.query;
|
|
1040
1078
|
const projectPath = getProjectRoot();
|
|
1041
|
-
Logger.log('扫描项目组件:', {
|
|
1079
|
+
Logger.log('扫描项目组件:', { includePaths, excludePaths });
|
|
1042
1080
|
// 读取外部依赖组件(从 .sloth/components.json)
|
|
1043
1081
|
let projectComponents = [];
|
|
1044
1082
|
const slothPath = path.join(projectPath, '.sloth', 'components.json');
|
|
@@ -1063,7 +1101,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1063
1101
|
type: p.type || 'any',
|
|
1064
1102
|
required: p.required || false,
|
|
1065
1103
|
})),
|
|
1066
|
-
description: c.description
|
|
1104
|
+
description: c.description,
|
|
1067
1105
|
import: c.import,
|
|
1068
1106
|
}));
|
|
1069
1107
|
Logger.log(`从 .sloth/components.json 加载了 ${projectComponents.length} 个项目组件`);
|
|
@@ -1082,7 +1120,6 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1082
1120
|
success: true,
|
|
1083
1121
|
data: {
|
|
1084
1122
|
total: projectComponents.length,
|
|
1085
|
-
platform,
|
|
1086
1123
|
projectComponents, // 项目组件
|
|
1087
1124
|
},
|
|
1088
1125
|
});
|
|
@@ -1096,43 +1133,28 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1096
1133
|
});
|
|
1097
1134
|
}
|
|
1098
1135
|
});
|
|
1099
|
-
//
|
|
1136
|
+
// 建议组件映射接口(基于 components.json 中组件的截图匹配)
|
|
1100
1137
|
app.post('/suggestMappings', upload.single('currentScreenshot'), async (req, res) => {
|
|
1101
1138
|
try {
|
|
1102
|
-
const {
|
|
1139
|
+
const { threshold = '0.8' } = req.body;
|
|
1103
1140
|
const currentScreenshotFile = req.file;
|
|
1104
|
-
if (!currentScreenshotFile
|
|
1141
|
+
if (!currentScreenshotFile) {
|
|
1105
1142
|
res.status(400).json({
|
|
1106
1143
|
success: false,
|
|
1107
|
-
message: '缺少必要参数: currentScreenshot
|
|
1144
|
+
message: '缺少必要参数: currentScreenshot',
|
|
1108
1145
|
});
|
|
1109
1146
|
return;
|
|
1110
1147
|
}
|
|
1111
|
-
Logger.log(`开始生成建议组件映射:
|
|
1112
|
-
// 检查当前 fileKey + nodeId 是否已经有 groupsData
|
|
1113
|
-
const currentGroupsData = await fileManager.loadGroupsData(fileKey, nodeId);
|
|
1114
|
-
if (currentGroupsData && currentGroupsData.length > 0) {
|
|
1115
|
-
Logger.log(`当前设计稿已有 ${currentGroupsData.length} 个分组,跳过自动建议`);
|
|
1116
|
-
res.json({
|
|
1117
|
-
success: true,
|
|
1118
|
-
data: {
|
|
1119
|
-
suggestions: [],
|
|
1120
|
-
reason: 'current_has_groups',
|
|
1121
|
-
message: '当前设计稿已有分组数据,不执行自动建议',
|
|
1122
|
-
},
|
|
1123
|
-
});
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
Logger.log('当前设计稿无分组数据,继续执行自动建议');
|
|
1148
|
+
Logger.log(`开始生成建议组件映射: threshold=${threshold}`);
|
|
1127
1149
|
// 保存当前设计稿截图到临时文件
|
|
1128
1150
|
const tempDir = path.join(fileManager.getBaseDir(), 'temp');
|
|
1129
1151
|
await fs.promises.mkdir(tempDir, { recursive: true });
|
|
1130
1152
|
const currentScreenshotPath = path.join(tempDir, `suggest_${Date.now()}.png`);
|
|
1131
1153
|
await fs.promises.writeFile(currentScreenshotPath, currentScreenshotFile.buffer);
|
|
1132
|
-
//
|
|
1133
|
-
const
|
|
1134
|
-
if (!
|
|
1135
|
-
Logger.log('
|
|
1154
|
+
// 从 components.json 加载所有组件
|
|
1155
|
+
const components = await fileManager.loadComponentsDatabase();
|
|
1156
|
+
if (!components || components.length === 0) {
|
|
1157
|
+
Logger.log('未找到已保存的组件,无法生成建议');
|
|
1136
1158
|
await fs.promises.unlink(currentScreenshotPath).catch(() => { });
|
|
1137
1159
|
res.json({
|
|
1138
1160
|
success: true,
|
|
@@ -1140,34 +1162,37 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1140
1162
|
});
|
|
1141
1163
|
return;
|
|
1142
1164
|
}
|
|
1143
|
-
Logger.log(
|
|
1165
|
+
Logger.log(`从 components.json 加载了 ${components.length} 个组件`);
|
|
1166
|
+
// 构建匹配任务:查找每个组件的截图文件
|
|
1144
1167
|
const matchTasks = [];
|
|
1145
|
-
for (const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
continue;
|
|
1151
|
-
const screenshotPath = fileManager.getScreenshotPath(storedFileKey, normalizedNodeId, group.screenshot.hash);
|
|
1152
|
-
const exists = await fileManager.screenshotExists(storedFileKey, normalizedNodeId, group.screenshot.hash);
|
|
1153
|
-
if (!exists)
|
|
1154
|
-
continue;
|
|
1155
|
-
matchTasks.push({
|
|
1156
|
-
path: screenshotPath,
|
|
1157
|
-
groupData: {
|
|
1158
|
-
fileKey: storedFileKey,
|
|
1159
|
-
nodeId: normalizedNodeId ?? 'root',
|
|
1160
|
-
componentMapping: group.componentMapping,
|
|
1161
|
-
userPrompt: group.userPrompt,
|
|
1162
|
-
originalRect: group.rect,
|
|
1163
|
-
componentName: group.componentName, // 传递组件名称
|
|
1164
|
-
screenshot: group.screenshot, // 传递截图信息(包含 hash)
|
|
1165
|
-
},
|
|
1166
|
-
});
|
|
1168
|
+
for (const component of components) {
|
|
1169
|
+
// 只处理有 signature(截图 hash)的组件
|
|
1170
|
+
if (!component.signature) {
|
|
1171
|
+
Logger.log(`组件 ${component.name} 没有 signature,跳过`);
|
|
1172
|
+
continue;
|
|
1167
1173
|
}
|
|
1174
|
+
// 根据 signature 搜索截图文件
|
|
1175
|
+
const screenshotPath = await fileManager.findScreenshotByHash(component.signature);
|
|
1176
|
+
if (!screenshotPath) {
|
|
1177
|
+
Logger.log(`组件 ${component.name} 的截图未找到 (hash: ${component.signature}),跳过`);
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
matchTasks.push({
|
|
1181
|
+
path: screenshotPath,
|
|
1182
|
+
componentData: {
|
|
1183
|
+
id: component.id,
|
|
1184
|
+
name: component.name,
|
|
1185
|
+
type: component.type,
|
|
1186
|
+
path: component.path,
|
|
1187
|
+
import: component.import,
|
|
1188
|
+
props: component.props,
|
|
1189
|
+
description: component.description,
|
|
1190
|
+
signature: component.signature,
|
|
1191
|
+
},
|
|
1192
|
+
});
|
|
1168
1193
|
}
|
|
1169
1194
|
if (matchTasks.length === 0) {
|
|
1170
|
-
Logger.log('
|
|
1195
|
+
Logger.log('没有可用的组件截图,返回空建议');
|
|
1171
1196
|
await fs.promises.unlink(currentScreenshotPath).catch(() => { });
|
|
1172
1197
|
res.json({
|
|
1173
1198
|
success: true,
|
|
@@ -1175,12 +1200,12 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1175
1200
|
});
|
|
1176
1201
|
return;
|
|
1177
1202
|
}
|
|
1178
|
-
Logger.log(`开始匹配,共 ${matchTasks.length}
|
|
1203
|
+
Logger.log(`开始匹配,共 ${matchTasks.length} 个组件截图`);
|
|
1179
1204
|
const matcher = new ImageMatcher();
|
|
1180
|
-
const matches = await matcher.batchMatch(currentScreenshotPath, matchTasks, parseFloat(threshold));
|
|
1205
|
+
const matches = await matcher.batchMatch(currentScreenshotPath, matchTasks.map((t) => ({ path: t.path, groupData: t.componentData })), parseFloat(threshold));
|
|
1181
1206
|
Logger.log(`匹配完成,找到 ${matches.length} 个相似组件`);
|
|
1182
1207
|
matches.forEach((match, index) => {
|
|
1183
|
-
Logger.log(` 匹配 ${index + 1}:
|
|
1208
|
+
Logger.log(` 匹配 ${index + 1}: 组件=${match.groupData?.name || 'N/A'}, 相似度=${Math.round(match.confidence * 100)}%, 位置=(${match.position.left}, ${match.position.top}, ${match.position.width}x${match.position.height})`);
|
|
1184
1209
|
});
|
|
1185
1210
|
await fs.promises.unlink(currentScreenshotPath).catch(() => { });
|
|
1186
1211
|
res.json({
|
|
@@ -1188,14 +1213,22 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1188
1213
|
data: {
|
|
1189
1214
|
suggestions: matches.map((match) => ({
|
|
1190
1215
|
confidence: Math.round(match.confidence * 100) / 100,
|
|
1191
|
-
position:
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1216
|
+
position: {
|
|
1217
|
+
left: match.position.left,
|
|
1218
|
+
top: match.position.top,
|
|
1219
|
+
width: match.position.width,
|
|
1220
|
+
height: match.position.height,
|
|
1221
|
+
},
|
|
1222
|
+
component: {
|
|
1223
|
+
id: match.groupData?.id,
|
|
1224
|
+
name: match.groupData?.name,
|
|
1225
|
+
type: match.groupData?.type,
|
|
1226
|
+
path: match.groupData?.path,
|
|
1227
|
+
import: match.groupData?.import,
|
|
1228
|
+
props: match.groupData?.props,
|
|
1229
|
+
description: match.groupData?.description,
|
|
1230
|
+
signature: match.groupData?.signature,
|
|
1231
|
+
},
|
|
1199
1232
|
})),
|
|
1200
1233
|
},
|
|
1201
1234
|
});
|
|
@@ -1241,7 +1274,12 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1241
1274
|
}
|
|
1242
1275
|
});
|
|
1243
1276
|
// 启动 HTTP 服务器,监听端口
|
|
1244
|
-
httpServer = app.listen(port, () => {
|
|
1277
|
+
httpServer = app.listen(port, async (err) => {
|
|
1278
|
+
if (err) {
|
|
1279
|
+
Logger.error('HTTP 服务器启动失败:', err);
|
|
1280
|
+
httpServer = null;
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1245
1283
|
Logger.log(`HTTP server listening on port ${port}`);
|
|
1246
1284
|
Logger.log(`SSE endpoint available at http://localhost:${port}/sse`);
|
|
1247
1285
|
Logger.log(`Message endpoint available at http://localhost:${port}/messages`);
|
|
@@ -1253,6 +1291,15 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1253
1291
|
Logger.log(`Framework Config at http://localhost:${port}/getFrameworkConfig`);
|
|
1254
1292
|
Logger.log(`Save Nodes endpoint available at http://localhost:${port}/saveNodes`);
|
|
1255
1293
|
Logger.log(`Logging reconnect endpoint available at http://localhost:${port}/reconnect-logging`);
|
|
1294
|
+
// 启动 Socket 服务器,监听端口 + 1
|
|
1295
|
+
try {
|
|
1296
|
+
socketServer = new SocketServer();
|
|
1297
|
+
await socketServer.start(port + 1);
|
|
1298
|
+
}
|
|
1299
|
+
catch (error) {
|
|
1300
|
+
Logger.error('Socket 服务器启动失败:', error);
|
|
1301
|
+
Logger.error('进入子进程模式');
|
|
1302
|
+
}
|
|
1256
1303
|
});
|
|
1257
1304
|
// 监听 SIGINT 信号,优雅关闭服务器和所有会话
|
|
1258
1305
|
process.on('SIGINT', async () => {
|
|
@@ -1260,6 +1307,8 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1260
1307
|
Logger.log('正在关闭服务器...');
|
|
1261
1308
|
// 清理等待中的认证请求
|
|
1262
1309
|
pendingRequests.clear();
|
|
1310
|
+
// 关闭 Socket 服务器
|
|
1311
|
+
await stopSocketServer();
|
|
1263
1312
|
// 关闭所有活跃的 SSE 和 streamable 传输,释放资源
|
|
1264
1313
|
await closeTransports(transports.sse);
|
|
1265
1314
|
await closeTransports(transports.streamable);
|
|
@@ -1282,30 +1331,66 @@ async function closeTransports(transports) {
|
|
|
1282
1331
|
// 认证功能 - 获取用户输入函数
|
|
1283
1332
|
export async function getUserInput(payload) {
|
|
1284
1333
|
return new Promise(async (resolve, reject) => {
|
|
1285
|
-
if (!httpServer) {
|
|
1286
|
-
reject(new Error('HTTP 服务器未运行。请先启动服务器。'));
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
1334
|
const token = uuidv4();
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1335
|
+
const port = getPort();
|
|
1336
|
+
const authUrl = `http://localhost:${port}/auth-page?token=${token}&fileKey=${payload.fileKey}&nodeId=${payload.nodeId}`;
|
|
1337
|
+
Logger.log('authUrl', authUrl);
|
|
1338
|
+
// 判断是主进程还是子进程
|
|
1339
|
+
const isMainProcess = httpServer !== null;
|
|
1340
|
+
if (isMainProcess) {
|
|
1341
|
+
// 主进程:存在 pendingRequests中, 在/submit里resolve
|
|
1342
|
+
Logger.log('主进程:使用 pendingRequests 等待认证响应');
|
|
1343
|
+
pendingRequests.set(token, {
|
|
1344
|
+
resolve: (value) => {
|
|
1345
|
+
pendingRequests.delete(token);
|
|
1346
|
+
resolve(value);
|
|
1347
|
+
},
|
|
1348
|
+
timeout: null,
|
|
1349
|
+
});
|
|
1350
|
+
Logger.log('getUserInput pendingRequests', [...pendingRequests.keys()]);
|
|
1351
|
+
}
|
|
1352
|
+
else {
|
|
1353
|
+
// 子进程:使用 Socket 客户端,监听SocketServer的submit-response消息
|
|
1354
|
+
Logger.log('子进程:使用 Socket 客户端等待认证响应');
|
|
1355
|
+
try {
|
|
1356
|
+
const { SocketClient } = await import('./socket-client.js');
|
|
1357
|
+
const socketClient = new SocketClient('localhost', port + 1);
|
|
1358
|
+
// 连接到 Socket 服务器
|
|
1359
|
+
await socketClient.connect();
|
|
1360
|
+
Logger.log('Socket 客户端已连接');
|
|
1361
|
+
// 注册 token 并等待响应
|
|
1362
|
+
const responsePromise = socketClient.registerToken(token);
|
|
1363
|
+
// 打开浏览器
|
|
1364
|
+
await open(authUrl);
|
|
1365
|
+
// 等待认证响应
|
|
1366
|
+
const response = await responsePromise;
|
|
1367
|
+
// 断开连接
|
|
1368
|
+
socketClient.disconnect();
|
|
1369
|
+
return resolve(response);
|
|
1370
|
+
}
|
|
1371
|
+
catch (err) {
|
|
1372
|
+
reject(new Error(`Socket 客户端错误: ${err.message}`));
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
// 打开浏览器(主进程)
|
|
1300
1377
|
try {
|
|
1301
1378
|
await open(authUrl);
|
|
1302
1379
|
}
|
|
1303
1380
|
catch (err) {
|
|
1304
|
-
|
|
1381
|
+
if (isMainProcess) {
|
|
1382
|
+
pendingRequests.delete(token);
|
|
1383
|
+
}
|
|
1305
1384
|
reject(new Error(`打开浏览器失败: ${err.message}`));
|
|
1306
1385
|
}
|
|
1307
1386
|
});
|
|
1308
1387
|
}
|
|
1388
|
+
// 停止 Socket 服务器
|
|
1389
|
+
export async function stopSocketServer() {
|
|
1390
|
+
if (socketServer) {
|
|
1391
|
+
await socketServer.stop();
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1309
1394
|
// 停止 HTTP 服务器,并关闭所有 SSE 传输
|
|
1310
1395
|
export async function stopHttpServer() {
|
|
1311
1396
|
if (!httpServer) {
|
|
@@ -1314,6 +1399,8 @@ export async function stopHttpServer() {
|
|
|
1314
1399
|
return new Promise((resolve, reject) => {
|
|
1315
1400
|
// 清理等待中的认证请求
|
|
1316
1401
|
pendingRequests.clear();
|
|
1402
|
+
// 关闭 Socket 服务器
|
|
1403
|
+
const closeSocket = stopSocketServer();
|
|
1317
1404
|
httpServer.close((err) => {
|
|
1318
1405
|
if (err) {
|
|
1319
1406
|
reject(err);
|
|
@@ -1324,7 +1411,7 @@ export async function stopHttpServer() {
|
|
|
1324
1411
|
const closing = Object.values(transports.sse).map((transport) => {
|
|
1325
1412
|
return transport.close();
|
|
1326
1413
|
});
|
|
1327
|
-
Promise.all(closing).then(() => {
|
|
1414
|
+
Promise.all([...closing, closeSocket]).then(() => {
|
|
1328
1415
|
resolve();
|
|
1329
1416
|
});
|
|
1330
1417
|
});
|