alemonjs-aichat 1.0.39 → 1.0.40
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/lib/assets/main.css-B0nVKAez.css +1 -0
- package/lib/assets/main.css-CGjsqb4M.css +1 -0
- package/lib/assets/main.css-Ca9AXT93.css +1 -0
- package/lib/assets/main.css-CtcHjnQk.css +1 -0
- package/lib/assets/main.css-DbmLXg_2.css +1 -0
- package/lib/assets/main.css-U3PRbisd.css +1 -0
- package/lib/assets/main.css.js +1 -1
- package/lib/image/conponent/PanelPreview.js +2200 -0
- package/lib/index.js +12 -0
- package/lib/panel/chat.js +487 -0
- package/lib/panel/config.js +224 -0
- package/lib/panel/groupConfig.js +170 -0
- package/lib/panel/server.js +441 -0
- package/lib/response/zreply/capi.js +127 -18
- package/lib/response/zreply/rapi.js +10 -3
- package/lib/response/zreply/tools.js +14 -3
- package/package.json +10 -4
package/lib/index.js
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { redis } from './redis.js';
|
|
2
2
|
import commands from './routes/commands.js';
|
|
3
3
|
import middleware from './routes/middleware.js';
|
|
4
|
+
import { createPanelServer } from './panel/server.js';
|
|
4
5
|
|
|
6
|
+
const startPanelServer = () => {
|
|
7
|
+
if (process.env.AICHAT_PANEL_DISABLED === "1")
|
|
8
|
+
return;
|
|
9
|
+
createPanelServer().catch((error) => {
|
|
10
|
+
logger.error({
|
|
11
|
+
message: "网页控制台启动失败",
|
|
12
|
+
error: error instanceof Error ? error.message : String(error),
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
};
|
|
5
16
|
var index = defineChildren({
|
|
6
17
|
register() {
|
|
7
18
|
return {
|
|
@@ -19,6 +30,7 @@ var index = defineChildren({
|
|
|
19
30
|
error: error.message,
|
|
20
31
|
});
|
|
21
32
|
});
|
|
33
|
+
startPanelServer();
|
|
22
34
|
},
|
|
23
35
|
});
|
|
24
36
|
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import redisClient from '../config.js';
|
|
2
|
+
import { CApiReply } from '../response/zreply/capi.js';
|
|
3
|
+
import { RApiReply } from '../response/zreply/rapi.js';
|
|
4
|
+
|
|
5
|
+
const PANEL_BOT_ID = "aichatbot";
|
|
6
|
+
const PANEL_DEFAULT_USER_ID = "webuser";
|
|
7
|
+
const PANEL_IMAGE_TOOL_NAMES = new Set([
|
|
8
|
+
"StableDiffusionGenerateImage",
|
|
9
|
+
"Photoshop",
|
|
10
|
+
"AgentUploadFileToR2",
|
|
11
|
+
]);
|
|
12
|
+
const withTimeout = async (promise, fallback, ms = 800) => Promise.race([
|
|
13
|
+
promise,
|
|
14
|
+
new Promise((resolve) => {
|
|
15
|
+
setTimeout(() => resolve(fallback), ms);
|
|
16
|
+
}),
|
|
17
|
+
]).catch(() => fallback);
|
|
18
|
+
const normalizeIdentityValue = (value, fallback) => {
|
|
19
|
+
const normalized = String(value || "").trim();
|
|
20
|
+
return normalized || fallback;
|
|
21
|
+
};
|
|
22
|
+
const getCurrentPanelGuid = async () => {
|
|
23
|
+
const keys = await withTimeout(redisClient.redis.keys("ai:currentAI:*"), []);
|
|
24
|
+
const firstKey = keys[0];
|
|
25
|
+
return firstKey ? firstKey.replace("ai:currentAI:", "") || "global" : "global";
|
|
26
|
+
};
|
|
27
|
+
const resolvePanelChatIdentity = async (guid, userId, nickname) => ({
|
|
28
|
+
guid: normalizeIdentityValue(guid, await getCurrentPanelGuid()),
|
|
29
|
+
userId: normalizeIdentityValue(userId, PANEL_DEFAULT_USER_ID),
|
|
30
|
+
nickname: normalizeIdentityValue(nickname, normalizeIdentityValue(userId, PANEL_DEFAULT_USER_ID)),
|
|
31
|
+
botId: PANEL_BOT_ID,
|
|
32
|
+
});
|
|
33
|
+
const getPanelChatKey = (guid, userId) => `ai:panel_chat:${guid}:${userId}`;
|
|
34
|
+
const getPanelTime = () => new Date().toLocaleTimeString("zh-CN", {
|
|
35
|
+
hour: "2-digit",
|
|
36
|
+
minute: "2-digit",
|
|
37
|
+
hour12: false,
|
|
38
|
+
});
|
|
39
|
+
const getPanelChatMessages = async (guid, userId) => {
|
|
40
|
+
const raw = await withTimeout(redisClient.redis.get(getPanelChatKey(guid, userId)), null);
|
|
41
|
+
if (!raw)
|
|
42
|
+
return [];
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(raw);
|
|
45
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const appendPanelChatMessages = async (guid, userId, messages) => {
|
|
52
|
+
if (!messages.length)
|
|
53
|
+
return;
|
|
54
|
+
const current = await getPanelChatMessages(guid, userId);
|
|
55
|
+
const next = [...current, ...messages].slice(-80);
|
|
56
|
+
await withTimeout(redisClient.redis.set(getPanelChatKey(guid, userId), JSON.stringify(next)), "0");
|
|
57
|
+
};
|
|
58
|
+
const clearPanelChatMessages = async (guid, userId) => {
|
|
59
|
+
await withTimeout(redisClient.redis.del(getPanelChatKey(guid, userId)), 0);
|
|
60
|
+
};
|
|
61
|
+
const extractFallbackText = (value) => {
|
|
62
|
+
if (typeof value === "string")
|
|
63
|
+
return value;
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
return value.map(extractFallbackText).filter(Boolean).join("\n");
|
|
66
|
+
}
|
|
67
|
+
if (value && typeof value === "object") {
|
|
68
|
+
return extractFallbackText(value.text ?? value.content ?? value.value ?? value.data?.text ?? "");
|
|
69
|
+
}
|
|
70
|
+
return "";
|
|
71
|
+
};
|
|
72
|
+
const normalizePanelImageUrl = (url) => {
|
|
73
|
+
let value = String(url || "").trim().replace(/\\/g, "/");
|
|
74
|
+
if (!value)
|
|
75
|
+
return "";
|
|
76
|
+
if (/^https?:\/\//i.test(value) || value.startsWith("data:image/")) {
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
value = value.replace(/^\.?\//, "").replace(/^public\//, "");
|
|
80
|
+
if (value.startsWith("image_out/"))
|
|
81
|
+
return `/${value}`;
|
|
82
|
+
if (value.startsWith("/"))
|
|
83
|
+
return value;
|
|
84
|
+
return value;
|
|
85
|
+
};
|
|
86
|
+
const uniquePanelImages = (images) => [
|
|
87
|
+
...new Set(images.map(normalizePanelImageUrl).filter(Boolean)),
|
|
88
|
+
];
|
|
89
|
+
const isPanelImageUrl = (url) => {
|
|
90
|
+
const value = String(url || "").trim().replace(/\\/g, "/");
|
|
91
|
+
if (!value)
|
|
92
|
+
return false;
|
|
93
|
+
if (value.startsWith("data:image/"))
|
|
94
|
+
return true;
|
|
95
|
+
if (/^\.?\/?(public\/)?image_out\//i.test(value))
|
|
96
|
+
return true;
|
|
97
|
+
return /^https?:\/\//i.test(value) && /\.(png|jpe?g|gif|webp|bmp|svg)([?#].*)?$/i.test(value);
|
|
98
|
+
};
|
|
99
|
+
const shouldCollectToolImages = (functionName, payload) => {
|
|
100
|
+
if (!PANEL_IMAGE_TOOL_NAMES.has(functionName))
|
|
101
|
+
return false;
|
|
102
|
+
if (functionName === "AgentUploadFileToR2") {
|
|
103
|
+
return payload?.uploadType === "image";
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
};
|
|
107
|
+
const extractPanelImagesFromString = (text) => {
|
|
108
|
+
const images = [];
|
|
109
|
+
if (isPanelImageUrl(text)) {
|
|
110
|
+
images.push(text);
|
|
111
|
+
}
|
|
112
|
+
const imgTagRegex = /<img=([^>]+)>/g;
|
|
113
|
+
const markdownRegex = /!\[[^\]]*]\(([^)]+)\)/g;
|
|
114
|
+
let match;
|
|
115
|
+
while ((match = imgTagRegex.exec(text)) !== null) {
|
|
116
|
+
images.push(match[1]);
|
|
117
|
+
}
|
|
118
|
+
while ((match = markdownRegex.exec(text)) !== null) {
|
|
119
|
+
images.push(match[1]);
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(text);
|
|
123
|
+
images.push(...extractPanelImages(parsed));
|
|
124
|
+
}
|
|
125
|
+
catch { }
|
|
126
|
+
return uniquePanelImages(images);
|
|
127
|
+
};
|
|
128
|
+
const extractPanelImages = (value) => {
|
|
129
|
+
if (!value)
|
|
130
|
+
return [];
|
|
131
|
+
if (typeof value === "string") {
|
|
132
|
+
return extractPanelImagesFromString(value);
|
|
133
|
+
}
|
|
134
|
+
if (Array.isArray(value)) {
|
|
135
|
+
return uniquePanelImages(value.flatMap(extractPanelImages));
|
|
136
|
+
}
|
|
137
|
+
if (typeof value !== "object")
|
|
138
|
+
return [];
|
|
139
|
+
const directKeys = [
|
|
140
|
+
"url",
|
|
141
|
+
"image_url",
|
|
142
|
+
"imageUrl",
|
|
143
|
+
"path",
|
|
144
|
+
"file",
|
|
145
|
+
"sendText",
|
|
146
|
+
];
|
|
147
|
+
const nestedKeys = ["data", "image", "images", "result", "results", "output"];
|
|
148
|
+
const images = [];
|
|
149
|
+
for (const key of directKeys) {
|
|
150
|
+
images.push(...extractPanelImages(value[key]));
|
|
151
|
+
}
|
|
152
|
+
for (const key of nestedKeys) {
|
|
153
|
+
images.push(...extractPanelImages(value[key]));
|
|
154
|
+
}
|
|
155
|
+
return uniquePanelImages(images);
|
|
156
|
+
};
|
|
157
|
+
const pushPanelMessage = (list, message) => {
|
|
158
|
+
const images = uniquePanelImages(message.images || []);
|
|
159
|
+
const next = {
|
|
160
|
+
...message,
|
|
161
|
+
images,
|
|
162
|
+
text: message.text || (images.length ? "[图片]" : ""),
|
|
163
|
+
time: message.time || getPanelTime(),
|
|
164
|
+
};
|
|
165
|
+
list.push(next);
|
|
166
|
+
return next;
|
|
167
|
+
};
|
|
168
|
+
const createPanelOutput = (messages, hooks) => {
|
|
169
|
+
const pushedImages = new Set();
|
|
170
|
+
const filterNewImages = (images) => {
|
|
171
|
+
const next = uniquePanelImages(images).filter((image) => {
|
|
172
|
+
if (pushedImages.has(image))
|
|
173
|
+
return false;
|
|
174
|
+
pushedImages.add(image);
|
|
175
|
+
return true;
|
|
176
|
+
});
|
|
177
|
+
return next;
|
|
178
|
+
};
|
|
179
|
+
const pushOutputMessage = async (message) => {
|
|
180
|
+
const next = pushPanelMessage(messages, message);
|
|
181
|
+
await hooks?.onMessage?.(next);
|
|
182
|
+
return next;
|
|
183
|
+
};
|
|
184
|
+
const pushToolImages = async (label, payload) => {
|
|
185
|
+
const images = filterNewImages(extractPanelImages(payload));
|
|
186
|
+
if (!images.length)
|
|
187
|
+
return;
|
|
188
|
+
await pushOutputMessage({
|
|
189
|
+
from: "bot",
|
|
190
|
+
name: PANEL_BOT_ID,
|
|
191
|
+
text: label,
|
|
192
|
+
images,
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
return {
|
|
196
|
+
...(hooks?.onDelta
|
|
197
|
+
? {
|
|
198
|
+
streamTextDelta: async (text) => {
|
|
199
|
+
if (!text)
|
|
200
|
+
return;
|
|
201
|
+
await hooks.onDelta?.(text);
|
|
202
|
+
},
|
|
203
|
+
}
|
|
204
|
+
: {}),
|
|
205
|
+
sendText: async (text) => {
|
|
206
|
+
await pushOutputMessage({
|
|
207
|
+
from: "system",
|
|
208
|
+
name: "系统",
|
|
209
|
+
text,
|
|
210
|
+
});
|
|
211
|
+
return [{ data: { message_id: `web-${Date.now()}` } }];
|
|
212
|
+
},
|
|
213
|
+
sendAIReply: async (aireply) => {
|
|
214
|
+
for (const item of aireply?.replyMessages || []) {
|
|
215
|
+
const images = filterNewImages(item.images || []);
|
|
216
|
+
if (!item.text && !images.length && !item.at?.length)
|
|
217
|
+
continue;
|
|
218
|
+
await pushOutputMessage({
|
|
219
|
+
from: "bot",
|
|
220
|
+
name: PANEL_BOT_ID,
|
|
221
|
+
text: item.text || "",
|
|
222
|
+
images,
|
|
223
|
+
at: item.at || [],
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
for (const item of aireply?.ttsMessages || []) {
|
|
227
|
+
await pushOutputMessage({
|
|
228
|
+
from: "bot",
|
|
229
|
+
name: PANEL_BOT_ID,
|
|
230
|
+
text: `[语音] ${item.text}`,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
sendRapiItems: async (items) => {
|
|
235
|
+
for (const item of items) {
|
|
236
|
+
if (item.type === "text") {
|
|
237
|
+
await pushOutputMessage({
|
|
238
|
+
from: "bot",
|
|
239
|
+
name: PANEL_BOT_ID,
|
|
240
|
+
text: item.content,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
else if (item.type === "image") {
|
|
244
|
+
const images = filterNewImages([item.url]);
|
|
245
|
+
if (!images.length)
|
|
246
|
+
continue;
|
|
247
|
+
await pushOutputMessage({
|
|
248
|
+
from: "bot",
|
|
249
|
+
name: PANEL_BOT_ID,
|
|
250
|
+
text: "",
|
|
251
|
+
images,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
else if (item.type === "audio") {
|
|
255
|
+
await pushOutputMessage({
|
|
256
|
+
from: "bot",
|
|
257
|
+
name: PANEL_BOT_ID,
|
|
258
|
+
text: `[语音] ${item.text}`,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
sendToolResult: async (functionName, payload) => {
|
|
264
|
+
if (!shouldCollectToolImages(functionName, payload))
|
|
265
|
+
return;
|
|
266
|
+
const label = functionName === "StableDiffusionGenerateImage" ? "画图结果" : "图片结果";
|
|
267
|
+
await pushToolImages(label, payload);
|
|
268
|
+
},
|
|
269
|
+
send: async (payload) => {
|
|
270
|
+
const text = extractFallbackText(payload);
|
|
271
|
+
const images = filterNewImages(extractPanelImages(payload));
|
|
272
|
+
if (text || images.length) {
|
|
273
|
+
await pushOutputMessage({
|
|
274
|
+
from: "system",
|
|
275
|
+
name: "系统",
|
|
276
|
+
text,
|
|
277
|
+
images,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return [{ data: { message_id: `web-${Date.now()}` } }];
|
|
281
|
+
},
|
|
282
|
+
delete: async () => undefined,
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
const createPanelEvent = (identity, message) => ({
|
|
286
|
+
name: "message.create",
|
|
287
|
+
Platform: "web",
|
|
288
|
+
BotId: identity.botId,
|
|
289
|
+
ChannelId: identity.guid,
|
|
290
|
+
UserId: identity.userId,
|
|
291
|
+
UserName: identity.nickname,
|
|
292
|
+
GroupName: `网页群聊 ${identity.guid}`,
|
|
293
|
+
MessageText: message,
|
|
294
|
+
msg: message,
|
|
295
|
+
raw_message: message,
|
|
296
|
+
value: {
|
|
297
|
+
raw_message: message,
|
|
298
|
+
message_type: "group",
|
|
299
|
+
group_id: identity.guid,
|
|
300
|
+
message: [{ type: "text", data: { text: message } }],
|
|
301
|
+
},
|
|
302
|
+
bot: {
|
|
303
|
+
nickname: PANEL_BOT_ID,
|
|
304
|
+
user_id: identity.botId,
|
|
305
|
+
},
|
|
306
|
+
nickname: identity.nickname,
|
|
307
|
+
user_id: identity.userId,
|
|
308
|
+
self_id: identity.botId,
|
|
309
|
+
guid: identity.guid,
|
|
310
|
+
uidkey: `${identity.guid}:${identity.nickname}(${identity.userId})`,
|
|
311
|
+
at: [
|
|
312
|
+
{
|
|
313
|
+
type: "at",
|
|
314
|
+
data: { qq: identity.botId, nickname: PANEL_BOT_ID },
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
atBot: true,
|
|
318
|
+
img: [],
|
|
319
|
+
originalMsg: message,
|
|
320
|
+
regexTriggered: true,
|
|
321
|
+
});
|
|
322
|
+
const sendPanelChatMessage = async (guid, userId, nickname, text) => {
|
|
323
|
+
const identity = await resolvePanelChatIdentity(guid, userId, nickname);
|
|
324
|
+
const content = text.trim();
|
|
325
|
+
const collected = [];
|
|
326
|
+
if (!content) {
|
|
327
|
+
pushPanelMessage(collected, {
|
|
328
|
+
from: "system",
|
|
329
|
+
name: "系统",
|
|
330
|
+
text: "消息不能为空",
|
|
331
|
+
});
|
|
332
|
+
await appendPanelChatMessages(identity.guid, identity.userId, collected);
|
|
333
|
+
return { identity, messages: collected };
|
|
334
|
+
}
|
|
335
|
+
await appendPanelChatMessages(identity.guid, identity.userId, [
|
|
336
|
+
{
|
|
337
|
+
from: "user",
|
|
338
|
+
name: identity.nickname,
|
|
339
|
+
text: content,
|
|
340
|
+
time: getPanelTime(),
|
|
341
|
+
},
|
|
342
|
+
]);
|
|
343
|
+
const aiConfig = await withTimeout(redisClient.getAIConfig(identity.guid), null, 1200);
|
|
344
|
+
if (!aiConfig) {
|
|
345
|
+
pushPanelMessage(collected, {
|
|
346
|
+
from: "system",
|
|
347
|
+
name: "系统",
|
|
348
|
+
text: "Redis连接不可用,无法读取当前 guid 的 AI 配置。",
|
|
349
|
+
});
|
|
350
|
+
await appendPanelChatMessages(identity.guid, identity.userId, collected);
|
|
351
|
+
return { identity, messages: collected };
|
|
352
|
+
}
|
|
353
|
+
if (!aiConfig.host.trim() || !aiConfig.key.trim() || !aiConfig.model.trim()) {
|
|
354
|
+
pushPanelMessage(collected, {
|
|
355
|
+
from: "system",
|
|
356
|
+
name: "系统",
|
|
357
|
+
text: `群号 ${identity.guid} 还没有可用 AI 配置,请先在配置页为这个 guid 切换 AI。`,
|
|
358
|
+
});
|
|
359
|
+
await appendPanelChatMessages(identity.guid, identity.userId, collected);
|
|
360
|
+
return { identity, messages: collected };
|
|
361
|
+
}
|
|
362
|
+
const event = createPanelEvent(identity, content);
|
|
363
|
+
const output = createPanelOutput(collected);
|
|
364
|
+
try {
|
|
365
|
+
if (aiConfig.rapi) {
|
|
366
|
+
await RApiReply(event, output);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
await CApiReply(event, output);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
pushPanelMessage(collected, {
|
|
374
|
+
from: "system",
|
|
375
|
+
name: "系统",
|
|
376
|
+
text: `AI回复出错了\n${error instanceof Error ? error.message : String(error)}`,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
if (!collected.length) {
|
|
380
|
+
pushPanelMessage(collected, {
|
|
381
|
+
from: "system",
|
|
382
|
+
name: "系统",
|
|
383
|
+
text: "AI没有返回可显示内容",
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
await appendPanelChatMessages(identity.guid, identity.userId, collected);
|
|
387
|
+
return { identity, messages: collected };
|
|
388
|
+
};
|
|
389
|
+
const sendPanelChatMessageStream = async (guid, userId, nickname, text, emit) => {
|
|
390
|
+
const identity = await resolvePanelChatIdentity(guid, userId, nickname);
|
|
391
|
+
const content = text.trim();
|
|
392
|
+
const collected = [];
|
|
393
|
+
let streamedText = "";
|
|
394
|
+
let hasStreamedDelta = false;
|
|
395
|
+
let clearedStreamMessage = false;
|
|
396
|
+
let replacedStreamMessage = false;
|
|
397
|
+
const clearStreamMessage = async () => {
|
|
398
|
+
if (!hasStreamedDelta || clearedStreamMessage || replacedStreamMessage) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
clearedStreamMessage = true;
|
|
402
|
+
await emit({ type: "clear" });
|
|
403
|
+
};
|
|
404
|
+
const emitMessage = async (message) => {
|
|
405
|
+
if (message.from === "bot") {
|
|
406
|
+
await clearStreamMessage();
|
|
407
|
+
}
|
|
408
|
+
await emit({ type: "message", message });
|
|
409
|
+
};
|
|
410
|
+
const pushSystemMessage = async (message) => {
|
|
411
|
+
const item = pushPanelMessage(collected, {
|
|
412
|
+
from: "system",
|
|
413
|
+
name: "系统",
|
|
414
|
+
text: message,
|
|
415
|
+
});
|
|
416
|
+
await emitMessage(item);
|
|
417
|
+
};
|
|
418
|
+
if (!content) {
|
|
419
|
+
await pushSystemMessage("消息不能为空");
|
|
420
|
+
await appendPanelChatMessages(identity.guid, identity.userId, collected);
|
|
421
|
+
await emit({ type: "done" });
|
|
422
|
+
return { identity, messages: collected };
|
|
423
|
+
}
|
|
424
|
+
await appendPanelChatMessages(identity.guid, identity.userId, [
|
|
425
|
+
{
|
|
426
|
+
from: "user",
|
|
427
|
+
name: identity.nickname,
|
|
428
|
+
text: content,
|
|
429
|
+
time: getPanelTime(),
|
|
430
|
+
},
|
|
431
|
+
]);
|
|
432
|
+
const aiConfig = await withTimeout(redisClient.getAIConfig(identity.guid), null, 1200);
|
|
433
|
+
if (!aiConfig) {
|
|
434
|
+
await pushSystemMessage("Redis连接不可用,无法读取当前 guid 的 AI 配置。");
|
|
435
|
+
await appendPanelChatMessages(identity.guid, identity.userId, collected);
|
|
436
|
+
await emit({ type: "done" });
|
|
437
|
+
return { identity, messages: collected };
|
|
438
|
+
}
|
|
439
|
+
if (!aiConfig.host.trim() || !aiConfig.key.trim() || !aiConfig.model.trim()) {
|
|
440
|
+
await pushSystemMessage(`群号 ${identity.guid} 还没有可用 AI 配置,请先在配置页为这个 guid 切换 AI。`);
|
|
441
|
+
await appendPanelChatMessages(identity.guid, identity.userId, collected);
|
|
442
|
+
await emit({ type: "done" });
|
|
443
|
+
return { identity, messages: collected };
|
|
444
|
+
}
|
|
445
|
+
const event = createPanelEvent(identity, content);
|
|
446
|
+
const output = createPanelOutput(collected, {
|
|
447
|
+
onDelta: async (delta) => {
|
|
448
|
+
hasStreamedDelta = true;
|
|
449
|
+
streamedText += delta;
|
|
450
|
+
await emit({ type: "delta", text: delta });
|
|
451
|
+
},
|
|
452
|
+
onMessage: emitMessage,
|
|
453
|
+
});
|
|
454
|
+
try {
|
|
455
|
+
if (aiConfig.rapi) {
|
|
456
|
+
await RApiReply(event, output);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
await CApiReply(event, output);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
await pushSystemMessage(`AI回复出错了\n${error instanceof Error ? error.message : String(error)}`);
|
|
464
|
+
}
|
|
465
|
+
if (!collected.length && streamedText.trim()) {
|
|
466
|
+
const message = pushPanelMessage(collected, {
|
|
467
|
+
from: "bot",
|
|
468
|
+
name: PANEL_BOT_ID,
|
|
469
|
+
text: streamedText.trim(),
|
|
470
|
+
});
|
|
471
|
+
if (!replacedStreamMessage && !clearedStreamMessage) {
|
|
472
|
+
await emit({ type: "replace", message });
|
|
473
|
+
replacedStreamMessage = true;
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
await emitMessage(message);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (!collected.length) {
|
|
480
|
+
await pushSystemMessage("AI没有返回可显示内容");
|
|
481
|
+
}
|
|
482
|
+
await appendPanelChatMessages(identity.guid, identity.userId, collected);
|
|
483
|
+
await emit({ type: "done" });
|
|
484
|
+
return { identity, messages: collected };
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
export { PANEL_BOT_ID, PANEL_DEFAULT_USER_ID, appendPanelChatMessages, clearPanelChatMessages, getPanelChatMessages, resolvePanelChatIdentity, sendPanelChatMessage, sendPanelChatMessageStream };
|