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.
@@ -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
- if (!vueConfig || Object.keys(vueConfig).length < 3) {
116
- await configManager.saveFrameworkConfig('vue', {
117
- chunkOptimizePrompt: chunkOptimizeCodePromptVue,
118
- aggregationOptimizePrompt: aggregationOptimizeCodePromptVue,
119
- finalOptimizePrompt: finalOptimizeCodePromptVue,
120
- ...(vueConfig || {}),
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 || chunkOptimizeCodePrompt,
332
- aggregationOptimizePrompt: savedPromptSetting?.aggregationOptimizePrompt || aggregationOptimizeCodePrompt,
333
- finalOptimizePrompt: savedPromptSetting?.finalOptimizePrompt || finalOptimizeCodePrompt,
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] = frameworkConfig;
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
- else {
616
- res.status(404).send('无效或已过期的 token');
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: `comp-${Date.now()}-${Math.random()}`,
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: `comp-${Date.now()}-${Math.random()}`,
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: `comp-${Date.now()}-${Math.random()}`,
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: `comp-${Date.now()}-${Math.random()}`,
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
- const allComponents = [...components, ...existingComponents];
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(`✅ 成功保存,当前共 ${allComponents.length} 个组件`);
1053
+ Logger.log(`✅ 成功保存:新增 ${addedCount} 个,更新 ${updatedCount} 个,共 ${allComponents.length} 个组件`);
998
1054
  res.json({
999
1055
  success: true,
1000
1056
  data: {
1001
- added: components.length,
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 { platform, includePaths, excludePaths } = req.query;
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('扫描项目组件:', { platform, includePaths, excludePaths });
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 || c.metadata?.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 { fileKey, nodeId, threshold = '0.8' } = req.body;
1139
+ const { threshold = '0.8' } = req.body;
1103
1140
  const currentScreenshotFile = req.file;
1104
- if (!currentScreenshotFile || !fileKey) {
1141
+ if (!currentScreenshotFile) {
1105
1142
  res.status(400).json({
1106
1143
  success: false,
1107
- message: '缺少必要参数: currentScreenshot, fileKey',
1144
+ message: '缺少必要参数: currentScreenshot',
1108
1145
  });
1109
1146
  return;
1110
1147
  }
1111
- Logger.log(`开始生成建议组件映射: fileKey=${fileKey}, nodeId=${nodeId}, threshold=${threshold}`);
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
- // 加载整个项目所有 fileKey 的所有 groupsData(跨文件匹配)
1133
- const allProjectGroupsData = await fileManager.loadAllProjectGroupsData();
1134
- if (!allProjectGroupsData || allProjectGroupsData.length === 0) {
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(`从整个项目加载了 ${allProjectGroupsData.length} 个节点的历史数据`);
1165
+ Logger.log(`从 components.json 加载了 ${components.length} 个组件`);
1166
+ // 构建匹配任务:查找每个组件的截图文件
1144
1167
  const matchTasks = [];
1145
- for (const { fileKey: storedFileKey, nodeId: storedNodeId, groups } of allProjectGroupsData) {
1146
- const normalizedNodeId = storedNodeId === 'root' ? undefined : storedNodeId;
1147
- for (const group of groups) {
1148
- // 只处理标记的分组
1149
- if (!group.marked || !group.screenshot)
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}: 来源 fileKey=${match.groupData?.fileKey}, nodeId=${match.groupData?.nodeId}, 组件=${match.groupData?.componentName || 'N/A'}, 相似度=${Math.round(match.confidence * 100)}%`);
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: match.position,
1192
- componentMapping: match.groupData?.componentMapping,
1193
- userPrompt: match.groupData?.userPrompt,
1194
- originalRect: match.groupData?.originalRect,
1195
- fileKey: match.groupData?.fileKey,
1196
- nodeId: match.groupData?.nodeId,
1197
- componentName: match.groupData?.componentName,
1198
- screenshotHash: match.groupData?.screenshot?.hash, // 返回截图 hash,用于匹配组件 signature
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 authUrl = `http://localhost:${PORT}/auth-page?token=${token}&fileKey=${payload.fileKey}&nodeId=${payload.nodeId}`;
1291
- // 存储解析函数 - 无超时限制
1292
- pendingRequests.set(token, {
1293
- resolve: (value) => {
1294
- pendingRequests.delete(token);
1295
- resolve(value);
1296
- },
1297
- timeout: null, // 不再使用超时
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
- pendingRequests.delete(token);
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
  });