sloth-d2c-mcp 1.0.4-beta76 → 1.0.4-beta78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/core/prompt-builder.js +90 -11
- package/dist/build/index.js +36 -10
- package/dist/build/server.js +152 -126
- package/dist/interceptor-web/dist/build-report.json +6 -6
- package/dist/interceptor-web/dist/detail.html +1 -1
- package/dist/interceptor-web/dist/index.html +1 -1
- package/package.json +3 -3
|
@@ -48,15 +48,85 @@ export function buildUserPromptText(userPrompt) {
|
|
|
48
48
|
return '';
|
|
49
49
|
return '\n## 用户提示词\n以下是用户针对该代码的优化提出的补充说明和要求,请在优化代码时特别关注这些指导并尽可能实现:\n' + userPrompt;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* 递归获取组件的继承链
|
|
53
|
+
* @param component 当前组件
|
|
54
|
+
* @param allComponents 所有组件映射表
|
|
55
|
+
* @param visited 已访问的组件名称(防止循环引用)
|
|
56
|
+
* @returns 继承链数组(从当前组件到最顶层父类)
|
|
57
|
+
*/
|
|
58
|
+
function getInheritanceChain(component, allComponents, visited = new Set()) {
|
|
59
|
+
const chain = [];
|
|
60
|
+
// 防止循环引用
|
|
61
|
+
if (visited.has(component.name)) {
|
|
62
|
+
return chain;
|
|
63
|
+
}
|
|
64
|
+
visited.add(component.name);
|
|
65
|
+
// 如果有父类,递归查找
|
|
66
|
+
if (component.super) {
|
|
67
|
+
const parentComponent = allComponents.get(component.super);
|
|
68
|
+
if (parentComponent) {
|
|
69
|
+
chain.push(parentComponent);
|
|
70
|
+
// 递归获取父类的继承链
|
|
71
|
+
const parentChain = getInheritanceChain(parentComponent, allComponents, visited);
|
|
72
|
+
chain.push(...parentChain);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return chain;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 格式化组件信息为提示词
|
|
79
|
+
* @param component 组件信息
|
|
80
|
+
* @param isParent 是否是父类组件
|
|
81
|
+
* @returns 格式化的组件提示词
|
|
82
|
+
*/
|
|
83
|
+
function formatComponentPrompt(component, isParent = false) {
|
|
84
|
+
let prompt = '';
|
|
85
|
+
const propsInfo = component.props && component.props.length > 0
|
|
86
|
+
? component.props.map((p) => `${p.name}${p.required ? ' (必需)' : ' (可选)'}: ${p.type || 'any'} - ${p.description}`).join(', ')
|
|
87
|
+
: '无';
|
|
88
|
+
const libInfo = component.lib ? ` (来自 ${component.lib})` : ' (项目组件)';
|
|
89
|
+
const descInfo = component.description ? `\n- **描述**: ${component.description}` : '';
|
|
90
|
+
const parentPrefix = isParent ? '#### ' : '### ';
|
|
91
|
+
const parentLabel = isParent ? ' [父类]' : '';
|
|
92
|
+
prompt += `${parentPrefix}${component.name}${libInfo}${parentLabel}\n`;
|
|
93
|
+
prompt += `- **文件路径**: ${component.path}\n`;
|
|
94
|
+
prompt += `- **导入方式**: ${component.import}\n`;
|
|
95
|
+
prompt += `- **Props**: ${propsInfo}${descInfo}\n`;
|
|
96
|
+
// 如果有 functions,也输出方法信息
|
|
97
|
+
if (component.functions && component.functions.length > 0) {
|
|
98
|
+
const functionsInfo = component.functions
|
|
99
|
+
.map((f) => {
|
|
100
|
+
const paramsStr = f.params && f.params.length > 0 ? f.params.map((p) => `${p.name}: ${p.type}`).join(', ') : '';
|
|
101
|
+
const returnStr = f.returnType ? `: ${f.returnType}` : '';
|
|
102
|
+
const descStr = f.description ? ` - ${f.description}` : '';
|
|
103
|
+
return `${f.name}(${paramsStr})${returnStr}${descStr}`;
|
|
104
|
+
})
|
|
105
|
+
.join('; ');
|
|
106
|
+
prompt += `- **方法**: ${functionsInfo}\n`;
|
|
107
|
+
}
|
|
108
|
+
// 如果有 super,标注继承关系
|
|
109
|
+
if (component.super) {
|
|
110
|
+
prompt += `- **继承自**: ${component.super}\n`;
|
|
111
|
+
}
|
|
112
|
+
prompt += '\n';
|
|
113
|
+
return prompt;
|
|
114
|
+
}
|
|
51
115
|
/**
|
|
52
116
|
* 构建组件映射提示词(包含组件映射和组件上下文和组件标记)
|
|
53
117
|
* @param componentMappings 组件映射数组
|
|
54
118
|
* @param componentContexts 所有组的组件上下文数组
|
|
55
119
|
* @param markedComponents 标记的组件数组
|
|
120
|
+
* @param componentsDatabase 完整的组件数据库(用于查找继承链)
|
|
56
121
|
* @returns 组件映射提示词
|
|
57
122
|
*/
|
|
58
|
-
export function buildComponentMappingPrompt(componentMappings, componentContexts = [], markedComponents = []) {
|
|
123
|
+
export function buildComponentMappingPrompt(componentMappings, componentContexts = [], markedComponents = [], componentsDatabase = []) {
|
|
59
124
|
let prompt = '';
|
|
125
|
+
// 构建完整的组件数据库映射表(用于查找继承链)
|
|
126
|
+
const databaseMap = new Map();
|
|
127
|
+
componentsDatabase.forEach((comp) => {
|
|
128
|
+
databaseMap.set(comp.name, comp);
|
|
129
|
+
});
|
|
60
130
|
// 收集所有组件:组件映射 + 组件上下文(按名称去重)
|
|
61
131
|
const allComponents = new Map();
|
|
62
132
|
// 添加组件映射的组件
|
|
@@ -74,19 +144,28 @@ export function buildComponentMappingPrompt(componentMappings, componentContexts
|
|
|
74
144
|
// 构建可用组件提示词
|
|
75
145
|
if (allComponents.size > 0) {
|
|
76
146
|
prompt += '\n\n## 可用组件\n\n以下是项目中可用的组件,请在最终代码中使用这些组件:\n\n';
|
|
147
|
+
// 记录已输出的组件,避免重复输出
|
|
148
|
+
const outputtedComponents = new Set();
|
|
77
149
|
allComponents.forEach((component) => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
150
|
+
// 输出当前组件
|
|
151
|
+
if (!outputtedComponents.has(component.path)) {
|
|
152
|
+
outputtedComponents.add(component.path);
|
|
153
|
+
prompt += formatComponentPrompt(component, false);
|
|
154
|
+
}
|
|
155
|
+
// 递归查找并输出继承链(从完整数据库中查找)
|
|
156
|
+
const inheritanceChain = getInheritanceChain(component, databaseMap);
|
|
157
|
+
if (inheritanceChain.length > 0) {
|
|
158
|
+
inheritanceChain.forEach((parentComp) => {
|
|
159
|
+
// 避免重复输出
|
|
160
|
+
if (!outputtedComponents.has(parentComp.path)) {
|
|
161
|
+
outputtedComponents.add(parentComp.path);
|
|
162
|
+
prompt += formatComponentPrompt(parentComp, true);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
87
166
|
});
|
|
88
167
|
prompt +=
|
|
89
|
-
'**重要**:在最终写入代码时,请根据设计稿需求合理传递 props
|
|
168
|
+
'**重要**:在最终写入代码时,请根据设计稿需求合理传递 props 参数。请按照项目路径规范合理引入组件。如果组件有继承关系,请注意父类的 props 和方法也可以使用。\n';
|
|
90
169
|
}
|
|
91
170
|
// 构建标记组件提示词
|
|
92
171
|
if (markedComponents.length > 0) {
|
package/dist/build/index.js
CHANGED
|
@@ -394,8 +394,11 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
394
394
|
});
|
|
395
395
|
Logger.log(`收集到 ${markedComponents.length} 个标记的组件`);
|
|
396
396
|
}
|
|
397
|
-
//
|
|
398
|
-
const
|
|
397
|
+
// 加载完整的组件数据库(用于查找继承链)
|
|
398
|
+
const componentsDatabase = await fileManager.loadComponentsDatabase();
|
|
399
|
+
Logger.log(`加载组件数据库,共 ${componentsDatabase.length} 个组件`);
|
|
400
|
+
// 构建组件映射提示词(包含组件映射、组件上下文、标记组件和完整组件数据库)
|
|
401
|
+
const componentMappingPrompt = buildComponentMappingPrompt(componentMappings, componentContexts, markedComponents, componentsDatabase);
|
|
399
402
|
// 使用提示词(已包含默认值)
|
|
400
403
|
return {
|
|
401
404
|
content: [
|
|
@@ -435,7 +438,7 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
|
|
|
435
438
|
.array(z.object({
|
|
436
439
|
name: z.string().describe('Component name in PascalCase'),
|
|
437
440
|
path: z.string().describe('Component file path relative to project root'),
|
|
438
|
-
signature: z.string().describe('Component
|
|
441
|
+
signature: z.string().optional().describe('Component Screenshot (SHA256 hash) for matching'),
|
|
439
442
|
type: z.enum(['button', 'input', 'card', 'text', 'image', 'container', 'list', 'custom']).optional(),
|
|
440
443
|
description: z.string().optional(),
|
|
441
444
|
import: z.string().optional().describe('Import statement for the component'),
|
|
@@ -447,6 +450,22 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
|
|
|
447
450
|
description: z.string().optional(),
|
|
448
451
|
}))
|
|
449
452
|
.optional(),
|
|
453
|
+
super: z.string().optional().describe('Parent class or base component name'),
|
|
454
|
+
functions: z
|
|
455
|
+
.array(z.object({
|
|
456
|
+
name: z.string().describe('Method or function name'),
|
|
457
|
+
params: z
|
|
458
|
+
.array(z.object({
|
|
459
|
+
name: z.string(),
|
|
460
|
+
type: z.string(),
|
|
461
|
+
}))
|
|
462
|
+
.optional()
|
|
463
|
+
.describe('Method parameters'),
|
|
464
|
+
returnType: z.string().optional().describe('Return type of the method'),
|
|
465
|
+
description: z.string().optional(),
|
|
466
|
+
}))
|
|
467
|
+
.optional()
|
|
468
|
+
.describe('Public methods or functions exposed by the component'),
|
|
450
469
|
}))
|
|
451
470
|
.describe('Array of components to mark and save'),
|
|
452
471
|
}, async (args) => {
|
|
@@ -466,11 +485,11 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
|
|
|
466
485
|
}
|
|
467
486
|
// 2. 加载现有组件
|
|
468
487
|
const existingComponents = await fileManager.loadComponentsDatabase();
|
|
469
|
-
// 3. 构建
|
|
470
|
-
const
|
|
488
|
+
// 3. 构建 path 索引(用于去重,path 一致则认为是同一组件)
|
|
489
|
+
const pathIndex = new Map();
|
|
471
490
|
existingComponents.forEach((comp, index) => {
|
|
472
|
-
if (comp.
|
|
473
|
-
|
|
491
|
+
if (comp.path) {
|
|
492
|
+
pathIndex.set(comp.path, index);
|
|
474
493
|
}
|
|
475
494
|
});
|
|
476
495
|
// 4. 处理新组件(合并逻辑)
|
|
@@ -478,7 +497,8 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
|
|
|
478
497
|
let addedCount = 0;
|
|
479
498
|
let updatedCount = 0;
|
|
480
499
|
for (const comp of components) {
|
|
481
|
-
|
|
500
|
+
// 使用 path 判断组件是否已存在(组件冲突检测)
|
|
501
|
+
const existingIndex = pathIndex.get(comp.path);
|
|
482
502
|
const storedComponent = {
|
|
483
503
|
id: generateComponentId(),
|
|
484
504
|
name: comp.name,
|
|
@@ -491,8 +511,14 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
|
|
|
491
511
|
required: p.required || false,
|
|
492
512
|
description: p.description,
|
|
493
513
|
})),
|
|
514
|
+
functions: (comp.functions || []).map((f) => ({
|
|
515
|
+
name: f.name,
|
|
516
|
+
params: (f.params || []).map((p) => ({ name: p.name, type: p.type })),
|
|
517
|
+
returnType: f.returnType,
|
|
518
|
+
description: f.description,
|
|
519
|
+
})),
|
|
520
|
+
super: comp.super,
|
|
494
521
|
description: comp.description,
|
|
495
|
-
signature: comp.signature,
|
|
496
522
|
};
|
|
497
523
|
if (existingIndex !== undefined) {
|
|
498
524
|
// 更新已存在的组件(保留原始导入时间)
|
|
@@ -506,7 +532,7 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
|
|
|
506
532
|
else {
|
|
507
533
|
// 添加新组件
|
|
508
534
|
existingComponents.push(storedComponent);
|
|
509
|
-
|
|
535
|
+
pathIndex.set(comp.path, existingComponents.length - 1);
|
|
510
536
|
addedCount++;
|
|
511
537
|
}
|
|
512
538
|
}
|
package/dist/build/server.js
CHANGED
|
@@ -833,6 +833,10 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
833
833
|
});
|
|
834
834
|
}
|
|
835
835
|
});
|
|
836
|
+
// 多文件分析限制
|
|
837
|
+
const MAX_FILES = 10; // 最多 10 个文件
|
|
838
|
+
const MAX_TOTAL_CHARS = 40000; // 总内容最多 40000 字符
|
|
839
|
+
const MAX_FILE_CHARS = 5000; // 单文件最多 5000 字符
|
|
836
840
|
/**
|
|
837
841
|
* 分析项目中的组件
|
|
838
842
|
*/
|
|
@@ -846,10 +850,17 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
846
850
|
});
|
|
847
851
|
return;
|
|
848
852
|
}
|
|
849
|
-
|
|
853
|
+
// 限制文件数量
|
|
854
|
+
if (filePaths.length > MAX_FILES) {
|
|
855
|
+
res.status(400).json({
|
|
856
|
+
success: false,
|
|
857
|
+
message: `文件数量超过限制,最多支持 ${MAX_FILES} 个文件`,
|
|
858
|
+
});
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
Logger.log(`批量分析 ${filePaths.length} 个项目文件`);
|
|
850
862
|
const projectPath = getProjectRoot();
|
|
851
|
-
|
|
852
|
-
// 尝试加载保存的提示词设置(如果提供了 fileKey 和 nodeId)
|
|
863
|
+
// 尝试加载保存的提示词设置
|
|
853
864
|
let savedPromptSetting = null;
|
|
854
865
|
if (fileKey && nodeId) {
|
|
855
866
|
try {
|
|
@@ -862,148 +873,163 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
862
873
|
}
|
|
863
874
|
const frameworkGuidePrompt = savedPromptSetting?.frameworkGuidePrompt;
|
|
864
875
|
const _componentAnalysisPrompt = savedPromptSetting?.componentAnalysisPrompt || componentAnalysisPrompt;
|
|
865
|
-
//
|
|
876
|
+
// 读取所有文件内容
|
|
877
|
+
const fileContents = [];
|
|
878
|
+
let totalChars = 0;
|
|
866
879
|
for (const filePath of filePaths) {
|
|
867
880
|
try {
|
|
868
881
|
const fullPath = path.join(projectPath, filePath);
|
|
869
882
|
// 安全检查:确保文件在项目目录内
|
|
870
883
|
if (!fullPath.startsWith(projectPath)) {
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
884
|
+
fileContents.push({
|
|
885
|
+
path: filePath,
|
|
886
|
+
content: '',
|
|
874
887
|
error: '无权访问该文件',
|
|
875
888
|
});
|
|
876
889
|
continue;
|
|
877
890
|
}
|
|
878
891
|
// 读取文件内容
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
// 构建提示词,替换占位符
|
|
885
|
-
const prompt = _componentAnalysisPrompt.replace(/{filename}/g, filename).replace(/{fileContent}/g, content);
|
|
886
|
-
// 调用 MCP Sampling API
|
|
887
|
-
const { content: { text }, } = await mcpServer.server.createMessage({
|
|
888
|
-
messages: [
|
|
889
|
-
frameworkGuidePrompt && {
|
|
890
|
-
role: 'user',
|
|
891
|
-
content: {
|
|
892
|
-
type: 'text',
|
|
893
|
-
text: frameworkGuidePrompt,
|
|
894
|
-
},
|
|
895
|
-
},
|
|
896
|
-
{
|
|
897
|
-
role: 'user',
|
|
898
|
-
content: {
|
|
899
|
-
type: 'text',
|
|
900
|
-
text: prompt,
|
|
901
|
-
},
|
|
902
|
-
},
|
|
903
|
-
].filter(Boolean),
|
|
904
|
-
maxTokens: 8000,
|
|
905
|
-
}, { timeout: 2 * 60 * 1000 });
|
|
906
|
-
// 解析 AI 返回的 JSON
|
|
907
|
-
const extractResult = extractJson(text);
|
|
908
|
-
if (extractResult.state === 'successful-parse' && extractResult.value) {
|
|
909
|
-
const analysisResult = extractResult.value;
|
|
910
|
-
// 构建组件信息
|
|
911
|
-
const component = {
|
|
912
|
-
id: generateComponentId(),
|
|
913
|
-
name: analysisResult.componentName || path.basename(filePath, path.extname(filePath)),
|
|
914
|
-
type: 'custom',
|
|
915
|
-
path: filePath,
|
|
916
|
-
import: analysisResult.import,
|
|
917
|
-
props: analysisResult.props || [],
|
|
918
|
-
description: analysisResult.description || '组件描述',
|
|
919
|
-
};
|
|
920
|
-
results.push({
|
|
921
|
-
success: true,
|
|
922
|
-
component,
|
|
923
|
-
filename: filePath,
|
|
924
|
-
});
|
|
925
|
-
Logger.log(`成功分析组件: ${filePath}, 组件名: ${component.name}`);
|
|
926
|
-
}
|
|
927
|
-
else {
|
|
928
|
-
// JSON 解析失败,使用默认值
|
|
929
|
-
Logger.warn(`解析组件分析结果失败: ${extractResult.error}, 文件: ${filePath}`);
|
|
930
|
-
results.push({
|
|
931
|
-
success: true,
|
|
932
|
-
component: {
|
|
933
|
-
id: generateComponentId(),
|
|
934
|
-
name: path.basename(filePath, path.extname(filePath)),
|
|
935
|
-
type: 'custom',
|
|
936
|
-
path: filePath,
|
|
937
|
-
import: `import { ${path.basename(filePath, path.extname(filePath))} } from './${filePath}'`,
|
|
938
|
-
props: [],
|
|
939
|
-
description: '组件描述',
|
|
940
|
-
},
|
|
941
|
-
filename: filePath,
|
|
942
|
-
});
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
catch (samplingError) {
|
|
946
|
-
Logger.error(`调用采样 API 失败: ${filePath}`, samplingError);
|
|
947
|
-
// 采样失败时使用默认值
|
|
948
|
-
results.push({
|
|
949
|
-
success: true,
|
|
950
|
-
component: {
|
|
951
|
-
id: generateComponentId(),
|
|
952
|
-
name: path.basename(filePath, path.extname(filePath)),
|
|
953
|
-
type: 'custom',
|
|
954
|
-
path: filePath,
|
|
955
|
-
import: `import { ${path.basename(filePath, path.extname(filePath))} } from './${filePath}'`,
|
|
956
|
-
props: [],
|
|
957
|
-
description: '组件描述',
|
|
958
|
-
},
|
|
959
|
-
filename: filePath,
|
|
960
|
-
});
|
|
961
|
-
}
|
|
892
|
+
let content = await fs.promises.readFile(fullPath, 'utf-8');
|
|
893
|
+
// 单文件截断
|
|
894
|
+
if (content.length > MAX_FILE_CHARS) {
|
|
895
|
+
content = content.slice(0, MAX_FILE_CHARS) + '\n// ... 内容已截断 ...';
|
|
896
|
+
Logger.warn(`文件 ${filePath} 内容过长,已截断至 ${MAX_FILE_CHARS} 字符`);
|
|
962
897
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
Logger.warn(
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
name: path.basename(filePath, path.extname(filePath)),
|
|
971
|
-
type: 'custom',
|
|
972
|
-
path: filePath,
|
|
973
|
-
import: `import { ${path.basename(filePath, path.extname(filePath))} } from './${filePath}'`,
|
|
974
|
-
props: [],
|
|
975
|
-
description: '组件描述',
|
|
976
|
-
},
|
|
977
|
-
filename: filePath,
|
|
898
|
+
// 检查总字符数
|
|
899
|
+
if (totalChars + content.length > MAX_TOTAL_CHARS) {
|
|
900
|
+
Logger.warn(`总内容超过 ${MAX_TOTAL_CHARS} 字符限制,跳过文件: ${filePath}`);
|
|
901
|
+
fileContents.push({
|
|
902
|
+
path: filePath,
|
|
903
|
+
content: '',
|
|
904
|
+
error: '总内容超过限制,已跳过',
|
|
978
905
|
});
|
|
906
|
+
continue;
|
|
979
907
|
}
|
|
908
|
+
totalChars += content.length;
|
|
909
|
+
fileContents.push({
|
|
910
|
+
path: filePath,
|
|
911
|
+
content,
|
|
912
|
+
});
|
|
980
913
|
}
|
|
981
914
|
catch (error) {
|
|
982
915
|
Logger.error(`读取文件失败: ${filePath}`, error);
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
916
|
+
fileContents.push({
|
|
917
|
+
path: filePath,
|
|
918
|
+
content: '',
|
|
986
919
|
error: error instanceof Error ? error.message : String(error),
|
|
987
920
|
});
|
|
988
921
|
}
|
|
989
922
|
}
|
|
990
|
-
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
.map((
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
923
|
+
// 过滤出成功读取的文件
|
|
924
|
+
const validFiles = fileContents.filter((f) => f.content && !f.error);
|
|
925
|
+
const errorFiles = fileContents.filter((f) => f.error);
|
|
926
|
+
if (validFiles.length === 0) {
|
|
927
|
+
res.json({
|
|
928
|
+
success: true,
|
|
929
|
+
data: {
|
|
930
|
+
total: filePaths.length,
|
|
931
|
+
succeeded: 0,
|
|
932
|
+
failed: errorFiles.length,
|
|
933
|
+
components: [],
|
|
934
|
+
errors: errorFiles.map((f) => ({ filename: f.path, error: f.error })),
|
|
935
|
+
},
|
|
936
|
+
});
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
// 构建多文件内容部分
|
|
940
|
+
const filesSection = validFiles
|
|
941
|
+
.map((f) => {
|
|
942
|
+
const ext = path.extname(f.path).slice(1) || 'txt';
|
|
943
|
+
return `### 文件:${f.path}\n\`\`\`${ext}\n${f.content}\n\`\`\``;
|
|
944
|
+
})
|
|
945
|
+
.join('\n\n');
|
|
946
|
+
// 构建最终 Prompt
|
|
947
|
+
const prompt = _componentAnalysisPrompt.replace('{filesSection}', filesSection);
|
|
948
|
+
Logger.log(`构建 Prompt 完成,总字符数: ${prompt.length}`);
|
|
949
|
+
// 调用 MCP Sampling API
|
|
950
|
+
if (!mcpServer) {
|
|
951
|
+
Logger.warn('MCP 服务器未初始化,返回空结果');
|
|
952
|
+
res.json({
|
|
953
|
+
success: true,
|
|
954
|
+
data: {
|
|
955
|
+
total: filePaths.length,
|
|
956
|
+
succeeded: 0,
|
|
957
|
+
failed: filePaths.length,
|
|
958
|
+
components: [],
|
|
959
|
+
errors: filePaths.map((p) => ({ filename: p, error: 'MCP 服务器未初始化' })),
|
|
960
|
+
},
|
|
961
|
+
});
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
try {
|
|
965
|
+
const { content: { text }, } = await mcpServer.server.createMessage({
|
|
966
|
+
messages: [
|
|
967
|
+
frameworkGuidePrompt && {
|
|
968
|
+
role: 'user',
|
|
969
|
+
content: {
|
|
970
|
+
type: 'text',
|
|
971
|
+
text: frameworkGuidePrompt,
|
|
972
|
+
},
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
role: 'user',
|
|
976
|
+
content: {
|
|
977
|
+
type: 'text',
|
|
978
|
+
text: prompt,
|
|
979
|
+
},
|
|
980
|
+
},
|
|
981
|
+
].filter(Boolean),
|
|
982
|
+
maxTokens: 8000,
|
|
983
|
+
}, { timeout: 2 * 60 * 1000 });
|
|
984
|
+
// 解析 AI 返回的 JSON
|
|
985
|
+
const extractResult = extractJson(text);
|
|
986
|
+
if (extractResult.state === 'successful-parse' && extractResult.value) {
|
|
987
|
+
const analysisResult = extractResult.value;
|
|
988
|
+
const components = (analysisResult.components || []).map((comp) => ({
|
|
989
|
+
id: generateComponentId(),
|
|
990
|
+
name: comp.componentName || 'Unknown',
|
|
991
|
+
type: 'custom',
|
|
992
|
+
path: comp.path || '',
|
|
993
|
+
import: comp.import || '',
|
|
994
|
+
props: comp.props || [],
|
|
995
|
+
description: comp.description || '',
|
|
996
|
+
super: comp.super || undefined,
|
|
997
|
+
functions: comp.functions || undefined,
|
|
998
|
+
}));
|
|
999
|
+
Logger.log(`成功分析 ${components.length} 个组件`);
|
|
1000
|
+
res.json({
|
|
1001
|
+
success: true,
|
|
1002
|
+
data: {
|
|
1003
|
+
total: filePaths.length,
|
|
1004
|
+
succeeded: validFiles.length,
|
|
1005
|
+
failed: errorFiles.length,
|
|
1006
|
+
components,
|
|
1007
|
+
errors: errorFiles.map((f) => ({ filename: f.path, error: f.error })),
|
|
1008
|
+
},
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
else {
|
|
1012
|
+
Logger.warn(`解析组件分析结果失败: ${extractResult.error}`);
|
|
1013
|
+
res.json({
|
|
1014
|
+
success: true,
|
|
1015
|
+
data: {
|
|
1016
|
+
total: filePaths.length,
|
|
1017
|
+
succeeded: 0,
|
|
1018
|
+
failed: filePaths.length,
|
|
1019
|
+
components: [],
|
|
1020
|
+
errors: [{ filename: 'all', error: `AI 返回结果解析失败: ${extractResult.error}` }],
|
|
1021
|
+
},
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
catch (samplingError) {
|
|
1026
|
+
Logger.error('调用采样 API 失败:', samplingError);
|
|
1027
|
+
res.status(500).json({
|
|
1028
|
+
success: false,
|
|
1029
|
+
message: '调用 Sampling 分析失败,请调用 #mark_components tool进行组件分析',
|
|
1030
|
+
error: samplingError instanceof Error ? samplingError.message : String(samplingError),
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1007
1033
|
}
|
|
1008
1034
|
catch (error) {
|
|
1009
1035
|
Logger.error('分析项目组件失败:', error);
|
|
@@ -1077,7 +1103,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1077
1103
|
const { includePaths, excludePaths } = req.query;
|
|
1078
1104
|
const projectPath = getProjectRoot();
|
|
1079
1105
|
Logger.log('扫描项目组件:', { includePaths, excludePaths });
|
|
1080
|
-
//
|
|
1106
|
+
// 读取项目组件(从 .sloth/components.json)
|
|
1081
1107
|
let projectComponents = [];
|
|
1082
1108
|
const slothPath = path.join(projectPath, '.sloth', 'components.json');
|
|
1083
1109
|
const fs = await import('fs/promises');
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"buildTime": "2025-12-
|
|
2
|
+
"buildTime": "2025-12-19T16:46:24.638Z",
|
|
3
3
|
"mode": "build",
|
|
4
4
|
"pages": {
|
|
5
5
|
"main": {
|
|
6
6
|
"file": "index.html",
|
|
7
|
-
"size":
|
|
7
|
+
"size": 1604164,
|
|
8
8
|
"sizeFormatted": "1.53 MB"
|
|
9
9
|
},
|
|
10
10
|
"detail": {
|
|
11
11
|
"file": "detail.html",
|
|
12
|
-
"size":
|
|
13
|
-
"sizeFormatted": "
|
|
12
|
+
"size": 280955,
|
|
13
|
+
"sizeFormatted": "274.37 KB"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
-
"totalSize":
|
|
17
|
-
"totalSizeFormatted": "1.
|
|
16
|
+
"totalSize": 1885119,
|
|
17
|
+
"totalSizeFormatted": "1.8 MB"
|
|
18
18
|
}
|