sloth-d2c-mcp 1.0.4-beta100

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.
Files changed (34) hide show
  1. package/README.md +83 -0
  2. package/cli/run.js +328 -0
  3. package/cli/sloth-server.log +1622 -0
  4. package/dist/build/config-manager/index.js +240 -0
  5. package/dist/build/core/prompt-builder.js +366 -0
  6. package/dist/build/core/sampling.js +375 -0
  7. package/dist/build/core/types.js +1 -0
  8. package/dist/build/index.js +852 -0
  9. package/dist/build/interceptor/client.js +142 -0
  10. package/dist/build/interceptor/vscode.js +143 -0
  11. package/dist/build/interceptor/web.js +28 -0
  12. package/dist/build/plugin/index.js +4 -0
  13. package/dist/build/plugin/loader.js +349 -0
  14. package/dist/build/plugin/manager.js +129 -0
  15. package/dist/build/plugin/types.js +6 -0
  16. package/dist/build/server.js +2116 -0
  17. package/dist/build/socket-client.js +166 -0
  18. package/dist/build/socket-server.js +260 -0
  19. package/dist/build/utils/client-capabilities.js +143 -0
  20. package/dist/build/utils/extract.js +168 -0
  21. package/dist/build/utils/file-manager.js +868 -0
  22. package/dist/build/utils/image-matcher.js +154 -0
  23. package/dist/build/utils/logger.js +90 -0
  24. package/dist/build/utils/opencv-loader.js +70 -0
  25. package/dist/build/utils/prompt-parser.js +46 -0
  26. package/dist/build/utils/tj.js +139 -0
  27. package/dist/build/utils/update.js +100 -0
  28. package/dist/build/utils/utils.js +184 -0
  29. package/dist/build/utils/vscode-logger.js +133 -0
  30. package/dist/build/utils/webpack-substitutions.js +196 -0
  31. package/dist/interceptor-web/dist/build-report.json +18 -0
  32. package/dist/interceptor-web/dist/detail.html +1 -0
  33. package/dist/interceptor-web/dist/index.html +1 -0
  34. package/package.json +96 -0
@@ -0,0 +1,240 @@
1
+ import { promises as fs } from 'fs';
2
+ import * as path from 'path';
3
+ import envPaths from 'env-paths';
4
+ import { Logger } from '../utils/logger.js';
5
+ export const defaultConfigData = {
6
+ convertSetting: {
7
+ framework: 'react',
8
+ convertScale: 1,
9
+ colorValue: 'hex',
10
+ },
11
+ imageSetting: {
12
+ imageStorageType: 'local',
13
+ imageStorageScale: 1,
14
+ imageIconType: 'svg',
15
+ // 本地存储配置
16
+ imageStoragePath: './static',
17
+ imageStorageNamingRule: '[name]_[contenthash:6]',
18
+ // OSS 配置
19
+ ossRegion: '',
20
+ ossAccessKeyId: '',
21
+ ossAccessKeySecret: '',
22
+ ossBucket: '',
23
+ ossPath: '',
24
+ ossCdnDomain: '',
25
+ // 接口上传配置
26
+ imageStorageApiUrl: '',
27
+ imageStorageApiFileField: 'file',
28
+ imageStorageApiUrlField: 'url',
29
+ imageStorageApiCustomHeader: '',
30
+ imageStorageApiCustomBody: '',
31
+ },
32
+ };
33
+ // 配置管理器类
34
+ export class ConfigManager {
35
+ paths; // 应用路径配置
36
+ configPath; // 配置文件路径
37
+ constructor(appName) {
38
+ this.paths = envPaths(appName);
39
+ this.configPath = path.join(this.paths.config, 'config.json');
40
+ }
41
+ /**
42
+ * 从文件加载配置
43
+ * @returns Promise<ConfigData> - 配置对象,如果文件不存在则返回空对象
44
+ */
45
+ async load() {
46
+ try {
47
+ const data = await fs.readFile(this.configPath, 'utf-8');
48
+ return JSON.parse(data);
49
+ }
50
+ catch (err) {
51
+ Logger.error(`配置文件加载出错。${err.toString()}`);
52
+ return {};
53
+ }
54
+ }
55
+ /**
56
+ * 保存配置到文件
57
+ * @param data - 要保存的配置对象
58
+ */
59
+ async save(data) {
60
+ try {
61
+ await fs.mkdir(path.dirname(this.configPath), { recursive: true }); // 确保目录存在
62
+ await fs.writeFile(this.configPath, JSON.stringify(data, null, 2)); // 格式化保存JSON
63
+ }
64
+ catch (error) {
65
+ Logger.error(error);
66
+ }
67
+ }
68
+ /**
69
+ * 获取配置文件路径
70
+ * @returns string - 配置文件的完整路径
71
+ */
72
+ getConfigPath() {
73
+ return this.configPath;
74
+ }
75
+ /**
76
+ * 检查配置文件是否存在
77
+ * @returns Promise<boolean> - 如果配置文件存在则返回true
78
+ */
79
+ async exists() {
80
+ try {
81
+ await fs.access(this.configPath);
82
+ return true;
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
88
+ /**
89
+ * 删除配置文件
90
+ */
91
+ async delete() {
92
+ try {
93
+ await fs.unlink(this.configPath);
94
+ }
95
+ catch (err) {
96
+ if (err.code !== 'ENOENT') {
97
+ throw err; // 如果不是文件不存在错误,则抛出异常
98
+ }
99
+ }
100
+ }
101
+ /**
102
+ * 获取特定 fileKey 的配置,如果不存在则返回默认配置
103
+ * @param fileKey - 文件键
104
+ * @returns Promise<{ convertSetting: ConvertSetting, imageSetting: ImageSetting }> - 合并后的配置
105
+ */
106
+ async getFileConfig(fileKey) {
107
+ const config = await this.load();
108
+ // 获取默认配置
109
+ const defaultConvertSetting = config.convertSetting || {};
110
+ const defaultImageSetting = config.imageSetting || {};
111
+ // 获取 fileKey 特定的配置
112
+ const fileConfig = config.fileConfigs?.[fileKey] || {};
113
+ // 合并配置:fileKey 配置优先于默认配置
114
+ const convertSetting = { ...defaultConvertSetting, ...fileConfig.convertSetting };
115
+ const imageSetting = { ...defaultImageSetting, ...fileConfig.imageSetting };
116
+ return { convertSetting, imageSetting };
117
+ }
118
+ /**
119
+ * 保存特定 fileKey 的配置
120
+ * @param fileKey - 文件键
121
+ * @param convertSetting - 转换设置(可选)
122
+ * @param imageSetting - 图片设置(可选)
123
+ */
124
+ async saveFileConfig(fileKey, convertSetting, imageSetting) {
125
+ const config = await this.load();
126
+ // 初始化 fileConfigs 如果不存在
127
+ if (!config.fileConfigs) {
128
+ config.fileConfigs = {};
129
+ }
130
+ // 初始化特定 fileKey 的配置如果不存在
131
+ if (!config.fileConfigs[fileKey]) {
132
+ config.fileConfigs[fileKey] = {};
133
+ }
134
+ // 更新配置
135
+ if (convertSetting) {
136
+ config.fileConfigs[fileKey].convertSetting = convertSetting;
137
+ }
138
+ if (imageSetting) {
139
+ config.fileConfigs[fileKey].imageSetting = imageSetting;
140
+ }
141
+ // 保存配置
142
+ await this.save(config);
143
+ }
144
+ /**
145
+ * 更新默认配置
146
+ * @param convertSetting - 默认转换设置(可选)
147
+ * @param imageSetting - 默认图片设置(可选)
148
+ */
149
+ async updateDefaultConfig(convertSetting, imageSetting) {
150
+ const config = await this.load();
151
+ if (convertSetting) {
152
+ config.convertSetting = convertSetting;
153
+ }
154
+ if (imageSetting) {
155
+ config.imageSetting = imageSetting;
156
+ }
157
+ await this.save(config);
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
+ }
239
+ }
240
+ export default ConfigManager;
@@ -0,0 +1,366 @@
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
+ // 替换 @组件名 为组件详细信息(只替换组件,不替换 Markdown 文件)
18
+ processedUserPrompt = processedUserPrompt.replace(mentionRegex, (match, display, id) => {
19
+ // 如果 id 包含路径分隔符,说明是文件,保持原样
20
+ if (id.includes('/') || id.includes('\\')) {
21
+ return match;
22
+ }
23
+ const component = componentMap.get(id || display);
24
+ if (component) {
25
+ const libInfo = component.lib ? ` (${component.lib})` : '';
26
+ const descInfo = component.description ? ` - ${component.description}` : '';
27
+ return `[组件: ${component.name}${libInfo}${descInfo}]`;
28
+ }
29
+ return match; // 如果找不到组件,保持原样
30
+ });
31
+ // 构建组件上下文说明
32
+ componentContextPrompt = '\n\n## 可用组件说明\n\n以下组件已加入上下文,你可以在代码生成时使用这些组件:\n\n';
33
+ group.componentContext.forEach((comp) => {
34
+ const propsInfo = comp.props && comp.props.length > 0 ? comp.props.map((p) => `${p.name}${p.required ? '*' : ''}: ${p.type || 'any'}`).join(', ') : '无 Props';
35
+ const libInfo = comp.lib ? ` (来自 ${comp.lib})` : ' (项目组件)';
36
+ const descInfo = comp.description ? `\n- **描述**: ${comp.description}` : '';
37
+ componentContextPrompt += `### ${comp.name}${libInfo}\n`;
38
+ componentContextPrompt += `- **Props**: ${propsInfo}${descInfo}\n\n`;
39
+ });
40
+ componentContextPrompt += '注意:在生成代码时,请根据设计稿的需求,合理使用上述组件,并正确传递 props 参数。\n';
41
+ }
42
+ return { processedUserPrompt, componentContextPrompt };
43
+ }
44
+ /**
45
+ * 处理 Markdown 文件上下文:解析 @文件名 并构建文档上下文提示词
46
+ * @param group 分组数据
47
+ * @returns 处理后的用户提示词和 Markdown 上下文提示词
48
+ */
49
+ export function buildMarkdownContext(group) {
50
+ let processedUserPrompt = group.userPrompt || '';
51
+ let markdownContextPrompt = '';
52
+ if (group.markdownContext && Array.isArray(group.markdownContext) && group.markdownContext.length > 0) {
53
+ // 解析 userPrompt 中的 @文件名(react-mentions 格式:@[文件名](文件路径))
54
+ const mentionRegex = /@\[([^\]]+)\]\(([^)]+)\)/g;
55
+ const markdownMap = new Map();
56
+ // 构建 Markdown 文件映射表
57
+ group.markdownContext.forEach((file) => {
58
+ markdownMap.set(file.path, file);
59
+ });
60
+ // 替换 @文件名 为文件详细信息(只替换文件,不替换组件)
61
+ processedUserPrompt = processedUserPrompt.replace(mentionRegex, (match, _display, id) => {
62
+ // 如果 id 不包含路径分隔符,说明是组件名,保持原样
63
+ if (!id.includes('/') && !id.includes('\\')) {
64
+ return match;
65
+ }
66
+ const markdownFile = markdownMap.get(id);
67
+ if (markdownFile) {
68
+ return `[文档: ${markdownFile.name}]`;
69
+ }
70
+ return match; // 如果找不到文件,保持原样
71
+ });
72
+ // 构建 Markdown 上下文说明
73
+ markdownContextPrompt = '\n\n## 参考文档\n\n以下是用户提供的参考文档,请在代码生成时参考这些文档中的规范、示例和最佳实践:\n\n';
74
+ group.markdownContext.forEach((file) => {
75
+ markdownContextPrompt += `### 📄 ${file.name}\n\n`;
76
+ if (file.content) {
77
+ markdownContextPrompt += '```markdown\n';
78
+ markdownContextPrompt += file.content;
79
+ markdownContextPrompt += '\n```\n\n';
80
+ }
81
+ else {
82
+ markdownContextPrompt += `**文件路径**: ${file.path}\n\n`;
83
+ }
84
+ });
85
+ markdownContextPrompt += '**重要**:请仔细阅读上述参考文档,遵循文档中的编码规范、API 使用方式和最佳实践,确保生成的代码符合项目要求。\n';
86
+ }
87
+ return { processedUserPrompt, markdownContextPrompt };
88
+ }
89
+ /**
90
+ * 构建用户提示词文本
91
+ * @param userPrompt 用户提示词
92
+ * @returns 格式化的提示词文本
93
+ */
94
+ export function buildUserPromptText(userPrompt) {
95
+ if (!userPrompt)
96
+ return '';
97
+ return '\n## 用户提示词\n以下是用户针对该代码的优化提出的补充说明和要求,请在优化代码时特别关注这些指导并尽可能实现:\n' + userPrompt;
98
+ }
99
+ /**
100
+ * 递归获取组件的继承链
101
+ * @param component 当前组件
102
+ * @param allComponents 所有组件映射表
103
+ * @param visited 已访问的组件名称(防止循环引用)
104
+ * @returns 继承链数组(从当前组件到最顶层父类)
105
+ */
106
+ function getInheritanceChain(component, allComponents, visited = new Set()) {
107
+ const chain = [];
108
+ // 防止循环引用
109
+ if (visited.has(component.name)) {
110
+ return chain;
111
+ }
112
+ visited.add(component.name);
113
+ // 如果有父类,递归查找
114
+ if (component.super) {
115
+ const parentComponent = allComponents.get(component.super);
116
+ if (parentComponent) {
117
+ chain.push(parentComponent);
118
+ // 递归获取父类的继承链
119
+ const parentChain = getInheritanceChain(parentComponent, allComponents, visited);
120
+ chain.push(...parentChain);
121
+ }
122
+ }
123
+ return chain;
124
+ }
125
+ /**
126
+ * 格式化组件信息为提示词
127
+ * @param component 组件信息
128
+ * @param isParent 是否是父类组件
129
+ * @returns 格式化的组件提示词
130
+ */
131
+ function formatComponentPrompt(component, isParent = false) {
132
+ let prompt = '';
133
+ const propsInfo = component.props && component.props.length > 0
134
+ ? component.props.map((p) => `${p.name}${p.required ? ' (必需)' : ' (可选)'}: ${p.type || 'any'} - ${p.description}`).join(', ')
135
+ : '无';
136
+ const libInfo = component.lib ? ` (来自 ${component.lib})` : ' (项目组件)';
137
+ const descInfo = component.description ? `\n- **描述**: ${component.description}` : '';
138
+ const parentPrefix = isParent ? '#### ' : '### ';
139
+ const parentLabel = isParent ? ' [父类]' : '';
140
+ prompt += `${parentPrefix}${component.name}${libInfo}${parentLabel}\n`;
141
+ prompt += `- **文件路径**: ${component.path}\n`;
142
+ prompt += `- **导入方式**: ${component.import}\n`;
143
+ prompt += `- **Props**: ${propsInfo}${descInfo}\n`;
144
+ // 如果有 functions,也输出方法信息
145
+ if (component.functions && component.functions.length > 0) {
146
+ const functionsInfo = component.functions
147
+ .map((f) => {
148
+ const paramsStr = f.params && f.params.length > 0 ? f.params.map((p) => `${p.name}: ${p.type}`).join(', ') : '';
149
+ const returnStr = f.returnType ? `: ${f.returnType}` : '';
150
+ const descStr = f.description ? ` - ${f.description}` : '';
151
+ return `${f.name}(${paramsStr})${returnStr}${descStr}`;
152
+ })
153
+ .join('; ');
154
+ prompt += `- **方法**: ${functionsInfo}\n`;
155
+ }
156
+ // 如果有 super,标注继承关系
157
+ if (component.super) {
158
+ prompt += `- **继承自**: ${component.super}\n`;
159
+ }
160
+ prompt += '\n';
161
+ return prompt;
162
+ }
163
+ /**
164
+ * 构建组件映射提示词(包含组件映射和组件上下文和组件标记)
165
+ * @param componentMappings 组件映射数组
166
+ * @param componentContexts 所有组的组件上下文数组
167
+ * @param markedComponents 标记的组件数组
168
+ * @param componentsDatabase 完整的组件数据库(用于查找继承链)
169
+ * @returns 组件映射提示词
170
+ */
171
+ export function buildComponentMappingPrompt(componentMappings, componentContexts = [], markedComponents = [], componentsDatabase = []) {
172
+ let prompt = '';
173
+ // 构建完整的组件数据库映射表(用于查找继承链)
174
+ const databaseMap = new Map();
175
+ componentsDatabase.forEach((comp) => {
176
+ databaseMap.set(comp.name, comp);
177
+ });
178
+ // 收集所有组件:组件映射 + 组件上下文(按名称去重)
179
+ const allComponents = new Map();
180
+ // 添加组件映射的组件
181
+ componentMappings.forEach((mapping) => {
182
+ allComponents.set(mapping.component.name, mapping.component);
183
+ });
184
+ // 添加组件上下文的组件(去重,不覆盖已存在的)
185
+ componentContexts.forEach((context) => {
186
+ context.components.forEach((comp) => {
187
+ if (!allComponents.has(comp.name)) {
188
+ allComponents.set(comp.name, comp);
189
+ }
190
+ });
191
+ });
192
+ // 构建可用组件提示词
193
+ if (allComponents.size > 0) {
194
+ prompt += '\n\n## 可用组件\n\n以下是项目中可用的组件,请在最终代码中使用这些组件:\n\n';
195
+ // 记录已输出的组件,避免重复输出
196
+ const outputtedComponents = new Set();
197
+ allComponents.forEach((component) => {
198
+ // 输出当前组件
199
+ if (!outputtedComponents.has(component.path)) {
200
+ outputtedComponents.add(component.path);
201
+ prompt += formatComponentPrompt(component, false);
202
+ }
203
+ // 递归查找并输出继承链(从完整数据库中查找)
204
+ const inheritanceChain = getInheritanceChain(component, databaseMap);
205
+ if (inheritanceChain.length > 0) {
206
+ inheritanceChain.forEach((parentComp) => {
207
+ // 避免重复输出
208
+ if (!outputtedComponents.has(parentComp.path)) {
209
+ outputtedComponents.add(parentComp.path);
210
+ prompt += formatComponentPrompt(parentComp, true);
211
+ }
212
+ });
213
+ }
214
+ });
215
+ prompt +=
216
+ '**重要**:在最终写入代码时,请根据设计稿需求合理传递组件参数。请按照项目路径规范合理引入组件。如果组件有继承关系,请注意父类的参数和方法也可以使用。\n';
217
+ }
218
+ // 构建组件映射的设计稿代码(用于 AI 提取组件参数)
219
+ const mappingsWithCode = componentMappings.filter((m) => m.code);
220
+ if (mappingsWithCode.length > 0) {
221
+ prompt += '\n\n## 组件映射设计稿代码\n\n';
222
+ prompt += '以下是已映射组件对应的设计稿绝对定位代码,请根据这些代码提取信息,在使用组件时传入相应参数:\n\n';
223
+ mappingsWithCode.forEach((mapping) => {
224
+ prompt += `### ${mapping.groupName}\n\n`;
225
+ prompt += '```html\n';
226
+ prompt += mapping.code;
227
+ prompt += '\n```\n\n';
228
+ });
229
+ prompt += '**重要**:请从上述 HTML 代码中提取信息,作为对应组件的初始化参数。\n';
230
+ }
231
+ // 构建标记组件提示词
232
+ if (markedComponents.length > 0) {
233
+ prompt += '\n\n## 组件标记任务\n\n';
234
+ prompt += '以下组件已被用户标记为可复用组件,**请在写入代码后调用 `mark_components` 工具保存这些组件信息**:\n\n';
235
+ prompt += '| 组件名 | signature |\n';
236
+ prompt += '|--------|-----------|';
237
+ markedComponents.forEach((comp) => {
238
+ prompt += `\n| ${comp.name} | ${comp.signature} |`;
239
+ });
240
+ prompt += '\n\n调用示例:\n';
241
+ prompt += '```json\n';
242
+ prompt += JSON.stringify({
243
+ components: markedComponents.map((comp) => ({
244
+ name: comp.name,
245
+ path: '按实际写入路径填写',
246
+ signature: comp.signature,
247
+ import: '按实际写入路径填写导入语句',
248
+ description: '组件的功能描述',
249
+ })),
250
+ }, null, 2);
251
+ prompt += '\n```\n\n';
252
+ prompt += '**重要**:组件标记后可在后续转码中自动匹配复用\n';
253
+ }
254
+ return prompt;
255
+ }
256
+ /**
257
+ * 构建子组占位符提示词
258
+ * @param placeholders 占位符信息数组
259
+ * @returns 占位符提示词
260
+ */
261
+ export function buildPlaceholderPrompt(placeholders, isSupportSampling = true) {
262
+ if (placeholders.length === 0) {
263
+ return '';
264
+ }
265
+ let prompt = `\n\n## 子组件占位符\n\n**重要**:以下占位符代表已实现的子组件${isSupportSampling ? ',请转换格式但不要重新实现其内部逻辑。' : ''}\n\n`;
266
+ placeholders.forEach((placeholder) => {
267
+ const kebabName = placeholder.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
268
+ prompt += `- \`<${kebabName}>\`\n`;
269
+ });
270
+ prompt += '\n将 HTML 标签转换为真实组件调用,但不需要添加对应的 import 语句,保持其在布局中的位置。\n';
271
+ return prompt;
272
+ }
273
+ /**
274
+ * 构建完整的 AI 代码修改提示词
275
+ * 用于用户确认变更后,生成可直接发送给 AI 的完整提示词
276
+ * @param confirmedChanges 用户确认的变更点列表
277
+ * @param options 配置选项
278
+ * @returns 完整的 AI 提示词
279
+ */
280
+ export function buildFullUpdatePromptForAI(confirmedChanges, options = {}) {
281
+ const { fileKey, nodeId, htmlDiff } = options;
282
+ if (!confirmedChanges || confirmedChanges.length === 0) {
283
+ return '没有需要修改的变更点。';
284
+ }
285
+ const typeLabels = {
286
+ layout: '布局变更',
287
+ style: '样式变更',
288
+ content: '内容变更',
289
+ structure: '结构变更',
290
+ };
291
+ let prompt = `# 设计稿变更 - 代码修改任务
292
+
293
+ ## 任务背景
294
+
295
+ 用户的 UI 设计稿发生了变更,需要根据以下变更点修改现有代码。这是一个**增量修改任务**,不是全新创建。
296
+
297
+ ## 前置要求
298
+
299
+ **请先确认当前上下文中是否包含需要修改的代码。**
300
+
301
+ 如果当前上下文中没有相关代码,请让用户:
302
+ 1. 提供需要修改的代码文件路径,以便读取
303
+ 2. 或直接粘贴需要修改的代码内容
304
+
305
+ ---
306
+
307
+ ## 变更信息
308
+
309
+ `;
310
+ // 添加设计稿信息(如果有)
311
+ if (fileKey) {
312
+ prompt += `- **设计稿标识**: ${fileKey}${nodeId ? ` / ${nodeId}` : ''}\n`;
313
+ }
314
+ prompt += `- **变更数量**: ${confirmedChanges.length} 个\n\n`;
315
+ // 变更点详情
316
+ prompt += `## 变更点详情\n\n`;
317
+ confirmedChanges.forEach((change, index) => {
318
+ prompt += `### ${index + 1}. ${change.title}\n\n`;
319
+ prompt += `- **类型**: ${typeLabels[change.type] || change.type}\n`;
320
+ prompt += `- **描述**: ${change.description}\n`;
321
+ prompt += `- **建议操作**: ${change.suggestedAction}\n`;
322
+ if (change.userPrompt) {
323
+ prompt += `- **用户补充**: ${change.userPrompt}\n`;
324
+ }
325
+ prompt += `\n`;
326
+ });
327
+ // 添加 HTML Diff(如果有)
328
+ if (htmlDiff && htmlDiff.trim()) {
329
+ prompt += `## 设计稿 HTML Diff
330
+
331
+ 以下是新旧设计稿的 HTML 差异,可帮助你精确定位需要修改的样式和布局:
332
+
333
+ \`\`\`diff
334
+ ${htmlDiff}
335
+ \`\`\`
336
+
337
+ **说明**:
338
+ - \`---\` 开头的行表示被删除的内容(旧设计稿)
339
+ - \`+++\` 开头的行表示新增的内容(新设计稿)
340
+ - HTML 使用 Tailwind CSS 类名
341
+
342
+ `;
343
+ }
344
+ // 修改指南
345
+ prompt += `## 修改要求
346
+
347
+ ### 核心原则
348
+
349
+ 1. **基于现有实现**: 分析当前项目的技术栈和代码实现方式,在此基础上进行修改
350
+ 2. **最小化变更**: 只修改与变更点相关的代码,保持其他部分不变
351
+ 3. **保持一致性**: 遵循现有代码的命名规范、代码风格和架构模式
352
+ 4. **增量修改**: 优先通过修改现有代码实现,避免大规模重构
353
+
354
+ ### 修改流程
355
+
356
+ 1. **分析现有代码**: 理解当前代码的技术栈、组件结构、样式方案
357
+ 2. **定位修改位置**: 根据变更点找到需要修改的具体代码位置
358
+ 3. **实施修改**: 按照现有代码风格进行修改
359
+ 4. **验证完整性**: 确保修改后代码可正常运行
360
+
361
+ ---
362
+
363
+ **请开始分析代码并执行修改。如果需要更多信息或代码上下文,请告诉我。**
364
+ `;
365
+ return prompt;
366
+ }