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.
@@ -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 = '\n\n## 子组件占位符\n\n**重要**:以下占位符代表已实现的子组件,请转换格式但不要重新实现其内部逻辑。\n\n';
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
  }
@@ -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({ fileKey: fileKey, nodeId: nodeId, mode: update ? 'update' : 'create' });
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({ fileKey: fileKey, nodeId: nodeId, mode: update ? 'update' : 'create' });
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({ fileKey: fileKey, nodeId: nodeId, mode: update ? 'update' : 'create' });
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({ fileKey: fileKey, nodeId: nodeId, mode: update ? 'update' : 'create' });
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 = true;
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
- isSupportSampling = await processSampling(groupsData, samplingConfig);
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: aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') + componentMappingPrompt,
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) {
@@ -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
- maxTokens: 48000
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
- const authUrl = `http://localhost:${port}/auth-page?token=${token}&fileKey=${payload.fileKey}&nodeId=${payload.nodeId}&mode=${payload.mode}`;
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;