sloth-d2c-mcp 1.0.4-beta76 → 1.0.4-beta78

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.
@@ -48,15 +48,85 @@ export function buildUserPromptText(userPrompt) {
48
48
  return '';
49
49
  return '\n## 用户提示词\n以下是用户针对该代码的优化提出的补充说明和要求,请在优化代码时特别关注这些指导并尽可能实现:\n' + userPrompt;
50
50
  }
51
+ /**
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
+ }
51
115
  /**
52
116
  * 构建组件映射提示词(包含组件映射和组件上下文和组件标记)
53
117
  * @param componentMappings 组件映射数组
54
118
  * @param componentContexts 所有组的组件上下文数组
55
119
  * @param markedComponents 标记的组件数组
120
+ * @param componentsDatabase 完整的组件数据库(用于查找继承链)
56
121
  * @returns 组件映射提示词
57
122
  */
58
- export function buildComponentMappingPrompt(componentMappings, componentContexts = [], markedComponents = []) {
123
+ export function buildComponentMappingPrompt(componentMappings, componentContexts = [], markedComponents = [], componentsDatabase = []) {
59
124
  let prompt = '';
125
+ // 构建完整的组件数据库映射表(用于查找继承链)
126
+ const databaseMap = new Map();
127
+ componentsDatabase.forEach((comp) => {
128
+ databaseMap.set(comp.name, comp);
129
+ });
60
130
  // 收集所有组件:组件映射 + 组件上下文(按名称去重)
61
131
  const allComponents = new Map();
62
132
  // 添加组件映射的组件
@@ -74,19 +144,28 @@ export function buildComponentMappingPrompt(componentMappings, componentContexts
74
144
  // 构建可用组件提示词
75
145
  if (allComponents.size > 0) {
76
146
  prompt += '\n\n## 可用组件\n\n以下是项目中可用的组件,请在最终代码中使用这些组件:\n\n';
147
+ // 记录已输出的组件,避免重复输出
148
+ const outputtedComponents = new Set();
77
149
  allComponents.forEach((component) => {
78
- const propsInfo = component.props && component.props.length > 0
79
- ? component.props.map((p) => `${p.name}${p.required ? ' (必需)' : ' (可选)'}: ${p.type || 'any'}`).join(', ')
80
- : '无';
81
- const libInfo = component.lib ? ` (来自 ${component.lib})` : ' (项目组件)';
82
- const descInfo = component.description ? `\n- **描述**: ${component.description}` : '';
83
- prompt += `### ${component.name}${libInfo}\n`;
84
- prompt += `- **文件路径**: ${component.path}\n`;
85
- prompt += `- **导入方式**: ${component.import}\n`;
86
- prompt += `- **Props**: ${propsInfo}${descInfo}\n\n`;
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
+ }
87
166
  });
88
167
  prompt +=
89
- '**重要**:在最终写入代码时,请根据设计稿需求合理传递 props 参数。请按照项目路径规范合理引入组件。\n';
168
+ '**重要**:在最终写入代码时,请根据设计稿需求合理传递 props 参数。请按照项目路径规范合理引入组件。如果组件有继承关系,请注意父类的 props 和方法也可以使用。\n';
90
169
  }
91
170
  // 构建标记组件提示词
92
171
  if (markedComponents.length > 0) {
@@ -394,8 +394,11 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
394
394
  });
395
395
  Logger.log(`收集到 ${markedComponents.length} 个标记的组件`);
396
396
  }
397
- // 构建组件映射提示词(包含组件映射、组件上下文和标记组件)
398
- const componentMappingPrompt = buildComponentMappingPrompt(componentMappings, componentContexts, markedComponents);
397
+ // 加载完整的组件数据库(用于查找继承链)
398
+ const componentsDatabase = await fileManager.loadComponentsDatabase();
399
+ Logger.log(`加载组件数据库,共 ${componentsDatabase.length} 个组件`);
400
+ // 构建组件映射提示词(包含组件映射、组件上下文、标记组件和完整组件数据库)
401
+ const componentMappingPrompt = buildComponentMappingPrompt(componentMappings, componentContexts, markedComponents, componentsDatabase);
399
402
  // 使用提示词(已包含默认值)
400
403
  return {
401
404
  content: [
@@ -435,7 +438,7 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
435
438
  .array(z.object({
436
439
  name: z.string().describe('Component name in PascalCase'),
437
440
  path: z.string().describe('Component file path relative to project root'),
438
- signature: z.string().describe('Component signature (SHA256 hash) for matching'),
441
+ signature: z.string().optional().describe('Component Screenshot (SHA256 hash) for matching'),
439
442
  type: z.enum(['button', 'input', 'card', 'text', 'image', 'container', 'list', 'custom']).optional(),
440
443
  description: z.string().optional(),
441
444
  import: z.string().optional().describe('Import statement for the component'),
@@ -447,6 +450,22 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
447
450
  description: z.string().optional(),
448
451
  }))
449
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'),
450
469
  }))
451
470
  .describe('Array of components to mark and save'),
452
471
  }, async (args) => {
@@ -466,11 +485,11 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
466
485
  }
467
486
  // 2. 加载现有组件
468
487
  const existingComponents = await fileManager.loadComponentsDatabase();
469
- // 3. 构建 signature 索引(用于去重)
470
- const signatureIndex = new Map();
488
+ // 3. 构建 path 索引(用于去重,path 一致则认为是同一组件)
489
+ const pathIndex = new Map();
471
490
  existingComponents.forEach((comp, index) => {
472
- if (comp.signature) {
473
- signatureIndex.set(comp.signature, index);
491
+ if (comp.path) {
492
+ pathIndex.set(comp.path, index);
474
493
  }
475
494
  });
476
495
  // 4. 处理新组件(合并逻辑)
@@ -478,7 +497,8 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
478
497
  let addedCount = 0;
479
498
  let updatedCount = 0;
480
499
  for (const comp of components) {
481
- const existingIndex = signatureIndex.get(comp.signature);
500
+ // 使用 path 判断组件是否已存在(组件冲突检测)
501
+ const existingIndex = pathIndex.get(comp.path);
482
502
  const storedComponent = {
483
503
  id: generateComponentId(),
484
504
  name: comp.name,
@@ -491,8 +511,14 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
491
511
  required: p.required || false,
492
512
  description: p.description,
493
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,
494
521
  description: comp.description,
495
- signature: comp.signature,
496
522
  };
497
523
  if (existingIndex !== undefined) {
498
524
  // 更新已存在的组件(保留原始导入时间)
@@ -506,7 +532,7 @@ mcpServer.tool('mark_components', 'Mark and save components to project component
506
532
  else {
507
533
  // 添加新组件
508
534
  existingComponents.push(storedComponent);
509
- signatureIndex.set(comp.signature, existingComponents.length - 1);
535
+ pathIndex.set(comp.path, existingComponents.length - 1);
510
536
  addedCount++;
511
537
  }
512
538
  }
@@ -833,6 +833,10 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
833
833
  });
834
834
  }
835
835
  });
836
+ // 多文件分析限制
837
+ const MAX_FILES = 10; // 最多 10 个文件
838
+ const MAX_TOTAL_CHARS = 40000; // 总内容最多 40000 字符
839
+ const MAX_FILE_CHARS = 5000; // 单文件最多 5000 字符
836
840
  /**
837
841
  * 分析项目中的组件
838
842
  */
@@ -846,10 +850,17 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
846
850
  });
847
851
  return;
848
852
  }
849
- Logger.log(`分析 ${filePaths.length} 个项目文件`);
853
+ // 限制文件数量
854
+ if (filePaths.length > MAX_FILES) {
855
+ res.status(400).json({
856
+ success: false,
857
+ message: `文件数量超过限制,最多支持 ${MAX_FILES} 个文件`,
858
+ });
859
+ return;
860
+ }
861
+ Logger.log(`批量分析 ${filePaths.length} 个项目文件`);
850
862
  const projectPath = getProjectRoot();
851
- const results = [];
852
- // 尝试加载保存的提示词设置(如果提供了 fileKey 和 nodeId)
863
+ // 尝试加载保存的提示词设置
853
864
  let savedPromptSetting = null;
854
865
  if (fileKey && nodeId) {
855
866
  try {
@@ -862,148 +873,163 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
862
873
  }
863
874
  const frameworkGuidePrompt = savedPromptSetting?.frameworkGuidePrompt;
864
875
  const _componentAnalysisPrompt = savedPromptSetting?.componentAnalysisPrompt || componentAnalysisPrompt;
865
- // 读取并分析每个文件
876
+ // 读取所有文件内容
877
+ const fileContents = [];
878
+ let totalChars = 0;
866
879
  for (const filePath of filePaths) {
867
880
  try {
868
881
  const fullPath = path.join(projectPath, filePath);
869
882
  // 安全检查:确保文件在项目目录内
870
883
  if (!fullPath.startsWith(projectPath)) {
871
- results.push({
872
- success: false,
873
- filename: filePath,
884
+ fileContents.push({
885
+ path: filePath,
886
+ content: '',
874
887
  error: '无权访问该文件',
875
888
  });
876
889
  continue;
877
890
  }
878
891
  // 读取文件内容
879
- const content = await fs.promises.readFile(fullPath, 'utf-8');
880
- const filename = path.basename(filePath);
881
- // 使用 MCP Sampling 进行实际分析
882
- if (mcpServer) {
883
- try {
884
- // 构建提示词,替换占位符
885
- const prompt = _componentAnalysisPrompt.replace(/{filename}/g, filename).replace(/{fileContent}/g, content);
886
- // 调用 MCP Sampling API
887
- const { content: { text }, } = await mcpServer.server.createMessage({
888
- messages: [
889
- frameworkGuidePrompt && {
890
- role: 'user',
891
- content: {
892
- type: 'text',
893
- text: frameworkGuidePrompt,
894
- },
895
- },
896
- {
897
- role: 'user',
898
- content: {
899
- type: 'text',
900
- text: prompt,
901
- },
902
- },
903
- ].filter(Boolean),
904
- maxTokens: 8000,
905
- }, { timeout: 2 * 60 * 1000 });
906
- // 解析 AI 返回的 JSON
907
- const extractResult = extractJson(text);
908
- if (extractResult.state === 'successful-parse' && extractResult.value) {
909
- const analysisResult = extractResult.value;
910
- // 构建组件信息
911
- const component = {
912
- id: generateComponentId(),
913
- name: analysisResult.componentName || path.basename(filePath, path.extname(filePath)),
914
- type: 'custom',
915
- path: filePath,
916
- import: analysisResult.import,
917
- props: analysisResult.props || [],
918
- description: analysisResult.description || '组件描述',
919
- };
920
- results.push({
921
- success: true,
922
- component,
923
- filename: filePath,
924
- });
925
- Logger.log(`成功分析组件: ${filePath}, 组件名: ${component.name}`);
926
- }
927
- else {
928
- // JSON 解析失败,使用默认值
929
- Logger.warn(`解析组件分析结果失败: ${extractResult.error}, 文件: ${filePath}`);
930
- results.push({
931
- success: true,
932
- component: {
933
- id: generateComponentId(),
934
- name: path.basename(filePath, path.extname(filePath)),
935
- type: 'custom',
936
- path: filePath,
937
- import: `import { ${path.basename(filePath, path.extname(filePath))} } from './${filePath}'`,
938
- props: [],
939
- description: '组件描述',
940
- },
941
- filename: filePath,
942
- });
943
- }
944
- }
945
- catch (samplingError) {
946
- Logger.error(`调用采样 API 失败: ${filePath}`, samplingError);
947
- // 采样失败时使用默认值
948
- results.push({
949
- success: true,
950
- component: {
951
- id: generateComponentId(),
952
- name: path.basename(filePath, path.extname(filePath)),
953
- type: 'custom',
954
- path: filePath,
955
- import: `import { ${path.basename(filePath, path.extname(filePath))} } from './${filePath}'`,
956
- props: [],
957
- description: '组件描述',
958
- },
959
- filename: filePath,
960
- });
961
- }
892
+ let content = await fs.promises.readFile(fullPath, 'utf-8');
893
+ // 单文件截断
894
+ if (content.length > MAX_FILE_CHARS) {
895
+ content = content.slice(0, MAX_FILE_CHARS) + '\n// ... 内容已截断 ...';
896
+ Logger.warn(`文件 ${filePath} 内容过长,已截断至 ${MAX_FILE_CHARS} 字符`);
962
897
  }
963
- else {
964
- // MCP 服务器未初始化,使用默认值
965
- Logger.warn('MCP 服务器未初始化,使用默认组件信息');
966
- results.push({
967
- success: true,
968
- component: {
969
- id: generateComponentId(),
970
- name: path.basename(filePath, path.extname(filePath)),
971
- type: 'custom',
972
- path: filePath,
973
- import: `import { ${path.basename(filePath, path.extname(filePath))} } from './${filePath}'`,
974
- props: [],
975
- description: '组件描述',
976
- },
977
- filename: filePath,
898
+ // 检查总字符数
899
+ if (totalChars + content.length > MAX_TOTAL_CHARS) {
900
+ Logger.warn(`总内容超过 ${MAX_TOTAL_CHARS} 字符限制,跳过文件: ${filePath}`);
901
+ fileContents.push({
902
+ path: filePath,
903
+ content: '',
904
+ error: '总内容超过限制,已跳过',
978
905
  });
906
+ continue;
979
907
  }
908
+ totalChars += content.length;
909
+ fileContents.push({
910
+ path: filePath,
911
+ content,
912
+ });
980
913
  }
981
914
  catch (error) {
982
915
  Logger.error(`读取文件失败: ${filePath}`, error);
983
- results.push({
984
- success: false,
985
- filename: filePath,
916
+ fileContents.push({
917
+ path: filePath,
918
+ content: '',
986
919
  error: error instanceof Error ? error.message : String(error),
987
920
  });
988
921
  }
989
922
  }
990
- const succeeded = results.filter((r) => r.success).length;
991
- const failed = results.filter((r) => !r.success).length;
992
- res.json({
993
- success: true,
994
- data: {
995
- total: filePaths.length,
996
- succeeded,
997
- failed,
998
- components: results.filter((r) => r.success).map((r) => r.component),
999
- errors: results
1000
- .filter((r) => !r.success)
1001
- .map((r) => ({
1002
- filename: r.filename,
1003
- error: r.error,
1004
- })),
1005
- },
1006
- });
923
+ // 过滤出成功读取的文件
924
+ const validFiles = fileContents.filter((f) => f.content && !f.error);
925
+ const errorFiles = fileContents.filter((f) => f.error);
926
+ if (validFiles.length === 0) {
927
+ res.json({
928
+ success: true,
929
+ data: {
930
+ total: filePaths.length,
931
+ succeeded: 0,
932
+ failed: errorFiles.length,
933
+ components: [],
934
+ errors: errorFiles.map((f) => ({ filename: f.path, error: f.error })),
935
+ },
936
+ });
937
+ return;
938
+ }
939
+ // 构建多文件内容部分
940
+ const filesSection = validFiles
941
+ .map((f) => {
942
+ const ext = path.extname(f.path).slice(1) || 'txt';
943
+ return `### 文件:${f.path}\n\`\`\`${ext}\n${f.content}\n\`\`\``;
944
+ })
945
+ .join('\n\n');
946
+ // 构建最终 Prompt
947
+ const prompt = _componentAnalysisPrompt.replace('{filesSection}', filesSection);
948
+ Logger.log(`构建 Prompt 完成,总字符数: ${prompt.length}`);
949
+ // 调用 MCP Sampling API
950
+ if (!mcpServer) {
951
+ Logger.warn('MCP 服务器未初始化,返回空结果');
952
+ res.json({
953
+ success: true,
954
+ data: {
955
+ total: filePaths.length,
956
+ succeeded: 0,
957
+ failed: filePaths.length,
958
+ components: [],
959
+ errors: filePaths.map((p) => ({ filename: p, error: 'MCP 服务器未初始化' })),
960
+ },
961
+ });
962
+ return;
963
+ }
964
+ try {
965
+ const { content: { text }, } = await mcpServer.server.createMessage({
966
+ messages: [
967
+ frameworkGuidePrompt && {
968
+ role: 'user',
969
+ content: {
970
+ type: 'text',
971
+ text: frameworkGuidePrompt,
972
+ },
973
+ },
974
+ {
975
+ role: 'user',
976
+ content: {
977
+ type: 'text',
978
+ text: prompt,
979
+ },
980
+ },
981
+ ].filter(Boolean),
982
+ maxTokens: 8000,
983
+ }, { timeout: 2 * 60 * 1000 });
984
+ // 解析 AI 返回的 JSON
985
+ const extractResult = extractJson(text);
986
+ if (extractResult.state === 'successful-parse' && extractResult.value) {
987
+ const analysisResult = extractResult.value;
988
+ const components = (analysisResult.components || []).map((comp) => ({
989
+ id: generateComponentId(),
990
+ name: comp.componentName || 'Unknown',
991
+ type: 'custom',
992
+ path: comp.path || '',
993
+ import: comp.import || '',
994
+ props: comp.props || [],
995
+ description: comp.description || '',
996
+ super: comp.super || undefined,
997
+ functions: comp.functions || undefined,
998
+ }));
999
+ Logger.log(`成功分析 ${components.length} 个组件`);
1000
+ res.json({
1001
+ success: true,
1002
+ data: {
1003
+ total: filePaths.length,
1004
+ succeeded: validFiles.length,
1005
+ failed: errorFiles.length,
1006
+ components,
1007
+ errors: errorFiles.map((f) => ({ filename: f.path, error: f.error })),
1008
+ },
1009
+ });
1010
+ }
1011
+ else {
1012
+ Logger.warn(`解析组件分析结果失败: ${extractResult.error}`);
1013
+ res.json({
1014
+ success: true,
1015
+ data: {
1016
+ total: filePaths.length,
1017
+ succeeded: 0,
1018
+ failed: filePaths.length,
1019
+ components: [],
1020
+ errors: [{ filename: 'all', error: `AI 返回结果解析失败: ${extractResult.error}` }],
1021
+ },
1022
+ });
1023
+ }
1024
+ }
1025
+ catch (samplingError) {
1026
+ Logger.error('调用采样 API 失败:', samplingError);
1027
+ res.status(500).json({
1028
+ success: false,
1029
+ message: '调用 Sampling 分析失败,请调用 #mark_components tool进行组件分析',
1030
+ error: samplingError instanceof Error ? samplingError.message : String(samplingError),
1031
+ });
1032
+ }
1007
1033
  }
1008
1034
  catch (error) {
1009
1035
  Logger.error('分析项目组件失败:', error);
@@ -1077,7 +1103,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
1077
1103
  const { includePaths, excludePaths } = req.query;
1078
1104
  const projectPath = getProjectRoot();
1079
1105
  Logger.log('扫描项目组件:', { includePaths, excludePaths });
1080
- // 读取外部依赖组件(从 .sloth/components.json)
1106
+ // 读取项目组件(从 .sloth/components.json)
1081
1107
  let projectComponents = [];
1082
1108
  const slothPath = path.join(projectPath, '.sloth', 'components.json');
1083
1109
  const fs = await import('fs/promises');
@@ -1,18 +1,18 @@
1
1
  {
2
- "buildTime": "2025-12-06T13:27:37.588Z",
2
+ "buildTime": "2025-12-19T16:46:24.638Z",
3
3
  "mode": "build",
4
4
  "pages": {
5
5
  "main": {
6
6
  "file": "index.html",
7
- "size": 1600710,
7
+ "size": 1604164,
8
8
  "sizeFormatted": "1.53 MB"
9
9
  },
10
10
  "detail": {
11
11
  "file": "detail.html",
12
- "size": 280523,
13
- "sizeFormatted": "273.95 KB"
12
+ "size": 280955,
13
+ "sizeFormatted": "274.37 KB"
14
14
  }
15
15
  },
16
- "totalSize": 1881233,
17
- "totalSizeFormatted": "1.79 MB"
16
+ "totalSize": 1885119,
17
+ "totalSizeFormatted": "1.8 MB"
18
18
  }