sloth-d2c-mcp 1.0.4-beta82 → 1.0.4-beta84
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 +3 -4
- package/dist/build/core/sampling.js +4 -10
- package/dist/build/index.js +44 -11
- package/dist/build/interceptor/vscode.js +4 -4
- package/dist/build/server.js +75 -17
- package/dist/build/utils/client-capabilities.js +143 -0
- package/dist/build/utils/file-manager.js +1 -4
- package/dist/build/utils/update.js +50 -4
- 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 +3 -3
|
@@ -33,8 +33,7 @@ export function buildComponentContext(group) {
|
|
|
33
33
|
componentContextPrompt += `### ${comp.name}${libInfo}\n`;
|
|
34
34
|
componentContextPrompt += `- **Props**: ${propsInfo}${descInfo}\n\n`;
|
|
35
35
|
});
|
|
36
|
-
componentContextPrompt +=
|
|
37
|
-
'注意:在生成代码时,请根据设计稿的需求,合理使用上述组件,并正确传递 props 参数。\n';
|
|
36
|
+
componentContextPrompt += '注意:在生成代码时,请根据设计稿的需求,合理使用上述组件,并正确传递 props 参数。\n';
|
|
38
37
|
}
|
|
39
38
|
return { processedUserPrompt, componentContextPrompt };
|
|
40
39
|
}
|
|
@@ -197,11 +196,11 @@ export function buildComponentMappingPrompt(componentMappings, componentContexts
|
|
|
197
196
|
* @param placeholders 占位符信息数组
|
|
198
197
|
* @returns 占位符提示词
|
|
199
198
|
*/
|
|
200
|
-
export function buildPlaceholderPrompt(placeholders) {
|
|
199
|
+
export function buildPlaceholderPrompt(placeholders, isSupportSampling = true) {
|
|
201
200
|
if (placeholders.length === 0) {
|
|
202
201
|
return '';
|
|
203
202
|
}
|
|
204
|
-
let prompt =
|
|
203
|
+
let prompt = `\n\n## 子组件占位符\n\n**重要**:以下占位符代表已实现的子组件${isSupportSampling ? ',请转换格式但不要重新实现其内部逻辑。' : ''}\n\n`;
|
|
205
204
|
placeholders.forEach((placeholder) => {
|
|
206
205
|
const kebabName = placeholder.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
207
206
|
prompt += `- \`<${kebabName}>\`\n`;
|
|
@@ -135,7 +135,7 @@ export function injectChildPlaceholders(nodeList, childResults, nestingInfo, par
|
|
|
135
135
|
* @returns 采样结果
|
|
136
136
|
*/
|
|
137
137
|
export async function sampleSingleGroup(group, nodeList, config) {
|
|
138
|
-
const { d2cNodeList, imageMap, convertConfig, frameworkPrompt, chunkPrompt, mcpServer, componentMappings, codeSnippets, componentContexts } = config;
|
|
138
|
+
const { d2cNodeList, imageMap, convertConfig, frameworkPrompt, chunkPrompt, mcpServer, componentMappings, codeSnippets, componentContexts, isSupportSampling } = config;
|
|
139
139
|
Logger.log(`开始采样组 ${group.groupIndex}, 元素数量: ${group.elements.length}, nodeList 长度: ${nodeList.length}`);
|
|
140
140
|
// 检查组件映射
|
|
141
141
|
const mappingResult = handleComponentMapping(group, componentMappings);
|
|
@@ -178,6 +178,8 @@ export async function sampleSingleGroup(group, nodeList, config) {
|
|
|
178
178
|
Logger.log(`组 ${group.groupIndex} 开始调用 AI 采样,代码长度: ${codeWithCustomName.length}, 是否有用户提示: ${!!group.userPrompt}, 是否有组件上下文: ${!!(group.componentContext && group.componentContext.length > 0)}, 占位符数量: ${placeholders.length}`);
|
|
179
179
|
// 执行采样
|
|
180
180
|
try {
|
|
181
|
+
if (!isSupportSampling)
|
|
182
|
+
throw new Error('不支持采样');
|
|
181
183
|
const { content: { text }, } = await mcpServer.server.createMessage({
|
|
182
184
|
messages: [
|
|
183
185
|
{
|
|
@@ -274,11 +276,7 @@ export async function sampleGroupRecursive(groupIndex, nestingInfo, config, samp
|
|
|
274
276
|
* @returns 是否支持采样
|
|
275
277
|
*/
|
|
276
278
|
export async function processSampling(groupsData, config) {
|
|
277
|
-
const { d2cNodeList, componentMappings, codeSnippets } = config;
|
|
278
|
-
let isSupportSampling = true;
|
|
279
|
-
if (!groupsData || groupsData.length === 0) {
|
|
280
|
-
return isSupportSampling;
|
|
281
|
-
}
|
|
279
|
+
const { d2cNodeList, componentMappings, codeSnippets, isSupportSampling } = config;
|
|
282
280
|
Logger.log(`开始处理 groupsData,总数: ${groupsData.length}`);
|
|
283
281
|
Logger.log(`groupsData 详情:`, groupsData.map((g) => ({ groupIndex: g.groupIndex, hasChildren: !!g.children, children: g.children })));
|
|
284
282
|
// 构建嵌套结构
|
|
@@ -295,7 +293,6 @@ export async function processSampling(groupsData, config) {
|
|
|
295
293
|
await sampleGroupRecursive(rootIndex, nestingInfo, config, samplingResults, 0);
|
|
296
294
|
}
|
|
297
295
|
catch (e) {
|
|
298
|
-
isSupportSampling = false;
|
|
299
296
|
Logger.log(`根组 ${rootIndex} 处理出错:`, e);
|
|
300
297
|
}
|
|
301
298
|
Logger.log(`========== 根组 ${rootIndex} 处理完成 ==========\n`);
|
|
@@ -327,7 +324,6 @@ export async function processSampling(groupsData, config) {
|
|
|
327
324
|
await sampleSingleGroup(group, nodeList, config);
|
|
328
325
|
}
|
|
329
326
|
catch (e) {
|
|
330
|
-
isSupportSampling = false;
|
|
331
327
|
Logger.log(`独立组 ${group.groupIndex} 处理出错:`, e);
|
|
332
328
|
}
|
|
333
329
|
}
|
|
@@ -349,7 +345,6 @@ export async function processSampling(groupsData, config) {
|
|
|
349
345
|
await sampleSingleGroup(group, nodeList, config);
|
|
350
346
|
}
|
|
351
347
|
catch (e) {
|
|
352
|
-
isSupportSampling = false;
|
|
353
348
|
Logger.log(`组 ${group.groupIndex} 处理出错:`, e);
|
|
354
349
|
}
|
|
355
350
|
Logger.log(`--- 组 ${group.groupIndex} 处理完成 ---\n`);
|
|
@@ -360,5 +355,4 @@ export async function processSampling(groupsData, config) {
|
|
|
360
355
|
Logger.log(` - 已生成代码片段数: ${codeSnippets.length}`);
|
|
361
356
|
Logger.log(` - 已映射组件数: ${componentMappings.length}`);
|
|
362
357
|
Logger.log(` - 组件映射详情:`, componentMappings.map((m) => `组${m.groupIndex} -> ${m.groupName}`));
|
|
363
|
-
return isSupportSampling;
|
|
364
358
|
}
|
package/dist/build/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { convertFigmaToD2CNode, generateAbsoluteHtml, getBoundingBox, getCode, chunkOptimizeCodePrompt, aggregationOptimizeCodePrompt, finalOptimizeCodePrompt, } from 'sloth-d2c-node/convert';
|
|
3
|
+
import { convertFigmaToD2CNode, generateAbsoluteHtml, getBoundingBox, getCode, chunkOptimizeCodePrompt, aggregationOptimizeCodePrompt, finalOptimizeCodePrompt, noSamplingAggregationPrompt, } from 'sloth-d2c-node/convert';
|
|
4
4
|
import axios from 'axios';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { ConfigManager, defaultConfigData, } from './config-manager/index.js';
|
|
@@ -14,6 +14,7 @@ import { Logger } from './utils/logger.js';
|
|
|
14
14
|
import { getAvailablePort, saveImageFile, replaceImageSrc } from './utils/utils.js';
|
|
15
15
|
import { processSampling, buildNestingStructure } from './core/sampling.js';
|
|
16
16
|
import { buildComponentMappingPrompt, buildFullUpdatePromptForAI, buildPlaceholderPrompt } from './core/prompt-builder.js';
|
|
17
|
+
import { detectClientApiSupport, clearCapabilitiesCache } from './utils/client-capabilities.js';
|
|
17
18
|
// @ts-ignore
|
|
18
19
|
import * as flatted from 'flatted';
|
|
19
20
|
import { promises as fs } from 'fs';
|
|
@@ -89,6 +90,8 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
89
90
|
const { fileKey, nodeId, depth, local, update = false } = args;
|
|
90
91
|
let config = await configManager.load();
|
|
91
92
|
let hasLaunchWebview = false;
|
|
93
|
+
// 检测客户端 API 支持程度
|
|
94
|
+
const clientApiSupport = await detectClientApiSupport(mcpServer);
|
|
92
95
|
// 检测 VSCode 扩展是否可用
|
|
93
96
|
const isVSCodePluginAvailable = await isVSCodeAvailable();
|
|
94
97
|
Logger.log(`VSCode 扩展检测结果: ${isVSCodePluginAvailable ? '可用' : '不可用'}`);
|
|
@@ -97,6 +100,8 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
97
100
|
const rootRes = await mcpServer.server.listRoots();
|
|
98
101
|
Logger.log('获取根目录:', rootRes);
|
|
99
102
|
root = rootRes.roots[0]?.uri?.slice(7) || './';
|
|
103
|
+
// 兼容window下roots返回的路径是url编码的
|
|
104
|
+
root = decodeURIComponent(root);
|
|
100
105
|
// 设置 fileManager 的工作目录根路径
|
|
101
106
|
fileManager.setWorkspaceRoot(root);
|
|
102
107
|
}
|
|
@@ -113,12 +118,22 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
113
118
|
if (isVSCodePluginAvailable) {
|
|
114
119
|
// 优先使用 VSCode 扩展
|
|
115
120
|
Logger.log('使用 VSCode 扩展获取用户输入');
|
|
116
|
-
configDataString = await getUserInputFromVSCode({
|
|
121
|
+
configDataString = await getUserInputFromVSCode({
|
|
122
|
+
fileKey: fileKey,
|
|
123
|
+
nodeId: nodeId,
|
|
124
|
+
mode: update ? 'update' : 'create',
|
|
125
|
+
clientApiSupport,
|
|
126
|
+
});
|
|
117
127
|
}
|
|
118
128
|
else {
|
|
119
129
|
// 降级到网页浏览器
|
|
120
130
|
Logger.log('VSCode 扩展不可用,降级到网页浏览器');
|
|
121
|
-
configDataString = await getUserInput({
|
|
131
|
+
configDataString = await getUserInput({
|
|
132
|
+
fileKey: fileKey,
|
|
133
|
+
nodeId: nodeId,
|
|
134
|
+
mode: update ? 'update' : 'create',
|
|
135
|
+
clientApiSupport,
|
|
136
|
+
});
|
|
122
137
|
}
|
|
123
138
|
console.log('getting configDataString', configDataString);
|
|
124
139
|
if (!configDataString || configDataString.trim() === '') {
|
|
@@ -186,10 +201,20 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
186
201
|
Logger.log(`收到网页工具调用参数:`, JSON.stringify(args, null, 2));
|
|
187
202
|
let configDataString;
|
|
188
203
|
if (isVSCodePluginAvailable) {
|
|
189
|
-
configDataString = await getUserInputFromVSCode({
|
|
204
|
+
configDataString = await getUserInputFromVSCode({
|
|
205
|
+
fileKey: fileKey,
|
|
206
|
+
nodeId: nodeId,
|
|
207
|
+
mode: update ? 'update' : 'create',
|
|
208
|
+
clientApiSupport,
|
|
209
|
+
});
|
|
190
210
|
}
|
|
191
211
|
else {
|
|
192
|
-
configDataString = await getUserInput({
|
|
212
|
+
configDataString = await getUserInput({
|
|
213
|
+
fileKey: fileKey,
|
|
214
|
+
nodeId: nodeId,
|
|
215
|
+
mode: update ? 'update' : 'create',
|
|
216
|
+
clientApiSupport,
|
|
217
|
+
});
|
|
193
218
|
}
|
|
194
219
|
console.log('收到网页提交数据', configDataString);
|
|
195
220
|
if (!configDataString || configDataString.trim() === '') {
|
|
@@ -225,7 +250,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
225
250
|
// 检查是否需要更新imageMap
|
|
226
251
|
try {
|
|
227
252
|
const imageNodeList = d2cNodeList?.filter((node) => ['IMG', 'ICON'].includes(node.type)) || [];
|
|
228
|
-
const { imageMap: updatedImageMap, updated } = await updateImageMapIfNeeded(imageMap, imageNodeList, oldFileConfig, rest);
|
|
253
|
+
const { imageMap: updatedImageMap, updated } = await updateImageMapIfNeeded(imageMap, imageNodeList, oldFileConfig, rest, local);
|
|
229
254
|
if (updated) {
|
|
230
255
|
// 更新imageMap并重新保存
|
|
231
256
|
Object.assign(imageMap, updatedImageMap);
|
|
@@ -263,8 +288,10 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
263
288
|
const chunkPrompt = savedPromptSetting?.chunkOptimizePrompt || chunkOptimizeCodePrompt;
|
|
264
289
|
const aggregationPrompt = savedPromptSetting?.aggregationOptimizePrompt || aggregationOptimizeCodePrompt;
|
|
265
290
|
const finalPrompt = savedPromptSetting?.finalOptimizePrompt || finalOptimizeCodePrompt;
|
|
291
|
+
const noSamplingPrompt = savedPromptSetting?.noSamplingAggregationPrompt || noSamplingAggregationPrompt;
|
|
266
292
|
const codeSnippets = [];
|
|
267
|
-
let isSupportSampling =
|
|
293
|
+
let isSupportSampling = clientApiSupport.sampling;
|
|
294
|
+
Logger.log(`客户端 API 支持检测: sampling=${isSupportSampling}, roots=${clientApiSupport.roots}`);
|
|
268
295
|
// 收集已映射的组件信息,用于在最终写入时添加提示词
|
|
269
296
|
const componentMappings = [];
|
|
270
297
|
// 收集所有组的组件上下文信息,用于在最终提示词中显示
|
|
@@ -280,10 +307,11 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
280
307
|
componentMappings,
|
|
281
308
|
codeSnippets,
|
|
282
309
|
componentContexts,
|
|
310
|
+
isSupportSampling,
|
|
283
311
|
};
|
|
284
312
|
// 深度遍历采样:针对每个分组生成 AI 相对布局树并转码
|
|
285
313
|
if (groupsData && groupsData?.length > 0) {
|
|
286
|
-
|
|
314
|
+
await processSampling(groupsData, samplingConfig);
|
|
287
315
|
// 采样完成后,更新组件名并保存到文件
|
|
288
316
|
try {
|
|
289
317
|
const groupsDataToSave = groupsData.map((group) => {
|
|
@@ -360,7 +388,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
360
388
|
.map((node) => ({
|
|
361
389
|
name: node.name || 'Unknown',
|
|
362
390
|
}));
|
|
363
|
-
const placeholderPrompt = buildPlaceholderPrompt(placeholders);
|
|
391
|
+
const placeholderPrompt = buildPlaceholderPrompt(placeholders, isSupportSampling);
|
|
364
392
|
Logger.log(`整合采样检测到 ${placeholders.length} 个分组占位符`);
|
|
365
393
|
// 获取代码
|
|
366
394
|
const code = await getCode({
|
|
@@ -371,6 +399,8 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
371
399
|
const replacedCode = replaceImageSrc(code, imageMap);
|
|
372
400
|
// 整合采样
|
|
373
401
|
try {
|
|
402
|
+
if (!isSupportSampling)
|
|
403
|
+
throw new Error('不支持采样');
|
|
374
404
|
// 使用提示词(已包含默认值)
|
|
375
405
|
const { content: { text }, } = await mcpServer.server.createMessage({
|
|
376
406
|
messages: [
|
|
@@ -398,7 +428,6 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
398
428
|
codeSnippets.unshift(pageCode);
|
|
399
429
|
}
|
|
400
430
|
catch (e) {
|
|
401
|
-
isSupportSampling = false;
|
|
402
431
|
Logger.log('调用分组外元素采样出错,降级到传统模式', e);
|
|
403
432
|
codeSnippets.unshift(placeholderPrompt + '\n' + replacedCode);
|
|
404
433
|
}
|
|
@@ -436,7 +465,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
436
465
|
}
|
|
437
466
|
: {
|
|
438
467
|
type: 'text',
|
|
439
|
-
text:
|
|
468
|
+
text: noSamplingPrompt + componentMappingPrompt,
|
|
440
469
|
},
|
|
441
470
|
...codeSnippets.map((code) => {
|
|
442
471
|
return {
|
|
@@ -504,6 +533,8 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
|
|
|
504
533
|
const rootRes = await mcpServer.server.listRoots();
|
|
505
534
|
Logger.log('获取根目录:', rootRes);
|
|
506
535
|
root = rootRes.roots[0]?.uri?.slice(7) || './';
|
|
536
|
+
// 兼容window下roots返回的路径是url编码的
|
|
537
|
+
root = decodeURIComponent(root);
|
|
507
538
|
// 设置 fileManager 的工作目录根路径
|
|
508
539
|
fileManager.setWorkspaceRoot(root);
|
|
509
540
|
}
|
|
@@ -1026,6 +1057,7 @@ process.on('SIGINT', async () => {
|
|
|
1026
1057
|
cleanupTauri();
|
|
1027
1058
|
cleanupWeb();
|
|
1028
1059
|
cleanupVSCode();
|
|
1060
|
+
clearCapabilitiesCache(); // 清理能力检测缓存
|
|
1029
1061
|
Logger.cleanup(); // 清理日志连接
|
|
1030
1062
|
await stopHttpServer();
|
|
1031
1063
|
process.exit(0);
|
|
@@ -1044,6 +1076,7 @@ process.on('SIGTERM', async () => {
|
|
|
1044
1076
|
cleanupTauri();
|
|
1045
1077
|
cleanupWeb();
|
|
1046
1078
|
cleanupVSCode();
|
|
1079
|
+
clearCapabilitiesCache(); // 清理能力检测缓存
|
|
1047
1080
|
Logger.cleanup(); // 清理日志连接
|
|
1048
1081
|
await stopHttpServer();
|
|
1049
1082
|
process.exit(0);
|
|
@@ -12,14 +12,14 @@ export class VSCodeCommunicator {
|
|
|
12
12
|
/**
|
|
13
13
|
* 长连接模式 - 直接等待用户输入,无超时限制
|
|
14
14
|
* @param sessionId 会话ID
|
|
15
|
-
* @param payload 传递给webview
|
|
15
|
+
* @param payload 传递给webview的数据(包含 clientApiSupport)
|
|
16
16
|
* @returns Promise<string> 返回用户输入的数据
|
|
17
17
|
*/
|
|
18
18
|
waitForUserInputLongConnection(sessionId, payload) {
|
|
19
19
|
return new Promise((resolve, reject) => {
|
|
20
20
|
const client = net.connect({ port: this.port, host: this.host }, () => {
|
|
21
21
|
console.log(`[MCP] 建立长连接等待用户输入,会话ID: ${sessionId}`);
|
|
22
|
-
// 发送长连接等待命令,包含 payload
|
|
22
|
+
// 发送长连接等待命令,包含 payload 数据(clientApiSupport 已在 payload 中)
|
|
23
23
|
const requestPayload = JSON.stringify({
|
|
24
24
|
cmd: 'waitForInput',
|
|
25
25
|
sessionId,
|
|
@@ -67,7 +67,7 @@ export class VSCodeCommunicator {
|
|
|
67
67
|
/**
|
|
68
68
|
* 从VSCode获取用户输入 - 主要方法
|
|
69
69
|
* 使用长连接模式,无超时限制
|
|
70
|
-
* @param payload
|
|
70
|
+
* @param payload 传输参数(包含 clientApiSupport)
|
|
71
71
|
* @returns Promise<string> 返回用户输入的数据
|
|
72
72
|
*/
|
|
73
73
|
async getUserInputFromVSCode(payload) {
|
|
@@ -116,7 +116,7 @@ export class VSCodeCommunicator {
|
|
|
116
116
|
const vscodeComm = new VSCodeCommunicator();
|
|
117
117
|
/**
|
|
118
118
|
* 从VSCode获取用户输入的便捷函数
|
|
119
|
-
* @param payload
|
|
119
|
+
* @param payload 传输参数(包含 clientApiSupport)
|
|
120
120
|
* @returns Promise<string> 返回用户输入的数据
|
|
121
121
|
*/
|
|
122
122
|
export async function getUserInputFromVSCode(payload) {
|
package/dist/build/server.js
CHANGED
|
@@ -31,7 +31,7 @@ const upload = multer({
|
|
|
31
31
|
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
|
|
32
32
|
});
|
|
33
33
|
// 导入默认提示词
|
|
34
|
-
import { chunkOptimizeCodePrompt, aggregationOptimizeCodePrompt, finalOptimizeCodePrompt, chunkOptimizeCodePromptVue, aggregationOptimizeCodePromptVue, finalOptimizeCodePromptVue, } from 'sloth-d2c-node/convert';
|
|
34
|
+
import { chunkOptimizeCodePrompt, aggregationOptimizeCodePrompt, finalOptimizeCodePrompt, chunkOptimizeCodePromptVue, aggregationOptimizeCodePromptVue, finalOptimizeCodePromptVue, noSamplingAggregationPrompt, noSamplingAggregationPromptVue, } from 'sloth-d2c-node/convert';
|
|
35
35
|
// 保存 HTTP 服务器实例
|
|
36
36
|
let httpServer = null;
|
|
37
37
|
// 保存 Socket 服务器实例
|
|
@@ -104,6 +104,7 @@ export async function loadConfig(mcpServer, configManagerInstance) {
|
|
|
104
104
|
aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
|
|
105
105
|
finalOptimizePrompt: finalOptimizeCodePrompt,
|
|
106
106
|
componentAnalysisPrompt: componentAnalysisPrompt,
|
|
107
|
+
noSamplingAggregationPrompt: noSamplingAggregationPrompt,
|
|
107
108
|
});
|
|
108
109
|
const reactConfigPath = configManager.getFrameworkConfigPath('react');
|
|
109
110
|
Logger.log(`已创建默认 react 配置文件: ${reactConfigPath}`);
|
|
@@ -116,6 +117,7 @@ export async function loadConfig(mcpServer, configManagerInstance) {
|
|
|
116
117
|
aggregationOptimizePrompt: aggregationOptimizeCodePromptVue,
|
|
117
118
|
finalOptimizePrompt: finalOptimizeCodePromptVue,
|
|
118
119
|
componentAnalysisPrompt: componentAnalysisPromptVue,
|
|
120
|
+
noSamplingAggregationPromptVue: noSamplingAggregationPromptVue,
|
|
119
121
|
});
|
|
120
122
|
const vueConfigPath = configManager.getFrameworkConfigPath('vue');
|
|
121
123
|
Logger.log(`已创建默认 vue 配置文件: ${vueConfigPath}`);
|
|
@@ -127,6 +129,7 @@ export async function loadConfig(mcpServer, configManagerInstance) {
|
|
|
127
129
|
aggregationOptimizePrompt: aggregationOptimizeCodePromptVue,
|
|
128
130
|
finalOptimizePrompt: finalOptimizeCodePromptVue,
|
|
129
131
|
componentAnalysisPrompt: componentAnalysisPromptVue,
|
|
132
|
+
noSamplingAggregationPrompt: noSamplingAggregationPromptVue,
|
|
130
133
|
...(vueConfig || {}),
|
|
131
134
|
});
|
|
132
135
|
}
|
|
@@ -347,17 +350,23 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
347
350
|
chunkOptimizePrompt: chunkOptimizeCodePrompt,
|
|
348
351
|
aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
|
|
349
352
|
finalOptimizePrompt: finalOptimizeCodePrompt,
|
|
353
|
+
componentAnalysisPrompt: componentAnalysisPrompt,
|
|
354
|
+
noSamplingAggregationPrompt: noSamplingAggregationPrompt,
|
|
350
355
|
}
|
|
351
356
|
: {
|
|
352
357
|
chunkOptimizePrompt: chunkOptimizeCodePromptVue,
|
|
353
358
|
aggregationOptimizePrompt: aggregationOptimizeCodePromptVue,
|
|
354
359
|
finalOptimizePrompt: finalOptimizeCodePromptVue,
|
|
360
|
+
componentAnalysisPrompt: componentAnalysisPromptVue,
|
|
361
|
+
noSamplingAggregationPrompt: noSamplingAggregationPromptVue,
|
|
355
362
|
});
|
|
356
363
|
// 如果指定了框架,加载框架配置的提示词
|
|
357
364
|
let promptSetting = {
|
|
358
365
|
chunkOptimizePrompt: savedPromptSetting?.chunkOptimizePrompt || curFrameworkDefaultConfig.chunkOptimizePrompt,
|
|
359
366
|
aggregationOptimizePrompt: savedPromptSetting?.aggregationOptimizePrompt || curFrameworkDefaultConfig.aggregationOptimizePrompt,
|
|
360
367
|
finalOptimizePrompt: savedPromptSetting?.finalOptimizePrompt || curFrameworkDefaultConfig.finalOptimizePrompt,
|
|
368
|
+
componentAnalysisPrompt: savedPromptSetting?.componentAnalysisPrompt || curFrameworkDefaultConfig.componentAnalysisPrompt,
|
|
369
|
+
noSamplingAggregationPrompt: savedPromptSetting?.noSamplingAggregationPrompt || curFrameworkDefaultConfig.noSamplingAggregationPrompt,
|
|
361
370
|
};
|
|
362
371
|
if (framework) {
|
|
363
372
|
// 加载框架配置
|
|
@@ -368,6 +377,8 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
368
377
|
chunkOptimizePrompt: frameworkConfig.chunkOptimizePrompt || promptSetting.chunkOptimizePrompt,
|
|
369
378
|
aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt || promptSetting.aggregationOptimizePrompt,
|
|
370
379
|
finalOptimizePrompt: frameworkConfig.finalOptimizePrompt || promptSetting.finalOptimizePrompt,
|
|
380
|
+
componentAnalysisPrompt: frameworkConfig.componentAnalysisPrompt || promptSetting.componentAnalysisPrompt,
|
|
381
|
+
noSamplingAggregationPrompt: frameworkConfig.noSamplingAggregationPrompt || promptSetting.noSamplingAggregationPrompt,
|
|
371
382
|
};
|
|
372
383
|
}
|
|
373
384
|
}
|
|
@@ -395,6 +406,8 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
395
406
|
chunkOptimizePrompt: chunkOptimizeCodePrompt,
|
|
396
407
|
aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
|
|
397
408
|
finalOptimizePrompt: finalOptimizeCodePrompt,
|
|
409
|
+
componentAnalysisPrompt: componentAnalysisPrompt,
|
|
410
|
+
noSamplingAggregationPrompt: noSamplingAggregationPrompt,
|
|
398
411
|
};
|
|
399
412
|
if (framework) {
|
|
400
413
|
// 加载框架配置
|
|
@@ -405,6 +418,8 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
405
418
|
chunkOptimizePrompt: frameworkConfig.chunkOptimizePrompt || promptSetting.chunkOptimizePrompt,
|
|
406
419
|
aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt || promptSetting.aggregationOptimizePrompt,
|
|
407
420
|
finalOptimizePrompt: frameworkConfig.finalOptimizePrompt || promptSetting.finalOptimizePrompt,
|
|
421
|
+
componentAnalysisPrompt: frameworkConfig.componentAnalysisPrompt || promptSetting.componentAnalysisPrompt,
|
|
422
|
+
noSamplingAggregationPrompt: frameworkConfig.noSamplingAggregationPrompt || promptSetting.noSamplingAggregationPrompt,
|
|
408
423
|
};
|
|
409
424
|
}
|
|
410
425
|
}
|
|
@@ -461,6 +476,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
461
476
|
aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt || aggregationOptimizeCodePrompt,
|
|
462
477
|
finalOptimizePrompt: frameworkConfig.finalOptimizePrompt || finalOptimizeCodePrompt,
|
|
463
478
|
componentAnalysisPrompt: frameworkConfig.componentAnalysisPrompt || componentAnalysisPrompt,
|
|
479
|
+
noSamplingAggregationPrompt: frameworkConfig.noSamplingAggregationPrompt || noSamplingAggregationPrompt,
|
|
464
480
|
};
|
|
465
481
|
res.json({
|
|
466
482
|
success: true,
|
|
@@ -528,6 +544,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
528
544
|
aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
|
|
529
545
|
finalOptimizePrompt: finalOptimizeCodePrompt,
|
|
530
546
|
componentAnalysisPrompt: componentAnalysisPrompt,
|
|
547
|
+
noSamplingAggregationPrompt: noSamplingAggregationPrompt,
|
|
531
548
|
};
|
|
532
549
|
const frameworkConfig = await configManager.loadFrameworkConfig(framework);
|
|
533
550
|
if (frameworkConfig && Object.keys(frameworkConfig).length > 0) {
|
|
@@ -537,6 +554,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
537
554
|
aggregationOptimizePrompt: frameworkConfig.aggregationOptimizePrompt || promptSetting.aggregationOptimizePrompt,
|
|
538
555
|
finalOptimizePrompt: frameworkConfig.finalOptimizePrompt || promptSetting.finalOptimizePrompt,
|
|
539
556
|
componentAnalysisPrompt: frameworkConfig.componentAnalysisPrompt || promptSetting.componentAnalysisPrompt,
|
|
557
|
+
noSamplingAggregationPrompt: frameworkConfig.noSamplingAggregationPrompt || promptSetting.noSamplingAggregationPrompt,
|
|
540
558
|
};
|
|
541
559
|
}
|
|
542
560
|
res.json({
|
|
@@ -554,6 +572,41 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
554
572
|
});
|
|
555
573
|
}
|
|
556
574
|
});
|
|
575
|
+
// 获取框架默认提示词接口(不读取用户配置,直接返回代码中的原始默认值)
|
|
576
|
+
app.get('/getDefaultFrameworkConfig', async (req, res) => {
|
|
577
|
+
try {
|
|
578
|
+
const framework = req.query.framework;
|
|
579
|
+
if (!framework) {
|
|
580
|
+
res.status(400).json({
|
|
581
|
+
success: false,
|
|
582
|
+
message: '缺少必要参数: framework',
|
|
583
|
+
});
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
// 根据框架返回对应的原始默认提示词
|
|
587
|
+
const isVue = framework.toLowerCase() === 'vue';
|
|
588
|
+
const promptSetting = {
|
|
589
|
+
chunkOptimizePrompt: isVue ? chunkOptimizeCodePromptVue : chunkOptimizeCodePrompt,
|
|
590
|
+
aggregationOptimizePrompt: isVue ? aggregationOptimizeCodePromptVue : aggregationOptimizeCodePrompt,
|
|
591
|
+
finalOptimizePrompt: isVue ? finalOptimizeCodePromptVue : finalOptimizeCodePrompt,
|
|
592
|
+
componentAnalysisPrompt: isVue ? componentAnalysisPromptVue : componentAnalysisPrompt,
|
|
593
|
+
noSamplingAggregationPrompt: isVue ? noSamplingAggregationPromptVue : noSamplingAggregationPrompt,
|
|
594
|
+
};
|
|
595
|
+
res.json({
|
|
596
|
+
success: true,
|
|
597
|
+
data: promptSetting,
|
|
598
|
+
message: '获取默认框架配置成功',
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
catch (error) {
|
|
602
|
+
Logger.error('获取默认框架配置失败:', error);
|
|
603
|
+
res.status(500).json({
|
|
604
|
+
success: false,
|
|
605
|
+
message: '获取默认框架配置失败',
|
|
606
|
+
error: error instanceof Error ? error.message : String(error),
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
});
|
|
557
610
|
// 认证页面
|
|
558
611
|
app.get('/auth-page', (req, res) => {
|
|
559
612
|
try {
|
|
@@ -1102,7 +1155,6 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1102
1155
|
try {
|
|
1103
1156
|
const { includePaths, excludePaths } = req.query;
|
|
1104
1157
|
const projectPath = getProjectRoot();
|
|
1105
|
-
Logger.log('扫描项目组件:', { includePaths, excludePaths });
|
|
1106
1158
|
// 读取项目组件(从 .sloth/components.json)
|
|
1107
1159
|
let projectComponents = [];
|
|
1108
1160
|
const slothPath = path.join(projectPath, '.sloth', 'components.json');
|
|
@@ -1467,7 +1519,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1467
1519
|
}
|
|
1468
1520
|
}
|
|
1469
1521
|
Logger.log(`[analyzeChange] ${label} 图片替换完成, 替换数: ${replaceCount}, HTML长度: ${html.length} -> ${processedHtml.length}`);
|
|
1470
|
-
return processedHtml
|
|
1522
|
+
return (processedHtml
|
|
1471
1523
|
// 去除 data-id、data-name、data-type 属性(不参与 diff)
|
|
1472
1524
|
.replace(/\s*data-id="[^"]*"/g, '')
|
|
1473
1525
|
.replace(/\s*data-name="[^"]*"/g, '')
|
|
@@ -1478,7 +1530,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1478
1530
|
.replace(/class="([^"]*)"/g, (_, classes) => {
|
|
1479
1531
|
const cleaned = classes.replace(/\s+/g, ' ').trim();
|
|
1480
1532
|
return `class="${cleaned}"`;
|
|
1481
|
-
});
|
|
1533
|
+
}));
|
|
1482
1534
|
};
|
|
1483
1535
|
oldHtml = processHtmlForDiff(oldHtml, oldImageMap, 'old');
|
|
1484
1536
|
newHtml = processHtmlForDiff(newHtml, newImageMap, 'new');
|
|
@@ -1534,7 +1586,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1534
1586
|
addedLines,
|
|
1535
1587
|
removedLines,
|
|
1536
1588
|
unchangedLines,
|
|
1537
|
-
changeRatio: ((addedLines + removedLines) / (addedLines + removedLines + unchangedLines) * 100).toFixed(1),
|
|
1589
|
+
changeRatio: (((addedLines + removedLines) / (addedLines + removedLines + unchangedLines)) * 100).toFixed(1),
|
|
1538
1590
|
};
|
|
1539
1591
|
// 5. 尝试使用 AI 总结变更(如果 MCP 服务器可用)
|
|
1540
1592
|
let aiSummary = [];
|
|
@@ -1542,9 +1594,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
1542
1594
|
try {
|
|
1543
1595
|
// 限制 diff 文本长度,避免 token 过大
|
|
1544
1596
|
const maxDiffLength = 48000;
|
|
1545
|
-
const truncatedDiff = diffText.length > maxDiffLength
|
|
1546
|
-
? diffText.substring(0, maxDiffLength) + '\n... (diff 内容已截断)'
|
|
1547
|
-
: diffText;
|
|
1597
|
+
const truncatedDiff = diffText.length > maxDiffLength ? diffText.substring(0, maxDiffLength) + '\n... (diff 内容已截断)' : diffText;
|
|
1548
1598
|
const prompt = `
|
|
1549
1599
|
你是一个专业的前端开发专家。请分析以下设计稿 HTML 的 diff 变更,总结出具体的变更点。
|
|
1550
1600
|
|
|
@@ -1579,12 +1629,14 @@ ${truncatedDiff}
|
|
|
1579
1629
|
Logger.log('=== AI 分析变更提示词 ===');
|
|
1580
1630
|
Logger.log(prompt);
|
|
1581
1631
|
Logger.log('=== 提示词结束 ===');
|
|
1582
|
-
const { content: { text } } = await mcpServer.server.createMessage({
|
|
1583
|
-
messages: [
|
|
1632
|
+
const { content: { text }, } = await mcpServer.server.createMessage({
|
|
1633
|
+
messages: [
|
|
1634
|
+
{
|
|
1584
1635
|
role: 'user',
|
|
1585
|
-
content: { type: 'text', text: prompt }
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1636
|
+
content: { type: 'text', text: prompt },
|
|
1637
|
+
},
|
|
1638
|
+
],
|
|
1639
|
+
maxTokens: 48000,
|
|
1588
1640
|
}, { timeout: 2 * 60 * 1000 });
|
|
1589
1641
|
// 解析 AI 返回的 JSON
|
|
1590
1642
|
const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/);
|
|
@@ -1598,13 +1650,15 @@ ${truncatedDiff}
|
|
|
1598
1650
|
}
|
|
1599
1651
|
// 6. 如果 AI 分析失败,生成基础摘要
|
|
1600
1652
|
if (aiSummary.length === 0 && changeSummary.totalChanges > 0) {
|
|
1601
|
-
aiSummary = [
|
|
1653
|
+
aiSummary = [
|
|
1654
|
+
{
|
|
1602
1655
|
id: 'change_1',
|
|
1603
1656
|
type: 'structure',
|
|
1604
1657
|
title: '设计稿结构变更',
|
|
1605
1658
|
description: `检测到 ${addedLines} 行新增内容和 ${removedLines} 行删除内容,变更比例约 ${changeSummary.changeRatio}%`,
|
|
1606
|
-
suggestedAction: '请检查新设计稿的布局和样式变化,更新相应的代码'
|
|
1607
|
-
}
|
|
1659
|
+
suggestedAction: '请检查新设计稿的布局和样式变化,更新相应的代码',
|
|
1660
|
+
},
|
|
1661
|
+
];
|
|
1608
1662
|
}
|
|
1609
1663
|
Logger.log(`变更分析完成: ${aiSummary.length} 个变更点`);
|
|
1610
1664
|
res.json({
|
|
@@ -1687,7 +1741,11 @@ export async function getUserInput(payload) {
|
|
|
1687
1741
|
return new Promise(async (resolve, reject) => {
|
|
1688
1742
|
const token = uuidv4();
|
|
1689
1743
|
const port = getPort();
|
|
1690
|
-
|
|
1744
|
+
// 构建 URL,从 payload 中提取 clientApiSupport
|
|
1745
|
+
const clientApiSupport = payload.clientApiSupport;
|
|
1746
|
+
const supportSampling = clientApiSupport?.sampling ? '1' : '0';
|
|
1747
|
+
const supportRoots = clientApiSupport?.roots ? '1' : '0';
|
|
1748
|
+
const authUrl = `http://localhost:${port}/auth-page?token=${token}&fileKey=${payload.fileKey}&nodeId=${payload.nodeId}&mode=${payload.mode}&supportSampling=${supportSampling}&supportRoots=${supportRoots}`;
|
|
1691
1749
|
Logger.log('authUrl', authUrl);
|
|
1692
1750
|
// 判断是主进程还是子进程
|
|
1693
1751
|
const isMainProcess = httpServer !== null;
|