sloth-d2c-mcp 1.0.4-beta69 → 1.0.4-beta71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ /**
2
+ * 映射数据存储
3
+ */
4
+ import { promises as fs } from 'fs';
5
+ import * as path from 'path';
6
+ /**
7
+ * 基于JSON文件的本地存储实现
8
+ */
9
+ export class LocalMappingStorage {
10
+ storageDir;
11
+ mappingsFile;
12
+ mappings = new Map();
13
+ initialized = false;
14
+ constructor(baseDir = '.') {
15
+ this.storageDir = path.join(baseDir, '.mcp', 'component-mappings');
16
+ this.mappingsFile = path.join(this.storageDir, 'mappings.json');
17
+ }
18
+ /**
19
+ * 初始化存储
20
+ */
21
+ async initialize() {
22
+ if (this.initialized)
23
+ return;
24
+ try {
25
+ // 确保目录存在
26
+ await fs.mkdir(this.storageDir, { recursive: true });
27
+ // 加载现有映射
28
+ try {
29
+ const data = await fs.readFile(this.mappingsFile, 'utf-8');
30
+ const mappingsArray = JSON.parse(data);
31
+ for (const mapping of mappingsArray) {
32
+ // 转换日期字符串为Date对象
33
+ mapping.createdAt = new Date(mapping.createdAt);
34
+ mapping.updatedAt = new Date(mapping.updatedAt);
35
+ this.mappings.set(mapping.id, mapping);
36
+ }
37
+ }
38
+ catch (error) {
39
+ if (error.code !== 'ENOENT') {
40
+ console.warn('加载映射文件失败:', error);
41
+ }
42
+ // 文件不存在是正常的,创建新的
43
+ }
44
+ this.initialized = true;
45
+ }
46
+ catch (error) {
47
+ console.error('初始化存储失败:', error);
48
+ throw error;
49
+ }
50
+ }
51
+ /**
52
+ * 持久化映射到文件
53
+ */
54
+ async persist() {
55
+ const mappingsArray = Array.from(this.mappings.values());
56
+ await fs.writeFile(this.mappingsFile, JSON.stringify(mappingsArray, null, 2), 'utf-8');
57
+ }
58
+ async saveMapping(mapping) {
59
+ await this.initialize();
60
+ this.mappings.set(mapping.id, mapping);
61
+ await this.persist();
62
+ }
63
+ async getMapping(id) {
64
+ await this.initialize();
65
+ return this.mappings.get(id) || null;
66
+ }
67
+ async findMappingByFigmaNode(figmaNodeId) {
68
+ await this.initialize();
69
+ for (const mapping of this.mappings.values()) {
70
+ if (mapping.figmaNodeId === figmaNodeId) {
71
+ return mapping;
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ async listMappings(filter) {
77
+ await this.initialize();
78
+ let mappings = Array.from(this.mappings.values());
79
+ if (filter) {
80
+ if (filter.platform) {
81
+ mappings = mappings.filter((m) => m.platform === filter.platform);
82
+ }
83
+ if (filter.figmaFileId) {
84
+ mappings = mappings.filter((m) => m.figmaNodeId.startsWith(filter.figmaFileId));
85
+ }
86
+ if (filter.componentId) {
87
+ mappings = mappings.filter((m) => m.componentId === filter.componentId);
88
+ }
89
+ if (filter.minConfidence !== undefined) {
90
+ mappings = mappings.filter((m) => m.confidence >= filter.minConfidence);
91
+ }
92
+ }
93
+ return mappings;
94
+ }
95
+ async deleteMapping(id) {
96
+ await this.initialize();
97
+ this.mappings.delete(id);
98
+ await this.persist();
99
+ }
100
+ async updateMapping(id, updates) {
101
+ await this.initialize();
102
+ const existing = this.mappings.get(id);
103
+ if (!existing) {
104
+ throw new Error(`映射不存在: ${id}`);
105
+ }
106
+ const updated = {
107
+ ...existing,
108
+ ...updates,
109
+ updatedAt: new Date(),
110
+ };
111
+ this.mappings.set(id, updated);
112
+ await this.persist();
113
+ }
114
+ /**
115
+ * 清空所有映射
116
+ */
117
+ async clear() {
118
+ await this.initialize();
119
+ this.mappings.clear();
120
+ await this.persist();
121
+ }
122
+ /**
123
+ * 导出映射数据
124
+ */
125
+ async export() {
126
+ await this.initialize();
127
+ return Array.from(this.mappings.values());
128
+ }
129
+ /**
130
+ * 导入映射数据
131
+ */
132
+ async import(mappings) {
133
+ await this.initialize();
134
+ for (const mapping of mappings) {
135
+ // 确保日期对象正确
136
+ mapping.createdAt = new Date(mapping.createdAt);
137
+ mapping.updatedAt = new Date(mapping.updatedAt);
138
+ this.mappings.set(mapping.id, mapping);
139
+ }
140
+ await this.persist();
141
+ }
142
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 组件映射系统 - 类型定义
3
+ */
4
+ export {};
@@ -156,5 +156,85 @@ export class ConfigManager {
156
156
  }
157
157
  await this.save(config);
158
158
  }
159
+ /**
160
+ * 添加框架到配置
161
+ * @param framework - 框架名称
162
+ */
163
+ async addFramework(framework) {
164
+ const config = await this.load();
165
+ if (!config.frameworks) {
166
+ config.frameworks = [];
167
+ }
168
+ const name = framework.trim();
169
+ if (!config.frameworks.find((f) => f.value === name)) {
170
+ config.frameworks.push({
171
+ label: name,
172
+ value: name.toLowerCase(),
173
+ isCustom: true,
174
+ });
175
+ await this.save(config);
176
+ }
177
+ }
178
+ /**
179
+ * 获取框架列表
180
+ * @returns Promise<Framework[]> - 框架列表
181
+ */
182
+ async getFrameworks() {
183
+ const config = await this.load();
184
+ return config.frameworks || [];
185
+ }
186
+ /**
187
+ * 获取框架配置文件路径
188
+ * @param framework - 框架名称
189
+ * @returns string - 框架配置文件路径
190
+ */
191
+ getFrameworkConfigPath(framework) {
192
+ return path.join(this.paths.config, `${framework}.json`);
193
+ }
194
+ /**
195
+ * 保存框架配置
196
+ * @param framework - 框架名称
197
+ * @param promptSetting - 提示词配置
198
+ */
199
+ async saveFrameworkConfig(framework, promptSetting) {
200
+ const frameworkPath = this.getFrameworkConfigPath(framework);
201
+ await fs.mkdir(path.dirname(frameworkPath), { recursive: true });
202
+ await fs.writeFile(frameworkPath, JSON.stringify(promptSetting, null, 2));
203
+ }
204
+ /**
205
+ * 加载框架配置
206
+ * @param framework - 框架名称
207
+ * @returns Promise<object> - 框架配置对象
208
+ */
209
+ async loadFrameworkConfig(framework) {
210
+ try {
211
+ const frameworkPath = this.getFrameworkConfigPath(framework);
212
+ const data = await fs.readFile(frameworkPath, 'utf-8');
213
+ return JSON.parse(data);
214
+ }
215
+ catch (err) {
216
+ if (err.code === 'ENOENT') {
217
+ // 文件不存在,返回空对象
218
+ return {};
219
+ }
220
+ Logger.error(`加载框架配置出错: ${err.toString()}`);
221
+ return {};
222
+ }
223
+ }
224
+ /**
225
+ * 检查框架配置文件是否存在
226
+ * @param framework - 框架名称
227
+ * @returns Promise<boolean> - 如果框架配置文件存在则返回true
228
+ */
229
+ async frameworkConfigExists(framework) {
230
+ try {
231
+ const frameworkPath = this.getFrameworkConfigPath(framework);
232
+ await fs.access(frameworkPath);
233
+ return true;
234
+ }
235
+ catch {
236
+ return false;
237
+ }
238
+ }
159
239
  }
160
240
  export default ConfigManager;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * 处理组件上下文:解析 @组件名 并构建组件说明
3
+ * @param group 分组数据
4
+ * @returns 处理后的用户提示词和组件上下文提示词
5
+ */
6
+ export function buildComponentContext(group) {
7
+ let processedUserPrompt = group.userPrompt || '';
8
+ let componentContextPrompt = '';
9
+ if (group.componentContext && Array.isArray(group.componentContext) && group.componentContext.length > 0) {
10
+ // 解析 userPrompt 中的 @组件名(react-mentions 格式:@[组件名](组件名))
11
+ const mentionRegex = /@\[([^\]]+)\]\(([^)]+)\)/g;
12
+ const componentMap = new Map();
13
+ // 构建组件映射表
14
+ group.componentContext.forEach((comp) => {
15
+ componentMap.set(comp.name, comp);
16
+ });
17
+ // 替换 @组件名 为组件详细信息
18
+ processedUserPrompt = processedUserPrompt.replace(mentionRegex, (match, display, id) => {
19
+ const component = componentMap.get(id || display);
20
+ if (component) {
21
+ const libInfo = component.lib ? ` (${component.lib})` : '';
22
+ const descInfo = component.description ? ` - ${component.description}` : '';
23
+ return `[组件: ${component.name}${libInfo}${descInfo}]`;
24
+ }
25
+ return match; // 如果找不到组件,保持原样
26
+ });
27
+ // 构建组件上下文说明
28
+ componentContextPrompt = '\n\n## 可用组件说明\n\n以下组件已加入上下文,你可以在代码生成时使用这些组件:\n\n';
29
+ group.componentContext.forEach((comp) => {
30
+ const propsInfo = comp.props && comp.props.length > 0 ? comp.props.map((p) => `${p.name}${p.required ? '*' : ''}: ${p.type || 'any'}`).join(', ') : '无 Props';
31
+ const libInfo = comp.lib ? ` (来自 ${comp.lib})` : ' (项目组件)';
32
+ const descInfo = comp.description ? `\n- **描述**: ${comp.description}` : '';
33
+ componentContextPrompt += `### ${comp.name}${libInfo}\n`;
34
+ componentContextPrompt += `- **Props**: ${propsInfo}${descInfo}\n\n`;
35
+ });
36
+ componentContextPrompt +=
37
+ '注意:在生成代码时,请根据设计稿的需求,合理使用上述组件,并正确传递 props 参数。\n';
38
+ }
39
+ return { processedUserPrompt, componentContextPrompt };
40
+ }
41
+ /**
42
+ * 构建用户提示词文本
43
+ * @param userPrompt 用户提示词
44
+ * @returns 格式化的提示词文本
45
+ */
46
+ export function buildUserPromptText(userPrompt) {
47
+ if (!userPrompt)
48
+ return '';
49
+ return '\n## 用户提示词\n以下是用户针对该代码的优化提出的补充说明和要求,请在优化代码时特别关注这些指导并尽可能实现:\n' + userPrompt;
50
+ }
51
+ /**
52
+ * 构建组件映射提示词(包含组件映射和组件上下文)
53
+ * @param componentMappings 组件映射数组
54
+ * @param componentContexts 所有组的组件上下文数组
55
+ * @returns 组件映射提示词
56
+ */
57
+ export function buildComponentMappingPrompt(componentMappings, componentContexts = []) {
58
+ // 收集所有组件:组件映射 + 组件上下文(按名称去重)
59
+ const allComponents = new Map();
60
+ // 添加组件映射的组件
61
+ componentMappings.forEach((mapping) => {
62
+ allComponents.set(mapping.component.name, mapping.component);
63
+ });
64
+ // 添加组件上下文的组件(去重,不覆盖已存在的)
65
+ componentContexts.forEach((context) => {
66
+ context.components.forEach((comp) => {
67
+ if (!allComponents.has(comp.name)) {
68
+ allComponents.set(comp.name, comp);
69
+ }
70
+ });
71
+ });
72
+ if (allComponents.size === 0) {
73
+ return '';
74
+ }
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
+ // 构建导入方式文案
83
+ const importType = component.importType || 'default'; // 默认为 default 导入
84
+ const importTypeText = importType === 'named' ? '具名导入' : '默认导入';
85
+ prompt += `### ${component.name}${libInfo}\n`;
86
+ prompt += `- **文件路径**: ${component.path}\n`;
87
+ prompt += `- **导入方式**: ${importTypeText}\n`;
88
+ prompt += `- **Props**: ${propsInfo}${descInfo}\n\n`;
89
+ });
90
+ prompt +=
91
+ '**重要**:在最终写入代码时,请严格按照上述"导入方式"来编写 import 语句。"具名导入"表示 import { 组件名 } from \'路径\',"默认导入"表示 import 组件名 from \'路径\'。请根据设计稿需求合理传递 props 参数。请按照项目路径规范合理使用相对路径和路径别名引入组件。\n';
92
+ return prompt;
93
+ }
94
+ /**
95
+ * 构建子组占位符提示词
96
+ * @param placeholders 占位符信息数组
97
+ * @returns 占位符提示词
98
+ */
99
+ export function buildPlaceholderPrompt(placeholders) {
100
+ if (placeholders.length === 0) {
101
+ return '';
102
+ }
103
+ let prompt = '\n\n## 子组件占位符\n\n**重要**:以下占位符代表已实现的子组件,请转换格式但不要重新实现其内部逻辑。\n\n';
104
+ placeholders.forEach((placeholder) => {
105
+ const kebabName = placeholder.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
106
+ prompt += `- \`<${kebabName}>\` → \`<${placeholder.name} />\`\n`;
107
+ });
108
+ prompt += '\n将 HTML 标签转换为 React 组件引用,不需要添加对应的 import 语句,保持其在布局中的位置。\n';
109
+ return prompt;
110
+ }