sloth-d2c-mcp 1.0.4-beta100

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.
Files changed (34) hide show
  1. package/README.md +83 -0
  2. package/cli/run.js +328 -0
  3. package/cli/sloth-server.log +1622 -0
  4. package/dist/build/config-manager/index.js +240 -0
  5. package/dist/build/core/prompt-builder.js +366 -0
  6. package/dist/build/core/sampling.js +375 -0
  7. package/dist/build/core/types.js +1 -0
  8. package/dist/build/index.js +852 -0
  9. package/dist/build/interceptor/client.js +142 -0
  10. package/dist/build/interceptor/vscode.js +143 -0
  11. package/dist/build/interceptor/web.js +28 -0
  12. package/dist/build/plugin/index.js +4 -0
  13. package/dist/build/plugin/loader.js +349 -0
  14. package/dist/build/plugin/manager.js +129 -0
  15. package/dist/build/plugin/types.js +6 -0
  16. package/dist/build/server.js +2116 -0
  17. package/dist/build/socket-client.js +166 -0
  18. package/dist/build/socket-server.js +260 -0
  19. package/dist/build/utils/client-capabilities.js +143 -0
  20. package/dist/build/utils/extract.js +168 -0
  21. package/dist/build/utils/file-manager.js +868 -0
  22. package/dist/build/utils/image-matcher.js +154 -0
  23. package/dist/build/utils/logger.js +90 -0
  24. package/dist/build/utils/opencv-loader.js +70 -0
  25. package/dist/build/utils/prompt-parser.js +46 -0
  26. package/dist/build/utils/tj.js +139 -0
  27. package/dist/build/utils/update.js +100 -0
  28. package/dist/build/utils/utils.js +184 -0
  29. package/dist/build/utils/vscode-logger.js +133 -0
  30. package/dist/build/utils/webpack-substitutions.js +196 -0
  31. package/dist/interceptor-web/dist/build-report.json +18 -0
  32. package/dist/interceptor-web/dist/detail.html +1 -0
  33. package/dist/interceptor-web/dist/index.html +1 -0
  34. package/package.json +96 -0
@@ -0,0 +1,2116 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import express from 'express';
3
+ // 流式 HTTP 传输实现
4
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
5
+ // 判断是否为初始化请求
6
+ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
7
+ // 日志工具类,带时间戳输出日志
8
+ import { Logger } from './utils/logger.js';
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+ import { v4 as uuidv4 } from 'uuid';
13
+ import { nanoid } from 'nanoid';
14
+ import open from 'open';
15
+ import { fileManager } from './index.js';
16
+ import * as flatted from 'flatted';
17
+ import multer from 'multer';
18
+ import { ImageMatcher } from './utils/image-matcher.js';
19
+ import { initOpenCV } from './utils/opencv-loader.js';
20
+ import { extractJson } from './utils/extract.js';
21
+ import { componentAnalysisPrompt, componentAnalysisPromptVue, chunkOptimizeCodePromptKuikly, aggregationOptimizeCodePromptKuikly, finalOptimizeCodePromptKuikly, chunkOptimizeCodePrompt, aggregationOptimizeCodePrompt, finalOptimizeCodePrompt, chunkOptimizeCodePromptVue, aggregationOptimizeCodePromptVue, finalOptimizeCodePromptVue, frameworkPromptKuikly, noSamplingAggregationPrompt, noSamplingAggregationPromptVue, noSamplingAggregationPromptKuikly, chunkOptimizeCodePromptIosOc, aggregationOptimizeCodePromptIosOc, finalOptimizeCodePromptIosOc, noSamplingAggregationPromptIosOc, chunkOptimizeCodePromptIosSwift, aggregationOptimizeCodePromptIosSwift, finalOptimizeCodePromptIosSwift, noSamplingAggregationPromptIosSwift, chunkOptimizeCodePromptTaro, aggregationOptimizeCodePromptTaro, finalOptimizeCodePromptTaro, noSamplingAggregationPromptTaro, chunkOptimizeCodePromptUniapp, aggregationOptimizeCodePromptUniapp, finalOptimizeCodePromptUniapp, noSamplingAggregationPromptUniapp, } from 'sloth-d2c-node/convert';
22
+ import { SocketServer } from './socket-server.js';
23
+ import { getParam } from './utils/utils.js';
24
+ import { loadPluginFromGlobalDirAsync } from './plugin/loader.js';
25
+ import { pluginManager } from './plugin/manager.js';
26
+ /**
27
+ * 生成组件 ID(4位随机字符串)
28
+ */
29
+ export const generateComponentId = () => `comp_${nanoid(4)}`;
30
+ // 配置 multer 用于文件上传
31
+ const storage = multer.memoryStorage();
32
+ const upload = multer({
33
+ storage,
34
+ limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
35
+ });
36
+ // 保存 HTTP 服务器实例
37
+ export let httpServer = null;
38
+ // 保存 Socket 服务器实例
39
+ export let socketServer = null;
40
+ // 管理所有活跃的传输对象,按 sessionId 分类
41
+ const transports = {
42
+ streamable: {}, // 流式 HTTP 传输
43
+ sse: {}, // SSE 传输
44
+ };
45
+ // 认证功能存储
46
+ const pendingRequests = new Map(); // 等待中的认证请求
47
+ let configStorage = {}; // 配置缓存
48
+ let configManager = null; // 配置管理器实例
49
+ const __filename = fileURLToPath(import.meta.url);
50
+ const __dirname = path.dirname(__filename);
51
+ // 获取端口号
52
+ let PORT = 3100;
53
+ // 导出获取端口号的函数
54
+ export function getPort() {
55
+ return PORT;
56
+ }
57
+ export async function loadConfig(mcpServer, configManagerInstance) {
58
+ if (configManagerInstance) {
59
+ configManager = configManagerInstance;
60
+ // 打印配置文件路径
61
+ try {
62
+ const configPath = configManager.getConfigPath();
63
+ Logger.log(`配置文件路径: ${configPath}`);
64
+ // 检查配置文件是否存在
65
+ const configExists = await configManager.exists();
66
+ Logger.log(`配置文件存在: ${configExists ? '是' : '否'}`);
67
+ let config = {};
68
+ if (configExists) {
69
+ // 如果配置文件存在,加载并显示配置信息
70
+ try {
71
+ config = await configManager.load();
72
+ mcpServer.setConfig(config.mcp);
73
+ Logger.log(`当前配置内容: ${JSON.stringify(config, null, 2)}`);
74
+ }
75
+ catch (error) {
76
+ Logger.warn(`读取配置文件失败: ${error}`);
77
+ }
78
+ }
79
+ // 默认框架列表
80
+ const defaultFrameworks = [
81
+ { value: 'react', label: 'React' },
82
+ { value: 'vue', label: 'Vue' },
83
+ { value: 'ios-oc', label: 'iOS OC' },
84
+ { value: 'ios-swift', label: 'iOS Swift' },
85
+ { value: 'kuikly', label: 'Kuikly' },
86
+ { value: 'taro', label: 'Taro' },
87
+ { value: 'uniapp', label: 'Uniapp' },
88
+ ];
89
+ // 初始化默认框架配置
90
+ if (!config.frameworks || config.frameworks.length === 0) {
91
+ Logger.log('初始化默认框架配置...');
92
+ config.frameworks = [...defaultFrameworks];
93
+ await configManager.save(config);
94
+ }
95
+ else {
96
+ // 检查是否缺少默认框架,补充缺失的
97
+ const existingValues = config.frameworks.map((fw) => fw.value);
98
+ const missingFrameworks = defaultFrameworks.filter((fw) => !existingValues.includes(fw.value));
99
+ if (missingFrameworks.length > 0) {
100
+ config.frameworks = [...config.frameworks, ...missingFrameworks];
101
+ await configManager.save(config);
102
+ }
103
+ }
104
+ // 检查 react.json 是否存在,如果不存在则创建
105
+ const reactConfigExists = await configManager.frameworkConfigExists('react');
106
+ if (!reactConfigExists) {
107
+ await configManager.saveFrameworkConfig('react', {
108
+ chunkOptimizePrompt: chunkOptimizeCodePrompt,
109
+ aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
110
+ finalOptimizePrompt: finalOptimizeCodePrompt,
111
+ componentAnalysisPrompt: componentAnalysisPrompt,
112
+ noSamplingAggregationPrompt: noSamplingAggregationPrompt,
113
+ });
114
+ const reactConfigPath = configManager.getFrameworkConfigPath('react');
115
+ Logger.log(`已创建默认 react 配置文件: ${reactConfigPath}`);
116
+ }
117
+ // 检查 vue.json 是否存在,如果不存在则创建
118
+ const vueConfigExists = await configManager.frameworkConfigExists('vue');
119
+ if (!vueConfigExists) {
120
+ await configManager.saveFrameworkConfig('vue', {
121
+ chunkOptimizePrompt: chunkOptimizeCodePromptVue,
122
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptVue,
123
+ finalOptimizePrompt: finalOptimizeCodePromptVue,
124
+ componentAnalysisPrompt: componentAnalysisPromptVue,
125
+ noSamplingAggregationPromptVue: noSamplingAggregationPromptVue,
126
+ });
127
+ const vueConfigPath = configManager.getFrameworkConfigPath('vue');
128
+ Logger.log(`已创建默认 vue 配置文件: ${vueConfigPath}`);
129
+ }
130
+ else {
131
+ const vueConfig = await configManager.loadFrameworkConfig('vue');
132
+ await configManager.saveFrameworkConfig('vue', {
133
+ chunkOptimizePrompt: chunkOptimizeCodePromptVue,
134
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptVue,
135
+ finalOptimizePrompt: finalOptimizeCodePromptVue,
136
+ componentAnalysisPrompt: componentAnalysisPromptVue,
137
+ noSamplingAggregationPrompt: noSamplingAggregationPromptVue,
138
+ ...(vueConfig || {}),
139
+ });
140
+ }
141
+ // 检查 ios-oc.json 是否存在,如果不存在则创建
142
+ const iosOcConfigExists = await configManager.frameworkConfigExists('ios-oc');
143
+ if (!iosOcConfigExists) {
144
+ await configManager.saveFrameworkConfig('ios-oc', {
145
+ chunkOptimizePrompt: chunkOptimizeCodePromptIosOc,
146
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptIosOc,
147
+ finalOptimizePrompt: finalOptimizeCodePromptIosOc,
148
+ componentAnalysisPrompt: '',
149
+ noSamplingAggregationPrompt: noSamplingAggregationPromptIosOc,
150
+ });
151
+ const iosOcConfigPath = configManager.getFrameworkConfigPath('ios-oc');
152
+ Logger.log(`已创建默认 ios-oc 配置文件: ${iosOcConfigPath}`);
153
+ }
154
+ else {
155
+ const iosOcConfig = await configManager.loadFrameworkConfig('ios-oc');
156
+ await configManager.saveFrameworkConfig('ios-oc', {
157
+ chunkOptimizePrompt: chunkOptimizeCodePromptIosOc,
158
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptIosOc,
159
+ finalOptimizePrompt: finalOptimizeCodePromptIosOc,
160
+ componentAnalysisPrompt: '',
161
+ noSamplingAggregationPrompt: noSamplingAggregationPromptIosOc,
162
+ ...(iosOcConfig || {}),
163
+ });
164
+ }
165
+ // 检查 ios-swift.json 是否存在,如果不存在则创建
166
+ const iosswiftConfigExists = await configManager.frameworkConfigExists('ios-swift');
167
+ if (!iosswiftConfigExists) {
168
+ await configManager.saveFrameworkConfig('ios-swift', {
169
+ chunkOptimizePrompt: chunkOptimizeCodePromptIosSwift,
170
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptIosSwift,
171
+ finalOptimizePrompt: finalOptimizeCodePromptIosSwift,
172
+ componentAnalysisPrompt: '',
173
+ noSamplingAggregationPrompt: noSamplingAggregationPromptIosSwift,
174
+ });
175
+ const iosswiftConfigPath = configManager.getFrameworkConfigPath('ios-swift');
176
+ Logger.log(`已创建默认 ios-swift 配置文件: ${iosswiftConfigPath}`);
177
+ }
178
+ else {
179
+ const iosSwiftConfig = await configManager.loadFrameworkConfig('ios-swift');
180
+ await configManager.saveFrameworkConfig('ios-swift', {
181
+ chunkOptimizePrompt: chunkOptimizeCodePromptIosSwift,
182
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptIosSwift,
183
+ finalOptimizePrompt: finalOptimizeCodePromptIosSwift,
184
+ componentAnalysisPrompt: '',
185
+ noSamplingAggregationPrompt: noSamplingAggregationPromptIosSwift,
186
+ ...(iosSwiftConfig || {}),
187
+ });
188
+ }
189
+ // 检查 kuikly.json 是否存在,如果不存在则创建
190
+ const kuiklyConfigExists = await configManager.frameworkConfigExists('kuikly');
191
+ if (!kuiklyConfigExists) {
192
+ await configManager.saveFrameworkConfig('kuikly', {
193
+ enableFrameworkGuide: true,
194
+ frameworkGuidePrompt: frameworkPromptKuikly,
195
+ chunkOptimizePrompt: chunkOptimizeCodePromptKuikly,
196
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptKuikly,
197
+ finalOptimizePrompt: finalOptimizeCodePromptKuikly,
198
+ componentAnalysisPrompt: '',
199
+ noSamplingAggregationPrompt: noSamplingAggregationPromptKuikly,
200
+ });
201
+ const kuiklyConfigPath = configManager.getFrameworkConfigPath('kuikly');
202
+ Logger.log(`已创建默认 kuikly 配置文件: ${kuiklyConfigPath}`);
203
+ }
204
+ else {
205
+ const kuiklyConfig = await configManager.loadFrameworkConfig('kuikly');
206
+ await configManager.saveFrameworkConfig('kuikly', {
207
+ enableFrameworkGuide: true,
208
+ frameworkGuidePrompt: frameworkPromptKuikly,
209
+ chunkOptimizePrompt: chunkOptimizeCodePromptKuikly,
210
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptKuikly,
211
+ finalOptimizePrompt: finalOptimizeCodePromptKuikly,
212
+ componentAnalysisPrompt: '',
213
+ noSamplingAggregationPrompt: noSamplingAggregationPromptKuikly,
214
+ ...(kuiklyConfig || {}),
215
+ });
216
+ }
217
+ // 检查 taro.json 是否存在,如果不存在则创建
218
+ const taroConfigExists = await configManager.frameworkConfigExists('taro');
219
+ if (!taroConfigExists) {
220
+ await configManager.saveFrameworkConfig('taro', {
221
+ chunkOptimizePrompt: chunkOptimizeCodePromptTaro,
222
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptTaro,
223
+ finalOptimizePrompt: finalOptimizeCodePromptTaro,
224
+ componentAnalysisPrompt: '',
225
+ noSamplingAggregationPrompt: noSamplingAggregationPromptTaro,
226
+ });
227
+ const taroConfigPath = configManager.getFrameworkConfigPath('taro');
228
+ Logger.log(`已创建默认 taro 配置文件: ${taroConfigPath}`);
229
+ }
230
+ else {
231
+ const taroConfig = await configManager.loadFrameworkConfig('taro');
232
+ await configManager.saveFrameworkConfig('taro', {
233
+ chunkOptimizePrompt: chunkOptimizeCodePromptTaro,
234
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptTaro,
235
+ finalOptimizePrompt: finalOptimizeCodePromptTaro,
236
+ componentAnalysisPrompt: '',
237
+ noSamplingAggregationPrompt: noSamplingAggregationPromptTaro,
238
+ ...(taroConfig || {}),
239
+ });
240
+ }
241
+ // 检查 uniapp.json 是否存在,如果不存在则创建
242
+ const uniappConfigExists = await configManager.frameworkConfigExists('uniapp');
243
+ if (!uniappConfigExists) {
244
+ await configManager.saveFrameworkConfig('uniapp', {
245
+ chunkOptimizePrompt: chunkOptimizeCodePromptUniapp,
246
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptUniapp,
247
+ finalOptimizePrompt: finalOptimizeCodePromptUniapp,
248
+ componentAnalysisPrompt: '',
249
+ noSamplingAggregationPrompt: noSamplingAggregationPromptUniapp,
250
+ });
251
+ const uniappConfigPath = configManager.getFrameworkConfigPath('uniapp');
252
+ Logger.log(`已创建默认 uniapp 配置文件: ${uniappConfigPath}`);
253
+ }
254
+ else {
255
+ const uniappConfig = await configManager.loadFrameworkConfig('uniapp');
256
+ await configManager.saveFrameworkConfig('uniapp', {
257
+ chunkOptimizePrompt: chunkOptimizeCodePromptUniapp,
258
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptUniapp,
259
+ finalOptimizePrompt: finalOptimizeCodePromptUniapp,
260
+ componentAnalysisPrompt: '',
261
+ noSamplingAggregationPrompt: noSamplingAggregationPromptUniapp,
262
+ ...(uniappConfig || {}),
263
+ });
264
+ }
265
+ }
266
+ catch (error) {
267
+ Logger.error(`获取配置信息时出错: ${error}`);
268
+ }
269
+ }
270
+ }
271
+ // 启动 HTTP 服务器,监听指定端口,注册各类路由
272
+ export async function startHttpServer(port = PORT, mcpServer, configManagerInstance, isWeb) {
273
+ const app = express();
274
+ PORT = port;
275
+ // 初始化 OpenCV
276
+ try {
277
+ await initOpenCV();
278
+ Logger.log('✅ OpenCV.js 初始化成功');
279
+ }
280
+ catch (error) {
281
+ Logger.error('❌ OpenCV.js 初始化失败:', error);
282
+ Logger.warn('图像匹配功能将不可用,但不影响其他功能');
283
+ }
284
+ // 存储配置管理器实例(如果提供)
285
+ loadConfig(mcpServer, configManagerInstance);
286
+ // if (configManagerInstance) {
287
+ // configManager = configManagerInstance;
288
+ // // 打印配置文件路径
289
+ // try {
290
+ // const configPath = configManager.getConfigPath();
291
+ // Logger.log(`配置文件路径: ${configPath}`);
292
+ // // 检查配置文件是否存在
293
+ // const configExists = await configManager.exists();
294
+ // Logger.log(`配置文件存在: ${configExists ? '是' : '否'}`);
295
+ // if (configExists) {
296
+ // // 如果配置文件存在,加载并显示配置信息
297
+ // try {
298
+ // const config = await configManager.load();
299
+ // mcpServer.setConfig(config.mcp);
300
+ // Logger.log(`当前配置内容: ${JSON.stringify(config, null, 2)}`);
301
+ // } catch (error) {
302
+ // Logger.warn(`读取配置文件失败: ${error}`);
303
+ // }
304
+ // }
305
+ // } catch (error) {
306
+ // Logger.error(`获取配置信息时出错: ${error}`);
307
+ // }
308
+ // }
309
+ // 仅对 /mcp /submit 路径解析 JSON 请求体,避免影响 SSE
310
+ app.use('/mcp', express.json());
311
+ app.use('/submit', express.json());
312
+ app.use('/saveNodes', express.json({
313
+ limit: '100mb',
314
+ }));
315
+ // 为认证端点添加中间件 - 需要同时支持 JSON 和表单数据
316
+ app.use(express.urlencoded({ extended: true }));
317
+ app.use('/saveGlobalConfig', express.json());
318
+ app.use('/analyzeComponents', express.json());
319
+ app.use('/saveComponents', express.json());
320
+ // 设置跨域
321
+ app.use((req, res, next) => {
322
+ res.header('Access-Control-Allow-Origin', '*');
323
+ res.header('Access-Control-Allow-Methods', 'GET, POST');
324
+ res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
325
+ next();
326
+ });
327
+ // app.use(express.json());
328
+ app.use((req, _res, next) => {
329
+ // 通过referer取url上面的uuid
330
+ const uuid = getParam(req.headers.referer || '', 'token') || '';
331
+ req.uuid = uuid;
332
+ req.fileManager = fileManager.withUUID(uuid);
333
+ req.workspaceRoot = req.fileManager.getWorkspaceRoot();
334
+ next();
335
+ });
336
+ // 处理流式 HTTP 的 POST 请求,支持会话复用和初始化
337
+ app.post('/mcp', async (req, res) => {
338
+ Logger.log('Received StreamableHTTP request');
339
+ const sessionId = req.headers['mcp-session-id']; // 获取会话ID
340
+ let transport;
341
+ if (sessionId && transports.streamable[sessionId]) {
342
+ // 已有会话,复用传输对象
343
+ Logger.log('Reusing existing StreamableHTTP transport for sessionId', sessionId);
344
+ transport = transports.streamable[sessionId];
345
+ }
346
+ else if (!sessionId && isInitializeRequest(req.body)) {
347
+ // 新会话初始化请求
348
+ Logger.log('New initialization request for StreamableHTTP sessionId', sessionId);
349
+ transport = new StreamableHTTPServerTransport({
350
+ sessionIdGenerator: () => randomUUID(), // 生成唯一 sessionId
351
+ onsessioninitialized: (sessionId) => {
352
+ // 会话初始化后,存储传输对象
353
+ transports.streamable[sessionId] = transport;
354
+ },
355
+ });
356
+ // 会话关闭时清理资源
357
+ transport.onclose = () => {
358
+ if (transport.sessionId) {
359
+ delete transports.streamable[transport.sessionId];
360
+ }
361
+ };
362
+ // 连接 MCP 服务器,建立上下文
363
+ if (isWeb === true) {
364
+ await mcpServer.connect(transport);
365
+ }
366
+ }
367
+ else {
368
+ // 非法请求,缺少有效 sessionId
369
+ Logger.log('Invalid request:', req.body);
370
+ res.status(400).json({
371
+ jsonrpc: '2.0',
372
+ error: {
373
+ code: -32000,
374
+ message: 'Bad Request: No valid session ID provided',
375
+ },
376
+ id: null,
377
+ });
378
+ return;
379
+ }
380
+ // 进度通知相关逻辑
381
+ let progressInterval = null;
382
+ const progressToken = req.body.params?._meta?.progressToken; // 获取进度令牌
383
+ let progress = 0;
384
+ Logger.log(`progressToken:${progressToken}`);
385
+ if (progressToken) {
386
+ Logger.log(`Setting up progress notifications for token ${progressToken} on session ${sessionId} 1112222222`);
387
+ // 定时发送进度通知
388
+ progressInterval = setInterval(async () => {
389
+ // Logger.log("Sending progress notification", progress);
390
+ await mcpServer.server.notification({
391
+ method: 'notifications/progress',
392
+ params: {
393
+ progress,
394
+ progressToken,
395
+ },
396
+ });
397
+ progress++;
398
+ }, 1000);
399
+ }
400
+ Logger.log('Handling StreamableHTTP request');
401
+ // 处理请求,响应客户端
402
+ await transport.handleRequest(req, res, req.body);
403
+ // 清理进度通知定时器
404
+ // if (progressInterval) {
405
+ // clearInterval(progressInterval);
406
+ // }
407
+ Logger.log('StreamableHTTP request handled');
408
+ });
409
+ // 处理 GET/DELETE 请求,用于会话终止或获取会话状态
410
+ const handleSessionRequest = async (req, res) => {
411
+ const sessionId = req.headers['mcp-session-id'];
412
+ if (!sessionId || !transports.streamable[sessionId]) {
413
+ res.status(400).send('Invalid or missing session ID');
414
+ return;
415
+ }
416
+ Logger.log(`Received session termination request for session ${sessionId}`);
417
+ try {
418
+ const transport = transports.streamable[sessionId];
419
+ await transport.handleRequest(req, res);
420
+ }
421
+ catch (error) {
422
+ console.error('Error handling session termination:', error);
423
+ if (!res.headersSent) {
424
+ res.status(500).send('Error processing session termination');
425
+ }
426
+ }
427
+ };
428
+ // GET 请求:获取会话状态
429
+ app.get('/mcp', handleSessionRequest);
430
+ // DELETE 请求:终止会话
431
+ app.delete('/mcp', handleSessionRequest);
432
+ // SSE 连接建立,分配 sessionId 并注册到 transports.sse
433
+ // app.get("/sse", async (req: any, res: any) => {
434
+ // Logger.log("Establishing new SSE connection");
435
+ // const transport = new SSEServerTransport("/messages", res);
436
+ // Logger.log(`New SSE connection established for sessionId ${transport.sessionId}`);
437
+ // Logger.log("/sse request headers:", req.headers);
438
+ // Logger.log("/sse request body:", req.body);
439
+ // transports.sse[transport.sessionId] = transport;
440
+ // // 连接关闭时清理资源
441
+ // res.on("close", () => {
442
+ // delete transports.sse[transport.sessionId];
443
+ // });
444
+ // // 连接 MCP 服务器,建立 SSE 通道
445
+ // await mcpServer.connect(transport);
446
+ // });
447
+ // SSE 消息推送接口,根据 sessionId 找到对应传输对象
448
+ app.post('/messages', async (req, res) => {
449
+ const sessionId = req.query.sessionId;
450
+ const transport = transports.sse[sessionId];
451
+ Logger.log('/messages request headers:', req.headers);
452
+ Logger.log('/messages request body:', req.body);
453
+ if (transport) {
454
+ Logger.log(`Received SSE message for sessionId ${sessionId}`);
455
+ await transport.handlePostMessage(req, res);
456
+ }
457
+ else {
458
+ res.status(400).send(`No transport found for sessionId ${sessionId}`);
459
+ return;
460
+ }
461
+ });
462
+ // 认证相关端点
463
+ // 获取配置接口,支持 fileKey 和 nodeId 参数
464
+ app.get('/getConfig', async (req, res) => {
465
+ try {
466
+ const fileKey = req.query.fileKey;
467
+ const nodeId = req.query.nodeId;
468
+ const framework = req.query.framework;
469
+ // const plugins = await listWorkspacePlugins(req.workspaceRoot)
470
+ const plugins = await socketServer?.getTokenExtraField(req.uuid, 'plugins');
471
+ if (fileKey) {
472
+ // 如果提供了 fileKey,返回该 fileKey 的特定配置
473
+ const globalConfig = await configManager.load();
474
+ // const fileConfig = globalConfig.fileConfigs?.[fileKey] || {}
475
+ const fileConfig = (await req.fileManager.loadConfigSetting(fileKey, nodeId)) || {};
476
+ // 从 fileManager 按 nodeId 加载 groupsData 和 promptSetting
477
+ // const fileManager = new FileManager('d2c-mcp')
478
+ const groupsData = await req.fileManager.loadGroupsData(fileKey, nodeId);
479
+ const savedPromptSetting = await req.fileManager.loadPromptSetting(fileKey, nodeId);
480
+ let curFramework = fileConfig?.convertSetting?.framework;
481
+ // 获取框架列表
482
+ const frameworks = await configManager.getFrameworks();
483
+ // 如果指定了框架,加载框架配置的提示词
484
+ let promptSetting = {
485
+ enableFrameworkGuide: savedPromptSetting?.frameworkGuidePrompt,
486
+ frameworkGuidePrompt: savedPromptSetting?.frameworkGuidePrompt,
487
+ chunkOptimizePrompt: savedPromptSetting?.chunkOptimizePrompt,
488
+ aggregationOptimizePrompt: savedPromptSetting?.aggregationOptimizePrompt,
489
+ finalOptimizePrompt: savedPromptSetting?.finalOptimizePrompt,
490
+ componentAnalysisPrompt: savedPromptSetting?.componentAnalysisPrompt,
491
+ noSamplingAggregationPrompt: savedPromptSetting?.noSamplingAggregationPrompt,
492
+ };
493
+ res.json({
494
+ success: true,
495
+ data: {
496
+ mcp: globalConfig.mcp || {},
497
+ convertSetting: fileConfig.convertSetting,
498
+ imageSetting: fileConfig.imageSetting,
499
+ promptSetting: promptSetting,
500
+ frameworks: frameworks,
501
+ defaultFramework: globalConfig.defaultFramework || 'react',
502
+ fileKey: fileKey,
503
+ groupsData: groupsData,
504
+ pluginSetting: plugins,
505
+ },
506
+ });
507
+ }
508
+ else {
509
+ // 如果没有提供 fileKey,返回默认配置
510
+ if (await configManager.exists()) {
511
+ configStorage = await configManager.load();
512
+ }
513
+ // 如果指定了框架,加载框架配置的提示词
514
+ let promptSetting = {
515
+ enableFrameworkGuide: false,
516
+ frameworkGuidePrompt: '',
517
+ chunkOptimizePrompt: chunkOptimizeCodePrompt,
518
+ aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
519
+ finalOptimizePrompt: finalOptimizeCodePrompt,
520
+ componentAnalysisPrompt: componentAnalysisPrompt,
521
+ noSamplingAggregationPrompt: noSamplingAggregationPrompt,
522
+ };
523
+ if (framework) {
524
+ // 加载框架配置
525
+ const frameworkConfig = await configManager.loadFrameworkConfig(framework);
526
+ if (frameworkConfig && Object.keys(frameworkConfig).length > 0) {
527
+ // 框架配置优先级更高
528
+ promptSetting = {
529
+ enableFrameworkGuide: frameworkConfig.frameworkGuidePrompt || promptSetting.enableFrameworkGuide ? true : false,
530
+ frameworkGuidePrompt: frameworkConfig.frameworkGuidePrompt || promptSetting.frameworkGuidePrompt,
531
+ chunkOptimizePrompt: frameworkConfig.chunkOptimizePrompt || promptSetting.chunkOptimizePrompt,
532
+ aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt || promptSetting.aggregationOptimizePrompt,
533
+ finalOptimizePrompt: frameworkConfig.finalOptimizePrompt || promptSetting.finalOptimizePrompt,
534
+ componentAnalysisPrompt: frameworkConfig.componentAnalysisPrompt || promptSetting.componentAnalysisPrompt,
535
+ noSamplingAggregationPrompt: frameworkConfig.noSamplingAggregationPrompt || promptSetting.noSamplingAggregationPrompt,
536
+ };
537
+ }
538
+ }
539
+ // 获取框架列表
540
+ const frameworks = await configManager.getFrameworks();
541
+ res.json({
542
+ success: true,
543
+ data: {
544
+ mcp: configStorage.mcp || {},
545
+ convertSetting: configStorage.convertSetting || {},
546
+ imageSetting: configStorage.imageSetting || {},
547
+ promptSetting: promptSetting,
548
+ frameworks: frameworks,
549
+ pluginSetting: plugins,
550
+ },
551
+ });
552
+ }
553
+ }
554
+ catch (error) {
555
+ Logger.error('获取配置失败:', error);
556
+ res.status(500).json({
557
+ success: false,
558
+ message: '获取配置失败',
559
+ error: error instanceof Error ? error.message : String(error),
560
+ });
561
+ }
562
+ });
563
+ app.get('/getHtml', async (req, res) => {
564
+ // 加载现有配置
565
+ // const fileManager = new FileManager('d2c-mcp')
566
+ const html = await fileManager.loadAbsoluteHtml(req.query.fileKey, req.query.nodeId);
567
+ res.json({
568
+ success: true,
569
+ data: html,
570
+ });
571
+ });
572
+ // 获取全局配置接口
573
+ app.get('/getGlobalConfig', async (req, res) => {
574
+ try {
575
+ const framework = req.query.framework;
576
+ const globalConfig = await configManager.load();
577
+ let frameworks = (await configManager.getFrameworks()) || [
578
+ { value: 'react', label: 'React' },
579
+ { value: 'vue', label: 'Vue' },
580
+ { value: 'kuikly', label: 'Kuikly' },
581
+ ];
582
+ const defaultFramework = globalConfig?.defaultFramework || 'react';
583
+ let promptSetting = {};
584
+ const fw = frameworks.find((f) => f.value === framework) ? framework : defaultFramework;
585
+ const frameworkConfig = await configManager.loadFrameworkConfig(fw);
586
+ promptSetting[fw] = {
587
+ ...frameworkConfig,
588
+ enableFrameworkGuide: frameworkConfig.enableFrameworkGuide || false,
589
+ frameworkGuidePrompt: frameworkConfig.frameworkGuidePrompt || '',
590
+ chunkOptimizePrompt: frameworkConfig.chunkOptimizePrompt || '',
591
+ aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt || '',
592
+ finalOptimizePrompt: frameworkConfig.finalOptimizePrompt || '',
593
+ componentAnalysisPrompt: frameworkConfig.componentAnalysisPrompt || '',
594
+ noSamplingAggregationPrompt: frameworkConfig.noSamplingAggregationPrompt || '',
595
+ };
596
+ res.json({
597
+ success: true,
598
+ data: {
599
+ frameworks,
600
+ defaultFramework,
601
+ promptSetting,
602
+ },
603
+ message: '获取成功',
604
+ });
605
+ }
606
+ catch (error) {
607
+ Logger.error('获取全局配置失败:', error);
608
+ res.status(500).json({
609
+ success: false,
610
+ message: '获取全局配置失败',
611
+ error: error instanceof Error ? error.message : String(error),
612
+ });
613
+ }
614
+ });
615
+ // 保存全局配置接口
616
+ app.post('/saveGlobalConfig', async (req, res) => {
617
+ try {
618
+ const { token, value } = req.body;
619
+ if (!token || !value) {
620
+ res.status(400).json({
621
+ success: false,
622
+ message: '请求体中缺少 token 或 value',
623
+ });
624
+ return;
625
+ }
626
+ const { frameworks, defaultFramework, promptSetting: promptSettingMap } = value;
627
+ const globalConfig = await configManager.load();
628
+ globalConfig.frameworks = frameworks;
629
+ globalConfig.defaultFramework = defaultFramework;
630
+ await Promise.all(Object.entries(promptSettingMap).map(([key, value]) => configManager.saveFrameworkConfig(key, value)));
631
+ await configManager.save(globalConfig);
632
+ res.json({
633
+ success: true,
634
+ message: '保存全局配置成功',
635
+ });
636
+ }
637
+ catch (error) {
638
+ Logger.error('保存全局配置失败:', error);
639
+ res.status(500).json({
640
+ success: false,
641
+ message: '保存全局配置失败',
642
+ error: error instanceof Error ? error.message : String(error),
643
+ });
644
+ }
645
+ });
646
+ // 获取框架配置接口
647
+ app.get('/getFrameworkConfig', async (req, res) => {
648
+ try {
649
+ const framework = req.query.framework;
650
+ if (!framework) {
651
+ res.status(400).json({
652
+ success: false,
653
+ message: '缺少必要参数: framework',
654
+ });
655
+ return;
656
+ }
657
+ let promptSetting = {
658
+ enableFrameworkGuide: false,
659
+ frameworkGuidePrompt: '',
660
+ chunkOptimizePrompt: chunkOptimizeCodePrompt,
661
+ aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
662
+ finalOptimizePrompt: finalOptimizeCodePrompt,
663
+ componentAnalysisPrompt: componentAnalysisPrompt,
664
+ noSamplingAggregationPrompt: noSamplingAggregationPrompt,
665
+ };
666
+ const frameworkConfig = await configManager.loadFrameworkConfig(framework);
667
+ if (frameworkConfig && Object.keys(frameworkConfig).length > 0) {
668
+ // 框架配置优先级更高
669
+ promptSetting = {
670
+ enableFrameworkGuide: frameworkConfig.enableFrameworkGuide || promptSetting.enableFrameworkGuide,
671
+ frameworkGuidePrompt: frameworkConfig.frameworkGuidePrompt || promptSetting.frameworkGuidePrompt,
672
+ chunkOptimizePrompt: frameworkConfig.chunkOptimizePrompt || promptSetting.chunkOptimizePrompt,
673
+ aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt || promptSetting.aggregationOptimizePrompt,
674
+ finalOptimizePrompt: frameworkConfig.finalOptimizePrompt || promptSetting.finalOptimizePrompt,
675
+ componentAnalysisPrompt: frameworkConfig.componentAnalysisPrompt || promptSetting.componentAnalysisPrompt,
676
+ noSamplingAggregationPrompt: frameworkConfig.noSamplingAggregationPrompt || promptSetting.noSamplingAggregationPrompt,
677
+ };
678
+ }
679
+ res.json({
680
+ success: true,
681
+ data: promptSetting,
682
+ message: '获取框架配置成功',
683
+ });
684
+ }
685
+ catch (error) {
686
+ Logger.error('获取框架配置失败:', error);
687
+ res.status(500).json({
688
+ success: false,
689
+ message: '获取框架配置失败',
690
+ error: error instanceof Error ? error.message : String(error),
691
+ });
692
+ }
693
+ });
694
+ // 获取框架默认提示词接口(不读取用户配置,直接返回代码中的原始默认值)
695
+ app.get('/getDefaultFrameworkConfig', async (req, res) => {
696
+ try {
697
+ const framework = req.query.framework;
698
+ if (!framework) {
699
+ res.status(400).json({
700
+ success: false,
701
+ message: '缺少必要参数: framework',
702
+ });
703
+ return;
704
+ }
705
+ // 根据框架返回对应的原始默认提示词
706
+ const isVue = framework.toLowerCase() === 'vue';
707
+ const isIosOc = framework.toLowerCase() === 'ios-oc';
708
+ const isIosSwift = framework.toLowerCase() === 'ios-swift';
709
+ const isKuikly = framework.toLowerCase() === 'kuikly';
710
+ const isTaro = framework.toLowerCase() === 'taro';
711
+ const isUniapp = framework.toLowerCase() === 'uniapp';
712
+ let promptSetting = {
713
+ enableFrameworkGuide: isKuikly ? true : false,
714
+ frameworkGuidePrompt: isKuikly ? frameworkPromptKuikly : '',
715
+ chunkOptimizePrompt: isKuikly
716
+ ? chunkOptimizeCodePromptKuikly
717
+ : isIosOc
718
+ ? chunkOptimizeCodePromptIosOc
719
+ : isIosSwift
720
+ ? chunkOptimizeCodePromptIosSwift
721
+ : isVue
722
+ ? chunkOptimizeCodePromptVue
723
+ : chunkOptimizeCodePrompt,
724
+ aggregationOptimizePrompt: isKuikly
725
+ ? aggregationOptimizeCodePromptKuikly
726
+ : isIosOc
727
+ ? aggregationOptimizeCodePromptIosOc
728
+ : isIosSwift
729
+ ? aggregationOptimizeCodePromptIosSwift
730
+ : isVue
731
+ ? aggregationOptimizeCodePromptVue
732
+ : aggregationOptimizeCodePrompt,
733
+ finalOptimizePrompt: isKuikly
734
+ ? finalOptimizeCodePromptKuikly
735
+ : isIosOc
736
+ ? finalOptimizeCodePromptIosOc
737
+ : isIosSwift
738
+ ? finalOptimizeCodePromptIosSwift
739
+ : isVue
740
+ ? finalOptimizeCodePromptVue
741
+ : finalOptimizeCodePrompt,
742
+ componentAnalysisPrompt: isKuikly ? '' : isIosOc ? '' : isIosSwift ? '' : isVue ? componentAnalysisPromptVue : componentAnalysisPrompt,
743
+ noSamplingAggregationPrompt: isKuikly
744
+ ? noSamplingAggregationPromptKuikly
745
+ : isIosOc
746
+ ? noSamplingAggregationPromptIosOc
747
+ : isIosSwift
748
+ ? noSamplingAggregationPromptIosSwift
749
+ : isVue
750
+ ? noSamplingAggregationPromptVue
751
+ : noSamplingAggregationPrompt,
752
+ };
753
+ if (isTaro) {
754
+ promptSetting.enableFrameworkGuide = false;
755
+ promptSetting.frameworkGuidePrompt = '';
756
+ promptSetting.chunkOptimizePrompt = chunkOptimizeCodePromptTaro;
757
+ promptSetting.aggregationOptimizePrompt = aggregationOptimizeCodePromptTaro;
758
+ promptSetting.finalOptimizePrompt = finalOptimizeCodePromptTaro;
759
+ promptSetting.noSamplingAggregationPrompt = noSamplingAggregationPromptTaro;
760
+ promptSetting.componentAnalysisPrompt = componentAnalysisPrompt;
761
+ }
762
+ if (isUniapp) {
763
+ promptSetting.enableFrameworkGuide = false;
764
+ promptSetting.frameworkGuidePrompt = '';
765
+ promptSetting.chunkOptimizePrompt = chunkOptimizeCodePromptUniapp;
766
+ promptSetting.aggregationOptimizePrompt = aggregationOptimizeCodePromptUniapp;
767
+ promptSetting.finalOptimizePrompt = finalOptimizeCodePromptUniapp;
768
+ promptSetting.noSamplingAggregationPrompt = noSamplingAggregationPromptUniapp;
769
+ promptSetting.componentAnalysisPrompt = componentAnalysisPromptVue;
770
+ }
771
+ res.json({
772
+ success: true,
773
+ data: promptSetting,
774
+ message: '获取默认框架配置成功1',
775
+ });
776
+ }
777
+ catch (error) {
778
+ Logger.error('获取默认框架配置失败:', error);
779
+ res.status(500).json({
780
+ success: false,
781
+ message: '获取默认框架配置失败',
782
+ error: error instanceof Error ? error.message : String(error),
783
+ });
784
+ }
785
+ });
786
+ // 获取项目中的 Markdown 文件列表(扫描 .sloth/doc 目录)
787
+ app.get('/getMarkdownFiles', async (req, res) => {
788
+ try {
789
+ const projectRoot = getProjectRoot(req.fileManager);
790
+ if (!projectRoot) {
791
+ res.json({
792
+ success: true,
793
+ data: [],
794
+ message: '未找到项目根目录',
795
+ });
796
+ return;
797
+ }
798
+ const markdownFiles = [];
799
+ const promptDir = path.join(projectRoot, '.sloth', 'prompt');
800
+ // 检查 .sloth/prompt 目录是否存在
801
+ if (!fs.existsSync(promptDir)) {
802
+ res.json({
803
+ success: true,
804
+ data: [],
805
+ message: '.sloth/prompt 目录不存在',
806
+ });
807
+ return;
808
+ }
809
+ // 递归查找 prompt 目录下的 .md 文件
810
+ const findMarkdownFiles = (dir) => {
811
+ try {
812
+ const items = fs.readdirSync(dir, { withFileTypes: true });
813
+ for (const item of items) {
814
+ const fullPath = path.join(dir, item.name);
815
+ const relativePath = path.relative(promptDir, fullPath);
816
+ if (item.isDirectory()) {
817
+ findMarkdownFiles(fullPath);
818
+ }
819
+ else if (item.isFile() && item.name.endsWith('.md')) {
820
+ markdownFiles.push({
821
+ path: path.join('.sloth', 'prompt', relativePath),
822
+ name: item.name,
823
+ });
824
+ }
825
+ }
826
+ }
827
+ catch (err) {
828
+ Logger.warn(`无法访问目录: ${dir}`, err);
829
+ }
830
+ };
831
+ findMarkdownFiles(promptDir);
832
+ res.json({
833
+ success: true,
834
+ data: markdownFiles,
835
+ message: `找到 ${markdownFiles.length} 个文档`,
836
+ });
837
+ }
838
+ catch (error) {
839
+ Logger.error('获取文档列表失败:', error);
840
+ res.status(500).json({
841
+ success: false,
842
+ message: '获取文档列表失败',
843
+ error: error instanceof Error ? error.message : String(error),
844
+ });
845
+ }
846
+ });
847
+ // 认证页面
848
+ app.get('/auth-page', (req, res) => {
849
+ try {
850
+ // 尝试找到相对于当前位置的模板路径
851
+ const isStdioMode = process.env.NODE_ENV === 'cli' || process.argv.includes('--stdio');
852
+ const isDevMode = process.argv.includes('--dev');
853
+ const templatePath = path.join(__dirname, isStdioMode && !isDevMode ? '../interceptor-web/dist/index.html' : '../../interceptor-web/dist/index.html');
854
+ Logger.log('templatePath', templatePath);
855
+ if (fs.existsSync(templatePath)) {
856
+ const htmlContent = fs.readFileSync(templatePath, 'utf8');
857
+ res.send(htmlContent);
858
+ }
859
+ else {
860
+ // 备用方案:简单的 HTML 表单
861
+ const fallbackHtml = `
862
+ <!DOCTYPE html>
863
+ <html>
864
+ <head>
865
+ <title>身份验证</title>
866
+ <meta charset="utf-8">
867
+ <meta name="viewport" content="width=device-width, initial-scale=1">
868
+ </head>
869
+ <body>
870
+ <div style="max-width: 500px; margin: 50px auto; padding: 20px; font-family: Arial, sans-serif;">
871
+ <h2>需要身份验证</h2>
872
+ <form id="authForm" method="POST" action="/submit">
873
+ <input type="hidden" name="token" value="${req.query.token || ''}" />
874
+ <div style="margin-bottom: 15px;">
875
+ <label for="value" style="display: block; margin-bottom: 5px;">请输入您的信息:</label>
876
+ <input type="text" id="value" name="value" required style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;" />
877
+ </div>
878
+ <button type="submit" style="background: #007cba; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer;">提交</button>
879
+ </form>
880
+ </div>
881
+ </body>
882
+ </html>`;
883
+ res.send(fallbackHtml);
884
+ }
885
+ }
886
+ catch (error) {
887
+ res.status(500).send('加载认证页面时出错');
888
+ }
889
+ });
890
+ // 提交认证信息,支持 fileKey 和 nodeId 参数
891
+ app.post('/submit', async (req, res) => {
892
+ try {
893
+ // 安全地解构请求体,处理可能为空的情况
894
+ const { token, value, fileKey, nodeId } = req.body || {};
895
+ Logger.log('submit req.body', token, value, fileKey, nodeId);
896
+ if (!token || !value) {
897
+ res.status(400).send('请求体中缺少 token 或 value');
898
+ return;
899
+ }
900
+ // 保存配置到对应的 fileKey 或默认配置
901
+ if (fileKey) {
902
+ // 保存到特定 fileKey 的配置
903
+ const globalConfig = await configManager.load();
904
+ if (!globalConfig.fileConfigs) {
905
+ globalConfig.fileConfigs = {};
906
+ }
907
+ if (!globalConfig.fileConfigs[fileKey]) {
908
+ globalConfig.fileConfigs[fileKey] = {};
909
+ }
910
+ if (value.convertSetting) {
911
+ globalConfig.fileConfigs[fileKey].convertSetting = value.convertSetting;
912
+ }
913
+ if (value.imageSetting) {
914
+ globalConfig.fileConfigs[fileKey].imageSetting = value.imageSetting;
915
+ }
916
+ await configManager.save(globalConfig);
917
+ Logger.log(`已保存 fileKey "${fileKey}" 的配置:`, value);
918
+ }
919
+ // 使用 fileManager 按 nodeId 保存 groupsData 和 promptSetting
920
+ // const fileManager = new FileManager('d2c-mcp')
921
+ if (fileKey && value.groupsData && Array.isArray(value.groupsData)) {
922
+ await req.fileManager.saveGroupsData(fileKey, nodeId, value.groupsData);
923
+ Logger.log(`已保存 groupsData 到 fileKey "${fileKey}", nodeId "${nodeId}":`, value.groupsData.length, '个分组');
924
+ }
925
+ if (fileKey && value.promptSetting) {
926
+ if (!value.promptSetting.enableFrameworkGuide) {
927
+ value.promptSetting.frameworkGuidePrompt = '';
928
+ }
929
+ await req.fileManager.savePromptSetting(fileKey, nodeId, value.promptSetting);
930
+ Logger.log(`已保存 promptSetting 到 fileKey "${fileKey}", nodeId "${nodeId}"`);
931
+ }
932
+ // 如果有 MCP 配置,更新全局配置
933
+ if (value.mcp) {
934
+ const globalConfig = await configManager.load();
935
+ globalConfig.mcp = { ...globalConfig.mcp, ...value.mcp };
936
+ await configManager.save(globalConfig);
937
+ Logger.log('已更新全局 MCP 配置');
938
+ }
939
+ // 首先尝试主进程的 pendingRequests(主进程场景),查询对应token的Promise,判断是否是主进程提交的
940
+ const request = pendingRequests.get(token);
941
+ Logger.log('submit pendingRequests', [...pendingRequests.keys()]);
942
+ if (request && request.resolve) {
943
+ request.resolve(JSON.stringify(value));
944
+ res.send('提交成功!您可以关闭此窗口。');
945
+ return;
946
+ }
947
+ // 如果主进程查不到pendingRequests,则判断是子进程提交的,通过 Socket 发送提交数据给子进程
948
+ socketServer?.sendSubmitResponse(token, value);
949
+ Logger.log(`已通过 Socket 发送认证响应: token=${token}`);
950
+ res.send('提交成功!您可以关闭此窗口。');
951
+ return;
952
+ }
953
+ catch (error) {
954
+ Logger.error('保存配置失败:', error);
955
+ res.status(500).send('保存配置失败: ' + (error instanceof Error ? error.message : String(error)));
956
+ }
957
+ });
958
+ // 保存节点数据端点
959
+ app.post('/saveNodes', async (req, res) => {
960
+ try {
961
+ console.log('req.body', req);
962
+ const { nodeList, fileKey, nodeId, imageMap, absoluteHtml } = req.body;
963
+ if (!nodeList || !fileKey || !nodeId) {
964
+ res.status(400).json({
965
+ success: false,
966
+ message: '缺少必要参数: nodeList, fileKey, nodeId',
967
+ });
968
+ return;
969
+ }
970
+ // const fileManager = new FileManager('d2c-mcp')
971
+ // 保存节点列表
972
+ await fileManager.saveFile(fileKey, nodeId, 'nodeList.json', nodeList);
973
+ // 保存图片映射
974
+ if (imageMap) {
975
+ await fileManager.saveFile(fileKey, nodeId, 'imageMap.json', flatted.stringify(imageMap));
976
+ }
977
+ // 保存绝对布局HTML
978
+ if (absoluteHtml) {
979
+ await fileManager.saveAbsoluteHtml(fileKey, nodeId, absoluteHtml);
980
+ }
981
+ Logger.log(`成功保存节点数据: fileKey=${fileKey}, nodeId=${nodeId}`);
982
+ res.json({
983
+ success: true,
984
+ message: '节点数据保存成功',
985
+ });
986
+ }
987
+ catch (error) {
988
+ Logger.error('保存节点数据失败:', error);
989
+ res.status(500).json({
990
+ success: false,
991
+ message: '保存节点数据失败',
992
+ error: error instanceof Error ? error.message : String(error),
993
+ });
994
+ }
995
+ });
996
+ // 日志重连端点
997
+ app.post('/reconnect-logging', (req, res) => {
998
+ Logger.log('收到来自 VSCode 扩展的日志重连请求');
999
+ // 重新初始化日志连接
1000
+ Logger.reconnectVSCodeLogging();
1001
+ res.status(200).json({
1002
+ success: true,
1003
+ message: '日志重连已启动',
1004
+ });
1005
+ });
1006
+ // 插件渲染器端点 - 返回JS代码字符串
1007
+ app.get('/pluginRenderer', async (req, res) => {
1008
+ console.log('pluginRenderer');
1009
+ const pluginName = req.query.pluginName;
1010
+ let pluginRenderer = null;
1011
+ try {
1012
+ // @ts-ignore
1013
+ pluginRenderer = await loadPluginFromGlobalDirAsync(pluginName);
1014
+ }
1015
+ catch (error) {
1016
+ console.error('加载插件渲染器失败:', error);
1017
+ res.type('application/javascript; charset=utf-8').send(`alert(${pluginName}加载失败)`);
1018
+ return;
1019
+ }
1020
+ const renderer = pluginRenderer.default.renderConfigForm;
1021
+ res.type('application/javascript; charset=utf-8').send(renderer);
1022
+ });
1023
+ // ==================== 组件映射相关接口 ====================
1024
+ /**
1025
+ * 获取项目根目录
1026
+ */
1027
+ function getProjectRoot(fs) {
1028
+ try {
1029
+ const projectPath = fs.getWorkspaceRoot();
1030
+ return projectPath || './';
1031
+ }
1032
+ catch (error) {
1033
+ Logger.warn('获取根目录时出错,使用默认路径:', error);
1034
+ return './';
1035
+ }
1036
+ }
1037
+ /**
1038
+ * 获取项目文件树
1039
+ */
1040
+ app.get('/getProjectFiles', async (req, res) => {
1041
+ try {
1042
+ const { directory = '' } = req.query;
1043
+ const projectPath = getProjectRoot(req.fileManager);
1044
+ const targetPath = path.join(projectPath, directory);
1045
+ Logger.log(`获取项目文件树: ${targetPath}`);
1046
+ // 排除的文件扩展名(静态资源和配置文件)
1047
+ const excludeExtensions = [
1048
+ // 图片
1049
+ '.png',
1050
+ '.jpg',
1051
+ '.jpeg',
1052
+ '.gif',
1053
+ '.svg',
1054
+ '.ico',
1055
+ '.webp',
1056
+ '.bmp',
1057
+ // 视频/音频
1058
+ '.mp4',
1059
+ '.avi',
1060
+ '.mov',
1061
+ '.mp3',
1062
+ '.wav',
1063
+ // 字体
1064
+ '.ttf',
1065
+ '.woff',
1066
+ '.woff2',
1067
+ '.eot',
1068
+ // 配置文件
1069
+ '.json',
1070
+ '.lock',
1071
+ '.yaml',
1072
+ '.yml',
1073
+ '.toml',
1074
+ // 文档
1075
+ '.md',
1076
+ '.txt',
1077
+ '.pdf',
1078
+ '.doc',
1079
+ '.docx',
1080
+ // 其他
1081
+ '.zip',
1082
+ '.tar',
1083
+ '.gz',
1084
+ '.rar',
1085
+ '.map',
1086
+ '.d.ts.map',
1087
+ ];
1088
+ // 递归读取文件树
1089
+ const buildFileTree = async (dirPath, relativePath = '') => {
1090
+ const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
1091
+ const files = [];
1092
+ for (const entry of entries) {
1093
+ // 排除特殊目录
1094
+ const excludeDirs = ['node_modules', 'dist', 'build', '.git', '.next', 'coverage', '.sloth', 'public', 'static', 'assets'];
1095
+ if (excludeDirs.includes(entry.name) || entry.name.startsWith('.')) {
1096
+ continue;
1097
+ }
1098
+ const entryRelativePath = path.join(relativePath, entry.name);
1099
+ const entryFullPath = path.join(dirPath, entry.name);
1100
+ if (entry.isDirectory()) {
1101
+ const children = await buildFileTree(entryFullPath, entryRelativePath);
1102
+ if (children.length > 0) {
1103
+ files.push({
1104
+ title: entry.name,
1105
+ key: entryRelativePath,
1106
+ path: entryRelativePath,
1107
+ isLeaf: false,
1108
+ selectable: false,
1109
+ children,
1110
+ });
1111
+ }
1112
+ }
1113
+ else {
1114
+ // 排除静态资源文件
1115
+ const ext = path.extname(entry.name).toLowerCase();
1116
+ if (!excludeExtensions.includes(ext)) {
1117
+ files.push({
1118
+ title: entry.name,
1119
+ key: entryRelativePath,
1120
+ path: entryRelativePath,
1121
+ isLeaf: true,
1122
+ selectable: true,
1123
+ });
1124
+ }
1125
+ }
1126
+ }
1127
+ return files;
1128
+ };
1129
+ const fileTree = await buildFileTree(targetPath);
1130
+ res.json({
1131
+ success: true,
1132
+ data: {
1133
+ projectPath,
1134
+ files: fileTree,
1135
+ },
1136
+ });
1137
+ }
1138
+ catch (error) {
1139
+ Logger.error('获取项目文件树失败:', error);
1140
+ res.status(500).json({
1141
+ success: false,
1142
+ message: '获取项目文件树失败',
1143
+ error: error instanceof Error ? error.message : String(error),
1144
+ });
1145
+ }
1146
+ });
1147
+ // 多文件分析限制
1148
+ const MAX_FILES = 10; // 最多 10 个文件
1149
+ const MAX_TOTAL_CHARS = 40000; // 总内容最多 40000 字符
1150
+ const MAX_FILE_CHARS = 5000; // 单文件最多 5000 字符
1151
+ /**
1152
+ * 分析项目中的组件
1153
+ */
1154
+ app.post('/analyzeComponents', async (req, res) => {
1155
+ try {
1156
+ const { filePaths, fileKey, nodeId } = req.body;
1157
+ if (!filePaths || !Array.isArray(filePaths)) {
1158
+ res.status(400).json({
1159
+ success: false,
1160
+ message: '缺少必要参数: filePaths',
1161
+ });
1162
+ return;
1163
+ }
1164
+ // 限制文件数量
1165
+ if (filePaths.length > MAX_FILES) {
1166
+ res.status(400).json({
1167
+ success: false,
1168
+ message: `文件数量超过限制,最多支持 ${MAX_FILES} 个文件`,
1169
+ });
1170
+ return;
1171
+ }
1172
+ Logger.log(`批量分析 ${filePaths.length} 个项目文件`);
1173
+ const projectPath = getProjectRoot(req.fileManager);
1174
+ // 尝试加载保存的提示词设置
1175
+ let savedPromptSetting = null;
1176
+ if (fileKey && nodeId) {
1177
+ try {
1178
+ savedPromptSetting = await req.fileManager.loadPromptSetting(fileKey, nodeId);
1179
+ Logger.log(`已加载提示词设置: fileKey=${fileKey}, nodeId=${nodeId}`);
1180
+ }
1181
+ catch (error) {
1182
+ Logger.warn(`加载提示词设置失败: ${error}`);
1183
+ }
1184
+ }
1185
+ const frameworkGuidePrompt = savedPromptSetting?.frameworkGuidePrompt;
1186
+ const _componentAnalysisPrompt = savedPromptSetting?.componentAnalysisPrompt || componentAnalysisPrompt;
1187
+ // 读取所有文件内容
1188
+ const fileContents = [];
1189
+ let totalChars = 0;
1190
+ for (const filePath of filePaths) {
1191
+ try {
1192
+ const fullPath = path.join(projectPath, filePath);
1193
+ // 安全检查:确保文件在项目目录内
1194
+ if (!fullPath.startsWith(projectPath)) {
1195
+ fileContents.push({
1196
+ path: filePath,
1197
+ content: '',
1198
+ error: '无权访问该文件',
1199
+ });
1200
+ continue;
1201
+ }
1202
+ // 读取文件内容
1203
+ let content = await fs.promises.readFile(fullPath, 'utf-8');
1204
+ // 单文件截断
1205
+ if (content.length > MAX_FILE_CHARS) {
1206
+ content = content.slice(0, MAX_FILE_CHARS) + '\n// ... 内容已截断 ...';
1207
+ Logger.warn(`文件 ${filePath} 内容过长,已截断至 ${MAX_FILE_CHARS} 字符`);
1208
+ }
1209
+ // 检查总字符数
1210
+ if (totalChars + content.length > MAX_TOTAL_CHARS) {
1211
+ Logger.warn(`总内容超过 ${MAX_TOTAL_CHARS} 字符限制,跳过文件: ${filePath}`);
1212
+ fileContents.push({
1213
+ path: filePath,
1214
+ content: '',
1215
+ error: '总内容超过限制,已跳过',
1216
+ });
1217
+ continue;
1218
+ }
1219
+ totalChars += content.length;
1220
+ fileContents.push({
1221
+ path: filePath,
1222
+ content,
1223
+ });
1224
+ }
1225
+ catch (error) {
1226
+ Logger.error(`读取文件失败: ${filePath}`, error);
1227
+ fileContents.push({
1228
+ path: filePath,
1229
+ content: '',
1230
+ error: error instanceof Error ? error.message : String(error),
1231
+ });
1232
+ }
1233
+ }
1234
+ // 过滤出成功读取的文件
1235
+ const validFiles = fileContents.filter((f) => f.content && !f.error);
1236
+ const errorFiles = fileContents.filter((f) => f.error);
1237
+ if (validFiles.length === 0) {
1238
+ res.json({
1239
+ success: true,
1240
+ data: {
1241
+ total: filePaths.length,
1242
+ succeeded: 0,
1243
+ failed: errorFiles.length,
1244
+ components: [],
1245
+ errors: errorFiles.map((f) => ({ filename: f.path, error: f.error })),
1246
+ },
1247
+ });
1248
+ return;
1249
+ }
1250
+ // 构建多文件内容部分
1251
+ const filesSection = validFiles
1252
+ .map((f) => {
1253
+ const ext = path.extname(f.path).slice(1) || 'txt';
1254
+ return `### 文件:${f.path}\n\`\`\`${ext}\n${f.content}\n\`\`\``;
1255
+ })
1256
+ .join('\n\n');
1257
+ // 构建最终 Prompt
1258
+ const prompt = _componentAnalysisPrompt.replace('{filesSection}', filesSection);
1259
+ Logger.log(`构建 Prompt 完成,总字符数: ${prompt.length}`);
1260
+ // 调用 MCP Sampling API
1261
+ if (!mcpServer) {
1262
+ Logger.warn('MCP 服务器未初始化,返回空结果');
1263
+ res.json({
1264
+ success: true,
1265
+ data: {
1266
+ total: filePaths.length,
1267
+ succeeded: 0,
1268
+ failed: filePaths.length,
1269
+ components: [],
1270
+ errors: filePaths.map((p) => ({ filename: p, error: 'MCP 服务器未初始化' })),
1271
+ },
1272
+ });
1273
+ return;
1274
+ }
1275
+ try {
1276
+ const { content: { text }, } = await mcpServer.server.createMessage({
1277
+ messages: [
1278
+ frameworkGuidePrompt && {
1279
+ role: 'user',
1280
+ content: {
1281
+ type: 'text',
1282
+ text: frameworkGuidePrompt,
1283
+ },
1284
+ },
1285
+ {
1286
+ role: 'user',
1287
+ content: {
1288
+ type: 'text',
1289
+ text: prompt,
1290
+ },
1291
+ },
1292
+ ].filter(Boolean),
1293
+ maxTokens: 48000,
1294
+ }, { timeout: 2 * 60 * 1000 });
1295
+ // 解析 AI 返回的 JSON
1296
+ const extractResult = extractJson(text);
1297
+ if (extractResult.state === 'successful-parse' && extractResult.value) {
1298
+ const analysisResult = extractResult.value;
1299
+ const components = (analysisResult.components || []).map((comp) => ({
1300
+ id: generateComponentId(),
1301
+ name: comp.componentName || 'Unknown',
1302
+ type: 'custom',
1303
+ path: comp.path || '',
1304
+ import: comp.import || '',
1305
+ props: comp.props || [],
1306
+ description: comp.description || '',
1307
+ super: comp.super || undefined,
1308
+ functions: comp.functions || undefined,
1309
+ }));
1310
+ Logger.log(`成功分析 ${components.length} 个组件`);
1311
+ res.json({
1312
+ success: true,
1313
+ data: {
1314
+ total: filePaths.length,
1315
+ succeeded: validFiles.length,
1316
+ failed: errorFiles.length,
1317
+ components,
1318
+ errors: errorFiles.map((f) => ({ filename: f.path, error: f.error })),
1319
+ },
1320
+ });
1321
+ }
1322
+ else {
1323
+ Logger.warn(`解析组件分析结果失败: ${extractResult.error}`);
1324
+ res.json({
1325
+ success: true,
1326
+ data: {
1327
+ total: filePaths.length,
1328
+ succeeded: 0,
1329
+ failed: filePaths.length,
1330
+ components: [],
1331
+ errors: [{ filename: 'all', error: `AI 返回结果解析失败: ${extractResult.error}` }],
1332
+ },
1333
+ });
1334
+ }
1335
+ }
1336
+ catch (samplingError) {
1337
+ Logger.error('调用采样 API 失败:', samplingError);
1338
+ res.status(500).json({
1339
+ success: false,
1340
+ message: '调用 Sampling 分析失败,请调用 #mark_components tool进行组件分析',
1341
+ error: samplingError instanceof Error ? samplingError.message : String(samplingError),
1342
+ });
1343
+ }
1344
+ }
1345
+ catch (error) {
1346
+ Logger.error('分析项目组件失败:', error);
1347
+ res.status(500).json({
1348
+ success: false,
1349
+ message: '分析项目组件失败',
1350
+ error: error instanceof Error ? error.message : String(error),
1351
+ });
1352
+ }
1353
+ });
1354
+ /**
1355
+ * 保存项目中的组件到components.json
1356
+ * 根据组件 id 自动判断是新增还是更新
1357
+ */
1358
+ app.post('/saveComponents', async (req, res) => {
1359
+ try {
1360
+ const { components } = req.body;
1361
+ if (!components || !Array.isArray(components)) {
1362
+ res.status(400).json({
1363
+ success: false,
1364
+ message: '无效的组件数据',
1365
+ });
1366
+ return;
1367
+ }
1368
+ Logger.log(`准备保存 ${components.length} 个组件`);
1369
+ // 读取现有组件
1370
+ const existingComponents = await req.fileManager.loadComponentsDatabase();
1371
+ const existingMap = new Map(existingComponents.map((c) => [c.id, c]));
1372
+ let addedCount = 0;
1373
+ let updatedCount = 0;
1374
+ // 根据 id 判断新增或更新
1375
+ for (const comp of components) {
1376
+ if (existingMap.has(comp.id)) {
1377
+ // 更新现有组件
1378
+ existingMap.set(comp.id, { ...existingMap.get(comp.id), ...comp });
1379
+ updatedCount++;
1380
+ }
1381
+ else {
1382
+ // 新增组件
1383
+ existingMap.set(comp.id, comp);
1384
+ addedCount++;
1385
+ }
1386
+ }
1387
+ const allComponents = Array.from(existingMap.values());
1388
+ // 保存到文件(带备份)
1389
+ await req.fileManager.saveComponentsDatabase(allComponents);
1390
+ Logger.log(`✅ 成功保存:新增 ${addedCount} 个,更新 ${updatedCount} 个,共 ${allComponents.length} 个组件`);
1391
+ res.json({
1392
+ success: true,
1393
+ data: {
1394
+ added: addedCount,
1395
+ updated: updatedCount,
1396
+ total: allComponents.length,
1397
+ components: allComponents,
1398
+ },
1399
+ message: '组件保存成功',
1400
+ });
1401
+ }
1402
+ catch (error) {
1403
+ Logger.error('保存组件失败:', error);
1404
+ res.status(500).json({
1405
+ success: false,
1406
+ message: '保存组件失败',
1407
+ error: error instanceof Error ? error.message : String(error),
1408
+ });
1409
+ }
1410
+ });
1411
+ // 扫描项目组件接口
1412
+ app.get('/scanComponents', async (req, res) => {
1413
+ try {
1414
+ const { includePaths, excludePaths } = req.query;
1415
+ const projectPath = getProjectRoot(req.fileManager);
1416
+ // 读取项目组件(从 .sloth/components.json)
1417
+ let projectComponents = [];
1418
+ const slothPath = path.join(projectPath, '.sloth', 'components.json');
1419
+ const fs = await import('fs/promises');
1420
+ try {
1421
+ await fs.access(slothPath);
1422
+ // 文件存在,读取内容
1423
+ const fileContent = await fs.readFile(slothPath, 'utf-8');
1424
+ const componentsData = JSON.parse(fileContent);
1425
+ // 验证数据结构
1426
+ if (Array.isArray(componentsData)) {
1427
+ // 验证每个组件的基本字段
1428
+ projectComponents = componentsData
1429
+ .filter((comp) => comp && comp.id && comp.name && comp.path)
1430
+ .map((c) => ({
1431
+ id: c.id,
1432
+ name: c.name,
1433
+ type: c.type || 'custom',
1434
+ path: c.path,
1435
+ props: (c.props || []).map((p) => ({
1436
+ name: p.name,
1437
+ type: p.type || 'any',
1438
+ required: p.required || false,
1439
+ })),
1440
+ description: c.description,
1441
+ import: c.import,
1442
+ }));
1443
+ Logger.log(`从 .sloth/components.json 加载了 ${projectComponents.length} 个项目组件`);
1444
+ }
1445
+ else {
1446
+ Logger.warn('.sloth/components.json 格式错误:应为组件数组');
1447
+ }
1448
+ }
1449
+ catch (error) {
1450
+ // 文件不存在或读取失败,忽略错误,继续返回项目组件
1451
+ if (error.code !== 'ENOENT') {
1452
+ Logger.warn('读取外部依赖组件失败:', error.message);
1453
+ }
1454
+ }
1455
+ res.json({
1456
+ success: true,
1457
+ data: {
1458
+ total: projectComponents.length,
1459
+ projectComponents, // 项目组件
1460
+ },
1461
+ });
1462
+ }
1463
+ catch (error) {
1464
+ Logger.error('扫描项目组件失败:', error);
1465
+ res.status(500).json({
1466
+ success: false,
1467
+ message: '扫描项目组件失败',
1468
+ error: error instanceof Error ? error.message : String(error),
1469
+ });
1470
+ }
1471
+ });
1472
+ // 建议组件映射接口(基于 components.json 中组件的截图匹配)
1473
+ app.post('/suggestMappings', upload.single('currentScreenshot'), async (req, res) => {
1474
+ try {
1475
+ const { threshold = '0.8' } = req.body;
1476
+ const currentScreenshotFile = req.file;
1477
+ if (!currentScreenshotFile) {
1478
+ res.status(400).json({
1479
+ success: false,
1480
+ message: '缺少必要参数: currentScreenshot',
1481
+ });
1482
+ return;
1483
+ }
1484
+ Logger.log(`开始生成建议组件映射: threshold=${threshold}`);
1485
+ // 保存当前设计稿截图到临时文件
1486
+ const tempDir = path.join(fileManager.getBaseDir(), 'temp');
1487
+ await fs.promises.mkdir(tempDir, { recursive: true });
1488
+ const currentScreenshotPath = path.join(tempDir, `suggest_${Date.now()}.png`);
1489
+ await fs.promises.writeFile(currentScreenshotPath, currentScreenshotFile.buffer);
1490
+ // 从 components.json 加载所有组件
1491
+ const components = await req.fileManager.loadComponentsDatabase();
1492
+ if (!components || components.length === 0) {
1493
+ Logger.log('未找到已保存的组件,无法生成建议');
1494
+ await fs.promises.unlink(currentScreenshotPath).catch(() => { });
1495
+ res.json({
1496
+ success: true,
1497
+ data: { suggestions: [] },
1498
+ });
1499
+ return;
1500
+ }
1501
+ Logger.log(`从 components.json 加载了 ${components.length} 个组件`);
1502
+ // 构建匹配任务:查找每个组件的截图文件
1503
+ const matchTasks = [];
1504
+ for (const component of components) {
1505
+ // 只处理有 signature(截图 hash)的组件
1506
+ if (!component.signature) {
1507
+ Logger.log(`组件 ${component.name} 没有 signature,跳过`);
1508
+ continue;
1509
+ }
1510
+ // 根据 signature 搜索截图文件
1511
+ const screenshotPath = await req.fileManager.findScreenshotByHash(component.signature);
1512
+ if (!screenshotPath) {
1513
+ Logger.log(`组件 ${component.name} 的截图未找到 (hash: ${component.signature}),跳过`);
1514
+ continue;
1515
+ }
1516
+ matchTasks.push({
1517
+ path: screenshotPath,
1518
+ componentData: {
1519
+ id: component.id,
1520
+ name: component.name,
1521
+ type: component.type,
1522
+ path: component.path,
1523
+ import: component.import,
1524
+ props: component.props,
1525
+ description: component.description,
1526
+ signature: component.signature,
1527
+ },
1528
+ });
1529
+ }
1530
+ if (matchTasks.length === 0) {
1531
+ Logger.log('没有可用的组件截图,返回空建议');
1532
+ await fs.promises.unlink(currentScreenshotPath).catch(() => { });
1533
+ res.json({
1534
+ success: true,
1535
+ data: { suggestions: [] },
1536
+ });
1537
+ return;
1538
+ }
1539
+ Logger.log(`开始匹配,共 ${matchTasks.length} 个组件截图`);
1540
+ const matcher = new ImageMatcher();
1541
+ const matches = await matcher.batchMatch(currentScreenshotPath, matchTasks.map((t) => ({ path: t.path, groupData: t.componentData })), parseFloat(threshold));
1542
+ Logger.log(`匹配完成,找到 ${matches.length} 个相似组件`);
1543
+ matches.forEach((match, index) => {
1544
+ 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})`);
1545
+ });
1546
+ await fs.promises.unlink(currentScreenshotPath).catch(() => { });
1547
+ res.json({
1548
+ success: true,
1549
+ data: {
1550
+ suggestions: matches.map((match) => ({
1551
+ confidence: Math.round(match.confidence * 100) / 100,
1552
+ position: {
1553
+ left: match.position.left,
1554
+ top: match.position.top,
1555
+ width: match.position.width,
1556
+ height: match.position.height,
1557
+ },
1558
+ component: {
1559
+ id: match.groupData?.id,
1560
+ name: match.groupData?.name,
1561
+ type: match.groupData?.type,
1562
+ path: match.groupData?.path,
1563
+ import: match.groupData?.import,
1564
+ props: match.groupData?.props,
1565
+ description: match.groupData?.description,
1566
+ signature: match.groupData?.signature,
1567
+ },
1568
+ })),
1569
+ },
1570
+ });
1571
+ }
1572
+ catch (error) {
1573
+ Logger.error('建议组件映射失败:', error);
1574
+ res.status(500).json({
1575
+ success: false,
1576
+ message: '建议组件映射失败',
1577
+ error: error instanceof Error ? error.message : String(error),
1578
+ });
1579
+ }
1580
+ });
1581
+ // 上传截图接口
1582
+ app.post('/uploadScreenshot', upload.single('file'), async (req, res) => {
1583
+ try {
1584
+ const { fileKey, nodeId, hash } = req.body;
1585
+ const file = req.file;
1586
+ if (!file || !fileKey || !nodeId || !hash) {
1587
+ res.status(400).json({
1588
+ success: false,
1589
+ message: '缺少必要参数: file, fileKey, nodeId, hash',
1590
+ });
1591
+ return;
1592
+ }
1593
+ Logger.log(`接收到截图上传请求: fileKey=${fileKey}, nodeId=${nodeId}, hash=${hash}`);
1594
+ // 保存截图到 .sloth/{fileKey}/{nodeId}/screenshots/{hash}.png
1595
+ await req.fileManager.saveScreenshot(fileKey, nodeId, hash, file.buffer);
1596
+ Logger.log(`截图上传成功: ${hash}.png`);
1597
+ res.json({
1598
+ success: true,
1599
+ message: '截图上传成功',
1600
+ hash,
1601
+ });
1602
+ }
1603
+ catch (error) {
1604
+ Logger.error('上传截图失败:', error);
1605
+ res.status(500).json({
1606
+ success: false,
1607
+ message: '上传截图失败',
1608
+ error: error instanceof Error ? error.message : String(error),
1609
+ });
1610
+ }
1611
+ });
1612
+ // 列出设计稿快照接口(用于 update 模式选择旧设计稿)
1613
+ app.get('/listDesignSnapshots', async (req, res) => {
1614
+ try {
1615
+ Logger.log('获取设计稿快照列表');
1616
+ const workspaceRoot = getProjectRoot(req.fileManager);
1617
+ Logger.log('workspaceRoot:', workspaceRoot);
1618
+ if (!workspaceRoot) {
1619
+ res.json({
1620
+ success: true,
1621
+ snapshots: [],
1622
+ });
1623
+ return;
1624
+ }
1625
+ const slothDir = path.join(workspaceRoot, '.sloth');
1626
+ const snapshots = [];
1627
+ try {
1628
+ const fileKeys = await fs.promises.readdir(slothDir);
1629
+ for (const fileKey of fileKeys) {
1630
+ // 跳过特殊文件
1631
+ if (fileKey === '.' || fileKey === 'components.json' || fileKey.startsWith('.'))
1632
+ continue;
1633
+ const fileKeyDir = path.join(slothDir, fileKey);
1634
+ const stat = await fs.promises.stat(fileKeyDir);
1635
+ if (!stat.isDirectory())
1636
+ continue;
1637
+ const nodeIds = await fs.promises.readdir(fileKeyDir);
1638
+ for (const nodeId of nodeIds) {
1639
+ const nodeDir = path.join(fileKeyDir, nodeId);
1640
+ const nodeStat = await fs.promises.stat(nodeDir);
1641
+ if (!nodeStat.isDirectory())
1642
+ continue;
1643
+ // 查找截图并转换为 base64
1644
+ let screenshotBase64 = '';
1645
+ const screenshotsDir = path.join(nodeDir, 'screenshots');
1646
+ try {
1647
+ const indexScreenshotPath = path.join(screenshotsDir, 'index.png');
1648
+ await fs.promises.access(indexScreenshotPath);
1649
+ // 读取截图文件并转换为 base64
1650
+ const imageBuffer = await fs.promises.readFile(indexScreenshotPath);
1651
+ screenshotBase64 = `data:image/png;base64,${imageBuffer.toString('base64')}`;
1652
+ }
1653
+ catch {
1654
+ // 没有 index.png,尝试找其他截图
1655
+ try {
1656
+ const screenshots = await fs.promises.readdir(screenshotsDir);
1657
+ if (screenshots.length > 0) {
1658
+ const firstScreenshot = screenshots[0];
1659
+ const screenshotPath = path.join(screenshotsDir, firstScreenshot);
1660
+ const imageBuffer = await fs.promises.readFile(screenshotPath);
1661
+ screenshotBase64 = `data:image/png;base64,${imageBuffer.toString('base64')}`;
1662
+ }
1663
+ }
1664
+ catch {
1665
+ // 没有截图目录
1666
+ }
1667
+ }
1668
+ // 加载 groupsData 获取名称和分组数量
1669
+ let name = `${fileKey}/${nodeId}`;
1670
+ snapshots.push({
1671
+ fileKey,
1672
+ nodeId,
1673
+ screenshotBase64,
1674
+ timestamp: nodeStat.mtime.toISOString(),
1675
+ name,
1676
+ });
1677
+ }
1678
+ }
1679
+ // 按时间倒序排列
1680
+ snapshots.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
1681
+ Logger.log(`找到 ${snapshots.length} 个设计稿快照`);
1682
+ res.json({
1683
+ success: true,
1684
+ snapshots,
1685
+ });
1686
+ }
1687
+ catch (error) {
1688
+ Logger.error('扫描设计稿快照失败:', error);
1689
+ res.json({
1690
+ success: true,
1691
+ snapshots: [],
1692
+ });
1693
+ }
1694
+ }
1695
+ catch (error) {
1696
+ Logger.error('获取设计稿快照列表失败:', error);
1697
+ res.status(500).json({
1698
+ success: false,
1699
+ message: '获取设计稿快照列表失败',
1700
+ error: error instanceof Error ? error.message : String(error),
1701
+ });
1702
+ }
1703
+ });
1704
+ // 分析设计稿变更接口(用于 update 模式)
1705
+ app.use('/analyzeChange', express.json());
1706
+ app.post('/analyzeChange', async (req, res) => {
1707
+ try {
1708
+ const { oldFileKey, oldNodeId, newFileKey, newNodeId } = req.body;
1709
+ if (!oldFileKey || !oldNodeId || !newFileKey || !newNodeId) {
1710
+ res.status(400).json({
1711
+ success: false,
1712
+ message: '缺少必要参数: oldFileKey, oldNodeId, newFileKey, newNodeId',
1713
+ });
1714
+ return;
1715
+ }
1716
+ Logger.log(`分析设计稿变更: old=${oldFileKey}/${oldNodeId}, new=${newFileKey}/${newNodeId}`);
1717
+ // 1. 加载新旧 HTML 和 imageMap
1718
+ let oldHtml = await fileManager.loadAbsoluteHtml(oldFileKey, oldNodeId, true);
1719
+ let newHtml = await fileManager.loadAbsoluteHtml(newFileKey, newNodeId, true);
1720
+ Logger.log(`[analyzeChange] 加载 HTML 完成: oldHtml=${oldHtml?.length || 0}字符, newHtml=${newHtml?.length || 0}字符`);
1721
+ // 加载 imageMap 用于替换图片路径(flatted 格式)
1722
+ let oldImageMap = {};
1723
+ let newImageMap = {};
1724
+ try {
1725
+ const oldImageMapStr = await fileManager.loadFile(oldFileKey, oldNodeId, 'imageMap.json');
1726
+ if (oldImageMapStr) {
1727
+ oldImageMap = flatted.parse(oldImageMapStr);
1728
+ Logger.log(`[analyzeChange] 加载旧 imageMap 成功, 条目数: ${Object.keys(oldImageMap).length}`);
1729
+ }
1730
+ else {
1731
+ Logger.log(`[analyzeChange] 旧 imageMap 为空`);
1732
+ }
1733
+ }
1734
+ catch (e) {
1735
+ Logger.log(`[analyzeChange] 加载旧 imageMap 失败: ${e}`);
1736
+ }
1737
+ try {
1738
+ const newImageMapStr = await fileManager.loadFile(newFileKey, newNodeId, 'imageMap.json');
1739
+ if (newImageMapStr) {
1740
+ newImageMap = flatted.parse(newImageMapStr);
1741
+ Logger.log(`[analyzeChange] 加载新 imageMap 成功, 条目数: ${Object.keys(newImageMap).length}`);
1742
+ }
1743
+ else {
1744
+ Logger.log(`[analyzeChange] 新 imageMap 为空`);
1745
+ }
1746
+ }
1747
+ catch (e) {
1748
+ Logger.log(`[analyzeChange] 加载新 imageMap 失败: ${e}`);
1749
+ }
1750
+ // 构建 base64 到 path 的映射表
1751
+ const buildBase64ToPathMap = (imageMap, label) => {
1752
+ const map = new Map();
1753
+ for (const [key, value] of Object.entries(imageMap)) {
1754
+ if (value?.base64 && value?.path) {
1755
+ map.set(value.base64, value.path);
1756
+ Logger.log(`[analyzeChange] ${label} 映射: key=${key}, path=${value.path}, base64长度=${value.base64?.length || 0}`);
1757
+ }
1758
+ }
1759
+ Logger.log(`[analyzeChange] ${label} 构建映射表完成, 有效映射数: ${map.size}`);
1760
+ return map;
1761
+ };
1762
+ // 处理 HTML 用于 diff 比较
1763
+ const processHtmlForDiff = (html, imageMap, label) => {
1764
+ // 构建 base64 到 path 的映射
1765
+ const base64ToPath = buildBase64ToPathMap(imageMap, label);
1766
+ // 替换图片 base64 为 path
1767
+ let processedHtml = html;
1768
+ let replaceCount = 0;
1769
+ for (const [base64, imgPath] of base64ToPath) {
1770
+ // 转义特殊字符用于正则匹配
1771
+ const escapedBase64 = base64.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1772
+ const beforeLength = processedHtml.length;
1773
+ processedHtml = processedHtml.replace(new RegExp(escapedBase64, 'g'), imgPath);
1774
+ if (processedHtml.length !== beforeLength) {
1775
+ replaceCount++;
1776
+ Logger.log(`[analyzeChange] ${label} 替换成功: ${imgPath}, 长度变化: ${beforeLength} -> ${processedHtml.length}`);
1777
+ }
1778
+ }
1779
+ Logger.log(`[analyzeChange] ${label} 图片替换完成, 替换数: ${replaceCount}, HTML长度: ${html.length} -> ${processedHtml.length}`);
1780
+ return (processedHtml
1781
+ // 去除 data-id、data-name、data-type 属性(不参与 diff)
1782
+ .replace(/\s*data-id="[^"]*"/g, '')
1783
+ .replace(/\s*data-name="[^"]*"/g, '')
1784
+ .replace(/\s*data-type="[^"]*"/g, '')
1785
+ // 去除 z-index 相关的 Tailwind 类名
1786
+ .replace(/\bz-\[\d+\]/g, '')
1787
+ // 清理 class 中可能产生的多余空格
1788
+ .replace(/class="([^"]*)"/g, (_, classes) => {
1789
+ const cleaned = classes.replace(/\s+/g, ' ').trim();
1790
+ return `class="${cleaned}"`;
1791
+ }));
1792
+ };
1793
+ oldHtml = processHtmlForDiff(oldHtml, oldImageMap, 'old');
1794
+ newHtml = processHtmlForDiff(newHtml, newImageMap, 'new');
1795
+ Logger.log(`[analyzeChange] HTML 处理完成: oldHtml=${oldHtml?.length || 0}字符, newHtml=${newHtml?.length || 0}字符`);
1796
+ if (!oldHtml) {
1797
+ res.status(404).json({
1798
+ success: false,
1799
+ message: '旧设计稿 HTML 不存在',
1800
+ });
1801
+ return;
1802
+ }
1803
+ if (!newHtml) {
1804
+ res.status(404).json({
1805
+ success: false,
1806
+ message: '新设计稿 HTML 不存在',
1807
+ });
1808
+ return;
1809
+ }
1810
+ // 2. 加载新旧 groupsData
1811
+ const oldGroupsData = (await req.fileManager.loadGroupsData(oldFileKey, oldNodeId)) || [];
1812
+ const newGroupsData = (await req.fileManager.loadGroupsData(newFileKey, newNodeId)) || [];
1813
+ // 3. 执行 HTML 差异分析
1814
+ // @ts-ignore
1815
+ const { diffLines } = await import('diff');
1816
+ const diffResult = diffLines(oldHtml, newHtml);
1817
+ // 统计变更并生成 diff 文本
1818
+ let addedLines = 0;
1819
+ let removedLines = 0;
1820
+ let unchangedLines = 0;
1821
+ const diffParts = [];
1822
+ for (const part of diffResult) {
1823
+ const lines = part.value.split('\n').filter((l) => l.trim()).length;
1824
+ if (part.added) {
1825
+ addedLines += lines;
1826
+ // 标记新增内容
1827
+ diffParts.push(`+++ ${part.value.trim()}`);
1828
+ }
1829
+ else if (part.removed) {
1830
+ removedLines += lines;
1831
+ // 标记删除内容
1832
+ diffParts.push(`--- ${part.value.trim()}`);
1833
+ }
1834
+ else {
1835
+ unchangedLines += lines;
1836
+ // 不输出未变更的内容,减少 token
1837
+ }
1838
+ }
1839
+ // 生成精简的 diff 文本(只包含变更部分)
1840
+ const diffText = diffParts.join('\n');
1841
+ // 4. 生成变更摘要
1842
+ const changeSummary = {
1843
+ totalChanges: addedLines + removedLines,
1844
+ addedLines,
1845
+ removedLines,
1846
+ unchangedLines,
1847
+ changeRatio: (((addedLines + removedLines) / (addedLines + removedLines + unchangedLines)) * 100).toFixed(1),
1848
+ };
1849
+ // 5. 尝试使用 AI 总结变更(如果 MCP 服务器可用)
1850
+ let aiSummary = [];
1851
+ if (mcpServer && changeSummary.totalChanges > 0) {
1852
+ try {
1853
+ // 限制 diff 文本长度,避免 token 过大
1854
+ const maxDiffLength = 48000;
1855
+ const truncatedDiff = diffText.length > maxDiffLength ? diffText.substring(0, maxDiffLength) + '\n... (diff 内容已截断)' : diffText;
1856
+ const prompt = `
1857
+ 你是一个专业的前端开发专家。请分析以下设计稿 HTML 的 diff 变更,总结出具体的变更点。
1858
+
1859
+ ## Diff 变更内容
1860
+ \`\`\`diff
1861
+ ${truncatedDiff}
1862
+ \`\`\`
1863
+
1864
+ 说明:
1865
+ - \`---\` 开头的行表示被删除的内容
1866
+ - \`+++\` 开头的行表示新增的内容
1867
+ - HTML 使用 Tailwind CSS 类名
1868
+
1869
+ 请按以下 JSON 格式输出变更点列表:
1870
+ \`\`\`json
1871
+ [
1872
+ {
1873
+ "id": "change_1",
1874
+ "type": "layout|style|content|structure",
1875
+ "title": "变更标题(简短)",
1876
+ "description": "详细描述变更内容",
1877
+ "suggestedAction": "建议的代码修改方向"
1878
+ }
1879
+ ]
1880
+ \`\`\`
1881
+
1882
+ 请确保:
1883
+ 1. 每个变更点都是独立的、可操作的
1884
+ 2. 描述要具体,包含位置、样式、尺寸等信息
1885
+ 3. 建议的操作要明确
1886
+ `;
1887
+ Logger.log('=== AI 分析变更提示词 ===');
1888
+ Logger.log(prompt);
1889
+ Logger.log('=== 提示词结束 ===');
1890
+ const { content: { text }, } = await mcpServer.server.createMessage({
1891
+ messages: [
1892
+ {
1893
+ role: 'user',
1894
+ content: { type: 'text', text: prompt },
1895
+ },
1896
+ ],
1897
+ maxTokens: 48000,
1898
+ }, { timeout: 2 * 60 * 1000 });
1899
+ // 解析 AI 返回的 JSON
1900
+ const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/);
1901
+ if (jsonMatch) {
1902
+ aiSummary = JSON.parse(jsonMatch[1]);
1903
+ }
1904
+ }
1905
+ catch (aiError) {
1906
+ Logger.warn('AI 分析变更失败,使用基础分析:', aiError);
1907
+ }
1908
+ }
1909
+ // 6. 如果 AI 分析失败,生成基础摘要
1910
+ if (aiSummary.length === 0 && changeSummary.totalChanges > 0) {
1911
+ aiSummary = [
1912
+ {
1913
+ id: 'change_1',
1914
+ type: 'structure',
1915
+ title: '设计稿结构变更',
1916
+ description: `检测到 ${addedLines} 行新增内容和 ${removedLines} 行删除内容,变更比例约 ${changeSummary.changeRatio}%`,
1917
+ suggestedAction: '请检查新设计稿的布局和样式变化,更新相应的代码',
1918
+ },
1919
+ ];
1920
+ }
1921
+ Logger.log(`变更分析完成: ${aiSummary.length} 个变更点`);
1922
+ res.json({
1923
+ success: true,
1924
+ data: {
1925
+ changeSummary,
1926
+ aiSummary,
1927
+ oldGroupsData,
1928
+ newGroupsData,
1929
+ diffText, // 返回 HTML diff 文本,用于 AI 代码修改
1930
+ },
1931
+ });
1932
+ }
1933
+ catch (error) {
1934
+ Logger.error('分析设计稿变更失败:', error);
1935
+ res.status(500).json({
1936
+ success: false,
1937
+ message: '分析设计稿变更失败',
1938
+ error: error instanceof Error ? error.message : String(error),
1939
+ });
1940
+ }
1941
+ });
1942
+ app.get('/getVersion', async (req, res) => {
1943
+ try {
1944
+ // 计算 package.json 的正确路径
1945
+ // 从 dist/build/server.js 到根目录的 package.json
1946
+ const packagePath = path.join(__dirname, '../../package.json');
1947
+ const packageContent = await fs.promises.readFile(packagePath, 'utf-8');
1948
+ const packageData = JSON.parse(packageContent);
1949
+ res.json({
1950
+ success: true,
1951
+ data: {
1952
+ version: packageData.version,
1953
+ name: packageData.name,
1954
+ },
1955
+ });
1956
+ }
1957
+ catch (error) {
1958
+ Logger.error('获取版本信息失败:', error);
1959
+ res.status(500).json({
1960
+ success: false,
1961
+ message: '获取版本信息失败',
1962
+ error: error instanceof Error ? error.message : String(error),
1963
+ });
1964
+ }
1965
+ });
1966
+ // 启动 HTTP 服务器,监听端口
1967
+ httpServer = app.listen(port, async (err) => {
1968
+ if (err) {
1969
+ httpServer = null;
1970
+ Logger.error('HTTP 服务器启动失败:', err, '退出进程');
1971
+ process.exit(0);
1972
+ }
1973
+ Logger.log(`HTTP server listening on port ${port}`);
1974
+ Logger.log(`SSE endpoint available at http://localhost:${port}/sse`);
1975
+ Logger.log(`Message endpoint available at http://localhost:${port}/messages`);
1976
+ Logger.log(`StreamableHTTP endpoint available at http://localhost:${port}/mcp`);
1977
+ Logger.log(`Auth endpoints available at http://localhost:${port}/auth-page`);
1978
+ Logger.log(`MCP Config at http://localhost:${port}/getConfig`);
1979
+ Logger.log(`Global Config at http://localhost:${port}/getGlobalConfig`);
1980
+ Logger.log(`Save Global Config endpoint at http://localhost:${port}/saveGlobalConfig`);
1981
+ Logger.log(`Framework Config at http://localhost:${port}/getFrameworkConfig`);
1982
+ Logger.log(`Save Nodes endpoint available at http://localhost:${port}/saveNodes`);
1983
+ Logger.log(`Logging reconnect endpoint available at http://localhost:${port}/reconnect-logging`);
1984
+ // 启动 Socket 服务器,监听端口 + 1
1985
+ try {
1986
+ socketServer = new SocketServer();
1987
+ await socketServer.start(port + 1);
1988
+ }
1989
+ catch (error) {
1990
+ Logger.error('Socket 服务器启动失败:', error);
1991
+ process.exit(0);
1992
+ }
1993
+ });
1994
+ // 监听 SIGINT 信号,优雅关闭服务器和所有会话
1995
+ process.on('SIGINT', async () => {
1996
+ Logger.log('SERVER: 收到SIGINT信号');
1997
+ Logger.log('正在关闭服务器...');
1998
+ // 清理等待中的认证请求
1999
+ pendingRequests.clear();
2000
+ // 关闭 Socket 服务器
2001
+ await stopHttpServer();
2002
+ // 关闭所有活跃的 SSE 和 streamable 传输,释放资源
2003
+ await closeTransports(transports.sse);
2004
+ await closeTransports(transports.streamable);
2005
+ Logger.log('服务器关闭完成');
2006
+ process.exit(0);
2007
+ });
2008
+ }
2009
+ // 批量关闭所有传输对象,确保资源清理
2010
+ async function closeTransports(transports) {
2011
+ for (const sessionId in transports) {
2012
+ try {
2013
+ await transports[sessionId].close(); // 调用传输对象的关闭方法
2014
+ delete transports[sessionId]; // 删除会话
2015
+ }
2016
+ catch (error) {
2017
+ console.error(`Error closing transport for session ${sessionId}:`, error);
2018
+ }
2019
+ }
2020
+ }
2021
+ // 认证功能 - 获取用户输入函数
2022
+ export async function getUserInput(payload) {
2023
+ return new Promise(async (resolve, reject) => {
2024
+ const token = uuidv4();
2025
+ const port = getPort();
2026
+ // 构建 URL,从 payload 中提取 clientApiSupport
2027
+ const clientApiSupport = payload.clientApiSupport;
2028
+ const supportSampling = clientApiSupport?.sampling ? '1' : '0';
2029
+ const supportRoots = clientApiSupport?.roots ? '1' : '0';
2030
+ const authUrl = `http://localhost:${port}/auth-page?token=${token}&fileKey=${payload.fileKey}&nodeId=${payload.nodeId}&mode=${payload.mode}&supportSampling=${supportSampling}&supportRoots=${supportRoots}`;
2031
+ Logger.log('authUrl', authUrl);
2032
+ // 判断是主进程还是子进程
2033
+ const isMainProcess = httpServer !== null;
2034
+ if (isMainProcess) {
2035
+ // 主进程:存在 pendingRequests中, 在/submit里resolve
2036
+ Logger.log('主进程:使用 pendingRequests 等待认证响应');
2037
+ pendingRequests.set(token, {
2038
+ resolve: (value) => {
2039
+ pendingRequests.delete(token);
2040
+ resolve(value);
2041
+ },
2042
+ timeout: null,
2043
+ });
2044
+ Logger.log('getUserInput pendingRequests', [...pendingRequests.keys()]);
2045
+ }
2046
+ else {
2047
+ // 子进程:使用 Socket 客户端,监听SocketServer的submit-response消息
2048
+ Logger.log('子进程:使用 Socket 客户端等待认证响应');
2049
+ try {
2050
+ const { SocketClient } = await import('./socket-client.js');
2051
+ const socketClient = new SocketClient('localhost', port + 1);
2052
+ // 连接到 Socket 服务器
2053
+ await socketClient.connect();
2054
+ Logger.log('Socket 客户端已连接');
2055
+ const workspaceRoot = fileManager.getWorkspaceRoot();
2056
+ // 注册 token 并等待响应,同时传递 extra 数据给主进程
2057
+ const socketParams = { workspaceRoot, plugins: pluginManager.getPlugins() };
2058
+ Logger.log('socketParams', socketParams);
2059
+ const responsePromise = socketClient.registerToken(token, socketParams);
2060
+ // 打开浏览器
2061
+ await open(authUrl);
2062
+ // 等待认证响应
2063
+ const response = await responsePromise;
2064
+ // 断开连接
2065
+ socketClient.disconnect();
2066
+ return resolve(response);
2067
+ }
2068
+ catch (err) {
2069
+ reject(new Error(`Socket 客户端错误: ${err.message}`));
2070
+ return;
2071
+ }
2072
+ }
2073
+ // 打开浏览器(主进程)
2074
+ try {
2075
+ await open(authUrl);
2076
+ }
2077
+ catch (err) {
2078
+ if (isMainProcess) {
2079
+ pendingRequests.delete(token);
2080
+ }
2081
+ reject(new Error(`打开浏览器失败: ${err.message}`));
2082
+ }
2083
+ });
2084
+ }
2085
+ // 停止 Socket 服务器
2086
+ export async function stopSocketServer() {
2087
+ if (socketServer) {
2088
+ await socketServer.stop();
2089
+ }
2090
+ }
2091
+ // 停止 HTTP 服务器,并关闭所有 SSE 传输
2092
+ export async function stopHttpServer() {
2093
+ // 关闭 Socket 服务器
2094
+ const closeSocket = stopSocketServer();
2095
+ if (!httpServer) {
2096
+ throw new Error('HTTP 服务器未运行');
2097
+ }
2098
+ return new Promise((resolve, reject) => {
2099
+ // 清理等待中的认证请求
2100
+ pendingRequests.clear();
2101
+ httpServer.close((err) => {
2102
+ if (err) {
2103
+ reject(err);
2104
+ return;
2105
+ }
2106
+ httpServer = null;
2107
+ // 关闭所有 SSE 传输
2108
+ const closing = Object.values(transports.sse).map((transport) => {
2109
+ return transport.close();
2110
+ });
2111
+ Promise.all([...closing, closeSocket]).then(() => {
2112
+ resolve();
2113
+ });
2114
+ });
2115
+ });
2116
+ }