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,154 @@
1
+ import Jimp from 'jimp';
2
+ import { getCV } from './opencv-loader.js';
3
+ import { Logger } from './logger.js';
4
+ export class ImageMatcher {
5
+ /**
6
+ * 使用 OpenCV 模板匹配在大图中查找小图
7
+ * @param haystackPath 大图路径(当前设计稿)
8
+ * @param needlePath 小图路径(历史截图)
9
+ * @param threshold 匹配阈值 (0-1)
10
+ */
11
+ async findMatch(haystackPath, needlePath, threshold = 0.8) {
12
+ try {
13
+ const startTime = Date.now();
14
+ Logger.log(`开始 OpenCV 模板匹配: ${needlePath}`);
15
+ // 获取 OpenCV 实例
16
+ const cv = getCV();
17
+ // 使用 Jimp 读取图片
18
+ const haystackImg = await Jimp.read(haystackPath);
19
+ const needleImg = await Jimp.read(needlePath);
20
+ Logger.log(`大图尺寸: ${haystackImg.bitmap.width}x${haystackImg.bitmap.height}`);
21
+ Logger.log(`小图尺寸: ${needleImg.bitmap.width}x${needleImg.bitmap.height}`);
22
+ // 转换为 OpenCV Mat
23
+ const haystack = this.jimpToMat(haystackImg, cv);
24
+ const needle = this.jimpToMat(needleImg, cv);
25
+ // 创建结果矩阵
26
+ const result = new cv.Mat();
27
+ const mask = new cv.Mat();
28
+ // 执行模板匹配
29
+ // TM_CCOEFF_NORMED: 归一化相关系数匹配,返回值 -1 到 1
30
+ cv.matchTemplate(haystack, needle, result, cv.TM_CCOEFF_NORMED, mask);
31
+ // 找到最佳匹配位置
32
+ const minMax = cv.minMaxLoc(result);
33
+ const maxVal = minMax.maxVal; // 最大相似度(-1 到 1)
34
+ const maxLoc = minMax.maxLoc; // 最佳匹配位置
35
+ const elapsedTime = Date.now() - startTime;
36
+ Logger.log(`OpenCV 匹配完成,耗时: ${elapsedTime}ms,最大相似度: ${maxVal.toFixed(4)}`);
37
+ // 清理内存
38
+ haystack.delete();
39
+ needle.delete();
40
+ result.delete();
41
+ mask.delete();
42
+ // 判断是否满足阈值
43
+ if (maxVal >= threshold) {
44
+ return {
45
+ matched: true,
46
+ confidence: maxVal,
47
+ position: {
48
+ left: maxLoc.x,
49
+ top: maxLoc.y,
50
+ width: needleImg.bitmap.width,
51
+ height: needleImg.bitmap.height,
52
+ },
53
+ };
54
+ }
55
+ Logger.log(`匹配失败,相似度 ${maxVal.toFixed(4)} 低于阈值 ${threshold}`);
56
+ return null;
57
+ }
58
+ catch (error) {
59
+ Logger.error('OpenCV 图像匹配失败:', error);
60
+ return null;
61
+ }
62
+ }
63
+ /**
64
+ * 将 Jimp 图像转换为 OpenCV Mat
65
+ */
66
+ jimpToMat(jimpImg, cv) {
67
+ const { width, height, data } = jimpImg.bitmap;
68
+ // 创建 Mat (RGBA 格式)
69
+ const mat = new cv.Mat(height, width, cv.CV_8UC4);
70
+ // 复制像素数据
71
+ mat.data.set(data);
72
+ // 转换为 RGB(模板匹配只需要 RGB,去掉 Alpha 通道)
73
+ const rgbMat = new cv.Mat();
74
+ cv.cvtColor(mat, rgbMat, cv.COLOR_RGBA2RGB);
75
+ mat.delete();
76
+ return rgbMat;
77
+ }
78
+ /**
79
+ * 使用多尺度模板匹配(处理截图可能有缩放的情况)
80
+ * @param haystackPath 大图路径
81
+ * @param needlePath 小图路径
82
+ * @param threshold 匹配阈值
83
+ * @param scales 尝试的缩放比例列表
84
+ */
85
+ async findMatchMultiScale(haystackPath, needlePath, threshold = 0.8, scales = [1.0, 0.95, 1.05, 0.9, 1.1]) {
86
+ Logger.log(`开始多尺度匹配,尝试 ${scales.length} 个缩放比例`);
87
+ let bestMatch = null;
88
+ for (const scale of scales) {
89
+ try {
90
+ // 读取并缩放模板图片
91
+ const needleImg = await Jimp.read(needlePath);
92
+ if (scale !== 1.0) {
93
+ needleImg.scale(scale);
94
+ Logger.log(`尝试缩放比例: ${scale}`);
95
+ }
96
+ // 保存临时缩放图片
97
+ const fs = await import('fs/promises');
98
+ const path = await import('path');
99
+ const tempPath = needlePath.replace('.png', `_scale${scale}.png`);
100
+ await needleImg.writeAsync(tempPath);
101
+ // 执行匹配
102
+ const result = await this.findMatch(haystackPath, tempPath, threshold);
103
+ // 清理临时文件
104
+ await fs.unlink(tempPath).catch(() => { });
105
+ if (result && (!bestMatch || result.confidence > bestMatch.confidence)) {
106
+ // 调整位置和尺寸(补偿缩放)
107
+ result.position.width = Math.round(result.position.width / scale);
108
+ result.position.height = Math.round(result.position.height / scale);
109
+ bestMatch = result;
110
+ Logger.log(`找到更好的匹配,缩放比例: ${scale},置信度: ${result.confidence.toFixed(4)}`);
111
+ }
112
+ // 如果找到完美匹配,提前退出
113
+ if (result && result.confidence >= 0.95) {
114
+ Logger.log('找到完美匹配,提前退出多尺度搜索');
115
+ break;
116
+ }
117
+ }
118
+ catch (error) {
119
+ Logger.error(`缩放比例 ${scale} 匹配失败:`, error);
120
+ continue;
121
+ }
122
+ }
123
+ if (bestMatch) {
124
+ Logger.log(`多尺度匹配完成,最佳置信度: ${bestMatch.confidence.toFixed(4)}`);
125
+ }
126
+ else {
127
+ Logger.log('多尺度匹配未找到符合要求的结果');
128
+ }
129
+ return bestMatch;
130
+ }
131
+ /**
132
+ * 批量匹配多个历史截图
133
+ */
134
+ async batchMatch(haystackPath, needlePaths, threshold = 0.8) {
135
+ Logger.log(`开始批量匹配 ${needlePaths.length} 个历史截图`);
136
+ const matches = [];
137
+ for (const { path, groupData } of needlePaths) {
138
+ try {
139
+ const result = await this.findMatch(haystackPath, path, threshold);
140
+ if (result && result.matched) {
141
+ result.groupData = groupData;
142
+ matches.push(result);
143
+ Logger.log(`匹配成功: ${groupData.componentMapping?.name || 'Unknown'}`);
144
+ }
145
+ }
146
+ catch (error) {
147
+ Logger.error(`批量匹配失败 (${path}):`, error);
148
+ continue;
149
+ }
150
+ }
151
+ Logger.log(`批量匹配完成,找到 ${matches.length} 个匹配`);
152
+ return matches;
153
+ }
154
+ }
@@ -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,70 @@
1
+ import { Logger } from './logger.js';
2
+ let cv = null;
3
+ let isOpencvReady = false;
4
+ /**
5
+ * 初始化 OpenCV.js
6
+ */
7
+ export async function initOpenCV() {
8
+ if (isOpencvReady && cv) {
9
+ Logger.log('OpenCV.js 已经初始化');
10
+ return;
11
+ }
12
+ try {
13
+ Logger.log('开始初始化 OpenCV.js...');
14
+ // 动态导入 OpenCV.js
15
+ const opencvModule = await import('@techstark/opencv-js');
16
+ cv = opencvModule.default || opencvModule;
17
+ return new Promise((resolve, reject) => {
18
+ const timeout = setTimeout(() => {
19
+ reject(new Error('OpenCV.js 初始化超时(30秒)'));
20
+ }, 30000);
21
+ if (cv.getBuildInformation) {
22
+ // OpenCV 已经加载完成
23
+ clearTimeout(timeout);
24
+ isOpencvReady = true;
25
+ Logger.log('OpenCV.js 已就绪');
26
+ Logger.log('OpenCV 版本信息:', cv.getBuildInformation());
27
+ resolve();
28
+ }
29
+ else {
30
+ // 等待 OpenCV 运行时初始化
31
+ cv.onRuntimeInitialized = () => {
32
+ clearTimeout(timeout);
33
+ isOpencvReady = true;
34
+ Logger.log('OpenCV.js 运行时初始化完成');
35
+ if (cv.getBuildInformation) {
36
+ Logger.log('OpenCV 版本信息:', cv.getBuildInformation());
37
+ }
38
+ resolve();
39
+ };
40
+ }
41
+ });
42
+ }
43
+ catch (error) {
44
+ Logger.error('OpenCV.js 初始化失败:', error);
45
+ throw error;
46
+ }
47
+ }
48
+ /**
49
+ * 检查 OpenCV 是否就绪
50
+ */
51
+ export function isReady() {
52
+ return isOpencvReady;
53
+ }
54
+ /**
55
+ * 获取 OpenCV 实例
56
+ */
57
+ export function getCV() {
58
+ if (!cv || !isOpencvReady) {
59
+ throw new Error('OpenCV.js 尚未初始化,请先调用 initOpenCV()');
60
+ }
61
+ return cv;
62
+ }
63
+ /**
64
+ * 清理 OpenCV 资源(如果需要)
65
+ */
66
+ export function cleanup() {
67
+ isOpencvReady = false;
68
+ cv = null;
69
+ Logger.log('OpenCV.js 资源已清理');
70
+ }
@@ -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
+ }
@@ -0,0 +1,139 @@
1
+ import * as os from 'os';
2
+ import axios from 'axios';
3
+ import pkg from 'node-machine-id';
4
+ import { Logger } from './logger.js';
5
+ const { machineIdSync } = pkg;
6
+ /*
7
+ * 统计上报
8
+ */
9
+ const isOnline = process.env.NODE_ENV === 'production';
10
+ let lastEvent = '';
11
+ /**
12
+ * 获取设备信息
13
+ */
14
+ const getDeviceInfo = () => {
15
+ return {
16
+ platform: os.platform(), // darwin, win32, linux
17
+ arch: os.arch(), // x64, arm64
18
+ osVersion: os.release(),
19
+ hostname: os.hostname(),
20
+ nodeVersion: process.version,
21
+ };
22
+ };
23
+ /**
24
+ * 获取用户信息(从环境变量或系统)
25
+ */
26
+ const getUserInfo = () => {
27
+ // 尝试从环境变量获取
28
+ const userId = process.env.USER_ID || String(os.userInfo().uid) || os.hostname();
29
+ const userName = process.env.USER || process.env.LOGNAME || os.userInfo().username;
30
+ return {
31
+ id: userId,
32
+ name: userName,
33
+ };
34
+ };
35
+ /**
36
+ * 自定义 beacon 上报
37
+ */
38
+ const customizebeaconReport = (eventName, params) => {
39
+ const user = getUserInfo();
40
+ const device = getDeviceInfo();
41
+ const machineId = machineIdSync(true);
42
+ const uniqueKey = `${machineId}/${user.id || 'unknown'}/${user.name || 'anonymous'}`;
43
+ // 设备标识
44
+ const deviceIdentifier = `${device.platform || 'Unknown'} ${device.arch || ''} ${device.osVersion || ''} Node${device.nodeVersion || ''}`;
45
+ axios
46
+ .post('https://otheve.beacon.qq.com/analytics/v2_upload?appkey=0WEB0IK7H05E5K4S', {
47
+ appVersion: '',
48
+ sdkId: 'js',
49
+ sdkVersion: '4.5.9-web',
50
+ mainAppKey: '0WEB0IK7H05E5K4S',
51
+ platformId: 3,
52
+ common: {
53
+ bizType: 'd2c_mcp',
54
+ platCode: '0',
55
+ utm_source: '',
56
+ plat: 'tme',
57
+ abtestName: '',
58
+ userStatus: '1',
59
+ accountType: '1',
60
+ uniqueKey: uniqueKey,
61
+ A2: '59x14SkHQFdNp6HrSnYmiSWGeT9tBBFY',
62
+ A8: '',
63
+ A12: 'zh-CN',
64
+ A17: '',
65
+ A23: '',
66
+ A50: '2846217',
67
+ A76: '0WEB0IK7H05E5K4S_' + Date.now(),
68
+ A101: deviceIdentifier, // 设备信息
69
+ A102: '',
70
+ A104: '',
71
+ A119: '',
72
+ A153: '',
73
+ },
74
+ events: [
75
+ {
76
+ eventCode: eventName,
77
+ eventTime: '' + Date.now(),
78
+ mapValue: {
79
+ ...params,
80
+ // 附加设备信息
81
+ devicePlatform: device.platform,
82
+ deviceArch: device.arch,
83
+ deviceOsVersion: device.osVersion,
84
+ nodeVersion: device.nodeVersion,
85
+ A99: 'Y',
86
+ A100: '370',
87
+ A72: '4.5.9-web',
88
+ A88: '1744294371735',
89
+ },
90
+ },
91
+ ],
92
+ })
93
+ .then((res) => {
94
+ Logger.info('Beacon report success:', res);
95
+ })
96
+ .catch((err) => {
97
+ // 静默处理上报错误,避免影响主流程
98
+ console.error('Beacon report error:', err.message);
99
+ });
100
+ };
101
+ /**
102
+ * 向 beacon 服务上报事件
103
+ */
104
+ const trackEvent = (eventName, params) => {
105
+ if (!eventName) {
106
+ return;
107
+ }
108
+ try {
109
+ customizebeaconReport(eventName, {
110
+ lastEvent,
111
+ eventCode: eventName,
112
+ ...params,
113
+ });
114
+ lastEvent = eventName;
115
+ }
116
+ catch (err) {
117
+ console.error('Track event error:', err);
118
+ }
119
+ };
120
+ /**
121
+ * 上报工具调用事件
122
+ */
123
+ export const trackToolCall = (toolName, extParam) => {
124
+ try {
125
+ if (toolName) {
126
+ trackEvent('mcp_tool_call', {
127
+ toolName,
128
+ ...extParam,
129
+ });
130
+ }
131
+ }
132
+ catch (err) {
133
+ console.error('Track tool call error:', err);
134
+ }
135
+ };
136
+ /**
137
+ * 导出获取设备信息和用户信息的工具函数
138
+ */
139
+ export { getDeviceInfo, getUserInfo };
@@ -0,0 +1,100 @@
1
+ import { Logger } from './logger.js';
2
+ import { getResourceMapAdvanced } from 'sloth-d2c-node/convert';
3
+ import { replacePathVariables } from './webpack-substitutions.js';
4
+ import { getSystemSupportFileName } from './utils.js';
5
+ import { sha1 } from 'js-sha1';
6
+ /**
7
+ * 移除 base64 字符串的前缀(如 data:image/png;base64,),只保留纯 base64 数据
8
+ * @param base64String 带前缀的 base64 字符串
9
+ * @returns 纯 base64 数据
10
+ */
11
+ export function removeBase64Prefix(base64String) {
12
+ if (!base64String)
13
+ return '';
14
+ const commaIndex = base64String.indexOf(',');
15
+ if (commaIndex !== -1) {
16
+ return base64String.slice(commaIndex + 1);
17
+ }
18
+ return base64String;
19
+ }
20
+ /**
21
+ * 检查imageSetting配置是否有变化,如果有变化则重新生成imageMap
22
+ * @param currentImageMap 当前的imageMap
23
+ * @param imageNodeList 图片节点列表
24
+ * @param oldConfig 旧的配置
25
+ * @param newConfig 新的配置
26
+ * @returns Promise<{ imageMap: any, updated: boolean }> - 返回新的imageMap和是否更新的标志
27
+ */
28
+ export async function updateImageMapIfNeeded(currentImageMap, imageNodeList, oldConfig, newConfig, local) {
29
+ if (local) {
30
+ Logger.log('本地模式,直接更新imageMap');
31
+ for (let node of imageNodeList) {
32
+ const type = node.srcType === 'PNG' ? 'png' : 'svg';
33
+ const base64 = removeBase64Prefix(currentImageMap[node.id]?.base64);
34
+ const { imageStorageType, imageStorageNamingRule, imageStoragePath } = newConfig?.imageSetting;
35
+ if (imageStorageType === 'local') {
36
+ let webpackPathRule = imageStorageNamingRule;
37
+ if (!webpackPathRule.endsWith('.[ext]')) {
38
+ webpackPathRule += '.[ext]';
39
+ }
40
+ const name = replacePathVariables(webpackPathRule, {
41
+ filename: [getSystemSupportFileName(node.name), type].join('.'),
42
+ chunk: {
43
+ id: node.id,
44
+ name: getSystemSupportFileName(node.name),
45
+ contentHash: {
46
+ [type]: sha1(base64).substring(0, 8),
47
+ },
48
+ },
49
+ contentHashType: type,
50
+ }, undefined);
51
+ const path = [imageStoragePath, name].join('/');
52
+ node.src = path;
53
+ currentImageMap[node.id].path = path;
54
+ }
55
+ }
56
+ return { imageMap: currentImageMap, updated: true };
57
+ }
58
+ // 提取相关的imageSetting配置项
59
+ const oldImageSetting = oldConfig?.imageSetting || {};
60
+ const newImageSetting = newConfig?.imageSetting || {};
61
+ // 检查影响imageMap生成的关键配置项
62
+ const relevantKeys = [
63
+ 'imageStorageType',
64
+ 'imageStorageScale',
65
+ 'imageIconType',
66
+ 'imageStoragePath',
67
+ 'imageStorageNamingRule',
68
+ 'ossRegion',
69
+ 'ossAccessKeyId',
70
+ 'ossAccessKeySecret',
71
+ 'ossBucket',
72
+ 'ossPath',
73
+ 'ossCdnDomain',
74
+ 'imageStorageApiUrl',
75
+ 'imageStorageApiFileField',
76
+ 'imageStorageApiUrlField',
77
+ 'imageStorageApiCustomHeader',
78
+ 'imageStorageApiCustomBody',
79
+ ];
80
+ // 检查是否有相关配置发生变化
81
+ const hasImageSettingChanged = relevantKeys.some((key) => {
82
+ return oldImageSetting[key] !== newImageSetting[key];
83
+ });
84
+ if (!hasImageSettingChanged) {
85
+ Logger.log('imageSetting配置未发生变化,使用现有imageMap');
86
+ return { imageMap: currentImageMap, updated: false };
87
+ }
88
+ Logger.log('检测到imageSetting配置变化,重新生成imageMap');
89
+ Logger.log('变化的配置项:', relevantKeys.filter((key) => oldImageSetting[key] !== newImageSetting[key]));
90
+ try {
91
+ // 重新生成imageMap
92
+ const newImageMap = await getResourceMapAdvanced(imageNodeList, newConfig);
93
+ Logger.log('已根据新配置重新生成imageMap');
94
+ return { imageMap: newImageMap, updated: true };
95
+ }
96
+ catch (error) {
97
+ Logger.log('重新生成imageMap失败,使用现有imageMap:', error);
98
+ return { imageMap: currentImageMap, updated: false };
99
+ }
100
+ }