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,382 @@
1
+ import { getBoundingBox, getCode } from 'sloth-d2c-node/convert';
2
+ import { Logger } from '../utils/logger.js';
3
+ import { resetNodeListPosition, replaceImageSrc } from '../utils/utils.js';
4
+ import { extractCodeAndComponents } from '../utils/extract.js';
5
+ import { buildComponentContext, buildUserPromptText, buildPlaceholderPrompt } from './prompt-builder.js';
6
+ /**
7
+ * 构建嵌套结构映射
8
+ * @param groupsData 分组数据数组
9
+ * @returns 嵌套结构信息
10
+ */
11
+ export function buildNestingStructure(groupsData) {
12
+ const childrenMap = new Map();
13
+ const groupMap = new Map();
14
+ const allChildIndices = new Set();
15
+ Logger.log(`开始构建嵌套结构,groupsData 数量: ${groupsData.length}`);
16
+ // 构建映射
17
+ groupsData.forEach((group) => {
18
+ groupMap.set(group.groupIndex, group);
19
+ // 如果 group 有 children 字段,使用它
20
+ if (group.children && Array.isArray(group.children) && group.children.length > 0) {
21
+ Logger.log(`组 ${group.groupIndex} 有子组:`, group.children);
22
+ childrenMap.set(group.groupIndex, group.children);
23
+ group.children.forEach((childIndex) => {
24
+ allChildIndices.add(childIndex);
25
+ });
26
+ }
27
+ else {
28
+ Logger.log(`组 ${group.groupIndex} 没有子组字段或子组为空`);
29
+ }
30
+ });
31
+ // 找出根组(没有作为其他组的子组)
32
+ const rootGroups = groupsData.filter((group) => !allChildIndices.has(group.groupIndex)).map((group) => group.groupIndex);
33
+ Logger.log(`嵌套结构构建完成:`);
34
+ Logger.log(` - 总组数: ${groupsData.length}`);
35
+ Logger.log(` - 有子组的组数: ${childrenMap.size}`);
36
+ Logger.log(` - 根组数量: ${rootGroups.length}`);
37
+ Logger.log(` - 根组索引:`, rootGroups);
38
+ Logger.log(` - 子组索引集合:`, Array.from(allChildIndices));
39
+ Logger.log(` - 子组映射详情:`, Array.from(childrenMap.entries()).map(([k, v]) => `组${k} -> [${v.join(', ')}]`));
40
+ return { childrenMap, rootGroups, groupMap, allChildIndices };
41
+ }
42
+ /**
43
+ * 处理组件映射(如果已映射则跳过采样)
44
+ * @param group 分组数据
45
+ * @param componentMappings 组件映射数组
46
+ * @returns 采样结果或 null
47
+ */
48
+ export function handleComponentMapping(group, componentMappings) {
49
+ if (group.componentMapping) {
50
+ const componentName = group.componentMapping.name || `Group${group.groupIndex + 1}`;
51
+ group.name = componentName;
52
+ componentMappings.push({
53
+ groupIndex: group.groupIndex,
54
+ groupName: componentName,
55
+ component: group.componentMapping,
56
+ });
57
+ return { skipped: true, groupIndex: group.groupIndex, componentName };
58
+ }
59
+ return null;
60
+ }
61
+ /**
62
+ * 注入子组占位符到父组的 nodeList
63
+ * @param nodeList 节点列表
64
+ * @param childResults 子组采样结果
65
+ * @param nestingInfo 嵌套结构信息
66
+ * @param parentGroupIndex 父组索引
67
+ */
68
+ export function injectChildPlaceholders(nodeList, childResults, nestingInfo, parentGroupIndex) {
69
+ const sampledChildren = childResults.filter((r) => !r.skipped && r.code);
70
+ if (sampledChildren.length === 0) {
71
+ Logger.log(`组 ${parentGroupIndex} 没有已采样的子组,跳过占位符注入`);
72
+ return;
73
+ }
74
+ Logger.log(`组 ${parentGroupIndex} 开始注入 ${sampledChildren.length} 个子组占位符`);
75
+ // 收集所有需要移除的子组节点 ID
76
+ const childNodeIdsToRemove = new Set();
77
+ sampledChildren.forEach((childResult) => {
78
+ const childGroup = nestingInfo.groupMap.get(childResult.groupIndex);
79
+ if (childGroup && childGroup.nodeList) {
80
+ childGroup.nodeList.forEach((node) => {
81
+ childNodeIdsToRemove.add(node.id);
82
+ });
83
+ }
84
+ });
85
+ // 从 nodeList 中移除子组的旧元素
86
+ const originalLength = nodeList.length;
87
+ for (let i = nodeList.length - 1; i >= 0; i--) {
88
+ if (childNodeIdsToRemove.has(nodeList[i].id)) {
89
+ nodeList.splice(i, 1);
90
+ }
91
+ }
92
+ Logger.log(` 已移除 ${originalLength - nodeList.length} 个子组元素`);
93
+ // 注入占位符
94
+ sampledChildren.forEach((childResult) => {
95
+ const childGroup = nestingInfo.groupMap.get(childResult.groupIndex);
96
+ if (childGroup && childGroup.nodeList) {
97
+ const { x, y, width, height, absoluteRenderX, absoluteRenderY, absoluteRenderWidth, absoluteRenderHeight } = getBoundingBox(childGroup.nodeList);
98
+ const placeholderName = childResult.componentName || childGroup.name || `Group${childResult.groupIndex + 1}`;
99
+ Logger.log(` 注入子组 ${childResult.groupIndex} 占位符: ${placeholderName}, 位置: (${x}, ${y}), 尺寸: ${width}×${height}`);
100
+ nodeList.push({
101
+ id: `G_${childResult.groupIndex}`,
102
+ name: placeholderName,
103
+ x,
104
+ y,
105
+ width,
106
+ height,
107
+ debuggerInfo: {
108
+ originNode: {
109
+ absoluteBoundingBox: {
110
+ x: absoluteRenderX,
111
+ y: absoluteRenderY,
112
+ width: absoluteRenderWidth,
113
+ height: absoluteRenderHeight,
114
+ },
115
+ absoluteRenderBounds: {
116
+ x: absoluteRenderX,
117
+ y: absoluteRenderY,
118
+ width: absoluteRenderWidth,
119
+ height: absoluteRenderHeight,
120
+ },
121
+ },
122
+ },
123
+ parentId: nodeList.find((node) => childGroup.nodeList?.find((groupNode) => groupNode.parentId === node.id))?.id,
124
+ type: 'COMPONENT',
125
+ });
126
+ }
127
+ });
128
+ Logger.log(`组 ${parentGroupIndex} 占位符注入完成,当前 nodeList 长度: ${nodeList.length}`);
129
+ }
130
+ /**
131
+ * 单个组的采样逻辑
132
+ * @param group 分组数据
133
+ * @param nodeList 节点列表
134
+ * @param config 采样配置
135
+ * @returns 采样结果
136
+ */
137
+ export async function sampleSingleGroup(group, nodeList, config) {
138
+ const { d2cNodeList, imageMap, convertConfig, chunkPrompt, mcpServer, componentMappings, codeSnippets, componentContexts } = config;
139
+ Logger.log(`开始采样组 ${group.groupIndex}, 元素数量: ${group.elements.length}, nodeList 长度: ${nodeList.length}`);
140
+ // 检查组件映射
141
+ const mappingResult = handleComponentMapping(group, componentMappings);
142
+ if (mappingResult) {
143
+ Logger.log(`组 ${group.groupIndex} 已映射组件,跳过采样: ${mappingResult.componentName}`);
144
+ return mappingResult;
145
+ }
146
+ // 重置节点位置
147
+ const resetNodeList = resetNodeListPosition(nodeList);
148
+ Logger.log(`组 ${group.groupIndex} 节点位置已重置,准备生成代码`);
149
+ // 获取代码
150
+ const code = await getCode({
151
+ d2cNodeList: resetNodeList,
152
+ config: convertConfig,
153
+ });
154
+ Logger.log(`组 ${group.groupIndex} 代码生成完成,代码长度: ${code.length}`);
155
+ // 使用 imageMap 的 path 替换 code 中对应的 src
156
+ const replacedCode = replaceImageSrc(code, imageMap);
157
+ const componentName = `Group${group.groupIndex + 1}`;
158
+ const codeWithCustomName = replacedCode.replace(/\bApp\b/g, componentName);
159
+ // 处理组件上下文
160
+ const { processedUserPrompt, componentContextPrompt } = buildComponentContext(group);
161
+ const userPromptText = buildUserPromptText(processedUserPrompt);
162
+ // 收集组件上下文信息
163
+ if (group.componentContext && Array.isArray(group.componentContext) && group.componentContext.length > 0) {
164
+ componentContexts.push({
165
+ groupIndex: group.groupIndex,
166
+ groupName: group.name || componentName,
167
+ components: group.componentContext,
168
+ });
169
+ Logger.log(`组 ${group.groupIndex} 已收集 ${group.componentContext.length} 个组件上下文`);
170
+ }
171
+ // 检测占位符元素(子组件)
172
+ const placeholders = nodeList
173
+ .filter((node) => node.type === 'COMPONENT' && node.id?.startsWith('G_'))
174
+ .map((node) => ({
175
+ name: node.name || 'Unknown',
176
+ }));
177
+ const placeholderPrompt = buildPlaceholderPrompt(placeholders);
178
+ Logger.log(`组 ${group.groupIndex} 开始调用 AI 采样,代码长度: ${codeWithCustomName.length}, 是否有用户提示: ${!!group.userPrompt}, 是否有组件上下文: ${!!(group.componentContext && group.componentContext.length > 0)}, 占位符数量: ${placeholders.length}`);
179
+ // 执行采样
180
+ try {
181
+ const { content: { text }, } = await mcpServer.server.createMessage({
182
+ messages: [
183
+ {
184
+ role: 'user',
185
+ content: {
186
+ type: 'text',
187
+ text: chunkPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') +
188
+ userPromptText +
189
+ componentContextPrompt +
190
+ placeholderPrompt,
191
+ },
192
+ },
193
+ {
194
+ role: 'user',
195
+ content: {
196
+ type: 'text',
197
+ text: codeWithCustomName,
198
+ },
199
+ },
200
+ ],
201
+ maxTokens: 48000,
202
+ }, { timeout: 5 * 60 * 1000 });
203
+ const { code: chunkCode, componentName: sampledName } = extractCodeAndComponents(text)[0];
204
+ Logger.log(`组 ${group.groupIndex} 采样成功,组件名称: ${sampledName}, 代码长度: ${chunkCode.length}`);
205
+ 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);
218
+ return {
219
+ code: chunkCode,
220
+ componentName: sampledName,
221
+ skipped: false,
222
+ groupIndex: group.groupIndex,
223
+ };
224
+ }
225
+ catch (e) {
226
+ 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
+ }
236
+ codeSnippets.push(fallbackCode);
237
+ Logger.log(`组 ${group.groupIndex} 使用降级代码,长度: ${fallbackCode.length}`);
238
+ return {
239
+ code: fallbackCode,
240
+ componentName,
241
+ skipped: false,
242
+ groupIndex: group.groupIndex,
243
+ };
244
+ }
245
+ }
246
+ /**
247
+ * 深度遍历采样函数(后序遍历:先子后父)
248
+ * @param groupIndex 组索引
249
+ * @param nestingInfo 嵌套结构信息
250
+ * @param config 采样配置
251
+ * @param samplingResults 采样结果映射
252
+ * @param depth 递归深度
253
+ * @returns 采样结果
254
+ */
255
+ export async function sampleGroupRecursive(groupIndex, nestingInfo, config, samplingResults, depth = 0) {
256
+ const { d2cNodeList } = config;
257
+ const indent = ' '.repeat(depth);
258
+ Logger.log(`${indent}[深度 ${depth}] 开始处理组 ${groupIndex}`);
259
+ const group = nestingInfo.groupMap.get(groupIndex);
260
+ if (!group) {
261
+ Logger.log(`${indent}[深度 ${depth}] 组 ${groupIndex} 不存在,跳过`);
262
+ return { skipped: true, groupIndex };
263
+ }
264
+ // 先递归处理所有子组(后序遍历)
265
+ const childIndices = nestingInfo.childrenMap.get(groupIndex) || [];
266
+ Logger.log(`${indent}[深度 ${depth}] 组 ${groupIndex} 有 ${childIndices.length} 个子组:`, childIndices);
267
+ const childResults = [];
268
+ for (const childIndex of childIndices) {
269
+ Logger.log(`${indent}[深度 ${depth}] 开始处理子组 ${childIndex}`);
270
+ const childResult = await sampleGroupRecursive(childIndex, nestingInfo, config, samplingResults, depth + 1);
271
+ childResults.push(childResult);
272
+ samplingResults.set(childIndex, childResult);
273
+ Logger.log(`${indent}[深度 ${depth}] 子组 ${childIndex} 处理完成,${childResult.skipped ? '已跳过' : '已采样'}, 组件名: ${childResult.componentName || 'N/A'}`);
274
+ }
275
+ // 处理当前组
276
+ const groupElements = group.elements;
277
+ let nodeList = d2cNodeList.filter((node) => groupElements.includes(node.id));
278
+ group.nodeList = nodeList;
279
+ Logger.log(`${indent}[深度 ${depth}] 组 ${groupIndex} 初始 nodeList 长度: ${nodeList.length}, 元素数量: ${groupElements.length}`);
280
+ // 如果有子组已采样,注入子组占位符
281
+ injectChildPlaceholders(nodeList, childResults, nestingInfo, groupIndex);
282
+ // 使用通用采样函数
283
+ Logger.log(`${indent}[深度 ${depth}] 组 ${groupIndex} 开始采样(注入占位符后 nodeList 长度: ${nodeList.length})`);
284
+ const result = await sampleSingleGroup(group, nodeList, config);
285
+ Logger.log(`${indent}[深度 ${depth}] 组 ${groupIndex} 采样完成,${result.skipped ? '已跳过' : '已采样'}, 组件名: ${result.componentName || 'N/A'}`);
286
+ return result;
287
+ }
288
+ /**
289
+ * 处理分组采样的主流程
290
+ * @param groupsData 分组数据
291
+ * @param config 采样配置
292
+ * @returns 是否支持采样
293
+ */
294
+ export async function processSampling(groupsData, config) {
295
+ const { d2cNodeList, componentMappings, codeSnippets } = config;
296
+ let isSupportSampling = true;
297
+ if (!groupsData || groupsData.length === 0) {
298
+ return isSupportSampling;
299
+ }
300
+ Logger.log(`开始处理 groupsData,总数: ${groupsData.length}`);
301
+ Logger.log(`groupsData 详情:`, groupsData.map((g) => ({ groupIndex: g.groupIndex, hasChildren: !!g.children, children: g.children })));
302
+ // 构建嵌套结构
303
+ const nestingInfo = buildNestingStructure(groupsData);
304
+ const samplingResults = new Map();
305
+ // 如果有嵌套结构,使用深度遍历
306
+ if (nestingInfo.rootGroups.length > 0 && nestingInfo.childrenMap.size > 0) {
307
+ Logger.log(`检测到嵌套结构,根组数量: ${nestingInfo.rootGroups.length}`);
308
+ Logger.log(`开始深度遍历采样,根组索引:`, nestingInfo.rootGroups);
309
+ // 串行处理所有根组(深度优先,后序遍历)
310
+ for (const rootIndex of nestingInfo.rootGroups) {
311
+ Logger.log(`\n========== 开始处理根组 ${rootIndex} ==========`);
312
+ try {
313
+ await sampleGroupRecursive(rootIndex, nestingInfo, config, samplingResults, 0);
314
+ }
315
+ catch (e) {
316
+ isSupportSampling = false;
317
+ Logger.log(`根组 ${rootIndex} 处理出错:`, e);
318
+ }
319
+ Logger.log(`========== 根组 ${rootIndex} 处理完成 ==========\n`);
320
+ }
321
+ // 处理没有嵌套关系的独立组
322
+ const processedGroupIndices = new Set();
323
+ nestingInfo.rootGroups.forEach((rootIndex) => {
324
+ processedGroupIndices.add(rootIndex);
325
+ const collectChildren = (parentIndex) => {
326
+ const children = nestingInfo.childrenMap.get(parentIndex) || [];
327
+ children.forEach((childIndex) => {
328
+ processedGroupIndices.add(childIndex);
329
+ collectChildren(childIndex);
330
+ });
331
+ };
332
+ collectChildren(rootIndex);
333
+ });
334
+ const independentGroups = groupsData.filter((group) => !processedGroupIndices.has(group.groupIndex) && !nestingInfo.childrenMap.has(group.groupIndex) && !nestingInfo.rootGroups.includes(group.groupIndex));
335
+ if (independentGroups.length > 0) {
336
+ Logger.log(`发现 ${independentGroups.length} 个独立组(不在嵌套结构中)`);
337
+ Logger.log(`已处理的组索引:`, Array.from(processedGroupIndices));
338
+ Logger.log(`独立组索引:`, independentGroups.map((g) => g.groupIndex));
339
+ for (const group of independentGroups) {
340
+ Logger.log(`处理独立组 ${group.groupIndex}`);
341
+ const groupElements = group.elements;
342
+ const nodeList = d2cNodeList.filter((node) => groupElements.includes(node.id));
343
+ group.nodeList = nodeList;
344
+ try {
345
+ await sampleSingleGroup(group, nodeList, config);
346
+ }
347
+ catch (e) {
348
+ isSupportSampling = false;
349
+ Logger.log(`独立组 ${group.groupIndex} 处理出错:`, e);
350
+ }
351
+ }
352
+ }
353
+ else {
354
+ Logger.log(`没有独立组需要处理,所有组都已在嵌套结构中处理`);
355
+ }
356
+ }
357
+ else {
358
+ // 没有嵌套结构,使用原来的平铺方式
359
+ Logger.log('未检测到嵌套结构,使用平铺方式采样');
360
+ Logger.log(`将按顺序处理 ${groupsData.length} 个组`);
361
+ for (const group of groupsData) {
362
+ Logger.log(`\n--- 处理组 ${group.groupIndex} (平铺模式) ---`);
363
+ const groupElements = group.elements;
364
+ const nodeList = d2cNodeList.filter((node) => groupElements.includes(node.id));
365
+ group.nodeList = nodeList;
366
+ try {
367
+ await sampleSingleGroup(group, nodeList, config);
368
+ }
369
+ catch (e) {
370
+ isSupportSampling = false;
371
+ Logger.log(`组 ${group.groupIndex} 处理出错:`, e);
372
+ }
373
+ Logger.log(`--- 组 ${group.groupIndex} 处理完成 ---\n`);
374
+ }
375
+ }
376
+ Logger.log(`\n采样完成统计:`);
377
+ Logger.log(` - 总组数: ${groupsData.length}`);
378
+ Logger.log(` - 已生成代码片段数: ${codeSnippets.length}`);
379
+ Logger.log(` - 已映射组件数: ${componentMappings.length}`);
380
+ Logger.log(` - 组件映射详情:`, componentMappings.map((m) => `组${m.groupIndex} -> ${m.groupName}`));
381
+ return isSupportSampling;
382
+ }
@@ -0,0 +1 @@
1
+ export {};