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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMessage,
|
|
1
|
+
import { useMessage, Audio, Text, ImageURL, ImageFile, Mention } from 'alemonjs';
|
|
2
2
|
import { log } from 'console';
|
|
3
3
|
import OpenAi from 'openai';
|
|
4
4
|
import { TTSClient } from '../../api/tts.js';
|
|
@@ -6,7 +6,7 @@ import { availableTools } from '../../api/aitools.js';
|
|
|
6
6
|
import redisClient, { DEFAULT_CAPI_CONTEXT_LIMIT } from '../../config.js';
|
|
7
7
|
import { getChatConfig, shouldSkipAIReply, buildUserMessage, getDeepThoughtReasoning } from './getChatConfig.js';
|
|
8
8
|
import { createTTSMessage } from './tts.js';
|
|
9
|
-
import { parseAIReply } from './tools.js';
|
|
9
|
+
import { parseAIReply, sendAIReplyToOutput } from './tools.js';
|
|
10
10
|
|
|
11
11
|
const selects = onSelects(["message.create", "private.message.create"]);
|
|
12
12
|
let currentModel = "派蒙-默认"; // 默认音色
|
|
@@ -16,6 +16,9 @@ const AUTO_SUMMARY_DETAIL_MAX_LENGTH = 2000;
|
|
|
16
16
|
const sendAIReply = async (reply, cfg, e, message) => {
|
|
17
17
|
const aireply = await parseAIReply(reply, cfg, e, message);
|
|
18
18
|
console.log("aireply", aireply);
|
|
19
|
+
if (await sendAIReplyToOutput(message, aireply)) {
|
|
20
|
+
return aireply;
|
|
21
|
+
}
|
|
19
22
|
if (aireply?.ttsMessages) {
|
|
20
23
|
for (const ttsMsg of aireply.ttsMessages) {
|
|
21
24
|
const ttsMessage = await createTTSMessage(ttsMsg.text, new TTSClient(), ttsMsg.model ?? currentModel);
|
|
@@ -31,6 +34,12 @@ const sendAIReply = async (reply, cfg, e, message) => {
|
|
|
31
34
|
}
|
|
32
35
|
return aireply;
|
|
33
36
|
};
|
|
37
|
+
const sendOutputText = async (message, text) => {
|
|
38
|
+
if (typeof message?.sendText === "function") {
|
|
39
|
+
return await message.sendText(text);
|
|
40
|
+
}
|
|
41
|
+
return await message.send(format(Text(text)));
|
|
42
|
+
};
|
|
34
43
|
const appendPendingGuidanceMessages = async (guid, messages) => {
|
|
35
44
|
const queuedMessages = await redisClient.drainAIGuidanceMessages(guid);
|
|
36
45
|
if (!queuedMessages.length) {
|
|
@@ -46,6 +55,30 @@ const appendPendingGuidanceMessages = async (guid, messages) => {
|
|
|
46
55
|
await redisClient.addAIChatHistoryBatch(guid, guidanceMessages);
|
|
47
56
|
return guidanceMessages;
|
|
48
57
|
};
|
|
58
|
+
const extractTextValue = (value) => {
|
|
59
|
+
if (typeof value === "string") {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
return value
|
|
64
|
+
.map((item) => extractTextValue(item))
|
|
65
|
+
.filter((text) => text.trim() !== "")
|
|
66
|
+
.join("\n");
|
|
67
|
+
}
|
|
68
|
+
if (value && typeof value === "object") {
|
|
69
|
+
return extractTextValue(value.text ?? value.content ?? value.summary ?? "");
|
|
70
|
+
}
|
|
71
|
+
return "";
|
|
72
|
+
};
|
|
73
|
+
const getToolCallDisplayContent = (message) => {
|
|
74
|
+
const content = extractTextValue(message?.content);
|
|
75
|
+
if (content.trim() !== "") {
|
|
76
|
+
return content;
|
|
77
|
+
}
|
|
78
|
+
return extractTextValue(message?.reasoning_content ??
|
|
79
|
+
message?.reasoningContent ??
|
|
80
|
+
message?.reasoning);
|
|
81
|
+
};
|
|
49
82
|
const getTextLength = (value) => {
|
|
50
83
|
if (typeof value === "string") {
|
|
51
84
|
return value.length;
|
|
@@ -159,7 +192,7 @@ const recordCapiContextStats = async (guid, model, messages, completion) => {
|
|
|
159
192
|
usageAvailable: Boolean(usage),
|
|
160
193
|
});
|
|
161
194
|
};
|
|
162
|
-
const CApiReply = async (e) => {
|
|
195
|
+
const CApiReply = async (e, outputMessage) => {
|
|
163
196
|
console.log("e.UserId", e.UserId, e.bot);
|
|
164
197
|
const cfg = await getChatConfig(e);
|
|
165
198
|
if (await shouldSkipAIReply(e, cfg)) {
|
|
@@ -168,7 +201,7 @@ const CApiReply = async (e) => {
|
|
|
168
201
|
};
|
|
169
202
|
}
|
|
170
203
|
// 创建消息
|
|
171
|
-
const [message] = useMessage(e);
|
|
204
|
+
const [message] = outputMessage ? [outputMessage] : useMessage(e);
|
|
172
205
|
const usermessage = await buildUserMessage(e, cfg, "capi");
|
|
173
206
|
try {
|
|
174
207
|
const openai = new OpenAi({
|
|
@@ -176,6 +209,79 @@ const CApiReply = async (e) => {
|
|
|
176
209
|
apiKey: cfg.config.key,
|
|
177
210
|
timeout: 300000,
|
|
178
211
|
});
|
|
212
|
+
const shouldStreamToOutput = typeof outputMessage?.streamTextDelta === "function";
|
|
213
|
+
const requestChatCompletion = async (params, requestMessages) => {
|
|
214
|
+
if (!shouldStreamToOutput) {
|
|
215
|
+
const completion = await openai.chat.completions.create(params);
|
|
216
|
+
await recordCapiContextStats(e.guid, cfg.config.model, requestMessages, completion);
|
|
217
|
+
return completion;
|
|
218
|
+
}
|
|
219
|
+
const streamParams = {
|
|
220
|
+
...params,
|
|
221
|
+
stream: true,
|
|
222
|
+
};
|
|
223
|
+
const stream = (await openai.chat.completions.create(streamParams));
|
|
224
|
+
let content = "";
|
|
225
|
+
let reasoningContent = "";
|
|
226
|
+
const toolCalls = [];
|
|
227
|
+
const ensureToolCall = (index) => {
|
|
228
|
+
if (!toolCalls[index]) {
|
|
229
|
+
toolCalls[index] = {
|
|
230
|
+
id: "",
|
|
231
|
+
type: "function",
|
|
232
|
+
function: {
|
|
233
|
+
name: "",
|
|
234
|
+
arguments: "",
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return toolCalls[index];
|
|
239
|
+
};
|
|
240
|
+
for await (const chunk of stream) {
|
|
241
|
+
const delta = chunk?.choices?.[0]?.delta || {};
|
|
242
|
+
const text = extractTextValue(delta.content ?? "");
|
|
243
|
+
if (text) {
|
|
244
|
+
content += text;
|
|
245
|
+
await outputMessage.streamTextDelta(text);
|
|
246
|
+
}
|
|
247
|
+
const reasoningDelta = extractTextValue(delta.reasoning_content ?? delta.reasoningContent ?? "");
|
|
248
|
+
if (reasoningDelta) {
|
|
249
|
+
reasoningContent += reasoningDelta;
|
|
250
|
+
}
|
|
251
|
+
if (Array.isArray(delta.tool_calls)) {
|
|
252
|
+
for (const item of delta.tool_calls) {
|
|
253
|
+
const indexValue = Number(item.index);
|
|
254
|
+
const index = Number.isFinite(indexValue) ? indexValue : 0;
|
|
255
|
+
const toolCall = ensureToolCall(index);
|
|
256
|
+
if (item.id)
|
|
257
|
+
toolCall.id = item.id;
|
|
258
|
+
if (item.type)
|
|
259
|
+
toolCall.type = item.type;
|
|
260
|
+
if (item.function?.name) {
|
|
261
|
+
toolCall.function.name += item.function.name;
|
|
262
|
+
}
|
|
263
|
+
if (item.function?.arguments) {
|
|
264
|
+
toolCall.function.arguments += item.function.arguments;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const normalizedToolCalls = toolCalls.filter((item) => item?.function?.name);
|
|
270
|
+
return {
|
|
271
|
+
choices: [
|
|
272
|
+
{
|
|
273
|
+
message: {
|
|
274
|
+
role: "assistant",
|
|
275
|
+
content,
|
|
276
|
+
reasoning_content: reasoningContent,
|
|
277
|
+
tool_calls: normalizedToolCalls.length
|
|
278
|
+
? normalizedToolCalls
|
|
279
|
+
: undefined,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
};
|
|
284
|
+
};
|
|
179
285
|
const systemMessage = {
|
|
180
286
|
role: "system",
|
|
181
287
|
content: cfg.systemPrompt + `\n性格约束:${cfg.config.systemPrompt}`,
|
|
@@ -196,18 +302,17 @@ const CApiReply = async (e) => {
|
|
|
196
302
|
}
|
|
197
303
|
const deepThoughtReasoning = getDeepThoughtReasoning(cfg);
|
|
198
304
|
Object.assign(createParams, deepThoughtReasoning);
|
|
199
|
-
|
|
305
|
+
if (!shouldStreamToOutput) {
|
|
306
|
+
createParams["stream"] = false;
|
|
307
|
+
}
|
|
200
308
|
// log("请求AI,参数:", createParams);
|
|
201
|
-
const stream =
|
|
202
|
-
await recordCapiContextStats(e.guid, cfg.config.model, messages, stream);
|
|
309
|
+
const stream = await requestChatCompletion(createParams, messages);
|
|
203
310
|
log("AI回复原始数据:\n", JSON.stringify(stream, null, 2));
|
|
204
311
|
let fullContent = "";
|
|
205
312
|
let toolCalls = [];
|
|
206
313
|
const completion = stream;
|
|
207
314
|
const aiReplyMessage = completion?.choices?.[0]?.message;
|
|
208
|
-
|
|
209
|
-
fullContent = aiReplyMessage.content;
|
|
210
|
-
}
|
|
315
|
+
fullContent = getToolCallDisplayContent(aiReplyMessage);
|
|
211
316
|
if (Array.isArray(aiReplyMessage?.tool_calls)) {
|
|
212
317
|
toolCalls = aiReplyMessage.tool_calls.map((tc) => ({
|
|
213
318
|
id: tc.id || "",
|
|
@@ -261,8 +366,7 @@ const CApiReply = async (e) => {
|
|
|
261
366
|
params.tool_choice = "auto";
|
|
262
367
|
}
|
|
263
368
|
Object.assign(params, getDeepThoughtReasoning(cfg));
|
|
264
|
-
res = await
|
|
265
|
-
await recordCapiContextStats(e.guid, cfg.config.model, messages, res);
|
|
369
|
+
res = await requestChatCompletion(params, messages);
|
|
266
370
|
if (!res.choices || res.choices.length === 0) {
|
|
267
371
|
log("AI未返回内容");
|
|
268
372
|
return;
|
|
@@ -274,7 +378,7 @@ const CApiReply = async (e) => {
|
|
|
274
378
|
log("AI返回工具调用,内容:-----", res.choices[0]);
|
|
275
379
|
// 先处理对话
|
|
276
380
|
if (toolRound >= 10 && toolRound % 10 === 0) {
|
|
277
|
-
await message
|
|
381
|
+
await sendOutputText(message, `AI正在连续调用工具,第 ${toolRound} 轮处理中,请稍等`);
|
|
278
382
|
}
|
|
279
383
|
messages.push(responseMessage);
|
|
280
384
|
log("模型决定调用工具:", responseMessage.tool_calls);
|
|
@@ -282,11 +386,14 @@ const CApiReply = async (e) => {
|
|
|
282
386
|
const useToolsMessage = responseMessage.tool_calls.map((toolCall) => {
|
|
283
387
|
return `工具调用: ${toolCall.function.name}(${cfg.toolConfig.toolPromptArgsSwitch === "1" ? toolCall.function.arguments : ""})`;
|
|
284
388
|
});
|
|
285
|
-
const msgId = await message
|
|
389
|
+
const msgId = await sendOutputText(message, useToolsMessage.join("\n"));
|
|
286
390
|
if (cfg.toolConfig.toolPromptRevokeSwitch === "1") {
|
|
287
391
|
// 10秒后撤回工具调用提示
|
|
288
392
|
setTimeout(() => {
|
|
289
|
-
|
|
393
|
+
const messageId = msgId?.[0]?.data?.message_id;
|
|
394
|
+
if (messageId && typeof message?.delete === "function") {
|
|
395
|
+
message.delete({ messageId });
|
|
396
|
+
}
|
|
290
397
|
}, 10000);
|
|
291
398
|
}
|
|
292
399
|
}
|
|
@@ -333,6 +440,9 @@ const CApiReply = async (e) => {
|
|
|
333
440
|
};
|
|
334
441
|
}
|
|
335
442
|
}
|
|
443
|
+
if (typeof message?.sendToolResult === "function") {
|
|
444
|
+
await message.sendToolResult(functionName, functionResult);
|
|
445
|
+
}
|
|
336
446
|
const toolContent = JSON.stringify(typeof functionResult === "undefined"
|
|
337
447
|
? { ok: true, result: null }
|
|
338
448
|
: functionResult);
|
|
@@ -366,8 +476,7 @@ const CApiReply = async (e) => {
|
|
|
366
476
|
Object.assign(params, deepThoughtReasoning);
|
|
367
477
|
// log("重新请求AI,参数:", params);
|
|
368
478
|
// 重新请求
|
|
369
|
-
res = await
|
|
370
|
-
await recordCapiContextStats(e.guid, cfg.config.model, messages, res);
|
|
479
|
+
res = await requestChatCompletion(params, messages);
|
|
371
480
|
// 检查是否还有工具调用需要处理
|
|
372
481
|
if (!res.choices || res.choices.length === 0) {
|
|
373
482
|
log("AI未返回内容");
|
|
@@ -400,7 +509,7 @@ const CApiReply = async (e) => {
|
|
|
400
509
|
catch (error) {
|
|
401
510
|
await redisClient.setAIToolLoopProcessing(e.guid, false);
|
|
402
511
|
console.error("AI回复出错", error);
|
|
403
|
-
message
|
|
512
|
+
await sendOutputText(message, "AI回复出错了\n" + error);
|
|
404
513
|
return {
|
|
405
514
|
next: true,
|
|
406
515
|
};
|
|
@@ -15,14 +15,14 @@ const drainPendingGuidanceInputs = async (guid) => {
|
|
|
15
15
|
.map((item) => item?.rapiMessage || item)
|
|
16
16
|
.filter(Boolean);
|
|
17
17
|
};
|
|
18
|
-
const RApiReply = async (e) => {
|
|
18
|
+
const RApiReply = async (e, outputMessage) => {
|
|
19
19
|
const cfg = await getChatConfig(e);
|
|
20
20
|
if (await shouldSkipAIReply(e, cfg)) {
|
|
21
21
|
return {
|
|
22
22
|
next: true,
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
const [message] = useMessage();
|
|
25
|
+
const [message] = outputMessage ? [outputMessage] : useMessage(e);
|
|
26
26
|
const openai = new OpenAi({
|
|
27
27
|
baseURL: cfg.config.host,
|
|
28
28
|
apiKey: cfg.config.key,
|
|
@@ -58,6 +58,9 @@ const RApiReply = async (e) => {
|
|
|
58
58
|
if (event.type === "response.output_text.delta") {
|
|
59
59
|
const text = event.delta ?? "";
|
|
60
60
|
fullText += text;
|
|
61
|
+
if (text && typeof message?.streamTextDelta === "function") {
|
|
62
|
+
await message.streamTextDelta(text);
|
|
63
|
+
}
|
|
61
64
|
}
|
|
62
65
|
// 更稳妥:从 output_item.done 读取完整 function_call
|
|
63
66
|
if (event.type === "response.output_item.done" &&
|
|
@@ -157,7 +160,11 @@ const RApiReply = async (e) => {
|
|
|
157
160
|
let newText = "";
|
|
158
161
|
for await (const event of toolResponse) {
|
|
159
162
|
if (event.type === "response.output_text.delta") {
|
|
160
|
-
|
|
163
|
+
const text = event.delta ?? "";
|
|
164
|
+
newText += text;
|
|
165
|
+
if (text && typeof message?.streamTextDelta === "function") {
|
|
166
|
+
await message.streamTextDelta(text);
|
|
167
|
+
}
|
|
161
168
|
}
|
|
162
169
|
if (event.type === "response.completed") {
|
|
163
170
|
previousResponseId = event.response.id;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Audio, ImageURL, ImageFile, Text, format, Mention } from 'alemonjs';
|
|
2
2
|
import redisClient from '../../config.js';
|
|
3
3
|
import { TTSClient } from '../../api/tts.js';
|
|
4
4
|
import { createTTSMessage } from './tts.js';
|
|
@@ -128,6 +128,12 @@ const sendReplyMessages = async (replyMessages, e, cfg, message) => {
|
|
|
128
128
|
await message.send(format(...components));
|
|
129
129
|
}
|
|
130
130
|
};
|
|
131
|
+
const sendParsedReplyToCustomOutput = async (message, aireply) => {
|
|
132
|
+
if (typeof message?.sendAIReply !== "function")
|
|
133
|
+
return false;
|
|
134
|
+
await message.sendAIReply(aireply);
|
|
135
|
+
return true;
|
|
136
|
+
};
|
|
131
137
|
const parseAIReply = async (newText, cfg, e, message) => {
|
|
132
138
|
const replyMessages = newText.split(new RegExp(`${cfg.botName}:`, "g"));
|
|
133
139
|
const replyMessagesResult = [];
|
|
@@ -199,7 +205,7 @@ const parseAIOutput = (raw) => {
|
|
|
199
205
|
const imgs = text.match(/!\[[^\]]+\]\([^\)]+\)/g) || [];
|
|
200
206
|
if (imgs.length) {
|
|
201
207
|
imgs.forEach((img) => {
|
|
202
|
-
const urlMatch = img.match(/!\[[^\]]+\]\([
|
|
208
|
+
const urlMatch = img.match(/!\[[^\]]+\]\(([^)]+)\)/);
|
|
203
209
|
if (urlMatch) {
|
|
204
210
|
result.push({ type: "image", url: urlMatch[1] });
|
|
205
211
|
}
|
|
@@ -240,6 +246,10 @@ const parseAIOutput = (raw) => {
|
|
|
240
246
|
return result;
|
|
241
247
|
};
|
|
242
248
|
const renderMessages = async (items, message) => {
|
|
249
|
+
if (typeof message?.sendRapiItems === "function") {
|
|
250
|
+
await message.sendRapiItems(items);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
243
253
|
const components = [];
|
|
244
254
|
for (const item of items) {
|
|
245
255
|
switch (item.type) {
|
|
@@ -259,5 +269,6 @@ const renderMessages = async (items, message) => {
|
|
|
259
269
|
}
|
|
260
270
|
await message.send(format(...components));
|
|
261
271
|
};
|
|
272
|
+
const sendAIReplyToOutput = sendParsedReplyToCustomOutput;
|
|
262
273
|
|
|
263
|
-
export { handleTTS, parseAIOutput, parseAIReply, renderMessages, sendReplyMessages };
|
|
274
|
+
export { handleTTS, parseAIOutput, parseAIReply, renderMessages, sendAIReplyToOutput, sendReplyMessages };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alemonjs-aichat",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.40",
|
|
4
4
|
"description": "alemonjs-aichat",
|
|
5
5
|
"author": "suancaixianyu",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"@types/ssh2": "^1.15.5",
|
|
25
25
|
"alemonjs": "2.1.65",
|
|
26
26
|
"cssnano": "^7",
|
|
27
|
-
"jsxp": "^1.4.0",
|
|
28
27
|
"lvyjs": "^0.2.25",
|
|
29
28
|
"pm2": "^5",
|
|
30
29
|
"tailwindcss": "3"
|
|
@@ -49,11 +48,18 @@
|
|
|
49
48
|
"@aws-sdk/client-s3": "^3.975.0",
|
|
50
49
|
"@aws-sdk/s3-request-presigner": "^3.975.0",
|
|
51
50
|
"ai": "^6.0.105",
|
|
51
|
+
"jsxp": "^1.4.0",
|
|
52
|
+
"koa": "^3.2.0",
|
|
53
|
+
"koa-router": "^14.0.0",
|
|
54
|
+
"koa-send": "^5.0.1",
|
|
55
|
+
"koa-static": "^5.0.0",
|
|
52
56
|
"mime-types": "^3.0.2",
|
|
53
57
|
"node-fetch": "^3.3.2",
|
|
54
58
|
"openai": "^6.34.0",
|
|
59
|
+
"react": "^19.2.5",
|
|
60
|
+
"react-dom": "^19.2.5",
|
|
55
61
|
"sharp": "^0.34.5",
|
|
56
|
-
"
|
|
57
|
-
"
|
|
62
|
+
"ssh2": "^1.17.0",
|
|
63
|
+
"zod": "^4.3.6"
|
|
58
64
|
}
|
|
59
65
|
}
|