sloth-d2c-mcp 1.0.4-beta92 → 1.0.4-beta93
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.
- package/dist/build/core/prompt-builder.js +50 -1
- package/dist/build/core/sampling.js +13 -8
- package/dist/build/index.js +8 -4
- package/dist/build/server.js +228 -72
- package/dist/build/utils/file-manager.js +62 -11
- package/dist/build/utils/prompt-parser.js +46 -0
- package/dist/interceptor-web/dist/build-report.json +5 -5
- package/dist/interceptor-web/dist/detail.html +1 -1
- package/dist/interceptor-web/dist/index.html +1 -1
- package/package.json +3 -3
|
@@ -279,25 +279,75 @@ export class FileManager {
|
|
|
279
279
|
* 保存 promptSetting 到特定的 nodeId 文件(保存到工作目录的 .sloth 文件夹)
|
|
280
280
|
* @param fileKey - Figma文件的key
|
|
281
281
|
* @param nodeId - 节点ID(可选)
|
|
282
|
-
* @param promptSetting -
|
|
282
|
+
* @param promptSetting - 提示词设置对象,格式为 { [key: string]: string }
|
|
283
283
|
*/
|
|
284
|
+
/**
|
|
285
|
+
* 将 camelCase 转换为 kebab-case
|
|
286
|
+
*/
|
|
287
|
+
camelToKebab(str) {
|
|
288
|
+
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* 将 kebab-case 转换为 camelCase
|
|
292
|
+
*/
|
|
293
|
+
kebabToCamel(str) {
|
|
294
|
+
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
295
|
+
}
|
|
284
296
|
async savePromptSetting(fileKey, nodeId, promptSetting) {
|
|
285
297
|
fileKey = '.';
|
|
286
298
|
nodeId = '.';
|
|
287
|
-
|
|
299
|
+
// 按 key 存储为独立的 md 文件
|
|
300
|
+
if (promptSetting && typeof promptSetting === 'object') {
|
|
301
|
+
for (const [key, value] of Object.entries(promptSetting)) {
|
|
302
|
+
if (typeof value === 'string' && value) {
|
|
303
|
+
// 将 camelCase key 转换为 kebab-case 文件名
|
|
304
|
+
const fileName = this.camelToKebab(key);
|
|
305
|
+
Logger.log(`fileName: ${fileName}`, 'this.workspaceRoot: ', this.workspaceRoot);
|
|
306
|
+
// 直接构建完整路径到 .sloth/prompt/xx.md
|
|
307
|
+
const filePath = path.join(this.workspaceRoot, '.sloth', 'd2c-prompt', `${fileName}.md`);
|
|
308
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
309
|
+
await fs.writeFile(filePath, value, 'utf-8');
|
|
310
|
+
Logger.log(`Prompt 文件已保存: ${filePath}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
288
314
|
}
|
|
289
315
|
/**
|
|
290
316
|
* 加载 promptSetting 从特定的 nodeId 文件
|
|
291
317
|
* @param fileKey - Figma文件的key
|
|
292
318
|
* @param nodeId - 节点ID(可选)
|
|
293
|
-
* @returns Promise<any> -
|
|
319
|
+
* @returns Promise<any> - 提示词设置对象,如果文件不存在则返回 null
|
|
294
320
|
*/
|
|
295
321
|
async loadPromptSetting(fileKey, nodeId) {
|
|
296
322
|
fileKey = '.';
|
|
297
323
|
nodeId = '.';
|
|
298
324
|
try {
|
|
299
|
-
|
|
300
|
-
|
|
325
|
+
// 兼容老格式:尝试读取 promptSetting.json
|
|
326
|
+
const oldContent = await this.loadFile(fileKey, nodeId, 'promptSetting.json', { useWorkspaceDir: true, skipParsePath: true });
|
|
327
|
+
if (oldContent) {
|
|
328
|
+
Logger.log('检测到老格式 promptSetting.json,准备删除');
|
|
329
|
+
// 删除老文件
|
|
330
|
+
const filePath = this.getWorkspaceFilePath(fileKey, nodeId, 'promptSetting.json', { skipParsePath: true });
|
|
331
|
+
await fs.unlink(filePath).catch((err) => Logger.log(`删除老文件失败: ${err}`));
|
|
332
|
+
return JSON.parse(oldContent);
|
|
333
|
+
}
|
|
334
|
+
// 读取新格式:所有 .md 文件,直接从 .sloth/d2c-prompt/ 目录读取
|
|
335
|
+
const dirPath = path.join(this.workspaceRoot, '.sloth', 'd2c-prompt');
|
|
336
|
+
const files = await fs.readdir(dirPath).catch(() => []);
|
|
337
|
+
const result = {};
|
|
338
|
+
for (const file of files) {
|
|
339
|
+
if (file.endsWith('.md')) {
|
|
340
|
+
// 将 kebab-case 文件名转换回 camelCase key
|
|
341
|
+
const kebabKey = file.replace(/\.md$/, '');
|
|
342
|
+
const key = this.kebabToCamel(kebabKey);
|
|
343
|
+
const filePath = path.join(dirPath, file);
|
|
344
|
+
const content = await fs.readFile(filePath, 'utf-8').catch(() => '');
|
|
345
|
+
if (content) {
|
|
346
|
+
result[key] = content;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
301
351
|
}
|
|
302
352
|
catch (error) {
|
|
303
353
|
Logger.log(`加载 promptSetting 失败,返回 null: ${error}`);
|
|
@@ -684,17 +734,17 @@ export class FileManager {
|
|
|
684
734
|
* @param nodeId - 节点ID(可选)
|
|
685
735
|
* @param content - 文件内容
|
|
686
736
|
*/
|
|
687
|
-
async saveAbsoluteHtml(fileKey, nodeId, content) {
|
|
737
|
+
async saveAbsoluteHtml(fileKey, nodeId, content, useWorkspaceDir = false) {
|
|
688
738
|
const filename = 'absolute.html';
|
|
689
739
|
// 先尝试加载现有文件
|
|
690
|
-
const existingContent = await this.loadFile(fileKey, nodeId, filename, { useWorkspaceDir
|
|
740
|
+
const existingContent = await this.loadFile(fileKey, nodeId, filename, { useWorkspaceDir });
|
|
691
741
|
// 如果存在且内容不一致,先归档旧版本
|
|
692
742
|
if (existingContent && existingContent.trim() !== content.trim()) {
|
|
693
743
|
await this.archiveAbsoluteHtml(fileKey, nodeId, existingContent);
|
|
694
744
|
Logger.log(`检测到 absolute.html 内容变化,已归档旧版本`);
|
|
695
745
|
}
|
|
696
746
|
// 保存新内容
|
|
697
|
-
await this.saveFile(fileKey, nodeId, filename, content, { useWorkspaceDir
|
|
747
|
+
await this.saveFile(fileKey, nodeId, filename, content, { useWorkspaceDir });
|
|
698
748
|
}
|
|
699
749
|
/**
|
|
700
750
|
* 归档 absolute.html 到带时间戳的文件夹
|
|
@@ -711,7 +761,8 @@ export class FileManager {
|
|
|
711
761
|
const now = new Date();
|
|
712
762
|
const timestamp = now.getFullYear().toString() +
|
|
713
763
|
(now.getMonth() + 1).toString().padStart(2, '0') +
|
|
714
|
-
now.getDate().toString().padStart(2, '0') +
|
|
764
|
+
now.getDate().toString().padStart(2, '0') +
|
|
765
|
+
'_' +
|
|
715
766
|
now.getHours().toString().padStart(2, '0') +
|
|
716
767
|
now.getMinutes().toString().padStart(2, '0') +
|
|
717
768
|
now.getSeconds().toString().padStart(2, '0');
|
|
@@ -747,8 +798,8 @@ export class FileManager {
|
|
|
747
798
|
* @param nodeId - 节点ID(可选)
|
|
748
799
|
* @returns Promise<string> - 文件内容
|
|
749
800
|
*/
|
|
750
|
-
async loadAbsoluteHtml(fileKey, nodeId) {
|
|
751
|
-
return this.loadFile(fileKey, nodeId, 'absolute.html', { useWorkspaceDir
|
|
801
|
+
async loadAbsoluteHtml(fileKey, nodeId, useWorkspaceDir = false) {
|
|
802
|
+
return this.loadFile(fileKey, nodeId, 'absolute.html', { useWorkspaceDir });
|
|
752
803
|
}
|
|
753
804
|
/**
|
|
754
805
|
* 列出 absolute.html 的所有历史版本
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { Logger } from './logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* 解析提示词中的艾特引用并读取文件内容
|
|
6
|
+
* @param prompt - 提示词字符串
|
|
7
|
+
* @param workspaceRoot - 工作目录根路径
|
|
8
|
+
* @returns 解析后的提示词(包含引用文件的内容)
|
|
9
|
+
*/
|
|
10
|
+
export async function parsePromptWithMentions(prompt, workspaceRoot) {
|
|
11
|
+
if (!prompt)
|
|
12
|
+
return prompt;
|
|
13
|
+
// 匹配 @[filename](filepath) 格式
|
|
14
|
+
const mentionRegex = /@\[([^\]]+)\]\(([^)]+)\)/g;
|
|
15
|
+
const matches = Array.from(prompt.matchAll(mentionRegex));
|
|
16
|
+
if (matches.length === 0)
|
|
17
|
+
return prompt;
|
|
18
|
+
// 使用 Map 去重(基于文件路径)
|
|
19
|
+
const mentionsMap = new Map();
|
|
20
|
+
for (const match of matches) {
|
|
21
|
+
const display = match[1];
|
|
22
|
+
const filePath = match[2];
|
|
23
|
+
// 如果已经处理过这个文件路径,跳过
|
|
24
|
+
if (mentionsMap.has(filePath))
|
|
25
|
+
continue;
|
|
26
|
+
try {
|
|
27
|
+
// 读取文件内容
|
|
28
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(workspaceRoot, filePath);
|
|
29
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
30
|
+
mentionsMap.set(filePath, { display, path: filePath, content });
|
|
31
|
+
Logger.log(`读取艾特引用文件: ${filePath}`);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
Logger.warn(`无法读取艾特引用文件 ${filePath}: ${error}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// 如果有引用的文件,在提示词最后拼接文档内容
|
|
38
|
+
if (mentionsMap.size > 0) {
|
|
39
|
+
let result = prompt + '\n\n---\n\n**参考文档:**\n\n';
|
|
40
|
+
mentionsMap.forEach((mention) => {
|
|
41
|
+
result += `\n### ${mention.display}\n\n${mention.content}\n`;
|
|
42
|
+
});
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
return prompt;
|
|
46
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"buildTime": "2026-01-
|
|
2
|
+
"buildTime": "2026-01-19T02:41:15.253Z",
|
|
3
3
|
"mode": "build",
|
|
4
4
|
"pages": {
|
|
5
5
|
"main": {
|
|
6
6
|
"file": "index.html",
|
|
7
|
-
"size":
|
|
7
|
+
"size": 1646449,
|
|
8
8
|
"sizeFormatted": "1.57 MB"
|
|
9
9
|
},
|
|
10
10
|
"detail": {
|
|
11
11
|
"file": "detail.html",
|
|
12
|
-
"size":
|
|
13
|
-
"sizeFormatted": "275.
|
|
12
|
+
"size": 281766,
|
|
13
|
+
"sizeFormatted": "275.16 KB"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
-
"totalSize":
|
|
16
|
+
"totalSize": 1928215,
|
|
17
17
|
"totalSizeFormatted": "1.84 MB"
|
|
18
18
|
}
|