sloth-d2c-mcp 1.0.4-beta70 → 1.0.4-beta71
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/cli/run.js +5 -0
- package/dist/build/component-mapping/adapter-manager.js +45 -0
- package/dist/build/component-mapping/adapters/base-adapter.js +137 -0
- package/dist/build/component-mapping/adapters/ios-adapter.js +697 -0
- package/dist/build/component-mapping/adapters/web-adapter.js +536 -0
- package/dist/build/component-mapping/index.js +32 -0
- package/dist/build/component-mapping/storage.js +142 -0
- package/dist/build/component-mapping/types.js +4 -0
- package/dist/build/config-manager/index.js +80 -0
- package/dist/build/core/prompt-builder.js +110 -0
- package/dist/build/core/sampling.js +382 -0
- package/dist/build/core/types.js +1 -0
- package/dist/build/index.js +320 -81
- package/dist/build/server.js +510 -14
- package/dist/build/utils/file-manager.js +301 -10
- package/dist/build/utils/image-matcher.js +154 -0
- package/dist/build/utils/opencv-loader.js +70 -0
- 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 +6 -3
package/dist/build/server.js
CHANGED
|
@@ -11,10 +11,20 @@ import * as path from 'path';
|
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
12
|
import { v4 as uuidv4 } from 'uuid';
|
|
13
13
|
import open from 'open';
|
|
14
|
-
import
|
|
14
|
+
import { fileManager } from './index.js';
|
|
15
15
|
import * as flatted from 'flatted';
|
|
16
|
+
import { ComponentMappingSystem } from './component-mapping/index.js';
|
|
17
|
+
import multer from 'multer';
|
|
18
|
+
import { ImageMatcher } from './utils/image-matcher.js';
|
|
19
|
+
import { initOpenCV } from './utils/opencv-loader.js';
|
|
20
|
+
// 配置 multer 用于文件上传
|
|
21
|
+
const storage = multer.memoryStorage();
|
|
22
|
+
const upload = multer({
|
|
23
|
+
storage,
|
|
24
|
+
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
|
|
25
|
+
});
|
|
16
26
|
// 导入默认提示词
|
|
17
|
-
import { chunkOptimizeCodePrompt, aggregationOptimizeCodePrompt, finalOptimizeCodePrompt } from 'sloth-d2c-node/convert';
|
|
27
|
+
import { chunkOptimizeCodePrompt, aggregationOptimizeCodePrompt, finalOptimizeCodePrompt, chunkOptimizeCodePromptVue, aggregationOptimizeCodePromptVue, finalOptimizeCodePromptVue, } from 'sloth-d2c-node/convert';
|
|
18
28
|
// 保存 HTTP 服务器实例
|
|
19
29
|
let httpServer = null;
|
|
20
30
|
// 管理所有活跃的传输对象,按 sessionId 分类
|
|
@@ -26,6 +36,7 @@ const transports = {
|
|
|
26
36
|
const pendingRequests = new Map(); // 等待中的认证请求
|
|
27
37
|
let configStorage = {}; // 配置缓存
|
|
28
38
|
let configManager = null; // 配置管理器实例
|
|
39
|
+
let mappingSystem = null; // 组件映射系统实例
|
|
29
40
|
const __filename = fileURLToPath(import.meta.url);
|
|
30
41
|
const __dirname = path.dirname(__filename);
|
|
31
42
|
// 获取端口号
|
|
@@ -40,10 +51,11 @@ export async function loadConfig(mcpServer, configManagerInstance) {
|
|
|
40
51
|
// 检查配置文件是否存在
|
|
41
52
|
const configExists = await configManager.exists();
|
|
42
53
|
Logger.log(`配置文件存在: ${configExists ? '是' : '否'}`);
|
|
54
|
+
let config = {};
|
|
43
55
|
if (configExists) {
|
|
44
56
|
// 如果配置文件存在,加载并显示配置信息
|
|
45
57
|
try {
|
|
46
|
-
|
|
58
|
+
config = await configManager.load();
|
|
47
59
|
mcpServer.setConfig(config.mcp);
|
|
48
60
|
Logger.log(`当前配置内容: ${JSON.stringify(config, null, 2)}`);
|
|
49
61
|
}
|
|
@@ -51,6 +63,49 @@ export async function loadConfig(mcpServer, configManagerInstance) {
|
|
|
51
63
|
Logger.warn(`读取配置文件失败: ${error}`);
|
|
52
64
|
}
|
|
53
65
|
}
|
|
66
|
+
// 初始化默认框架配置
|
|
67
|
+
if (!config.frameworks || config.frameworks.length === 0) {
|
|
68
|
+
Logger.log('初始化默认框架配置...');
|
|
69
|
+
if (!config.frameworks) {
|
|
70
|
+
config.frameworks = [];
|
|
71
|
+
}
|
|
72
|
+
if (typeof config.frameworks[0] === 'string') {
|
|
73
|
+
config.frameworks = config.frameworks.map((f) => {
|
|
74
|
+
return {
|
|
75
|
+
label: f.charAt(0).toUpperCase() + f.slice(1),
|
|
76
|
+
value: f,
|
|
77
|
+
isCustom: f !== 'react' && f !== 'vue', // todo: 暂时兼容,仅 react 是默认框架,若新增框架需调整
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (!config.frameworks.find((fw) => fw.value === 'react') || !config.frameworks.find((fw) => fw.value === 'vue')) {
|
|
82
|
+
const newFrameworks = config.frameworks.filter((fw) => fw.value !== 'react' || fw.value !== 'vue');
|
|
83
|
+
config.frameworks = [{ value: 'react', label: 'React' }, { value: 'vue', label: 'Vue' }, ...newFrameworks];
|
|
84
|
+
await configManager.save(config);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// 检查 react.json 是否存在,如果不存在则创建
|
|
88
|
+
const reactConfigExists = await configManager.frameworkConfigExists('react');
|
|
89
|
+
if (!reactConfigExists) {
|
|
90
|
+
await configManager.saveFrameworkConfig('react', {
|
|
91
|
+
chunkOptimizePrompt: chunkOptimizeCodePrompt,
|
|
92
|
+
aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
|
|
93
|
+
finalOptimizePrompt: finalOptimizeCodePrompt,
|
|
94
|
+
});
|
|
95
|
+
const reactConfigPath = configManager.getFrameworkConfigPath('react');
|
|
96
|
+
Logger.log(`已创建默认 react 配置文件: ${reactConfigPath}`);
|
|
97
|
+
}
|
|
98
|
+
// 检查 vue.json 是否存在,如果不存在则创建
|
|
99
|
+
const vueConfigExists = await configManager.frameworkConfigExists('vue');
|
|
100
|
+
if (!vueConfigExists) {
|
|
101
|
+
await configManager.saveFrameworkConfig('vue', {
|
|
102
|
+
chunkOptimizePrompt: chunkOptimizeCodePromptVue,
|
|
103
|
+
aggregationOptimizePrompt: aggregationOptimizeCodePromptVue,
|
|
104
|
+
finalOptimizePrompt: finalOptimizeCodePromptVue,
|
|
105
|
+
});
|
|
106
|
+
const vueConfigPath = configManager.getFrameworkConfigPath('vue');
|
|
107
|
+
Logger.log(`已创建默认 vue 配置文件: ${vueConfigPath}`);
|
|
108
|
+
}
|
|
54
109
|
}
|
|
55
110
|
catch (error) {
|
|
56
111
|
Logger.error(`获取配置信息时出错: ${error}`);
|
|
@@ -61,6 +116,15 @@ export async function loadConfig(mcpServer, configManagerInstance) {
|
|
|
61
116
|
export async function startHttpServer(port = PORT, mcpServer, configManagerInstance, isWeb) {
|
|
62
117
|
const app = express();
|
|
63
118
|
PORT = port;
|
|
119
|
+
// 初始化 OpenCV
|
|
120
|
+
try {
|
|
121
|
+
await initOpenCV();
|
|
122
|
+
Logger.log('✅ OpenCV.js 初始化成功');
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
Logger.error('❌ OpenCV.js 初始化失败:', error);
|
|
126
|
+
Logger.warn('图像匹配功能将不可用,但不影响其他功能');
|
|
127
|
+
}
|
|
64
128
|
// 存储配置管理器实例(如果提供)
|
|
65
129
|
loadConfig(mcpServer, configManagerInstance);
|
|
66
130
|
// if (configManagerInstance) {
|
|
@@ -90,10 +154,11 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
90
154
|
app.use('/mcp', express.json());
|
|
91
155
|
app.use('/submit', express.json());
|
|
92
156
|
app.use('/saveNodes', express.json({
|
|
93
|
-
limit: '100mb'
|
|
157
|
+
limit: '100mb',
|
|
94
158
|
}));
|
|
95
159
|
// 为认证端点添加中间件 - 需要同时支持 JSON 和表单数据
|
|
96
160
|
app.use(express.urlencoded({ extended: true }));
|
|
161
|
+
app.use('/saveGlobalConfig', express.json());
|
|
97
162
|
// 设置跨域
|
|
98
163
|
app.use((req, res, next) => {
|
|
99
164
|
res.header('Access-Control-Allow-Origin', '*');
|
|
@@ -234,20 +299,36 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
234
299
|
try {
|
|
235
300
|
const fileKey = req.query.fileKey;
|
|
236
301
|
const nodeId = req.query.nodeId;
|
|
302
|
+
const framework = req.query.framework;
|
|
237
303
|
if (fileKey) {
|
|
238
304
|
// 如果提供了 fileKey,返回该 fileKey 的特定配置
|
|
239
305
|
const globalConfig = await configManager.load();
|
|
240
|
-
const fileConfig = globalConfig.fileConfigs?.[fileKey] || {}
|
|
306
|
+
// const fileConfig = globalConfig.fileConfigs?.[fileKey] || {}
|
|
307
|
+
const fileConfig = (await fileManager.loadConfigSetting(fileKey, nodeId)) || {};
|
|
241
308
|
// 从 fileManager 按 nodeId 加载 groupsData 和 promptSetting
|
|
242
|
-
const fileManager = new FileManager('d2c-mcp')
|
|
309
|
+
// const fileManager = new FileManager('d2c-mcp')
|
|
243
310
|
const groupsData = await fileManager.loadGroupsData(fileKey, nodeId);
|
|
244
311
|
const savedPromptSetting = await fileManager.loadPromptSetting(fileKey, nodeId);
|
|
245
|
-
//
|
|
246
|
-
|
|
312
|
+
// 如果指定了框架,加载框架配置的提示词
|
|
313
|
+
let promptSetting = {
|
|
247
314
|
chunkOptimizePrompt: savedPromptSetting?.chunkOptimizePrompt || chunkOptimizeCodePrompt,
|
|
248
315
|
aggregationOptimizePrompt: savedPromptSetting?.aggregationOptimizePrompt || aggregationOptimizeCodePrompt,
|
|
249
316
|
finalOptimizePrompt: savedPromptSetting?.finalOptimizePrompt || finalOptimizeCodePrompt,
|
|
250
317
|
};
|
|
318
|
+
if (framework) {
|
|
319
|
+
// 加载框架配置
|
|
320
|
+
const frameworkConfig = await configManager.loadFrameworkConfig(framework);
|
|
321
|
+
if (frameworkConfig && Object.keys(frameworkConfig).length > 0) {
|
|
322
|
+
// 框架配置优先级更高,但如果框架配置为空,则使用已保存的或默认的
|
|
323
|
+
promptSetting = {
|
|
324
|
+
chunkOptimizePrompt: frameworkConfig.chunkOptimizePrompt || promptSetting.chunkOptimizePrompt,
|
|
325
|
+
aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt || promptSetting.aggregationOptimizePrompt,
|
|
326
|
+
finalOptimizePrompt: frameworkConfig.finalOptimizePrompt || promptSetting.finalOptimizePrompt,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// 获取框架列表
|
|
331
|
+
const frameworks = await configManager.getFrameworks();
|
|
251
332
|
res.json({
|
|
252
333
|
success: true,
|
|
253
334
|
data: {
|
|
@@ -255,6 +336,8 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
255
336
|
convertSetting: fileConfig.convertSetting,
|
|
256
337
|
imageSetting: fileConfig.imageSetting,
|
|
257
338
|
promptSetting: promptSetting,
|
|
339
|
+
frameworks: frameworks,
|
|
340
|
+
defaultFramework: globalConfig.defaultFramework || 'react',
|
|
258
341
|
fileKey: fileKey,
|
|
259
342
|
groupsData: groupsData,
|
|
260
343
|
},
|
|
@@ -265,12 +348,26 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
265
348
|
if (await configManager.exists()) {
|
|
266
349
|
configStorage = await configManager.load();
|
|
267
350
|
}
|
|
268
|
-
//
|
|
269
|
-
|
|
351
|
+
// 如果指定了框架,加载框架配置的提示词
|
|
352
|
+
let promptSetting = {
|
|
270
353
|
chunkOptimizePrompt: chunkOptimizeCodePrompt,
|
|
271
354
|
aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
|
|
272
355
|
finalOptimizePrompt: finalOptimizeCodePrompt,
|
|
273
356
|
};
|
|
357
|
+
if (framework) {
|
|
358
|
+
// 加载框架配置
|
|
359
|
+
const frameworkConfig = await configManager.loadFrameworkConfig(framework);
|
|
360
|
+
if (frameworkConfig && Object.keys(frameworkConfig).length > 0) {
|
|
361
|
+
// 框架配置优先级更高
|
|
362
|
+
promptSetting = {
|
|
363
|
+
chunkOptimizePrompt: frameworkConfig.chunkOptimizePrompt || promptSetting.chunkOptimizePrompt,
|
|
364
|
+
aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt || promptSetting.aggregationOptimizePrompt,
|
|
365
|
+
finalOptimizePrompt: frameworkConfig.finalOptimizePrompt || promptSetting.finalOptimizePrompt,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// 获取框架列表
|
|
370
|
+
const frameworks = await configManager.getFrameworks();
|
|
274
371
|
res.json({
|
|
275
372
|
success: true,
|
|
276
373
|
data: {
|
|
@@ -278,6 +375,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
278
375
|
convertSetting: configStorage.convertSetting || {},
|
|
279
376
|
imageSetting: configStorage.imageSetting || {},
|
|
280
377
|
promptSetting: promptSetting,
|
|
378
|
+
frameworks: frameworks,
|
|
281
379
|
},
|
|
282
380
|
});
|
|
283
381
|
}
|
|
@@ -293,13 +391,117 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
293
391
|
});
|
|
294
392
|
app.get('/getHtml', async (req, res) => {
|
|
295
393
|
// 加载现有配置
|
|
296
|
-
const fileManager = new FileManager('d2c-mcp')
|
|
394
|
+
// const fileManager = new FileManager('d2c-mcp')
|
|
297
395
|
const html = await fileManager.loadFile(req.query.fileKey, req.query.nodeId, 'absolute.html');
|
|
298
396
|
res.json({
|
|
299
397
|
success: true,
|
|
300
398
|
data: html,
|
|
301
399
|
});
|
|
302
400
|
});
|
|
401
|
+
// 获取全局配置接口
|
|
402
|
+
app.get('/getGlobalConfig', async (req, res) => {
|
|
403
|
+
try {
|
|
404
|
+
const framework = req.query.framework;
|
|
405
|
+
const globalConfig = await configManager.load();
|
|
406
|
+
let frameworks = (await configManager.getFrameworks()) || [
|
|
407
|
+
{ value: 'react', label: 'React' },
|
|
408
|
+
{ value: 'vue', label: 'Vue' },
|
|
409
|
+
];
|
|
410
|
+
const defaultFramework = globalConfig?.defaultFramework || 'react';
|
|
411
|
+
let promptSetting = {};
|
|
412
|
+
const fw = frameworks.find((f) => f.value === framework) ? framework : defaultFramework;
|
|
413
|
+
const frameworkConfig = await configManager.loadFrameworkConfig(fw);
|
|
414
|
+
promptSetting[fw] = frameworkConfig;
|
|
415
|
+
res.json({
|
|
416
|
+
success: true,
|
|
417
|
+
data: {
|
|
418
|
+
frameworks,
|
|
419
|
+
defaultFramework,
|
|
420
|
+
promptSetting,
|
|
421
|
+
},
|
|
422
|
+
message: '获取成功',
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
Logger.error('获取全局配置失败:', error);
|
|
427
|
+
res.status(500).json({
|
|
428
|
+
success: false,
|
|
429
|
+
message: '获取全局配置失败',
|
|
430
|
+
error: error instanceof Error ? error.message : String(error),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
// 保存全局配置接口
|
|
435
|
+
app.post('/saveGlobalConfig', async (req, res) => {
|
|
436
|
+
try {
|
|
437
|
+
const { token, value } = req.body;
|
|
438
|
+
if (!token || !value) {
|
|
439
|
+
res.status(400).json({
|
|
440
|
+
success: false,
|
|
441
|
+
message: '请求体中缺少 token 或 value',
|
|
442
|
+
});
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const { frameworks, defaultFramework, promptSetting: promptSettingMap } = value;
|
|
446
|
+
const globalConfig = await configManager.load();
|
|
447
|
+
globalConfig.frameworks = frameworks;
|
|
448
|
+
globalConfig.defaultFramework = defaultFramework;
|
|
449
|
+
await Promise.all(Object.entries(promptSettingMap).map(([key, value]) => configManager.saveFrameworkConfig(key, value)));
|
|
450
|
+
await configManager.save(globalConfig);
|
|
451
|
+
res.json({
|
|
452
|
+
success: true,
|
|
453
|
+
message: '保存全局配置成功',
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
Logger.error('保存全局配置失败:', error);
|
|
458
|
+
res.status(500).json({
|
|
459
|
+
success: false,
|
|
460
|
+
message: '保存全局配置失败',
|
|
461
|
+
error: error instanceof Error ? error.message : String(error),
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
// 获取框架配置接口
|
|
466
|
+
app.get('/getFrameworkConfig', async (req, res) => {
|
|
467
|
+
try {
|
|
468
|
+
const framework = req.query.framework;
|
|
469
|
+
if (!framework) {
|
|
470
|
+
res.status(400).json({
|
|
471
|
+
success: false,
|
|
472
|
+
message: '缺少必要参数: framework',
|
|
473
|
+
});
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
let promptSetting = {
|
|
477
|
+
chunkOptimizePrompt: chunkOptimizeCodePrompt,
|
|
478
|
+
aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
|
|
479
|
+
finalOptimizePrompt: finalOptimizeCodePrompt,
|
|
480
|
+
};
|
|
481
|
+
const frameworkConfig = await configManager.loadFrameworkConfig(framework);
|
|
482
|
+
if (frameworkConfig && Object.keys(frameworkConfig).length > 0) {
|
|
483
|
+
// 框架配置优先级更高
|
|
484
|
+
promptSetting = {
|
|
485
|
+
chunkOptimizePrompt: frameworkConfig.chunkOptimizePrompt,
|
|
486
|
+
aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt,
|
|
487
|
+
finalOptimizePrompt: frameworkConfig.finalOptimizePrompt,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
res.json({
|
|
491
|
+
success: true,
|
|
492
|
+
data: promptSetting,
|
|
493
|
+
message: '获取框架配置成功',
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
Logger.error('获取框架配置失败:', error);
|
|
498
|
+
res.status(500).json({
|
|
499
|
+
success: false,
|
|
500
|
+
message: '获取框架配置失败',
|
|
501
|
+
error: error instanceof Error ? error.message : String(error),
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
});
|
|
303
505
|
// 认证页面
|
|
304
506
|
app.get('/auth-page', (req, res) => {
|
|
305
507
|
try {
|
|
@@ -372,7 +574,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
372
574
|
Logger.log(`已保存 fileKey "${fileKey}" 的配置:`, value);
|
|
373
575
|
}
|
|
374
576
|
// 使用 fileManager 按 nodeId 保存 groupsData 和 promptSetting
|
|
375
|
-
const fileManager = new FileManager('d2c-mcp')
|
|
577
|
+
// const fileManager = new FileManager('d2c-mcp')
|
|
376
578
|
if (fileKey && value.groupsData && Array.isArray(value.groupsData)) {
|
|
377
579
|
await fileManager.saveGroupsData(fileKey, nodeId, value.groupsData);
|
|
378
580
|
Logger.log(`已保存 groupsData 到 fileKey "${fileKey}", nodeId "${nodeId}":`, value.groupsData.length, '个分组');
|
|
@@ -414,9 +616,9 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
414
616
|
});
|
|
415
617
|
return;
|
|
416
618
|
}
|
|
417
|
-
const fileManager = new FileManager('d2c-mcp')
|
|
619
|
+
// const fileManager = new FileManager('d2c-mcp')
|
|
418
620
|
// 保存节点列表
|
|
419
|
-
await fileManager.saveFile(fileKey, nodeId, 'nodeList.json',
|
|
621
|
+
await fileManager.saveFile(fileKey, nodeId, 'nodeList.json', nodeList);
|
|
420
622
|
// 保存图片映射
|
|
421
623
|
if (imageMap) {
|
|
422
624
|
await fileManager.saveFile(fileKey, nodeId, 'imageMap.json', flatted.stringify(imageMap));
|
|
@@ -450,6 +652,297 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
450
652
|
message: '日志重连已启动',
|
|
451
653
|
});
|
|
452
654
|
});
|
|
655
|
+
// ==================== 组件映射相关接口 ====================
|
|
656
|
+
/**
|
|
657
|
+
* 获取项目根目录
|
|
658
|
+
*/
|
|
659
|
+
function getProjectRoot() {
|
|
660
|
+
try {
|
|
661
|
+
const projectPath = fileManager.getWorkspaceRoot();
|
|
662
|
+
return projectPath || './';
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
Logger.warn('获取根目录时出错,使用默认路径:', error);
|
|
666
|
+
return './';
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* 获取或创建映射系统实例
|
|
671
|
+
*/
|
|
672
|
+
async function getMappingSystem() {
|
|
673
|
+
if (!mappingSystem) {
|
|
674
|
+
const projectPath = getProjectRoot();
|
|
675
|
+
mappingSystem = new ComponentMappingSystem(projectPath);
|
|
676
|
+
await mappingSystem.initialize();
|
|
677
|
+
Logger.log('组件映射系统已初始化,项目路径:', projectPath);
|
|
678
|
+
}
|
|
679
|
+
return mappingSystem;
|
|
680
|
+
}
|
|
681
|
+
// 扫描项目组件接口(同时返回项目组件和外部依赖组件)
|
|
682
|
+
app.get('/scanComponents', async (req, res) => {
|
|
683
|
+
try {
|
|
684
|
+
const { platform, includePaths, excludePaths } = req.query;
|
|
685
|
+
if (!platform) {
|
|
686
|
+
res.status(400).json({
|
|
687
|
+
success: false,
|
|
688
|
+
message: '缺少必要参数: platform',
|
|
689
|
+
});
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
Logger.log('扫描项目组件:', { platform, includePaths, excludePaths });
|
|
693
|
+
const system = await getMappingSystem();
|
|
694
|
+
const adapter = system.adapterManager.getAdapter(platform);
|
|
695
|
+
if (!adapter) {
|
|
696
|
+
res.status(400).json({
|
|
697
|
+
success: false,
|
|
698
|
+
message: `不支持的平台: ${platform}`,
|
|
699
|
+
});
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const projectPath = getProjectRoot();
|
|
703
|
+
Logger.log('开始扫描,项目路径:', projectPath);
|
|
704
|
+
// 扫描项目组件
|
|
705
|
+
const components = await adapter.scanComponents(projectPath, {
|
|
706
|
+
includePaths,
|
|
707
|
+
excludePaths,
|
|
708
|
+
});
|
|
709
|
+
Logger.log(`扫描完成,发现 ${components.length} 个项目组件`);
|
|
710
|
+
// 将绝对路径转换为相对路径
|
|
711
|
+
const normalizePath = (filePath) => {
|
|
712
|
+
if (!filePath || !projectPath)
|
|
713
|
+
return filePath;
|
|
714
|
+
try {
|
|
715
|
+
const path = require('path');
|
|
716
|
+
const relativePath = path.relative(projectPath, filePath);
|
|
717
|
+
// 统一使用正斜杠
|
|
718
|
+
return relativePath.replace(/\\/g, '/');
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
return filePath;
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
const projectComponents = components.map((c) => ({
|
|
725
|
+
id: c.id,
|
|
726
|
+
name: c.name,
|
|
727
|
+
type: c.type,
|
|
728
|
+
path: normalizePath(c.path),
|
|
729
|
+
props: c.props.map((p) => ({
|
|
730
|
+
name: p.name,
|
|
731
|
+
type: p.type,
|
|
732
|
+
required: p.required,
|
|
733
|
+
})),
|
|
734
|
+
framework: c.metadata.framework,
|
|
735
|
+
description: c.metadata.description,
|
|
736
|
+
source: 'project', // 标记为项目组件
|
|
737
|
+
importType: c.metadata.importType || 'default', // 导入类型
|
|
738
|
+
signature: c.metadata.signature,
|
|
739
|
+
}));
|
|
740
|
+
// 读取外部依赖组件(从 .sloth/components.json)
|
|
741
|
+
let externalComponents = [];
|
|
742
|
+
const slothPath = path.join(projectPath, '.sloth', 'components.json');
|
|
743
|
+
const fs = await import('fs/promises');
|
|
744
|
+
try {
|
|
745
|
+
await fs.access(slothPath);
|
|
746
|
+
// 文件存在,读取内容
|
|
747
|
+
const fileContent = await fs.readFile(slothPath, 'utf-8');
|
|
748
|
+
const componentsData = JSON.parse(fileContent);
|
|
749
|
+
// 验证数据结构
|
|
750
|
+
if (Array.isArray(componentsData)) {
|
|
751
|
+
// 验证每个组件的基本字段
|
|
752
|
+
externalComponents = componentsData
|
|
753
|
+
.filter((comp) => comp && comp.id && comp.name && comp.path)
|
|
754
|
+
.map((c) => ({
|
|
755
|
+
id: c.id,
|
|
756
|
+
name: c.name,
|
|
757
|
+
type: c.type || 'custom',
|
|
758
|
+
path: c.path,
|
|
759
|
+
props: (c.props || []).map((p) => ({
|
|
760
|
+
name: p.name,
|
|
761
|
+
type: p.type || 'any',
|
|
762
|
+
required: p.required || false,
|
|
763
|
+
})),
|
|
764
|
+
framework: c.framework || c.metadata?.framework || 'React',
|
|
765
|
+
description: c.description || c.metadata?.description,
|
|
766
|
+
source: 'external', // 标记为外部依赖组件
|
|
767
|
+
lib: c.lib, // 业务库名称
|
|
768
|
+
importType: c.importType || c.metadata?.importType || 'named', // 导入类型,外部组件默认为具名导入
|
|
769
|
+
}));
|
|
770
|
+
Logger.log(`从 .sloth/components.json 加载了 ${externalComponents.length} 个外部组件`);
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
Logger.warn('.sloth/components.json 格式错误:应为组件数组');
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
catch (error) {
|
|
777
|
+
// 文件不存在或读取失败,忽略错误,继续返回项目组件
|
|
778
|
+
if (error.code !== 'ENOENT') {
|
|
779
|
+
Logger.warn('读取外部依赖组件失败:', error.message);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
res.json({
|
|
783
|
+
success: true,
|
|
784
|
+
data: {
|
|
785
|
+
total: projectComponents.length + externalComponents.length,
|
|
786
|
+
platform,
|
|
787
|
+
projectPath,
|
|
788
|
+
projectComponents, // 项目组件
|
|
789
|
+
externalComponents, // 外部依赖组件
|
|
790
|
+
},
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
794
|
+
Logger.error('扫描项目组件失败:', error);
|
|
795
|
+
res.status(500).json({
|
|
796
|
+
success: false,
|
|
797
|
+
message: '扫描项目组件失败',
|
|
798
|
+
error: error instanceof Error ? error.message : String(error),
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
// 建议组件映射接口(基于历史组件截图匹配)
|
|
803
|
+
app.post('/suggestMappings', upload.single('currentScreenshot'), async (req, res) => {
|
|
804
|
+
try {
|
|
805
|
+
const { fileKey, nodeId, threshold = '0.8' } = req.body;
|
|
806
|
+
const currentScreenshotFile = req.file;
|
|
807
|
+
if (!currentScreenshotFile || !fileKey) {
|
|
808
|
+
res.status(400).json({
|
|
809
|
+
success: false,
|
|
810
|
+
message: '缺少必要参数: currentScreenshot, fileKey',
|
|
811
|
+
});
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
Logger.log(`开始生成建议组件映射: fileKey=${fileKey}, nodeId=${nodeId}, threshold=${threshold}`);
|
|
815
|
+
// 检查当前 fileKey + nodeId 是否已经有 groupsData
|
|
816
|
+
const currentGroupsData = await fileManager.loadGroupsData(fileKey, nodeId);
|
|
817
|
+
if (currentGroupsData && currentGroupsData.length > 0) {
|
|
818
|
+
Logger.log(`当前设计稿已有 ${currentGroupsData.length} 个分组,跳过自动建议`);
|
|
819
|
+
res.json({
|
|
820
|
+
success: true,
|
|
821
|
+
data: {
|
|
822
|
+
suggestions: [],
|
|
823
|
+
reason: 'current_has_groups',
|
|
824
|
+
message: '当前设计稿已有分组数据,不执行自动建议',
|
|
825
|
+
},
|
|
826
|
+
});
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
Logger.log('当前设计稿无分组数据,继续执行自动建议');
|
|
830
|
+
// 保存当前设计稿截图到临时文件
|
|
831
|
+
const tempDir = path.join(fileManager.getBaseDir(), 'temp');
|
|
832
|
+
await fs.promises.mkdir(tempDir, { recursive: true });
|
|
833
|
+
const currentScreenshotPath = path.join(tempDir, `suggest_${Date.now()}.png`);
|
|
834
|
+
await fs.promises.writeFile(currentScreenshotPath, currentScreenshotFile.buffer);
|
|
835
|
+
// 加载整个项目所有 fileKey 的所有 groupsData(跨文件匹配)
|
|
836
|
+
const allProjectGroupsData = await fileManager.loadAllProjectGroupsData();
|
|
837
|
+
if (!allProjectGroupsData || allProjectGroupsData.length === 0) {
|
|
838
|
+
Logger.log('未找到历史分组数据,无法生成建议');
|
|
839
|
+
await fs.promises.unlink(currentScreenshotPath).catch(() => { });
|
|
840
|
+
res.json({
|
|
841
|
+
success: true,
|
|
842
|
+
data: { suggestions: [] },
|
|
843
|
+
});
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
Logger.log(`从整个项目加载了 ${allProjectGroupsData.length} 个节点的历史数据`);
|
|
847
|
+
const matchTasks = [];
|
|
848
|
+
for (const { fileKey: storedFileKey, nodeId: storedNodeId, groups } of allProjectGroupsData) {
|
|
849
|
+
const normalizedNodeId = storedNodeId === 'root' ? undefined : storedNodeId;
|
|
850
|
+
for (const group of groups) {
|
|
851
|
+
// 只处理标记的分组
|
|
852
|
+
if (!group.marked || !group.screenshot)
|
|
853
|
+
continue;
|
|
854
|
+
const screenshotPath = fileManager.getScreenshotPath(storedFileKey, normalizedNodeId, group.screenshot.hash);
|
|
855
|
+
const exists = await fileManager.screenshotExists(storedFileKey, normalizedNodeId, group.screenshot.hash);
|
|
856
|
+
if (!exists)
|
|
857
|
+
continue;
|
|
858
|
+
matchTasks.push({
|
|
859
|
+
path: screenshotPath,
|
|
860
|
+
groupData: {
|
|
861
|
+
fileKey: storedFileKey,
|
|
862
|
+
nodeId: normalizedNodeId ?? 'root',
|
|
863
|
+
componentMapping: group.componentMapping,
|
|
864
|
+
userPrompt: group.userPrompt,
|
|
865
|
+
originalRect: group.rect,
|
|
866
|
+
componentName: group.componentName, // 传递组件名称
|
|
867
|
+
screenshot: group.screenshot, // 传递截图信息(包含 hash)
|
|
868
|
+
},
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
if (matchTasks.length === 0) {
|
|
873
|
+
Logger.log('没有可用的历史截图,返回空建议');
|
|
874
|
+
await fs.promises.unlink(currentScreenshotPath).catch(() => { });
|
|
875
|
+
res.json({
|
|
876
|
+
success: true,
|
|
877
|
+
data: { suggestions: [] },
|
|
878
|
+
});
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
Logger.log(`开始匹配,共 ${matchTasks.length} 个历史组件截图`);
|
|
882
|
+
const matcher = new ImageMatcher();
|
|
883
|
+
const matches = await matcher.batchMatch(currentScreenshotPath, matchTasks, parseFloat(threshold));
|
|
884
|
+
Logger.log(`匹配完成,找到 ${matches.length} 个相似组件`);
|
|
885
|
+
matches.forEach((match, index) => {
|
|
886
|
+
Logger.log(` 匹配 ${index + 1}: 来源 fileKey=${match.groupData?.fileKey}, nodeId=${match.groupData?.nodeId}, 组件=${match.groupData?.componentName || 'N/A'}, 相似度=${Math.round(match.confidence * 100)}%`);
|
|
887
|
+
});
|
|
888
|
+
await fs.promises.unlink(currentScreenshotPath).catch(() => { });
|
|
889
|
+
res.json({
|
|
890
|
+
success: true,
|
|
891
|
+
data: {
|
|
892
|
+
suggestions: matches.map((match) => ({
|
|
893
|
+
confidence: Math.round(match.confidence * 100) / 100,
|
|
894
|
+
position: match.position,
|
|
895
|
+
componentMapping: match.groupData?.componentMapping,
|
|
896
|
+
userPrompt: match.groupData?.userPrompt,
|
|
897
|
+
originalRect: match.groupData?.originalRect,
|
|
898
|
+
fileKey: match.groupData?.fileKey,
|
|
899
|
+
nodeId: match.groupData?.nodeId,
|
|
900
|
+
componentName: match.groupData?.componentName,
|
|
901
|
+
screenshotHash: match.groupData?.screenshot?.hash, // 返回截图 hash,用于匹配组件 signature
|
|
902
|
+
})),
|
|
903
|
+
},
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
catch (error) {
|
|
907
|
+
Logger.error('建议组件映射失败:', error);
|
|
908
|
+
res.status(500).json({
|
|
909
|
+
success: false,
|
|
910
|
+
message: '建议组件映射失败',
|
|
911
|
+
error: error instanceof Error ? error.message : String(error),
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
// 上传截图接口
|
|
916
|
+
app.post('/uploadScreenshot', upload.single('file'), async (req, res) => {
|
|
917
|
+
try {
|
|
918
|
+
const { fileKey, nodeId, hash } = req.body;
|
|
919
|
+
const file = req.file;
|
|
920
|
+
if (!file || !fileKey || !nodeId || !hash) {
|
|
921
|
+
res.status(400).json({
|
|
922
|
+
success: false,
|
|
923
|
+
message: '缺少必要参数: file, fileKey, nodeId, hash',
|
|
924
|
+
});
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
Logger.log(`接收到截图上传请求: fileKey=${fileKey}, nodeId=${nodeId}, hash=${hash}`);
|
|
928
|
+
// 保存截图到 .sloth/{fileKey}/{nodeId}/screenshots/{hash}.png
|
|
929
|
+
await fileManager.saveScreenshot(fileKey, nodeId, hash, file.buffer);
|
|
930
|
+
Logger.log(`截图上传成功: ${hash}.png`);
|
|
931
|
+
res.json({
|
|
932
|
+
success: true,
|
|
933
|
+
message: '截图上传成功',
|
|
934
|
+
hash,
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
catch (error) {
|
|
938
|
+
Logger.error('上传截图失败:', error);
|
|
939
|
+
res.status(500).json({
|
|
940
|
+
success: false,
|
|
941
|
+
message: '上传截图失败',
|
|
942
|
+
error: error instanceof Error ? error.message : String(error),
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
});
|
|
453
946
|
// 启动 HTTP 服务器,监听端口
|
|
454
947
|
httpServer = app.listen(port, () => {
|
|
455
948
|
Logger.log(`HTTP server listening on port ${port}`);
|
|
@@ -458,6 +951,9 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
458
951
|
Logger.log(`StreamableHTTP endpoint available at http://localhost:${port}/mcp`);
|
|
459
952
|
Logger.log(`Auth endpoints available at http://localhost:${port}/auth-page`);
|
|
460
953
|
Logger.log(`MCP Config at http://localhost:${port}/getConfig`);
|
|
954
|
+
Logger.log(`Global Config at http://localhost:${port}/getGlobalConfig`);
|
|
955
|
+
Logger.log(`Save Global Config endpoint at http://localhost:${port}/saveGlobalConfig`);
|
|
956
|
+
Logger.log(`Framework Config at http://localhost:${port}/getFrameworkConfig`);
|
|
461
957
|
Logger.log(`Save Nodes endpoint available at http://localhost:${port}/saveNodes`);
|
|
462
958
|
Logger.log(`Logging reconnect endpoint available at http://localhost:${port}/reconnect-logging`);
|
|
463
959
|
});
|