sloth-d2c-mcp 1.0.4-beta75 → 1.0.4-beta77

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.
@@ -49,12 +49,84 @@ export function buildUserPromptText(userPrompt) {
49
49
  return '\n## 用户提示词\n以下是用户针对该代码的优化提出的补充说明和要求,请在优化代码时特别关注这些指导并尽可能实现:\n' + userPrompt;
50
50
  }
51
51
  /**
52
- * 构建组件映射提示词(包含组件映射和组件上下文)
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
+ }
115
+ /**
116
+ * 构建组件映射提示词(包含组件映射和组件上下文和组件标记)
53
117
  * @param componentMappings 组件映射数组
54
118
  * @param componentContexts 所有组的组件上下文数组
119
+ * @param markedComponents 标记的组件数组
120
+ * @param componentsDatabase 完整的组件数据库(用于查找继承链)
55
121
  * @returns 组件映射提示词
56
122
  */
57
- export function buildComponentMappingPrompt(componentMappings, componentContexts = []) {
123
+ export function buildComponentMappingPrompt(componentMappings, componentContexts = [], markedComponents = [], componentsDatabase = []) {
124
+ let prompt = '';
125
+ // 构建完整的组件数据库映射表(用于查找继承链)
126
+ const databaseMap = new Map();
127
+ componentsDatabase.forEach((comp) => {
128
+ databaseMap.set(comp.name, comp);
129
+ });
58
130
  // 收集所有组件:组件映射 + 组件上下文(按名称去重)
59
131
  const allComponents = new Map();
60
132
  // 添加组件映射的组件
@@ -69,23 +141,55 @@ export function buildComponentMappingPrompt(componentMappings, componentContexts
69
141
  }
70
142
  });
71
143
  });
72
- if (allComponents.size === 0) {
73
- return '';
144
+ // 构建可用组件提示词
145
+ if (allComponents.size > 0) {
146
+ prompt += '\n\n## 可用组件\n\n以下是项目中可用的组件,请在最终代码中使用这些组件:\n\n';
147
+ // 记录已输出的组件,避免重复输出
148
+ const outputtedComponents = new Set();
149
+ allComponents.forEach((component) => {
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
+ }
166
+ });
167
+ prompt +=
168
+ '**重要**:在最终写入代码时,请根据设计稿需求合理传递 props 参数。请按照项目路径规范合理引入组件。如果组件有继承关系,请注意父类的 props 和方法也可以使用。\n';
169
+ }
170
+ // 构建标记组件提示词
171
+ if (markedComponents.length > 0) {
172
+ prompt += '\n\n## 组件标记任务\n\n';
173
+ prompt += '以下组件已被用户标记为可复用组件,**请在写入代码后调用 `mark_components` 工具保存这些组件信息**:\n\n';
174
+ prompt += '| 组件名 | signature |\n';
175
+ prompt += '|--------|-----------|';
176
+ markedComponents.forEach((comp) => {
177
+ prompt += `\n| ${comp.name} | ${comp.signature} |`;
178
+ });
179
+ prompt += '\n\n调用示例:\n';
180
+ prompt += '```json\n';
181
+ prompt += JSON.stringify({
182
+ components: markedComponents.map((comp) => ({
183
+ name: comp.name,
184
+ path: '按实际写入路径填写',
185
+ signature: comp.signature,
186
+ import: '按实际写入路径填写导入语句',
187
+ description: '组件的功能描述',
188
+ })),
189
+ }, null, 2);
190
+ prompt += '\n```\n\n';
191
+ prompt += '**重要**:组件标记后可在后续转码中自动匹配复用\n';
74
192
  }
75
- let prompt = '\n\n## 可用组件\n\n以下是项目中可用的组件,请在最终代码中使用这些组件:\n\n';
76
- allComponents.forEach((component) => {
77
- const propsInfo = component.props && component.props.length > 0
78
- ? component.props.map((p) => `${p.name}${p.required ? ' (必需)' : ' (可选)'}: ${p.type || 'any'}`).join(', ')
79
- : '无';
80
- const libInfo = component.lib ? ` (来自 ${component.lib})` : ' (项目组件)';
81
- const descInfo = component.description ? `\n- **描述**: ${component.description}` : '';
82
- prompt += `### ${component.name}${libInfo}\n`;
83
- prompt += `- **文件路径**: ${component.path}\n`;
84
- prompt += `- **导入方式**: ${component.import}\n`;
85
- prompt += `- **Props**: ${propsInfo}${descInfo}\n\n`;
86
- });
87
- prompt +=
88
- '**重要**:在最终写入代码时,请根据设计稿需求合理传递 props 参数。请按照项目路径规范合理引入组件。\n';
89
193
  return prompt;
90
194
  }
91
195
  /**
@@ -100,8 +204,8 @@ export function buildPlaceholderPrompt(placeholders) {
100
204
  let prompt = '\n\n## 子组件占位符\n\n**重要**:以下占位符代表已实现的子组件,请转换格式但不要重新实现其内部逻辑。\n\n';
101
205
  placeholders.forEach((placeholder) => {
102
206
  const kebabName = placeholder.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
103
- prompt += `- \`<${kebabName}>\` → \`<${placeholder.name} />\`\n`;
207
+ prompt += `- \`<${kebabName}>\`\n`;
104
208
  });
105
- prompt += '\n将 HTML 标签转换为 React 组件引用,不需要添加对应的 import 语句,保持其在布局中的位置。\n';
209
+ prompt += '\n将 HTML 标签转换为真实组件调用,但不需要添加对应的 import 语句,保持其在布局中的位置。\n';
106
210
  return prompt;
107
211
  }
@@ -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, chunkPrompt, mcpServer, componentMappings, codeSnippets, componentContexts } = config;
138
+ const { d2cNodeList, imageMap, convertConfig, frameworkPrompt, chunkPrompt, mcpServer, componentMappings, codeSnippets, componentContexts } = config;
139
139
  Logger.log(`开始采样组 ${group.groupIndex}, 元素数量: ${group.elements.length}, nodeList 长度: ${nodeList.length}`);
140
140
  // 检查组件映射
141
141
  const mappingResult = handleComponentMapping(group, componentMappings);
@@ -184,7 +184,8 @@ export async function sampleSingleGroup(group, nodeList, config) {
184
184
  role: 'user',
185
185
  content: {
186
186
  type: 'text',
187
- text: chunkPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') +
187
+ text: frameworkPrompt +
188
+ chunkPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') +
188
189
  userPromptText +
189
190
  componentContextPrompt +
190
191
  placeholderPrompt,
@@ -203,18 +204,7 @@ export async function sampleSingleGroup(group, nodeList, config) {
203
204
  const { code: chunkCode, componentName: sampledName } = extractCodeAndComponents(text)[0];
204
205
  Logger.log(`组 ${group.groupIndex} 采样成功,组件名称: ${sampledName}, 代码长度: ${chunkCode.length}`);
205
206
  group.name = sampledName;
206
- // 如果分组被标记且有截图,注入 signature 注释
207
- let finalCode = chunkCode;
208
- if (group.marked && group.screenshot?.hash) {
209
- const document = `/**
210
- * ${sampledName} 组件
211
- * signature: ${group.screenshot.hash}
212
- */
213
- `;
214
- finalCode = document + chunkCode;
215
- Logger.log(`组 ${group.groupIndex} 已标记,注入 signature: ${group.screenshot.hash}, 组件名: ${sampledName}`);
216
- }
217
- codeSnippets.push(finalCode);
207
+ codeSnippets.push(chunkCode);
218
208
  return {
219
209
  code: chunkCode,
220
210
  componentName: sampledName,
@@ -224,15 +214,7 @@ export async function sampleSingleGroup(group, nodeList, config) {
224
214
  }
225
215
  catch (e) {
226
216
  Logger.log(`组 ${group.groupIndex} 调用采样出错:`, e);
227
- let fallbackCode = userPromptText + componentContextPrompt + placeholderPrompt + `\n // Group${group.groupIndex + 1} \n` + codeWithCustomName;
228
- // 如果分组被标记且有截图,注入 signature 注释
229
- if (group.marked && group.screenshot?.hash) {
230
- const document = `/**
231
- * signature: ${group.screenshot.hash}
232
- */
233
- `;
234
- fallbackCode = document + fallbackCode;
235
- }
217
+ const fallbackCode = userPromptText + componentContextPrompt + placeholderPrompt + `\n // Group${group.groupIndex + 1} \n` + codeWithCustomName;
236
218
  codeSnippets.push(fallbackCode);
237
219
  Logger.log(`组 ${group.groupIndex} 使用降级代码,长度: ${fallbackCode.length}`);
238
220
  return {
@@ -7,7 +7,7 @@ import { ConfigManager, defaultConfigData, } from './config-manager/index.js';
7
7
  import { cleanup as cleanupTauri } from './interceptor/client.js';
8
8
  import { cleanup as cleanupVSCode, getUserInputFromVSCode, isVSCodeAvailable } from './interceptor/vscode.js';
9
9
  import { cleanup as cleanupWeb, getUserInput } from './interceptor/web.js';
10
- import { loadConfig, startHttpServer, stopHttpServer } from './server.js';
10
+ import { loadConfig, startHttpServer, stopHttpServer, generateComponentId, stopSocketServer } from './server.js';
11
11
  import { FileManager } from './utils/file-manager.js';
12
12
  import { updateImageMapIfNeeded } from './utils/update.js';
13
13
  import { Logger } from './utils/logger.js';
@@ -20,6 +20,32 @@ import { promises as fs } from 'fs';
20
20
  import * as path from 'path';
21
21
  import { fileURLToPath } from 'url';
22
22
  import { extractCodeAndComponents } from './utils/extract.js';
23
+ import { trackToolCall } from './utils/tj.js';
24
+ /**
25
+ * 启动http & socket监听
26
+ * @param init 是否是初始化,初始化时需要连接stdio transport
27
+ */
28
+ export async function startListening(init = false) {
29
+ const isStdioMode = process.env.NODE_ENV === 'cli' || process.argv.includes('--stdio');
30
+ const configManager = new ConfigManager('d2c-mcp');
31
+ const port = await getAvailablePort();
32
+ if (isStdioMode) {
33
+ Logger.log(`Stdio模式启动`);
34
+ await loadConfig(mcpServer, configManager);
35
+ if (init) {
36
+ const transport = new StdioServerTransport();
37
+ await mcpServer.connect(transport);
38
+ }
39
+ // 启动 HTTP 服务器 - 仅启用web服务
40
+ await startHttpServer(port, mcpServer, configManager, false);
41
+ }
42
+ else {
43
+ // 启动 HTTP 服务器 - 这是核心功能,不依赖 VSCode 日志
44
+ await startHttpServer(port, mcpServer, configManager, true);
45
+ }
46
+ // 服务器启动成功后再尝试使用 Logger(包含 VSCode 日志)
47
+ Logger.log(`Server started successfully on port ${port}!`);
48
+ }
23
49
  export class D2CMcpServer extends McpServer {
24
50
  figmaApiKey = '';
25
51
  baseURL = '';
@@ -53,6 +79,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
53
79
  local: z.boolean().optional().describe('Whether to use local data cache, default is false'),
54
80
  }, async (args) => {
55
81
  try {
82
+ trackToolCall('d2c_figma');
56
83
  Logger.log(`收到工具调用参数:`, JSON.stringify(args, null, 2));
57
84
  const { fileKey, nodeId, depth, local } = args;
58
85
  let config = await configManager.load();
@@ -71,6 +98,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
71
98
  catch (error) {
72
99
  Logger.log('获取根目录时出错:', error);
73
100
  }
101
+ await startListening();
74
102
  // 没有配置figmaApiKey,无法预览,直接唤起配置页面
75
103
  if (!config.mcp?.figmaApiKey) {
76
104
  hasLaunchWebview = true;
@@ -154,6 +182,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
154
182
  else {
155
183
  configDataString = await getUserInput({ fileKey: fileKey, nodeId: nodeId });
156
184
  }
185
+ console.log('收到网页提交数据', configDataString);
157
186
  if (!configDataString || configDataString.trim() === '') {
158
187
  throw new Error('未提供有效的转码配置');
159
188
  }
@@ -203,7 +232,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
203
232
  const savedPromptSetting = await fileManager.loadPromptSetting(fileKey, nodeId);
204
233
  const convertConfig = config.fileConfigs?.[fileKey] || defaultConfigData;
205
234
  // 获取提示词,优先使用用户保存的提示词,否则使用默认提示词
206
- const frameworkPrompt = savedPromptSetting?.frameworkGuidePrompt;
235
+ const frameworkPrompt = savedPromptSetting?.enableFrameworkGuide ? savedPromptSetting?.frameworkGuidePrompt : '';
207
236
  const chunkPrompt = savedPromptSetting?.chunkOptimizePrompt || chunkOptimizeCodePrompt;
208
237
  const aggregationPrompt = savedPromptSetting?.aggregationOptimizePrompt || aggregationOptimizeCodePrompt;
209
238
  const finalPrompt = savedPromptSetting?.finalOptimizePrompt || finalOptimizeCodePrompt;
@@ -230,7 +259,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
230
259
  isSupportSampling = await processSampling(groupsData, samplingConfig);
231
260
  // 采样完成后,更新组件名并保存到文件
232
261
  try {
233
- const groupsDataToSave = groupsData.map(group => {
262
+ const groupsDataToSave = groupsData.map((group) => {
234
263
  // 将提取的组件名更新到 componentName(仅标记的分组)
235
264
  if (group.marked && group.name) {
236
265
  group.componentName = group.name;
@@ -241,7 +270,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
241
270
  return groupWithoutNodeList;
242
271
  });
243
272
  await fileManager.saveGroupsData(fileKey, nodeId, groupsDataToSave);
244
- Logger.log(`已保存更新后的 groupsData,包含 ${groupsData.filter(g => g.marked).length} 个标记的组件`);
273
+ Logger.log(`已保存更新后的 groupsData,包含 ${groupsData.filter((g) => g.marked).length} 个标记的组件`);
245
274
  }
246
275
  catch (error) {
247
276
  Logger.error(`保存 groupsData 失败:`, error);
@@ -322,7 +351,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
322
351
  role: 'user',
323
352
  content: {
324
353
  type: 'text',
325
- text: aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') + placeholderPrompt,
354
+ text: frameworkPrompt + aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') + placeholderPrompt,
326
355
  },
327
356
  },
328
357
  {
@@ -352,8 +381,24 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
352
381
  Logger.log('处理完成,生成的代码片段数量:', codeSnippets.length);
353
382
  Logger.log('已映射的组件数量:', componentMappings.length);
354
383
  Logger.log('组件上下文数量:', componentContexts.length);
355
- // 构建组件映射提示词(包含组件映射和组件上下文)
356
- const componentMappingPrompt = buildComponentMappingPrompt(componentMappings, componentContexts);
384
+ // 收集标记的组件(用于提示 LLM 调用 mark_components)
385
+ const markedComponents = [];
386
+ if (groupsData && groupsData.length > 0) {
387
+ groupsData.forEach((group) => {
388
+ if (group.marked && group.screenshot?.hash && group.name) {
389
+ markedComponents.push({
390
+ name: group.name,
391
+ signature: group.screenshot.hash,
392
+ });
393
+ }
394
+ });
395
+ Logger.log(`收集到 ${markedComponents.length} 个标记的组件`);
396
+ }
397
+ // 加载完整的组件数据库(用于查找继承链)
398
+ const componentsDatabase = await fileManager.loadComponentsDatabase();
399
+ Logger.log(`加载组件数据库,共 ${componentsDatabase.length} 个组件`);
400
+ // 构建组件映射提示词(包含组件映射、组件上下文、标记组件和完整组件数据库)
401
+ const componentMappingPrompt = buildComponentMappingPrompt(componentMappings, componentContexts, markedComponents, componentsDatabase);
357
402
  // 使用提示词(已包含默认值)
358
403
  return {
359
404
  content: [
@@ -387,6 +432,135 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
387
432
  };
388
433
  }
389
434
  });
435
+ // 标记并保存组件到 components.json
436
+ mcpServer.tool('mark_components', 'Mark and save components to project components.json for future reuse and matching', {
437
+ components: z
438
+ .array(z.object({
439
+ name: z.string().describe('Component name in PascalCase'),
440
+ path: z.string().describe('Component file path relative to project root'),
441
+ signature: z.string().optional().describe('Component Screenshot (SHA256 hash) for matching'),
442
+ type: z.enum(['button', 'input', 'card', 'text', 'image', 'container', 'list', 'custom']).optional(),
443
+ description: z.string().optional(),
444
+ import: z.string().optional().describe('Import statement for the component'),
445
+ props: z
446
+ .array(z.object({
447
+ name: z.string(),
448
+ type: z.string().optional(),
449
+ required: z.boolean().optional(),
450
+ description: z.string().optional(),
451
+ }))
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'),
469
+ }))
470
+ .describe('Array of components to mark and save'),
471
+ }, async (args) => {
472
+ try {
473
+ const { components } = args;
474
+ // 1. 设置 workspaceRoot
475
+ let root = './';
476
+ try {
477
+ const rootRes = await mcpServer.server.listRoots();
478
+ Logger.log('获取根目录:', rootRes);
479
+ root = rootRes.roots[0]?.uri?.slice(7) || './';
480
+ // 设置 fileManager 的工作目录根路径
481
+ fileManager.setWorkspaceRoot(root);
482
+ }
483
+ catch (error) {
484
+ Logger.log('获取根目录时出错:', error);
485
+ }
486
+ // 2. 加载现有组件
487
+ const existingComponents = await fileManager.loadComponentsDatabase();
488
+ // 3. 构建 path 索引(用于去重,path 一致则认为是同一组件)
489
+ const pathIndex = new Map();
490
+ existingComponents.forEach((comp, index) => {
491
+ if (comp.path) {
492
+ pathIndex.set(comp.path, index);
493
+ }
494
+ });
495
+ // 4. 处理新组件(合并逻辑)
496
+ const now = new Date().toISOString();
497
+ let addedCount = 0;
498
+ let updatedCount = 0;
499
+ for (const comp of components) {
500
+ // 使用 path 判断组件是否已存在(组件冲突检测)
501
+ const existingIndex = pathIndex.get(comp.path);
502
+ const storedComponent = {
503
+ id: generateComponentId(),
504
+ name: comp.name,
505
+ type: comp.type || 'custom',
506
+ path: comp.path,
507
+ import: comp.import || `import { ${comp.name} } from '${comp.path}'`,
508
+ props: (comp.props || []).map((p) => ({
509
+ name: p.name,
510
+ type: p.type || 'any',
511
+ required: p.required || false,
512
+ description: p.description,
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,
521
+ description: comp.description,
522
+ };
523
+ if (existingIndex !== undefined) {
524
+ // 更新已存在的组件(保留原始导入时间)
525
+ const originalImportedAt = existingComponents[existingIndex].importedAt;
526
+ existingComponents[existingIndex] = {
527
+ ...storedComponent,
528
+ importedAt: originalImportedAt,
529
+ };
530
+ updatedCount++;
531
+ }
532
+ else {
533
+ // 添加新组件
534
+ existingComponents.push(storedComponent);
535
+ pathIndex.set(comp.path, existingComponents.length - 1);
536
+ addedCount++;
537
+ }
538
+ }
539
+ // 5. 保存到文件
540
+ await fileManager.saveComponentsDatabase(existingComponents);
541
+ // 6. 返回结果
542
+ Logger.log(`mark_components: 新增 ${addedCount} 个,更新 ${updatedCount} 个,共 ${existingComponents.length} 个组件`);
543
+ return {
544
+ content: [
545
+ {
546
+ type: 'text',
547
+ text: `✅ 组件标记完成:新增 ${addedCount} 个,更新 ${updatedCount} 个,共 ${existingComponents.length} 个组件`,
548
+ },
549
+ ],
550
+ };
551
+ }
552
+ catch (error) {
553
+ Logger.error('mark_components 处理失败:', error);
554
+ return {
555
+ content: [
556
+ {
557
+ type: 'text',
558
+ text: `❌ 标记组件失败: ${error instanceof Error ? error.message : String(error)}`,
559
+ },
560
+ ],
561
+ };
562
+ }
563
+ });
390
564
  // 启动 HTTP 服务器
391
565
  async function main() {
392
566
  // 检查是否是版本命令
@@ -760,7 +934,7 @@ async function main() {
760
934
  success: true,
761
935
  framework: frameworkName,
762
936
  configPath: frameworkPath,
763
- created: !configExists
937
+ created: !configExists,
764
938
  }, null, 2));
765
939
  }
766
940
  else {
@@ -778,7 +952,7 @@ async function main() {
778
952
  if (isJsonOutput) {
779
953
  console.log(JSON.stringify({
780
954
  success: false,
781
- error: error.toString()
955
+ error: error.toString(),
782
956
  }, null, 2));
783
957
  }
784
958
  else {
@@ -791,24 +965,8 @@ async function main() {
791
965
  try {
792
966
  // 先输出基本启动信息到控制台,确保即使 VSCode 日志失败也能看到启动过程
793
967
  console.log(`[${new Date().toISOString()}] Starting Figma transcoding interceptor MCP server...`);
794
- const port = await getAvailablePort();
795
968
  // Create config manager instance to pass to server
796
- const configManager = new ConfigManager('d2c-mcp');
797
- const isStdioMode = process.env.NODE_ENV === 'cli' || process.argv.includes('--stdio');
798
- if (isStdioMode) {
799
- Logger.log(`Stdio模式启动`);
800
- await loadConfig(mcpServer, configManager);
801
- const transport = new StdioServerTransport();
802
- await mcpServer.connect(transport);
803
- // 启动 HTTP 服务器 - 仅启用web服务
804
- await startHttpServer(port, mcpServer, configManager, false);
805
- }
806
- else {
807
- // 启动 HTTP 服务器 - 这是核心功能,不依赖 VSCode 日志
808
- await startHttpServer(port, mcpServer, configManager, true);
809
- }
810
- // 服务器启动成功后再尝试使用 Logger(包含 VSCode 日志)
811
- Logger.log(`Server started successfully on port ${port}!`);
969
+ await startListening(true);
812
970
  }
813
971
  catch (error) {
814
972
  // 确保错误信息能够输出,即使 VSCode 日志服务不可用
@@ -852,6 +1010,7 @@ process.on('SIGTERM', async () => {
852
1010
  catch (error) {
853
1011
  // 忽略日志错误,确保清理过程继续
854
1012
  }
1013
+ await stopSocketServer();
855
1014
  cleanupTauri();
856
1015
  cleanupWeb();
857
1016
  cleanupVSCode();