sloth-d2c-mcp 1.0.4-beta65
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/README.md +100 -0
- package/cli/run.js +102 -0
- package/dist/build/config-manager/index.js +160 -0
- package/dist/build/index.js +839 -0
- package/dist/build/interceptor/client.js +142 -0
- package/dist/build/interceptor/vscode.js +143 -0
- package/dist/build/interceptor/web.js +28 -0
- package/dist/build/server.js +539 -0
- package/dist/build/utils/extract.js +166 -0
- package/dist/build/utils/file-manager.js +241 -0
- package/dist/build/utils/logger.js +90 -0
- package/dist/build/utils/update.js +54 -0
- package/dist/build/utils/utils.js +165 -0
- package/dist/build/utils/vscode-logger.js +133 -0
- package/dist/build/utils/webpack-substitutions.js +196 -0
- package/dist/interceptor-web/dist/build-report.json +18 -0
- package/dist/interceptor-web/dist/detail.html +1 -0
- package/dist/interceptor-web/dist/index.html +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 从LLM的文本输出中提取JSON代码块
|
|
3
|
+
* @param text LLM返回的文本内容
|
|
4
|
+
* @returns 提取到的JSON对象和解析状态
|
|
5
|
+
*/
|
|
6
|
+
export function extractJson(text) {
|
|
7
|
+
try {
|
|
8
|
+
// 移除文本前后的空白字符
|
|
9
|
+
const trimmedText = text.trim();
|
|
10
|
+
// 正则表达式匹配 ```json 代码块
|
|
11
|
+
const jsonBlockRegex = /```json\s*\n([\s\S]*?)\n\s*```/gi;
|
|
12
|
+
const matches = Array.from(trimmedText.matchAll(jsonBlockRegex));
|
|
13
|
+
if (matches.length > 0) {
|
|
14
|
+
// 取最后一个匹配的JSON代码块
|
|
15
|
+
const jsonContent = matches[matches.length - 1][1].trim();
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(jsonContent);
|
|
18
|
+
return { state: 'successful-parse', value: parsed };
|
|
19
|
+
}
|
|
20
|
+
catch (parseError) {
|
|
21
|
+
return {
|
|
22
|
+
state: 'failed-parse',
|
|
23
|
+
error: `JSON解析失败: ${parseError instanceof Error ? parseError.message : String(parseError)}`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// 如果没有找到 ```json 代码块,尝试直接解析整个文本
|
|
28
|
+
// 寻找可能的JSON对象(以 { 开始,以 } 结束)
|
|
29
|
+
const jsonObjectRegex = /\{[\s\S]*\}/;
|
|
30
|
+
const jsonMatch = trimmedText.match(jsonObjectRegex);
|
|
31
|
+
if (jsonMatch) {
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
34
|
+
return { state: 'successful-parse', value: parsed };
|
|
35
|
+
}
|
|
36
|
+
catch (parseError) {
|
|
37
|
+
return {
|
|
38
|
+
state: 'failed-parse',
|
|
39
|
+
error: `JSON对象解析失败: ${parseError instanceof Error ? parseError.message : String(parseError)}`
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// 尝试寻找数组格式的JSON(以 [ 开始,以 ] 结束)
|
|
44
|
+
const jsonArrayRegex = /\[[\s\S]*\]/;
|
|
45
|
+
const arrayMatch = trimmedText.match(jsonArrayRegex);
|
|
46
|
+
if (arrayMatch) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(arrayMatch[0]);
|
|
49
|
+
return { state: 'successful-parse', value: parsed };
|
|
50
|
+
}
|
|
51
|
+
catch (parseError) {
|
|
52
|
+
return {
|
|
53
|
+
state: 'failed-parse',
|
|
54
|
+
error: `JSON数组解析失败: ${parseError instanceof Error ? parseError.message : String(parseError)}`
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
state: 'failed-parse',
|
|
60
|
+
error: '未找到有效的JSON内容。请确保响应包含 ```json 代码块或有效的JSON对象/数组。'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return {
|
|
65
|
+
state: 'failed-parse',
|
|
66
|
+
error: `提取JSON时发生错误: ${error instanceof Error ? error.message : String(error)}`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 从 markdown 文本中提取代码块
|
|
72
|
+
* 遵循 CommonMark 规范,使用三个反引号包围的代码块格式
|
|
73
|
+
* @param text 包含 markdown 代码块的文本
|
|
74
|
+
* @returns 提取到的代码块数组
|
|
75
|
+
*/
|
|
76
|
+
export function extractCodeBlocks(text) {
|
|
77
|
+
const trimmedText = text.trim();
|
|
78
|
+
// 匹配 markdown 代码块的正则表达式
|
|
79
|
+
// 兼容以下情况:
|
|
80
|
+
// - 结尾处没有换行直接关闭 ```
|
|
81
|
+
// - Windows 换行 \r\n
|
|
82
|
+
// - 语言标记包含短横线(如 "c++" 之类极少数场景,放宽为 [\w-])
|
|
83
|
+
// 格式: ```language\ncode\n?```
|
|
84
|
+
const codeBlockRegex = /```([\w-]+)?\s*\r?\n([\s\S]*?)\r?\n?\s*```/g;
|
|
85
|
+
const codeBlocks = [];
|
|
86
|
+
let match;
|
|
87
|
+
while ((match = codeBlockRegex.exec(trimmedText)) !== null) {
|
|
88
|
+
const language = match[1];
|
|
89
|
+
const code = match[2].trim();
|
|
90
|
+
if (code) {
|
|
91
|
+
const componentName = extractComponentName(code, language);
|
|
92
|
+
codeBlocks.push({
|
|
93
|
+
language,
|
|
94
|
+
code,
|
|
95
|
+
componentName
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return codeBlocks;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 从代码中提取组件名
|
|
103
|
+
* 支持多种常见的组件定义模式
|
|
104
|
+
* @param code 代码内容
|
|
105
|
+
* @param language 编程语言
|
|
106
|
+
* @returns 组件名或 undefined
|
|
107
|
+
*/
|
|
108
|
+
export function extractComponentName(code, language) {
|
|
109
|
+
// 移除注释和空白字符
|
|
110
|
+
const cleanCode = code.trim();
|
|
111
|
+
// React 函数组件模式
|
|
112
|
+
const patterns = [
|
|
113
|
+
// export default function ComponentName / export default async function ComponentName
|
|
114
|
+
/export\s+default\s+(?:async\s+)?function\s+([A-Z][a-zA-Z0-9]*)/,
|
|
115
|
+
// export async function ComponentName / export function ComponentName
|
|
116
|
+
/export\s+(?:async\s+)?function\s+([A-Z][a-zA-Z0-9]*)/,
|
|
117
|
+
// function ComponentName
|
|
118
|
+
/^function\s+([A-Z][a-zA-Z0-9]*)/m,
|
|
119
|
+
// export default memo(function ComponentName ...) / React.memo
|
|
120
|
+
/export\s+default\s+(?:React\.)?memo\(\s*function\s+([A-Z][a-zA-Z0-9]*)/,
|
|
121
|
+
// export default forwardRef(..., function ComponentName ...) / React.forwardRef
|
|
122
|
+
/export\s+default\s+(?:React\.)?forwardRef\([^,]*,\s*function\s+([A-Z][a-zA-Z0-9]*)/,
|
|
123
|
+
// export default NamedComponent
|
|
124
|
+
/export\s+default\s+([A-Z][a-zA-Z0-9]*)/,
|
|
125
|
+
// export const ComponentName: Type = ... (可选类型注解)
|
|
126
|
+
/export\s+const\s+([A-Z][a-zA-Z0-9]*)\s*(?::[^=]+)?\s*=/,
|
|
127
|
+
// const ComponentName: Type = ... (可选类型注解)
|
|
128
|
+
/const\s+([A-Z][a-zA-Z0-9]*)\s*(?::[^=]+)?\s*=/,
|
|
129
|
+
// class ComponentName / export default class ComponentName
|
|
130
|
+
/export\s+default\s+class\s+([A-Z][a-zA-Z0-9]*)/,
|
|
131
|
+
/class\s+([A-Z][a-zA-Z0-9]*)/,
|
|
132
|
+
// export { ComponentName } / export { ComponentName as default }
|
|
133
|
+
/export\s*\{\s*([A-Z][a-zA-Z0-9]*)\s*(?:as\s*default)?\s*\}/,
|
|
134
|
+
// interface ComponentNameProps / type ComponentNameProps (用于推断组件名)
|
|
135
|
+
/interface\s+([A-Z][a-zA-Z0-9]*)Props/,
|
|
136
|
+
/type\s+([A-Z][a-zA-Z0-9]*)Props/
|
|
137
|
+
];
|
|
138
|
+
for (const pattern of patterns) {
|
|
139
|
+
const match = cleanCode.match(pattern);
|
|
140
|
+
if (match && match[1]) {
|
|
141
|
+
let componentName = match[1];
|
|
142
|
+
// 如果是 Props 接口,移除 Props 后缀
|
|
143
|
+
if (componentName.endsWith('Props')) {
|
|
144
|
+
componentName = componentName.slice(0, -5);
|
|
145
|
+
}
|
|
146
|
+
// 验证组件名符合 PascalCase 规范
|
|
147
|
+
if (/^[A-Z][a-zA-Z0-9]*$/.test(componentName)) {
|
|
148
|
+
return componentName;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 从文本中提取所有代码块并获取组件信息
|
|
156
|
+
* @param text 包含代码块的文本
|
|
157
|
+
* @returns 包含代码和组件名的信息数组
|
|
158
|
+
*/
|
|
159
|
+
export function extractCodeAndComponents(text) {
|
|
160
|
+
const codeBlocks = extractCodeBlocks(text);
|
|
161
|
+
return codeBlocks.map(block => ({
|
|
162
|
+
code: block.code,
|
|
163
|
+
componentName: block.componentName,
|
|
164
|
+
language: block.language
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
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
|
+
/**
|
|
6
|
+
* 通用文件管理器
|
|
7
|
+
* 可以保存任何类型的文件,使用fileKey和nodeId组织目录结构
|
|
8
|
+
*/
|
|
9
|
+
export class FileManager {
|
|
10
|
+
paths; // 应用路径配置
|
|
11
|
+
baseDir; // 基础存储目录
|
|
12
|
+
constructor(appName) {
|
|
13
|
+
this.paths = envPaths(appName);
|
|
14
|
+
this.baseDir = path.join(this.paths.data, 'files');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 生成文件路径
|
|
18
|
+
* @param fileKey - Figma文件的key
|
|
19
|
+
* @param nodeId - 节点ID(可选)
|
|
20
|
+
* @param filename - 文件名
|
|
21
|
+
* @returns 完整的文件路径
|
|
22
|
+
*/
|
|
23
|
+
getFilePath(fileKey, nodeId, filename) {
|
|
24
|
+
// 清理文件名中的特殊字符
|
|
25
|
+
const cleanFileKey = fileKey.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
26
|
+
const cleanNodeId = nodeId ? nodeId.replace(/[^a-zA-Z0-9-_:]/g, '_') : 'root';
|
|
27
|
+
const cleanFilename = filename.replace(/[^a-zA-Z0-9-_.]/g, '_');
|
|
28
|
+
return path.join(this.baseDir, cleanFileKey, cleanNodeId, cleanFilename);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 保存文件内容
|
|
32
|
+
* @param fileKey - Figma文件的key
|
|
33
|
+
* @param nodeId - 节点ID(可选)
|
|
34
|
+
* @param filename - 文件名
|
|
35
|
+
* @param content - 文件内容
|
|
36
|
+
*/
|
|
37
|
+
async saveFile(fileKey, nodeId, filename, content) {
|
|
38
|
+
try {
|
|
39
|
+
const filePath = this.getFilePath(fileKey, nodeId, filename);
|
|
40
|
+
// 确保目录存在
|
|
41
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
42
|
+
// 保存文件内容
|
|
43
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
44
|
+
Logger.log(`文件已保存: ${filePath}`);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
Logger.error(`保存文件失败: ${error}`);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 读取文件内容
|
|
53
|
+
* @param fileKey - Figma文件的key
|
|
54
|
+
* @param nodeId - 节点ID(可选)
|
|
55
|
+
* @param filename - 文件名
|
|
56
|
+
* @returns Promise<string> - 文件内容,如果文件不存在则返回空字符串
|
|
57
|
+
*/
|
|
58
|
+
async loadFile(fileKey, nodeId, filename) {
|
|
59
|
+
try {
|
|
60
|
+
const filePath = this.getFilePath(fileKey, nodeId, filename);
|
|
61
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
62
|
+
return content;
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err.code === 'ENOENT') {
|
|
66
|
+
Logger.log(`文件不存在: ${filename}`);
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
Logger.error(`读取文件失败: ${err.toString()}`);
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 检查文件是否存在
|
|
75
|
+
* @param fileKey - Figma文件的key
|
|
76
|
+
* @param nodeId - 节点ID(可选)
|
|
77
|
+
* @param filename - 文件名
|
|
78
|
+
* @returns Promise<boolean> - 如果文件存在则返回true
|
|
79
|
+
*/
|
|
80
|
+
async exists(fileKey, nodeId, filename) {
|
|
81
|
+
try {
|
|
82
|
+
const filePath = this.getFilePath(fileKey, nodeId, filename);
|
|
83
|
+
await fs.access(filePath);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 删除文件
|
|
92
|
+
* @param fileKey - Figma文件的key
|
|
93
|
+
* @param nodeId - 节点ID(可选)
|
|
94
|
+
* @param filename - 文件名
|
|
95
|
+
*/
|
|
96
|
+
async deleteFile(fileKey, nodeId, filename) {
|
|
97
|
+
try {
|
|
98
|
+
const filePath = this.getFilePath(fileKey, nodeId, filename);
|
|
99
|
+
await fs.unlink(filePath);
|
|
100
|
+
Logger.log(`文件已删除: ${filePath}`);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
if (err.code !== 'ENOENT') {
|
|
104
|
+
Logger.error(`删除文件失败: ${err.toString()}`);
|
|
105
|
+
throw err; // 如果不是文件不存在错误,则抛出异常
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 清理特定fileKey的所有文件
|
|
111
|
+
* @param fileKey - Figma文件的key
|
|
112
|
+
*/
|
|
113
|
+
async cleanupFileKey(fileKey) {
|
|
114
|
+
try {
|
|
115
|
+
const cleanFileKey = fileKey.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
116
|
+
const fileKeyDir = path.join(this.baseDir, cleanFileKey);
|
|
117
|
+
await fs.rm(fileKeyDir, { recursive: true, force: true });
|
|
118
|
+
Logger.log(`文件Key目录已清理: ${fileKeyDir}`);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
Logger.error(`清理文件Key目录失败: ${error}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 清理特定fileKey和nodeId的所有文件
|
|
126
|
+
* @param fileKey - Figma文件的key
|
|
127
|
+
* @param nodeId - 节点ID(可选)
|
|
128
|
+
*/
|
|
129
|
+
async cleanupNode(fileKey, nodeId) {
|
|
130
|
+
try {
|
|
131
|
+
const cleanFileKey = fileKey.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
132
|
+
const cleanNodeId = nodeId ? nodeId.replace(/[^a-zA-Z0-9-_:]/g, '_') : 'root';
|
|
133
|
+
const nodeDir = path.join(this.baseDir, cleanFileKey, cleanNodeId);
|
|
134
|
+
await fs.rm(nodeDir, { recursive: true, force: true });
|
|
135
|
+
Logger.log(`节点目录已清理: ${nodeDir}`);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
Logger.error(`清理节点目录失败: ${error}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 清理整个存储目录
|
|
143
|
+
*/
|
|
144
|
+
async cleanup() {
|
|
145
|
+
try {
|
|
146
|
+
await fs.rm(this.baseDir, { recursive: true, force: true });
|
|
147
|
+
Logger.log(`存储目录已清理: ${this.baseDir}`);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
Logger.error(`清理存储目录失败: ${error}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 获取文件的完整路径(公共方法)
|
|
155
|
+
* @param fileKey - Figma文件的key
|
|
156
|
+
* @param nodeId - 节点ID(可选)
|
|
157
|
+
* @param filename - 文件名
|
|
158
|
+
* @returns string - 文件的完整路径
|
|
159
|
+
*/
|
|
160
|
+
getFilePathPublic(fileKey, nodeId, filename) {
|
|
161
|
+
return this.getFilePath(fileKey, nodeId, filename);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 获取基础存储目录路径
|
|
165
|
+
* @returns string - 基础存储目录的完整路径
|
|
166
|
+
*/
|
|
167
|
+
getBaseDir() {
|
|
168
|
+
return this.baseDir;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* 获取特定fileKey的目录路径
|
|
172
|
+
* @param fileKey - Figma文件的key
|
|
173
|
+
* @returns string - fileKey目录的完整路径
|
|
174
|
+
*/
|
|
175
|
+
getFileKeyDir(fileKey) {
|
|
176
|
+
const cleanFileKey = fileKey.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
177
|
+
return path.join(this.baseDir, cleanFileKey);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 获取特定fileKey和nodeId的目录路径
|
|
181
|
+
* @param fileKey - Figma文件的key
|
|
182
|
+
* @param nodeId - 节点ID(可选)
|
|
183
|
+
* @returns string - 节点目录的完整路径
|
|
184
|
+
*/
|
|
185
|
+
getNodeDir(fileKey, nodeId) {
|
|
186
|
+
const cleanFileKey = fileKey.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
187
|
+
const cleanNodeId = nodeId ? nodeId.replace(/[^a-zA-Z0-9-_:]/g, '_') : 'root';
|
|
188
|
+
return path.join(this.baseDir, cleanFileKey, cleanNodeId);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* 保存 groupsData 到特定的 nodeId 文件
|
|
192
|
+
* @param fileKey - Figma文件的key
|
|
193
|
+
* @param nodeId - 节点ID(可选)
|
|
194
|
+
* @param groupsData - 分组数据
|
|
195
|
+
*/
|
|
196
|
+
async saveGroupsData(fileKey, nodeId, groupsData) {
|
|
197
|
+
await this.saveFile(fileKey, nodeId, 'groupsData.json', JSON.stringify(groupsData, null, 2));
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* 加载 groupsData 从特定的 nodeId 文件
|
|
201
|
+
* @param fileKey - Figma文件的key
|
|
202
|
+
* @param nodeId - 节点ID(可选)
|
|
203
|
+
* @returns Promise<any[]> - 分组数据,如果文件不存在则返回空数组
|
|
204
|
+
*/
|
|
205
|
+
async loadGroupsData(fileKey, nodeId) {
|
|
206
|
+
try {
|
|
207
|
+
const content = await this.loadFile(fileKey, nodeId, 'groupsData.json');
|
|
208
|
+
return content ? JSON.parse(content) : [];
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
Logger.log(`加载 groupsData 失败,返回空数组: ${error}`);
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 保存 promptSetting 到特定的 nodeId 文件
|
|
217
|
+
* @param fileKey - Figma文件的key
|
|
218
|
+
* @param nodeId - 节点ID(可选)
|
|
219
|
+
* @param promptSetting - 提示词设置
|
|
220
|
+
*/
|
|
221
|
+
async savePromptSetting(fileKey, nodeId, promptSetting) {
|
|
222
|
+
await this.saveFile(fileKey, nodeId, 'promptSetting.json', JSON.stringify(promptSetting, null, 2));
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* 加载 promptSetting 从特定的 nodeId 文件
|
|
226
|
+
* @param fileKey - Figma文件的key
|
|
227
|
+
* @param nodeId - 节点ID(可选)
|
|
228
|
+
* @returns Promise<any> - 提示词设置,如果文件不存在则返回 null
|
|
229
|
+
*/
|
|
230
|
+
async loadPromptSetting(fileKey, nodeId) {
|
|
231
|
+
try {
|
|
232
|
+
const content = await this.loadFile(fileKey, nodeId, 'promptSetting.json');
|
|
233
|
+
return content ? JSON.parse(content) : null;
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
Logger.log(`加载 promptSetting 失败,返回 null: ${error}`);
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
export default FileManager;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { VSCodeLogger } from './vscode-logger.js';
|
|
2
|
+
// 日志工具类,带时间戳输出日志,同时发送到 VSCode
|
|
3
|
+
export class Logger {
|
|
4
|
+
static vscodeLogger = null;
|
|
5
|
+
static vscodeLoggerInitialized = false;
|
|
6
|
+
static log(...args) {
|
|
7
|
+
const timestamp = new Date().toISOString();
|
|
8
|
+
console.log(`[${timestamp}]`, ...args);
|
|
9
|
+
// fs.writeFileSync('log.txt', `[${timestamp}] ${args.join(' ')}\n`, { flag: 'a' });
|
|
10
|
+
// 同时发送到 VSCode
|
|
11
|
+
this.tryVSCodeLog('log', ...args);
|
|
12
|
+
}
|
|
13
|
+
static error(...args) {
|
|
14
|
+
const timestamp = new Date().toISOString();
|
|
15
|
+
console.error(`[${timestamp}]`, ...args);
|
|
16
|
+
// 同时发送到 VSCode
|
|
17
|
+
this.tryVSCodeLog('error', ...args);
|
|
18
|
+
}
|
|
19
|
+
static warn(...args) {
|
|
20
|
+
const timestamp = new Date().toISOString();
|
|
21
|
+
console.warn(`[${timestamp}]`, ...args);
|
|
22
|
+
// 同时发送到 VSCode
|
|
23
|
+
this.tryVSCodeLog('warn', ...args);
|
|
24
|
+
}
|
|
25
|
+
static info(...args) {
|
|
26
|
+
const timestamp = new Date().toISOString();
|
|
27
|
+
console.info(`[${timestamp}]`, ...args);
|
|
28
|
+
// 同时发送到 VSCode
|
|
29
|
+
this.tryVSCodeLog('info', ...args);
|
|
30
|
+
}
|
|
31
|
+
// 初始化 VSCode 日志服务
|
|
32
|
+
static initVSCodeLogger() {
|
|
33
|
+
if (this.vscodeLoggerInitialized)
|
|
34
|
+
return;
|
|
35
|
+
try {
|
|
36
|
+
this.vscodeLogger = VSCodeLogger.getInstance();
|
|
37
|
+
this.vscodeLoggerInitialized = true;
|
|
38
|
+
console.log(`[${new Date().toISOString()}] VSCode 日志服务初始化成功`);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
this.vscodeLogger = null;
|
|
42
|
+
this.vscodeLoggerInitialized = true;
|
|
43
|
+
console.warn(`[${new Date().toISOString()}] VSCode 日志服务初始化失败,将仅使用控制台日志:`, error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// 尝试发送日志到 VSCode
|
|
47
|
+
static tryVSCodeLog(level, ...args) {
|
|
48
|
+
if (!this.vscodeLoggerInitialized) {
|
|
49
|
+
this.initVSCodeLogger();
|
|
50
|
+
}
|
|
51
|
+
if (this.vscodeLogger) {
|
|
52
|
+
try {
|
|
53
|
+
this.vscodeLogger[level](...args);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
// 静默忽略 VSCode 日志发送错误,避免影响主要功能
|
|
57
|
+
// 只在第一次失败时输出警告
|
|
58
|
+
if (this.vscodeLogger !== null) {
|
|
59
|
+
console.warn(`[${new Date().toISOString()}] VSCode 日志服务连接中断,切换到仅控制台模式`);
|
|
60
|
+
this.vscodeLogger = null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// 重连 VSCode 日志
|
|
66
|
+
static reconnectVSCodeLogging() {
|
|
67
|
+
try {
|
|
68
|
+
console.log(`[${new Date().toISOString()}] 正在重新连接 VSCode 日志服务...`);
|
|
69
|
+
this.vscodeLoggerInitialized = false;
|
|
70
|
+
this.vscodeLogger = null;
|
|
71
|
+
this.initVSCodeLogger();
|
|
72
|
+
this.log('VSCode 日志服务重连完成');
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
this.error('VSCode 日志服务重连失败:', error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// 清理资源
|
|
79
|
+
static cleanup() {
|
|
80
|
+
try {
|
|
81
|
+
if (this.vscodeLogger) {
|
|
82
|
+
this.vscodeLogger.destroy();
|
|
83
|
+
this.vscodeLogger = null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
// 忽略清理错误
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Logger } from './logger.js';
|
|
2
|
+
import { getResourceMapAdvanced } from 'sloth-d2c-node/convert';
|
|
3
|
+
/**
|
|
4
|
+
* 检查imageSetting配置是否有变化,如果有变化则重新生成imageMap
|
|
5
|
+
* @param currentImageMap 当前的imageMap
|
|
6
|
+
* @param imageNodeList 图片节点列表
|
|
7
|
+
* @param oldConfig 旧的配置
|
|
8
|
+
* @param newConfig 新的配置
|
|
9
|
+
* @returns Promise<{ imageMap: any, updated: boolean }> - 返回新的imageMap和是否更新的标志
|
|
10
|
+
*/
|
|
11
|
+
export async function updateImageMapIfNeeded(currentImageMap, imageNodeList, oldConfig, newConfig) {
|
|
12
|
+
// 提取相关的imageSetting配置项
|
|
13
|
+
const oldImageSetting = oldConfig?.imageSetting || {};
|
|
14
|
+
const newImageSetting = newConfig?.imageSetting || {};
|
|
15
|
+
// 检查影响imageMap生成的关键配置项
|
|
16
|
+
const relevantKeys = [
|
|
17
|
+
'imageStorageType',
|
|
18
|
+
'imageStorageScale',
|
|
19
|
+
'imageIconType',
|
|
20
|
+
'imageStoragePath',
|
|
21
|
+
'imageStorageNamingRule',
|
|
22
|
+
'ossRegion',
|
|
23
|
+
'ossAccessKeyId',
|
|
24
|
+
'ossAccessKeySecret',
|
|
25
|
+
'ossBucket',
|
|
26
|
+
'ossPath',
|
|
27
|
+
'ossCdnDomain',
|
|
28
|
+
'imageStorageApiUrl',
|
|
29
|
+
'imageStorageApiFileField',
|
|
30
|
+
'imageStorageApiUrlField',
|
|
31
|
+
'imageStorageApiCustomHeader',
|
|
32
|
+
'imageStorageApiCustomBody'
|
|
33
|
+
];
|
|
34
|
+
// 检查是否有相关配置发生变化
|
|
35
|
+
const hasImageSettingChanged = relevantKeys.some(key => {
|
|
36
|
+
return oldImageSetting[key] !== newImageSetting[key];
|
|
37
|
+
});
|
|
38
|
+
if (!hasImageSettingChanged) {
|
|
39
|
+
Logger.log('imageSetting配置未发生变化,使用现有imageMap');
|
|
40
|
+
return { imageMap: currentImageMap, updated: false };
|
|
41
|
+
}
|
|
42
|
+
Logger.log('检测到imageSetting配置变化,重新生成imageMap');
|
|
43
|
+
Logger.log('变化的配置项:', relevantKeys.filter(key => oldImageSetting[key] !== newImageSetting[key]));
|
|
44
|
+
try {
|
|
45
|
+
// 重新生成imageMap
|
|
46
|
+
const newImageMap = await getResourceMapAdvanced(imageNodeList, newConfig);
|
|
47
|
+
Logger.log('已根据新配置重新生成imageMap');
|
|
48
|
+
return { imageMap: newImageMap, updated: true };
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
Logger.log('重新生成imageMap失败,使用现有imageMap:', error);
|
|
52
|
+
return { imageMap: currentImageMap, updated: false };
|
|
53
|
+
}
|
|
54
|
+
}
|