sloth-d2c-mcp 1.0.4-beta75 → 1.0.4-beta77
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 +124 -20
- package/dist/build/core/sampling.js +5 -23
- package/dist/build/index.js +185 -26
- package/dist/build/server.js +366 -253
- 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);
|
|
@@ -793,6 +833,10 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
793
833
|
});
|
|
794
834
|
}
|
|
795
835
|
});
|
|
836
|
+
// 多文件分析限制
|
|
837
|
+
const MAX_FILES = 10; // 最多 10 个文件
|
|
838
|
+
const MAX_TOTAL_CHARS = 40000; // 总内容最多 40000 字符
|
|
839
|
+
const MAX_FILE_CHARS = 5000; // 单文件最多 5000 字符
|
|
796
840
|
/**
|
|
797
841
|
* 分析项目中的组件
|
|
798
842
|
*/
|
|
@@ -806,10 +850,17 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
806
850
|
});
|
|
807
851
|
return;
|
|
808
852
|
}
|
|
809
|
-
|
|
853
|
+
// 限制文件数量
|
|
854
|
+
if (filePaths.length > MAX_FILES) {
|
|
855
|
+
res.status(400).json({
|
|
856
|
+
success: false,
|
|
857
|
+
message: `文件数量超过限制,最多支持 ${MAX_FILES} 个文件`,
|
|
858
|
+
});
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
Logger.log(`批量分析 ${filePaths.length} 个项目文件`);
|
|
810
862
|
const projectPath = getProjectRoot();
|
|
811
|
-
|
|
812
|
-
// 尝试加载保存的提示词设置(如果提供了 fileKey 和 nodeId)
|
|
863
|
+
// 尝试加载保存的提示词设置
|
|
813
864
|
let savedPromptSetting = null;
|
|
814
865
|
if (fileKey && nodeId) {
|
|
815
866
|
try {
|
|
@@ -822,148 +873,163 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
822
873
|
}
|
|
823
874
|
const frameworkGuidePrompt = savedPromptSetting?.frameworkGuidePrompt;
|
|
824
875
|
const _componentAnalysisPrompt = savedPromptSetting?.componentAnalysisPrompt || componentAnalysisPrompt;
|
|
825
|
-
//
|
|
876
|
+
// 读取所有文件内容
|
|
877
|
+
const fileContents = [];
|
|
878
|
+
let totalChars = 0;
|
|
826
879
|
for (const filePath of filePaths) {
|
|
827
880
|
try {
|
|
828
881
|
const fullPath = path.join(projectPath, filePath);
|
|
829
882
|
// 安全检查:确保文件在项目目录内
|
|
830
883
|
if (!fullPath.startsWith(projectPath)) {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
884
|
+
fileContents.push({
|
|
885
|
+
path: filePath,
|
|
886
|
+
content: '',
|
|
834
887
|
error: '无权访问该文件',
|
|
835
888
|
});
|
|
836
889
|
continue;
|
|
837
890
|
}
|
|
838
891
|
// 读取文件内容
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
// 构建提示词,替换占位符
|
|
845
|
-
const prompt = _componentAnalysisPrompt.replace(/{filename}/g, filename).replace(/{fileContent}/g, content);
|
|
846
|
-
// 调用 MCP Sampling API
|
|
847
|
-
const { content: { text }, } = await mcpServer.server.createMessage({
|
|
848
|
-
messages: [
|
|
849
|
-
frameworkGuidePrompt && {
|
|
850
|
-
role: 'user',
|
|
851
|
-
content: {
|
|
852
|
-
type: 'text',
|
|
853
|
-
text: frameworkGuidePrompt,
|
|
854
|
-
},
|
|
855
|
-
},
|
|
856
|
-
{
|
|
857
|
-
role: 'user',
|
|
858
|
-
content: {
|
|
859
|
-
type: 'text',
|
|
860
|
-
text: prompt,
|
|
861
|
-
},
|
|
862
|
-
},
|
|
863
|
-
].filter(Boolean),
|
|
864
|
-
maxTokens: 8000,
|
|
865
|
-
}, { timeout: 2 * 60 * 1000 });
|
|
866
|
-
// 解析 AI 返回的 JSON
|
|
867
|
-
const extractResult = extractJson(text);
|
|
868
|
-
if (extractResult.state === 'successful-parse' && extractResult.value) {
|
|
869
|
-
const analysisResult = extractResult.value;
|
|
870
|
-
// 构建组件信息
|
|
871
|
-
const component = {
|
|
872
|
-
id: `comp-${Date.now()}-${Math.random()}`,
|
|
873
|
-
name: analysisResult.componentName || path.basename(filePath, path.extname(filePath)),
|
|
874
|
-
type: 'custom',
|
|
875
|
-
path: filePath,
|
|
876
|
-
import: analysisResult.import,
|
|
877
|
-
props: analysisResult.props || [],
|
|
878
|
-
description: analysisResult.description || '组件描述',
|
|
879
|
-
};
|
|
880
|
-
results.push({
|
|
881
|
-
success: true,
|
|
882
|
-
component,
|
|
883
|
-
filename: filePath,
|
|
884
|
-
});
|
|
885
|
-
Logger.log(`成功分析组件: ${filePath}, 组件名: ${component.name}`);
|
|
886
|
-
}
|
|
887
|
-
else {
|
|
888
|
-
// JSON 解析失败,使用默认值
|
|
889
|
-
Logger.warn(`解析组件分析结果失败: ${extractResult.error}, 文件: ${filePath}`);
|
|
890
|
-
results.push({
|
|
891
|
-
success: true,
|
|
892
|
-
component: {
|
|
893
|
-
id: `comp-${Date.now()}-${Math.random()}`,
|
|
894
|
-
name: path.basename(filePath, path.extname(filePath)),
|
|
895
|
-
type: 'custom',
|
|
896
|
-
path: filePath,
|
|
897
|
-
import: `import { ${path.basename(filePath, path.extname(filePath))} } from './${filePath}'`,
|
|
898
|
-
props: [],
|
|
899
|
-
description: '组件描述',
|
|
900
|
-
},
|
|
901
|
-
filename: filePath,
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
catch (samplingError) {
|
|
906
|
-
Logger.error(`调用采样 API 失败: ${filePath}`, samplingError);
|
|
907
|
-
// 采样失败时使用默认值
|
|
908
|
-
results.push({
|
|
909
|
-
success: true,
|
|
910
|
-
component: {
|
|
911
|
-
id: `comp-${Date.now()}-${Math.random()}`,
|
|
912
|
-
name: path.basename(filePath, path.extname(filePath)),
|
|
913
|
-
type: 'custom',
|
|
914
|
-
path: filePath,
|
|
915
|
-
import: `import { ${path.basename(filePath, path.extname(filePath))} } from './${filePath}'`,
|
|
916
|
-
props: [],
|
|
917
|
-
description: '组件描述',
|
|
918
|
-
},
|
|
919
|
-
filename: filePath,
|
|
920
|
-
});
|
|
921
|
-
}
|
|
892
|
+
let content = await fs.promises.readFile(fullPath, 'utf-8');
|
|
893
|
+
// 单文件截断
|
|
894
|
+
if (content.length > MAX_FILE_CHARS) {
|
|
895
|
+
content = content.slice(0, MAX_FILE_CHARS) + '\n// ... 内容已截断 ...';
|
|
896
|
+
Logger.warn(`文件 ${filePath} 内容过长,已截断至 ${MAX_FILE_CHARS} 字符`);
|
|
922
897
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
Logger.warn(
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
name: path.basename(filePath, path.extname(filePath)),
|
|
931
|
-
type: 'custom',
|
|
932
|
-
path: filePath,
|
|
933
|
-
import: `import { ${path.basename(filePath, path.extname(filePath))} } from './${filePath}'`,
|
|
934
|
-
props: [],
|
|
935
|
-
description: '组件描述',
|
|
936
|
-
},
|
|
937
|
-
filename: filePath,
|
|
898
|
+
// 检查总字符数
|
|
899
|
+
if (totalChars + content.length > MAX_TOTAL_CHARS) {
|
|
900
|
+
Logger.warn(`总内容超过 ${MAX_TOTAL_CHARS} 字符限制,跳过文件: ${filePath}`);
|
|
901
|
+
fileContents.push({
|
|
902
|
+
path: filePath,
|
|
903
|
+
content: '',
|
|
904
|
+
error: '总内容超过限制,已跳过',
|
|
938
905
|
});
|
|
906
|
+
continue;
|
|
939
907
|
}
|
|
908
|
+
totalChars += content.length;
|
|
909
|
+
fileContents.push({
|
|
910
|
+
path: filePath,
|
|
911
|
+
content,
|
|
912
|
+
});
|
|
940
913
|
}
|
|
941
914
|
catch (error) {
|
|
942
915
|
Logger.error(`读取文件失败: ${filePath}`, error);
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
916
|
+
fileContents.push({
|
|
917
|
+
path: filePath,
|
|
918
|
+
content: '',
|
|
946
919
|
error: error instanceof Error ? error.message : String(error),
|
|
947
920
|
});
|
|
948
921
|
}
|
|
949
922
|
}
|
|
950
|
-
|
|
951
|
-
const
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
.map((
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
923
|
+
// 过滤出成功读取的文件
|
|
924
|
+
const validFiles = fileContents.filter((f) => f.content && !f.error);
|
|
925
|
+
const errorFiles = fileContents.filter((f) => f.error);
|
|
926
|
+
if (validFiles.length === 0) {
|
|
927
|
+
res.json({
|
|
928
|
+
success: true,
|
|
929
|
+
data: {
|
|
930
|
+
total: filePaths.length,
|
|
931
|
+
succeeded: 0,
|
|
932
|
+
failed: errorFiles.length,
|
|
933
|
+
components: [],
|
|
934
|
+
errors: errorFiles.map((f) => ({ filename: f.path, error: f.error })),
|
|
935
|
+
},
|
|
936
|
+
});
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
// 构建多文件内容部分
|
|
940
|
+
const filesSection = validFiles
|
|
941
|
+
.map((f) => {
|
|
942
|
+
const ext = path.extname(f.path).slice(1) || 'txt';
|
|
943
|
+
return `### 文件:${f.path}\n\`\`\`${ext}\n${f.content}\n\`\`\``;
|
|
944
|
+
})
|
|
945
|
+
.join('\n\n');
|
|
946
|
+
// 构建最终 Prompt
|
|
947
|
+
const prompt = _componentAnalysisPrompt.replace('{filesSection}', filesSection);
|
|
948
|
+
Logger.log(`构建 Prompt 完成,总字符数: ${prompt.length}`);
|
|
949
|
+
// 调用 MCP Sampling API
|
|
950
|
+
if (!mcpServer) {
|
|
951
|
+
Logger.warn('MCP 服务器未初始化,返回空结果');
|
|
952
|
+
res.json({
|
|
953
|
+
success: true,
|
|
954
|
+
data: {
|
|
955
|
+
total: filePaths.length,
|
|
956
|
+
succeeded: 0,
|
|
957
|
+
failed: filePaths.length,
|
|
958
|
+
components: [],
|
|
959
|
+
errors: filePaths.map((p) => ({ filename: p, error: 'MCP 服务器未初始化' })),
|
|
960
|
+
},
|
|
961
|
+
});
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
try {
|
|
965
|
+
const { content: { text }, } = await mcpServer.server.createMessage({
|
|
966
|
+
messages: [
|
|
967
|
+
frameworkGuidePrompt && {
|
|
968
|
+
role: 'user',
|
|
969
|
+
content: {
|
|
970
|
+
type: 'text',
|
|
971
|
+
text: frameworkGuidePrompt,
|
|
972
|
+
},
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
role: 'user',
|
|
976
|
+
content: {
|
|
977
|
+
type: 'text',
|
|
978
|
+
text: prompt,
|
|
979
|
+
},
|
|
980
|
+
},
|
|
981
|
+
].filter(Boolean),
|
|
982
|
+
maxTokens: 8000,
|
|
983
|
+
}, { timeout: 2 * 60 * 1000 });
|
|
984
|
+
// 解析 AI 返回的 JSON
|
|
985
|
+
const extractResult = extractJson(text);
|
|
986
|
+
if (extractResult.state === 'successful-parse' && extractResult.value) {
|
|
987
|
+
const analysisResult = extractResult.value;
|
|
988
|
+
const components = (analysisResult.components || []).map((comp) => ({
|
|
989
|
+
id: generateComponentId(),
|
|
990
|
+
name: comp.componentName || 'Unknown',
|
|
991
|
+
type: 'custom',
|
|
992
|
+
path: comp.path || '',
|
|
993
|
+
import: comp.import || '',
|
|
994
|
+
props: comp.props || [],
|
|
995
|
+
description: comp.description || '',
|
|
996
|
+
super: comp.super || undefined,
|
|
997
|
+
functions: comp.functions || undefined,
|
|
998
|
+
}));
|
|
999
|
+
Logger.log(`成功分析 ${components.length} 个组件`);
|
|
1000
|
+
res.json({
|
|
1001
|
+
success: true,
|
|
1002
|
+
data: {
|
|
1003
|
+
total: filePaths.length,
|
|
1004
|
+
succeeded: validFiles.length,
|
|
1005
|
+
failed: errorFiles.length,
|
|
1006
|
+
components,
|
|
1007
|
+
errors: errorFiles.map((f) => ({ filename: f.path, error: f.error })),
|
|
1008
|
+
},
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
else {
|
|
1012
|
+
Logger.warn(`解析组件分析结果失败: ${extractResult.error}`);
|
|
1013
|
+
res.json({
|
|
1014
|
+
success: true,
|
|
1015
|
+
data: {
|
|
1016
|
+
total: filePaths.length,
|
|
1017
|
+
succeeded: 0,
|
|
1018
|
+
failed: filePaths.length,
|
|
1019
|
+
components: [],
|
|
1020
|
+
errors: [{ filename: 'all', error: `AI 返回结果解析失败: ${extractResult.error}` }],
|
|
1021
|
+
},
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
catch (samplingError) {
|
|
1026
|
+
Logger.error('调用采样 API 失败:', samplingError);
|
|
1027
|
+
res.status(500).json({
|
|
1028
|
+
success: false,
|
|
1029
|
+
message: '调用 Sampling 分析失败,请调用 #mark_components tool进行组件分析',
|
|
1030
|
+
error: samplingError instanceof Error ? samplingError.message : String(samplingError),
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
967
1033
|
}
|
|
968
1034
|
catch (error) {
|
|
969
1035
|
Logger.error('分析项目组件失败:', error);
|
|
@@ -976,6 +1042,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
976
1042
|
});
|
|
977
1043
|
/**
|
|
978
1044
|
* 保存项目中的组件到components.json
|
|
1045
|
+
* 根据组件 id 自动判断是新增还是更新
|
|
979
1046
|
*/
|
|
980
1047
|
app.post('/saveComponents', async (req, res) => {
|
|
981
1048
|
try {
|
|
@@ -990,19 +1057,35 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
990
1057
|
Logger.log(`准备保存 ${components.length} 个组件`);
|
|
991
1058
|
// 读取现有组件
|
|
992
1059
|
const existingComponents = await fileManager.loadComponentsDatabase();
|
|
993
|
-
|
|
994
|
-
|
|
1060
|
+
const existingMap = new Map(existingComponents.map((c) => [c.id, c]));
|
|
1061
|
+
let addedCount = 0;
|
|
1062
|
+
let updatedCount = 0;
|
|
1063
|
+
// 根据 id 判断新增或更新
|
|
1064
|
+
for (const comp of components) {
|
|
1065
|
+
if (existingMap.has(comp.id)) {
|
|
1066
|
+
// 更新现有组件
|
|
1067
|
+
existingMap.set(comp.id, { ...existingMap.get(comp.id), ...comp });
|
|
1068
|
+
updatedCount++;
|
|
1069
|
+
}
|
|
1070
|
+
else {
|
|
1071
|
+
// 新增组件
|
|
1072
|
+
existingMap.set(comp.id, comp);
|
|
1073
|
+
addedCount++;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
const allComponents = Array.from(existingMap.values());
|
|
995
1077
|
// 保存到文件(带备份)
|
|
996
1078
|
await fileManager.saveComponentsDatabase(allComponents);
|
|
997
|
-
Logger.log(`✅
|
|
1079
|
+
Logger.log(`✅ 成功保存:新增 ${addedCount} 个,更新 ${updatedCount} 个,共 ${allComponents.length} 个组件`);
|
|
998
1080
|
res.json({
|
|
999
1081
|
success: true,
|
|
1000
1082
|
data: {
|
|
1001
|
-
added:
|
|
1083
|
+
added: addedCount,
|
|
1084
|
+
updated: updatedCount,
|
|
1002
1085
|
total: allComponents.length,
|
|
1003
1086
|
components: allComponents,
|
|
1004
1087
|
},
|
|
1005
|
-
message: '
|
|
1088
|
+
message: '组件保存成功',
|
|
1006
1089
|
});
|
|
1007
1090
|
}
|
|
1008
1091
|
catch (error) {
|
|
@@ -1014,32 +1097,13 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1014
1097
|
});
|
|
1015
1098
|
}
|
|
1016
1099
|
});
|
|
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
|
-
// 扫描项目组件接口(同时返回项目组件和外部依赖组件)
|
|
1100
|
+
// 扫描项目组件接口
|
|
1030
1101
|
app.get('/scanComponents', async (req, res) => {
|
|
1031
1102
|
try {
|
|
1032
|
-
const {
|
|
1033
|
-
if (!platform) {
|
|
1034
|
-
res.status(400).json({
|
|
1035
|
-
success: false,
|
|
1036
|
-
message: '缺少必要参数: platform',
|
|
1037
|
-
});
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1103
|
+
const { includePaths, excludePaths } = req.query;
|
|
1040
1104
|
const projectPath = getProjectRoot();
|
|
1041
|
-
Logger.log('扫描项目组件:', {
|
|
1042
|
-
//
|
|
1105
|
+
Logger.log('扫描项目组件:', { includePaths, excludePaths });
|
|
1106
|
+
// 读取项目组件(从 .sloth/components.json)
|
|
1043
1107
|
let projectComponents = [];
|
|
1044
1108
|
const slothPath = path.join(projectPath, '.sloth', 'components.json');
|
|
1045
1109
|
const fs = await import('fs/promises');
|
|
@@ -1063,7 +1127,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1063
1127
|
type: p.type || 'any',
|
|
1064
1128
|
required: p.required || false,
|
|
1065
1129
|
})),
|
|
1066
|
-
description: c.description
|
|
1130
|
+
description: c.description,
|
|
1067
1131
|
import: c.import,
|
|
1068
1132
|
}));
|
|
1069
1133
|
Logger.log(`从 .sloth/components.json 加载了 ${projectComponents.length} 个项目组件`);
|
|
@@ -1082,7 +1146,6 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1082
1146
|
success: true,
|
|
1083
1147
|
data: {
|
|
1084
1148
|
total: projectComponents.length,
|
|
1085
|
-
platform,
|
|
1086
1149
|
projectComponents, // 项目组件
|
|
1087
1150
|
},
|
|
1088
1151
|
});
|
|
@@ -1096,43 +1159,28 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1096
1159
|
});
|
|
1097
1160
|
}
|
|
1098
1161
|
});
|
|
1099
|
-
//
|
|
1162
|
+
// 建议组件映射接口(基于 components.json 中组件的截图匹配)
|
|
1100
1163
|
app.post('/suggestMappings', upload.single('currentScreenshot'), async (req, res) => {
|
|
1101
1164
|
try {
|
|
1102
|
-
const {
|
|
1165
|
+
const { threshold = '0.8' } = req.body;
|
|
1103
1166
|
const currentScreenshotFile = req.file;
|
|
1104
|
-
if (!currentScreenshotFile
|
|
1167
|
+
if (!currentScreenshotFile) {
|
|
1105
1168
|
res.status(400).json({
|
|
1106
1169
|
success: false,
|
|
1107
|
-
message: '缺少必要参数: currentScreenshot
|
|
1170
|
+
message: '缺少必要参数: currentScreenshot',
|
|
1108
1171
|
});
|
|
1109
1172
|
return;
|
|
1110
1173
|
}
|
|
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('当前设计稿无分组数据,继续执行自动建议');
|
|
1174
|
+
Logger.log(`开始生成建议组件映射: threshold=${threshold}`);
|
|
1127
1175
|
// 保存当前设计稿截图到临时文件
|
|
1128
1176
|
const tempDir = path.join(fileManager.getBaseDir(), 'temp');
|
|
1129
1177
|
await fs.promises.mkdir(tempDir, { recursive: true });
|
|
1130
1178
|
const currentScreenshotPath = path.join(tempDir, `suggest_${Date.now()}.png`);
|
|
1131
1179
|
await fs.promises.writeFile(currentScreenshotPath, currentScreenshotFile.buffer);
|
|
1132
|
-
//
|
|
1133
|
-
const
|
|
1134
|
-
if (!
|
|
1135
|
-
Logger.log('
|
|
1180
|
+
// 从 components.json 加载所有组件
|
|
1181
|
+
const components = await fileManager.loadComponentsDatabase();
|
|
1182
|
+
if (!components || components.length === 0) {
|
|
1183
|
+
Logger.log('未找到已保存的组件,无法生成建议');
|
|
1136
1184
|
await fs.promises.unlink(currentScreenshotPath).catch(() => { });
|
|
1137
1185
|
res.json({
|
|
1138
1186
|
success: true,
|
|
@@ -1140,34 +1188,37 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1140
1188
|
});
|
|
1141
1189
|
return;
|
|
1142
1190
|
}
|
|
1143
|
-
Logger.log(
|
|
1191
|
+
Logger.log(`从 components.json 加载了 ${components.length} 个组件`);
|
|
1192
|
+
// 构建匹配任务:查找每个组件的截图文件
|
|
1144
1193
|
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
|
-
});
|
|
1194
|
+
for (const component of components) {
|
|
1195
|
+
// 只处理有 signature(截图 hash)的组件
|
|
1196
|
+
if (!component.signature) {
|
|
1197
|
+
Logger.log(`组件 ${component.name} 没有 signature,跳过`);
|
|
1198
|
+
continue;
|
|
1167
1199
|
}
|
|
1200
|
+
// 根据 signature 搜索截图文件
|
|
1201
|
+
const screenshotPath = await fileManager.findScreenshotByHash(component.signature);
|
|
1202
|
+
if (!screenshotPath) {
|
|
1203
|
+
Logger.log(`组件 ${component.name} 的截图未找到 (hash: ${component.signature}),跳过`);
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
matchTasks.push({
|
|
1207
|
+
path: screenshotPath,
|
|
1208
|
+
componentData: {
|
|
1209
|
+
id: component.id,
|
|
1210
|
+
name: component.name,
|
|
1211
|
+
type: component.type,
|
|
1212
|
+
path: component.path,
|
|
1213
|
+
import: component.import,
|
|
1214
|
+
props: component.props,
|
|
1215
|
+
description: component.description,
|
|
1216
|
+
signature: component.signature,
|
|
1217
|
+
},
|
|
1218
|
+
});
|
|
1168
1219
|
}
|
|
1169
1220
|
if (matchTasks.length === 0) {
|
|
1170
|
-
Logger.log('
|
|
1221
|
+
Logger.log('没有可用的组件截图,返回空建议');
|
|
1171
1222
|
await fs.promises.unlink(currentScreenshotPath).catch(() => { });
|
|
1172
1223
|
res.json({
|
|
1173
1224
|
success: true,
|
|
@@ -1175,12 +1226,12 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1175
1226
|
});
|
|
1176
1227
|
return;
|
|
1177
1228
|
}
|
|
1178
|
-
Logger.log(`开始匹配,共 ${matchTasks.length}
|
|
1229
|
+
Logger.log(`开始匹配,共 ${matchTasks.length} 个组件截图`);
|
|
1179
1230
|
const matcher = new ImageMatcher();
|
|
1180
|
-
const matches = await matcher.batchMatch(currentScreenshotPath, matchTasks, parseFloat(threshold));
|
|
1231
|
+
const matches = await matcher.batchMatch(currentScreenshotPath, matchTasks.map((t) => ({ path: t.path, groupData: t.componentData })), parseFloat(threshold));
|
|
1181
1232
|
Logger.log(`匹配完成,找到 ${matches.length} 个相似组件`);
|
|
1182
1233
|
matches.forEach((match, index) => {
|
|
1183
|
-
Logger.log(` 匹配 ${index + 1}:
|
|
1234
|
+
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
1235
|
});
|
|
1185
1236
|
await fs.promises.unlink(currentScreenshotPath).catch(() => { });
|
|
1186
1237
|
res.json({
|
|
@@ -1188,14 +1239,22 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1188
1239
|
data: {
|
|
1189
1240
|
suggestions: matches.map((match) => ({
|
|
1190
1241
|
confidence: Math.round(match.confidence * 100) / 100,
|
|
1191
|
-
position:
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1242
|
+
position: {
|
|
1243
|
+
left: match.position.left,
|
|
1244
|
+
top: match.position.top,
|
|
1245
|
+
width: match.position.width,
|
|
1246
|
+
height: match.position.height,
|
|
1247
|
+
},
|
|
1248
|
+
component: {
|
|
1249
|
+
id: match.groupData?.id,
|
|
1250
|
+
name: match.groupData?.name,
|
|
1251
|
+
type: match.groupData?.type,
|
|
1252
|
+
path: match.groupData?.path,
|
|
1253
|
+
import: match.groupData?.import,
|
|
1254
|
+
props: match.groupData?.props,
|
|
1255
|
+
description: match.groupData?.description,
|
|
1256
|
+
signature: match.groupData?.signature,
|
|
1257
|
+
},
|
|
1199
1258
|
})),
|
|
1200
1259
|
},
|
|
1201
1260
|
});
|
|
@@ -1241,7 +1300,12 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1241
1300
|
}
|
|
1242
1301
|
});
|
|
1243
1302
|
// 启动 HTTP 服务器,监听端口
|
|
1244
|
-
httpServer = app.listen(port, () => {
|
|
1303
|
+
httpServer = app.listen(port, async (err) => {
|
|
1304
|
+
if (err) {
|
|
1305
|
+
Logger.error('HTTP 服务器启动失败:', err);
|
|
1306
|
+
httpServer = null;
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1245
1309
|
Logger.log(`HTTP server listening on port ${port}`);
|
|
1246
1310
|
Logger.log(`SSE endpoint available at http://localhost:${port}/sse`);
|
|
1247
1311
|
Logger.log(`Message endpoint available at http://localhost:${port}/messages`);
|
|
@@ -1253,6 +1317,15 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1253
1317
|
Logger.log(`Framework Config at http://localhost:${port}/getFrameworkConfig`);
|
|
1254
1318
|
Logger.log(`Save Nodes endpoint available at http://localhost:${port}/saveNodes`);
|
|
1255
1319
|
Logger.log(`Logging reconnect endpoint available at http://localhost:${port}/reconnect-logging`);
|
|
1320
|
+
// 启动 Socket 服务器,监听端口 + 1
|
|
1321
|
+
try {
|
|
1322
|
+
socketServer = new SocketServer();
|
|
1323
|
+
await socketServer.start(port + 1);
|
|
1324
|
+
}
|
|
1325
|
+
catch (error) {
|
|
1326
|
+
Logger.error('Socket 服务器启动失败:', error);
|
|
1327
|
+
Logger.error('进入子进程模式');
|
|
1328
|
+
}
|
|
1256
1329
|
});
|
|
1257
1330
|
// 监听 SIGINT 信号,优雅关闭服务器和所有会话
|
|
1258
1331
|
process.on('SIGINT', async () => {
|
|
@@ -1260,6 +1333,8 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1260
1333
|
Logger.log('正在关闭服务器...');
|
|
1261
1334
|
// 清理等待中的认证请求
|
|
1262
1335
|
pendingRequests.clear();
|
|
1336
|
+
// 关闭 Socket 服务器
|
|
1337
|
+
await stopSocketServer();
|
|
1263
1338
|
// 关闭所有活跃的 SSE 和 streamable 传输,释放资源
|
|
1264
1339
|
await closeTransports(transports.sse);
|
|
1265
1340
|
await closeTransports(transports.streamable);
|
|
@@ -1282,30 +1357,66 @@ async function closeTransports(transports) {
|
|
|
1282
1357
|
// 认证功能 - 获取用户输入函数
|
|
1283
1358
|
export async function getUserInput(payload) {
|
|
1284
1359
|
return new Promise(async (resolve, reject) => {
|
|
1285
|
-
if (!httpServer) {
|
|
1286
|
-
reject(new Error('HTTP 服务器未运行。请先启动服务器。'));
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
1360
|
const token = uuidv4();
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1361
|
+
const port = getPort();
|
|
1362
|
+
const authUrl = `http://localhost:${port}/auth-page?token=${token}&fileKey=${payload.fileKey}&nodeId=${payload.nodeId}`;
|
|
1363
|
+
Logger.log('authUrl', authUrl);
|
|
1364
|
+
// 判断是主进程还是子进程
|
|
1365
|
+
const isMainProcess = httpServer !== null;
|
|
1366
|
+
if (isMainProcess) {
|
|
1367
|
+
// 主进程:存在 pendingRequests中, 在/submit里resolve
|
|
1368
|
+
Logger.log('主进程:使用 pendingRequests 等待认证响应');
|
|
1369
|
+
pendingRequests.set(token, {
|
|
1370
|
+
resolve: (value) => {
|
|
1371
|
+
pendingRequests.delete(token);
|
|
1372
|
+
resolve(value);
|
|
1373
|
+
},
|
|
1374
|
+
timeout: null,
|
|
1375
|
+
});
|
|
1376
|
+
Logger.log('getUserInput pendingRequests', [...pendingRequests.keys()]);
|
|
1377
|
+
}
|
|
1378
|
+
else {
|
|
1379
|
+
// 子进程:使用 Socket 客户端,监听SocketServer的submit-response消息
|
|
1380
|
+
Logger.log('子进程:使用 Socket 客户端等待认证响应');
|
|
1381
|
+
try {
|
|
1382
|
+
const { SocketClient } = await import('./socket-client.js');
|
|
1383
|
+
const socketClient = new SocketClient('localhost', port + 1);
|
|
1384
|
+
// 连接到 Socket 服务器
|
|
1385
|
+
await socketClient.connect();
|
|
1386
|
+
Logger.log('Socket 客户端已连接');
|
|
1387
|
+
// 注册 token 并等待响应
|
|
1388
|
+
const responsePromise = socketClient.registerToken(token);
|
|
1389
|
+
// 打开浏览器
|
|
1390
|
+
await open(authUrl);
|
|
1391
|
+
// 等待认证响应
|
|
1392
|
+
const response = await responsePromise;
|
|
1393
|
+
// 断开连接
|
|
1394
|
+
socketClient.disconnect();
|
|
1395
|
+
return resolve(response);
|
|
1396
|
+
}
|
|
1397
|
+
catch (err) {
|
|
1398
|
+
reject(new Error(`Socket 客户端错误: ${err.message}`));
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
// 打开浏览器(主进程)
|
|
1300
1403
|
try {
|
|
1301
1404
|
await open(authUrl);
|
|
1302
1405
|
}
|
|
1303
1406
|
catch (err) {
|
|
1304
|
-
|
|
1407
|
+
if (isMainProcess) {
|
|
1408
|
+
pendingRequests.delete(token);
|
|
1409
|
+
}
|
|
1305
1410
|
reject(new Error(`打开浏览器失败: ${err.message}`));
|
|
1306
1411
|
}
|
|
1307
1412
|
});
|
|
1308
1413
|
}
|
|
1414
|
+
// 停止 Socket 服务器
|
|
1415
|
+
export async function stopSocketServer() {
|
|
1416
|
+
if (socketServer) {
|
|
1417
|
+
await socketServer.stop();
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1309
1420
|
// 停止 HTTP 服务器,并关闭所有 SSE 传输
|
|
1310
1421
|
export async function stopHttpServer() {
|
|
1311
1422
|
if (!httpServer) {
|
|
@@ -1314,6 +1425,8 @@ export async function stopHttpServer() {
|
|
|
1314
1425
|
return new Promise((resolve, reject) => {
|
|
1315
1426
|
// 清理等待中的认证请求
|
|
1316
1427
|
pendingRequests.clear();
|
|
1428
|
+
// 关闭 Socket 服务器
|
|
1429
|
+
const closeSocket = stopSocketServer();
|
|
1317
1430
|
httpServer.close((err) => {
|
|
1318
1431
|
if (err) {
|
|
1319
1432
|
reject(err);
|
|
@@ -1324,7 +1437,7 @@ export async function stopHttpServer() {
|
|
|
1324
1437
|
const closing = Object.values(transports.sse).map((transport) => {
|
|
1325
1438
|
return transport.close();
|
|
1326
1439
|
});
|
|
1327
|
-
Promise.all(closing).then(() => {
|
|
1440
|
+
Promise.all([...closing, closeSocket]).then(() => {
|
|
1328
1441
|
resolve();
|
|
1329
1442
|
});
|
|
1330
1443
|
});
|