forked from lioensky/VCPToolBox
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmessageProcessor.js
More file actions
296 lines (260 loc) · 15.5 KB
/
messageProcessor.js
File metadata and controls
296 lines (260 loc) · 15.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
// modules/messageProcessor.js
const fs = require('fs').promises;
const path = require('path');
const lunarCalendar = require('chinese-lunar-calendar');
const agentManager = require('./agentManager.js'); // 引入新的Agent管理器
const tvsManager = require('./tvsManager.js'); // 引入新的TVS管理器
const DEFAULT_TIMEZONE = process.env.DEFAULT_TIMEZONE || 'Asia/Shanghai';
const REPORT_TIMEZONE = process.env.REPORT_TIMEZONE || 'Asia/Shanghai'; // 新增:用于控制 AI 报告的时间,默认回退到中国时区
const AGENT_DIR = path.join(__dirname, '..', 'Agent');
const TVS_DIR = path.join(__dirname, '..', 'TVStxt');
const VCP_ASYNC_RESULTS_DIR = path.join(__dirname, '..', 'VCPAsyncResults');
async function resolveAllVariables(text, model, role, context, processingStack = new Set()) {
if (text == null) return '';
let processedText = String(text);
// 通用正则表达式,匹配所有 {{...}} 格式的占位符
const placeholderRegex = /\{\{([a-zA-Z0-9_:]+)\}\}/g;
const matches = [...processedText.matchAll(placeholderRegex)];
// 提取所有潜在的别名(去除 "agent:" 前缀)
const allAliases = new Set(matches.map(match => match[1].replace(/^agent:/, '')));
for (const alias of allAliases) {
// 关键:使用 agentManager 来判断这是否是一个真正的Agent
if (agentManager.isAgent(alias)) {
if (processingStack.has(alias)) {
console.error(`[AgentManager] Circular dependency detected! Stack: [${[...processingStack].join(' -> ')} -> ${alias}]`);
const errorMessage = `[Error: Circular agent reference detected for '${alias}']`;
processedText = processedText.replaceAll(`{{${alias}}}`, errorMessage).replaceAll(`{{agent:${alias}}}`, errorMessage);
continue;
}
const agentContent = await agentManager.getAgentPrompt(alias);
processingStack.add(alias);
const resolvedAgentContent = await resolveAllVariables(agentContent, model, role, context, processingStack);
processingStack.delete(alias);
// 替换两种可能的Agent占位符格式
processedText = processedText.replaceAll(`{{${alias}}}`, resolvedAgentContent);
processedText = processedText.replaceAll(`{{agent:${alias}}}`, resolvedAgentContent);
}
}
// 在所有Agent都被递归展开后,处理剩余的非Agent占位符
processedText = await replacePriorityVariables(processedText, context, role);
processedText = await replaceOtherVariables(processedText, model, role, context);
return processedText;
}
async function replaceOtherVariables(text, model, role, context) {
const { pluginManager, cachedEmojiLists, detectors, superDetectors, DEBUG_MODE } = context;
if (text == null) return '';
let processedText = String(text);
if (role === 'system') {
for (const envKey in process.env) {
if (envKey.startsWith('Tar') || envKey.startsWith('Var')) {
const placeholder = `{{${envKey}}}`;
if (processedText.includes(placeholder)) {
const value = process.env[envKey];
if (value && typeof value === 'string' && value.toLowerCase().endsWith('.txt')) {
const fileContent = await tvsManager.getContent(value);
// 检查内容是否表示错误
if (fileContent.startsWith('[变量文件') || fileContent.startsWith('[处理变量文件')) {
processedText = processedText.replaceAll(placeholder, fileContent);
} else {
const resolvedContent = await replaceOtherVariables(fileContent, model, role, context);
processedText = processedText.replaceAll(placeholder, resolvedContent);
}
} else {
processedText = processedText.replaceAll(placeholder, value || `[未配置 ${envKey}]`);
}
}
}
}
let sarPromptToInject = null;
const modelToPromptMap = new Map();
for (const envKey in process.env) {
if (/^SarModel\d+$/.test(envKey)) {
const index = envKey.substring(8);
const promptKey = `SarPrompt${index}`;
let promptValue = process.env[promptKey];
const models = process.env[envKey];
if (promptValue && models) {
if (typeof promptValue === 'string' && promptValue.toLowerCase().endsWith('.txt')) {
const fileContent = await tvsManager.getContent(promptValue);
// 检查内容是否表示错误
if (fileContent.startsWith('[变量文件') || fileContent.startsWith('[处理变量文件')) {
promptValue = fileContent;
} else {
promptValue = await replaceOtherVariables(fileContent, model, role, context);
}
}
const modelList = models.split(',').map(m => m.trim()).filter(m => m);
for (const m of modelList) {
modelToPromptMap.set(m, promptValue);
}
}
}
}
if (model && modelToPromptMap.has(model)) {
sarPromptToInject = modelToPromptMap.get(model);
}
const sarPlaceholderRegex = /\{\{Sar[a-zA-Z0-9_]+\}\}/g;
if (sarPromptToInject !== null) {
processedText = processedText.replaceAll(sarPlaceholderRegex, sarPromptToInject);
} else {
processedText = processedText.replaceAll(sarPlaceholderRegex, '');
}
const now = new Date();
if (DEBUG_MODE) {
console.log(`[TimeVar] Raw Date: ${now.toISOString()}`);
console.log(`[TimeVar] Default Timezone (for internal use): ${DEFAULT_TIMEZONE}`);
console.log(`[TimeVar] Report Timezone (for AI prompt): ${REPORT_TIMEZONE}`);
}
// 使用 REPORT_TIMEZONE 替换时间占位符
const date = now.toLocaleDateString('zh-CN', { timeZone: REPORT_TIMEZONE });
processedText = processedText.replace(/\{\{Date\}\}/g, date);
const time = now.toLocaleTimeString('zh-CN', { timeZone: REPORT_TIMEZONE });
processedText = processedText.replace(/\{\{Time\}\}/g, time);
const today = now.toLocaleDateString('zh-CN', { weekday: 'long', timeZone: REPORT_TIMEZONE });
processedText = processedText.replace(/\{\{Today\}\}/g, today);
const year = now.getFullYear();
const month = now.getMonth() + 1;
const day = now.getDate();
const lunarDate = lunarCalendar.getLunar(year, month, day);
let yearName = lunarDate.lunarYear.replace('年', '');
let festivalInfo = `${yearName}${lunarDate.zodiac}年${lunarDate.dateStr}`;
if (lunarDate.solarTerm) festivalInfo += ` ${lunarDate.solarTerm}`;
processedText = processedText.replace(/\{\{Festival\}\}/g, festivalInfo);
const staticPlaceholderValues = pluginManager.getAllPlaceholderValues(); // Use the getter
if (staticPlaceholderValues && staticPlaceholderValues.size > 0) {
for (const [placeholder, value] of staticPlaceholderValues.entries()) {
const placeholderRegex = new RegExp(placeholder.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'g');
// The getter now returns the correct string value
processedText = processedText.replace(placeholderRegex, value || `[${placeholder} 信息不可用]`);
}
}
const individualPluginDescriptions = pluginManager.getIndividualPluginDescriptions();
if (individualPluginDescriptions && individualPluginDescriptions.size > 0) {
for (const [placeholderKey, description] of individualPluginDescriptions) {
processedText = processedText.replaceAll(`{{${placeholderKey}}}`, description || `[${placeholderKey} 信息不可用]`);
}
}
if (processedText.includes('{{VCPAllTools}}')) {
const vcpDescriptionsList = [];
if (individualPluginDescriptions && individualPluginDescriptions.size > 0) {
for (const description of individualPluginDescriptions.values()) {
vcpDescriptionsList.push(description);
}
}
const allVcpToolsString = vcpDescriptionsList.length > 0 ? vcpDescriptionsList.join('\n\n---\n\n') : '没有可用的VCP工具描述信息';
processedText = processedText.replaceAll('{{VCPAllTools}}', allVcpToolsString);
}
if (process.env.PORT) {
processedText = processedText.replaceAll('{{Port}}', process.env.PORT);
}
const effectiveImageKey = pluginManager.getResolvedPluginConfigValue('ImageServer', 'Image_Key');
if (processedText && typeof processedText === 'string' && effectiveImageKey) {
processedText = processedText.replaceAll('{{Image_Key}}', effectiveImageKey);
} else if (processedText && typeof processedText === 'string' && processedText.includes('{{Image_Key}}')) {
if (DEBUG_MODE) console.warn('[replaceOtherVariables] {{Image_Key}} placeholder found in text, but ImageServer plugin or its Image_Key is not resolved. Placeholder will not be replaced.');
}
for (const rule of detectors) {
if (typeof rule.detector === 'string' && rule.detector.length > 0 && typeof rule.output === 'string') {
processedText = processedText.replaceAll(rule.detector, rule.output);
}
}
}
for (const rule of superDetectors) {
if (typeof rule.detector === 'string' && rule.detector.length > 0 && typeof rule.output === 'string') {
processedText = processedText.replaceAll(rule.detector, rule.output);
}
}
const asyncResultPlaceholderRegex = /\{\{VCP_ASYNC_RESULT::([a-zA-Z0-9_.-]+)::([a-zA-Z0-9_-]+)\}\}/g;
let asyncMatch;
let tempAsyncProcessedText = processedText;
const promises = [];
while ((asyncMatch = asyncResultPlaceholderRegex.exec(processedText)) !== null) {
const placeholder = asyncMatch[0];
const pluginName = asyncMatch[1];
const requestId = asyncMatch[2];
promises.push(
(async () => {
const resultFilePath = path.join(VCP_ASYNC_RESULTS_DIR, `${pluginName}-${requestId}.json`);
try {
const fileContent = await fs.readFile(resultFilePath, 'utf-8');
const callbackData = JSON.parse(fileContent);
let replacementText = `[任务 ${pluginName} (ID: ${requestId}) 已完成]`;
if (callbackData && callbackData.message) {
replacementText = callbackData.message;
} else if (callbackData && callbackData.status === 'Succeed') {
replacementText = `任务 ${pluginName} (ID: ${requestId}) 已成功完成。详情: ${JSON.stringify(callbackData.data || callbackData.result || callbackData)}`;
} else if (callbackData && callbackData.status === 'Failed') {
replacementText = `任务 ${pluginName} (ID: ${requestId}) 处理失败。原因: ${callbackData.reason || JSON.stringify(callbackData.data || callbackData.error || callbackData)}`;
}
tempAsyncProcessedText = tempAsyncProcessedText.replace(placeholder, replacementText);
} catch (error) {
if (error.code === 'ENOENT') {
tempAsyncProcessedText = tempAsyncProcessedText.replace(placeholder, `[任务 ${pluginName} (ID: ${requestId}) 结果待更新...]`);
} else {
console.error(`[replaceOtherVariables] Error processing async placeholder ${placeholder}:`, error);
tempAsyncProcessedText = tempAsyncProcessedText.replace(placeholder, `[获取任务 ${pluginName} (ID: ${requestId}) 结果时出错]`);
}
}
})()
);
}
await Promise.all(promises);
processedText = tempAsyncProcessedText;
return processedText;
}
async function replacePriorityVariables(text, context, role) {
const { pluginManager, cachedEmojiLists, DEBUG_MODE } = context;
if (text == null) return '';
let processedText = String(text);
// 只在 system role 中处理
if (role !== 'system') {
return processedText;
}
// --- 表情包处理 ---
const emojiPlaceholderRegex = /\{\{(.+?表情包)\}\}/g;
let emojiMatch;
while ((emojiMatch = emojiPlaceholderRegex.exec(processedText)) !== null) {
const placeholder = emojiMatch[0];
const emojiName = emojiMatch[1];
const emojiList = cachedEmojiLists.get(emojiName);
processedText = processedText.replaceAll(placeholder, emojiList || `[${emojiName}列表不可用]`);
}
// --- 日记本处理 (已修复循环风险) ---
const diaryPlaceholderRegex = /\{\{(.+?)日记本\}\}/g;
let allDiariesData = {};
const allDiariesDataString = pluginManager.getPlaceholderValue("{{AllCharacterDiariesData}}");
if (allDiariesDataString && !allDiariesDataString.startsWith("[Placeholder")) {
try {
allDiariesData = JSON.parse(allDiariesDataString);
} catch (e) {
console.error(`[replacePriorityVariables] Failed to parse AllCharacterDiariesData JSON: ${e.message}. Data: ${allDiariesDataString.substring(0, 100)}...`);
}
} else if (allDiariesDataString && allDiariesDataString.startsWith("[Placeholder")) {
if (DEBUG_MODE) console.warn(`[replacePriorityVariables] Placeholder {{AllCharacterDiariesData}} not found or not yet populated. Value: ${allDiariesDataString}`);
}
// Step 1: Find all unique diary placeholders in the original text to avoid loops.
const matches = [...processedText.matchAll(diaryPlaceholderRegex)];
const uniquePlaceholders = [...new Set(matches.map(match => match[0]))];
// Step 2: Iterate through the unique placeholders and replace them.
for (const placeholder of uniquePlaceholders) {
// Extract character name from placeholder like "{{小雨日记本}}" -> "小雨"
const characterNameMatch = placeholder.match(/\{\{(.+?)日记本\}\}/);
if (characterNameMatch && characterNameMatch[1]) {
const characterName = characterNameMatch[1];
let diaryContent = `[${characterName}日记本内容为空或未从插件获取]`;
if (allDiariesData.hasOwnProperty(characterName)) {
diaryContent = allDiariesData[characterName];
}
// Replace all instances of this specific placeholder.
// This is safe because we are iterating over a pre-determined list, not re-scanning the string.
processedText = processedText.replaceAll(placeholder, diaryContent);
}
}
return processedText;
}
module.exports = {
// 导出主函数,并重命名旧函数以供内部调用
replaceAgentVariables: resolveAllVariables,
replaceOtherVariables,
replacePriorityVariables
};