alemonjs-aichat 1.0.28 → 1.0.30-beta.0
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/api/aitools.js +218 -82
- package/lib/api/security.js +33 -0
- package/lib/api/workspace.js +13 -0
- package/lib/config.js +61 -0
- package/lib/data/help.json.js +1 -1
- package/lib/middleware/mw.js +15 -8
- package/lib/redis.js +1 -0
- package/lib/response/config/res.js +70 -4
- package/lib/response/setting/res.js +53 -34
- package/lib/response/zreply/capi.js +33 -7
- package/lib/response/zreply/getChatConfig.js +141 -32
- package/lib/response/zreply/rapi.js +140 -17
- package/lib/response/zreply/tools.js +240 -0
- package/lib/routes/commands.js +2 -7
- package/package.json +2 -2
package/lib/api/aitools.js
CHANGED
|
@@ -5,6 +5,11 @@ import { uploadImageToR2 } from '../s3.js';
|
|
|
5
5
|
import { TTSClient } from './tts.js';
|
|
6
6
|
import { loadSkillDetail } from './loadSkill.js';
|
|
7
7
|
import help from '../data/help.json.js';
|
|
8
|
+
import redisClient from '../config.js';
|
|
9
|
+
import { isPrivateIP, validateURL } from './security.js';
|
|
10
|
+
import { spawn } from 'child_process';
|
|
11
|
+
import * as dns from 'node:dns/promises';
|
|
12
|
+
import { getWorkspace } from './workspace.js';
|
|
8
13
|
|
|
9
14
|
const value = getConfigValue().aiChat || {};
|
|
10
15
|
const getPrompt = async (text) => {
|
|
@@ -167,6 +172,10 @@ const tools = [
|
|
|
167
172
|
parameters: {
|
|
168
173
|
type: "object",
|
|
169
174
|
properties: {
|
|
175
|
+
userId: {
|
|
176
|
+
type: "string",
|
|
177
|
+
description: "执行命令的用户ID",
|
|
178
|
+
},
|
|
170
179
|
command: {
|
|
171
180
|
type: "string",
|
|
172
181
|
description: "要执行的终端命令",
|
|
@@ -193,36 +202,151 @@ const tools = [
|
|
|
193
202
|
},
|
|
194
203
|
},
|
|
195
204
|
},
|
|
205
|
+
// {
|
|
206
|
+
// type: "function",
|
|
207
|
+
// function: {
|
|
208
|
+
// name: "EvalCode",
|
|
209
|
+
// description: "执行JavaScript代码",
|
|
210
|
+
// parameters: {
|
|
211
|
+
// type: "object",
|
|
212
|
+
// properties: {
|
|
213
|
+
// code: {
|
|
214
|
+
// type: "string",
|
|
215
|
+
// description: "要执行的JavaScript代码",
|
|
216
|
+
// },
|
|
217
|
+
// },
|
|
218
|
+
// required: ["code"],
|
|
219
|
+
// },
|
|
220
|
+
// },
|
|
221
|
+
// },
|
|
222
|
+
{
|
|
223
|
+
type: "function",
|
|
224
|
+
function: {
|
|
225
|
+
name: "GetCommand",
|
|
226
|
+
description: "获取当前框架可用的指令, 查看当前框架每个命令的使用方法和说明. 当用户需要获取指令列表时, 可以调用这个工具来获取当前框架可用的指令列表. 例如, 当用户输入'帮助'或'指令列表'等类似的关键词时, 你可以调用这个工具来获取指令列表并回复给用户. 指令列表会包含每个指令的文本和说明, 例如: [{ command: '帮助', description: '获取帮助信息' }, { command: '看看配置', description: '查看当前AI配置' }]",
|
|
227
|
+
parameters: {
|
|
228
|
+
type: "object",
|
|
229
|
+
properties: {},
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
196
233
|
{
|
|
197
234
|
type: "function",
|
|
198
235
|
function: {
|
|
199
|
-
name: "
|
|
200
|
-
description: "
|
|
236
|
+
name: "MemoryOperation",
|
|
237
|
+
description: "这个工具用于获取或修改对于用户的记忆数据, 包括用户形象, 用户偏好, 历史对话等. 你可以使用这个工具来存储一些需要长期记忆的信息, 例如用户的兴趣爱好, 重要事件等. 当你想要获取或修改这些信息时, 可以调用这个工具并提供具体的操作指令和数据. 例如, 在聊天过程中他对你的态度,以及他喜欢的东西等,可以通过这个工具及时记录下来, 也可以通过这个工具获取之前的对话记录,便于继续之前的话题",
|
|
201
238
|
parameters: {
|
|
202
239
|
type: "object",
|
|
203
240
|
properties: {
|
|
204
|
-
|
|
241
|
+
userID: {
|
|
242
|
+
type: "string",
|
|
243
|
+
description: "要获取或修改的用户数据的用户ID",
|
|
244
|
+
},
|
|
245
|
+
operation: {
|
|
246
|
+
type: "string",
|
|
247
|
+
description: "要执行的操作类型,例如'get'表示获取数据,'set'表示修改数据",
|
|
248
|
+
enum: ["get", "set"],
|
|
249
|
+
},
|
|
250
|
+
dataType: {
|
|
205
251
|
type: "string",
|
|
206
|
-
description: "
|
|
252
|
+
description: "要获取或修改的数据类型,例如'user'表示用户相关,获取或修改用户数据,'chatHistory'表示历史对话,历史对话只能获取不能修改",
|
|
253
|
+
enum: ["user", "chatHistory"],
|
|
254
|
+
},
|
|
255
|
+
data: {
|
|
256
|
+
type: "string",
|
|
257
|
+
description: "要获取或修改的数据内容, 例如当operation为'set'时,dataType为'user'时, data应该是要存储的数据内容, 例如用户喜欢的颜色是蓝色, 则data可以是'favoriteColor:blue', 当operation为'get'时, dataType为'user'时, data就不需要传入值, 它会返回之前存储的数据, 当operation为'get'时, dataType为'chatHistory'时, data不传入值获取摘要列表, 传入id获取对应的摘要详情",
|
|
207
258
|
},
|
|
208
259
|
},
|
|
209
|
-
required: ["code"],
|
|
210
260
|
},
|
|
211
261
|
},
|
|
212
262
|
},
|
|
213
263
|
{
|
|
214
264
|
type: "function",
|
|
215
265
|
function: {
|
|
216
|
-
name: "
|
|
217
|
-
description:
|
|
266
|
+
name: "ping",
|
|
267
|
+
description: `
|
|
268
|
+
检测目标主机是否可达。
|
|
269
|
+
|
|
270
|
+
【限制】
|
|
271
|
+
- 仅允许公网域名
|
|
272
|
+
- 禁止 IP 地址(防止内网扫描)
|
|
273
|
+
- 自动限制次数(例如 3 次)
|
|
274
|
+
|
|
275
|
+
`,
|
|
218
276
|
parameters: {
|
|
219
277
|
type: "object",
|
|
220
|
-
properties: {
|
|
278
|
+
properties: {
|
|
279
|
+
host: { type: "string" },
|
|
280
|
+
},
|
|
281
|
+
required: ["host"],
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
type: "function",
|
|
287
|
+
function: {
|
|
288
|
+
name: "http_request",
|
|
289
|
+
description: `
|
|
290
|
+
发送 HTTP 请求以获取网页或 API 数据。
|
|
291
|
+
|
|
292
|
+
【能力范围】
|
|
293
|
+
- 支持 GET / POST 请求
|
|
294
|
+
- 返回文本或 JSON 内容
|
|
295
|
+
- 自动处理常见编码
|
|
296
|
+
|
|
297
|
+
【限制】
|
|
298
|
+
- 禁止访问云元数据地址(如 169.254.169.254)
|
|
299
|
+
- 请求超时限制为 5 秒
|
|
300
|
+
- 响应大小限制(例如 1MB)
|
|
301
|
+
|
|
302
|
+
【使用建议】
|
|
303
|
+
- 优先使用此工具获取网页内容,而不是使用 shell
|
|
304
|
+
- 适合抓取网页、调用 API、获取数据
|
|
305
|
+
`,
|
|
306
|
+
parameters: {
|
|
307
|
+
type: "object",
|
|
308
|
+
properties: {
|
|
309
|
+
url: { type: "string" },
|
|
310
|
+
method: { type: "string", enum: ["GET", "POST"] },
|
|
311
|
+
body: { type: "string" },
|
|
312
|
+
},
|
|
313
|
+
required: ["url"],
|
|
221
314
|
},
|
|
222
315
|
},
|
|
223
316
|
},
|
|
224
317
|
];
|
|
225
318
|
const availableTools = {
|
|
319
|
+
MemoryOperation: async ({ userID, operation, dataType, data }) => {
|
|
320
|
+
if (operation === "set") {
|
|
321
|
+
if (dataType === "user") {
|
|
322
|
+
// 存储用户数据到redis, key为user:${userID}:data, value为data
|
|
323
|
+
await redisClient.setUserData(userID, data);
|
|
324
|
+
return { success: true };
|
|
325
|
+
}
|
|
326
|
+
if (dataType === "chatHistory") {
|
|
327
|
+
return "历史对话数据只能获取不能修改";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (operation === "get") {
|
|
331
|
+
if (dataType === "user") {
|
|
332
|
+
// 从redis获取用户数据, key为user:${userID}:data
|
|
333
|
+
const userData = await redisClient.getUserData(userID);
|
|
334
|
+
return { success: true, data: userData };
|
|
335
|
+
}
|
|
336
|
+
if (dataType === "chatHistory") {
|
|
337
|
+
// 从redis获取聊天历史, key为chatHistory:${userID}
|
|
338
|
+
const [guid, id] = data?.split(":") || [];
|
|
339
|
+
if (guid && id) {
|
|
340
|
+
const chatHistory = await redisClient.getSummaryDetail(guid, id);
|
|
341
|
+
return { success: true, data: chatHistory };
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
const summaryList = await redisClient.getSummaryList();
|
|
345
|
+
return { success: true, data: summaryList };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
},
|
|
226
350
|
/**
|
|
227
351
|
* 使用 Stable Diffusion 生成图片
|
|
228
352
|
* @param {string} prompt - 正向提示词
|
|
@@ -574,88 +698,55 @@ const availableTools = {
|
|
|
574
698
|
const models = await ttsClient.getModels("v4");
|
|
575
699
|
return models;
|
|
576
700
|
},
|
|
577
|
-
/**
|
|
578
|
-
* 获取 ollama 模型列表
|
|
579
|
-
* @returns ollama 模型列表
|
|
580
|
-
*/
|
|
581
|
-
OllamaListModels: async () => {
|
|
582
|
-
const res = await fetch("http://localhost:11434/v1/models");
|
|
583
|
-
if (!res.ok) {
|
|
584
|
-
return null;
|
|
585
|
-
}
|
|
586
|
-
const data = await res.json();
|
|
587
|
-
return data.data;
|
|
588
|
-
},
|
|
589
|
-
/**
|
|
590
|
-
* 安装 ollama 模型
|
|
591
|
-
* @param modelName 模型名称
|
|
592
|
-
* @returns 安装结果
|
|
593
|
-
*/
|
|
594
|
-
OllamaInstallModel: async (modelName) => {
|
|
595
|
-
try {
|
|
596
|
-
const res = await fetch("http://localhost:11434/v1/models", {
|
|
597
|
-
method: "POST",
|
|
598
|
-
headers: {
|
|
599
|
-
"Content-Type": "application/json",
|
|
600
|
-
},
|
|
601
|
-
body: JSON.stringify({
|
|
602
|
-
model: modelName,
|
|
603
|
-
}),
|
|
604
|
-
});
|
|
605
|
-
if (!res.ok) {
|
|
606
|
-
const err = await res.text();
|
|
607
|
-
console.log(err);
|
|
608
|
-
return { success: false, error: err };
|
|
609
|
-
}
|
|
610
|
-
const data = await res.json();
|
|
611
|
-
console.log("安装结果", data);
|
|
612
|
-
return { success: true };
|
|
613
|
-
}
|
|
614
|
-
catch (error) {
|
|
615
|
-
console.error("Error installing Ollama model:", error);
|
|
616
|
-
return { success: false, error: error };
|
|
617
|
-
}
|
|
618
|
-
},
|
|
619
701
|
/**
|
|
620
702
|
* 运行cmd命令
|
|
621
703
|
* @param exec 命令字符串
|
|
622
704
|
* @returns 命令执行结果
|
|
623
705
|
*/
|
|
624
|
-
exec: async ({ command }) => {
|
|
625
|
-
const
|
|
706
|
+
exec: async ({ userId, command }) => {
|
|
707
|
+
const workspace = getWorkspace(userId);
|
|
626
708
|
return new Promise((resolve) => {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
709
|
+
const child = spawn("docker", [
|
|
710
|
+
"run",
|
|
711
|
+
"--rm",
|
|
712
|
+
"--memory=256m",
|
|
713
|
+
"--cpus=0.5",
|
|
714
|
+
"--pids-limit=64",
|
|
715
|
+
"--network=none",
|
|
716
|
+
"--read-only",
|
|
717
|
+
"-v",
|
|
718
|
+
`${workspace}:/workspace`,
|
|
719
|
+
"-w",
|
|
720
|
+
"/workspace",
|
|
721
|
+
"alpine",
|
|
722
|
+
"sh",
|
|
723
|
+
"-c",
|
|
724
|
+
command,
|
|
725
|
+
]);
|
|
726
|
+
let output = "";
|
|
727
|
+
child.stdout.on("data", (d) => (output += d.toString()));
|
|
728
|
+
child.stderr.on("data", (d) => (output += d.toString()));
|
|
729
|
+
child.on("close", () => resolve(output));
|
|
641
730
|
});
|
|
642
731
|
},
|
|
643
|
-
/**
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
EvalCode: async ({ code }) => {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
}
|
|
732
|
+
// /**
|
|
733
|
+
// * 执行eval
|
|
734
|
+
// * @param code 代码字符串
|
|
735
|
+
// * @returns 执行结果
|
|
736
|
+
// */
|
|
737
|
+
// EvalCode: async ({ code }) => {
|
|
738
|
+
// try {
|
|
739
|
+
// if (code.includes("exec") || code.includes("process")) {
|
|
740
|
+
// return "代码中包含敏感词,无法执行";
|
|
741
|
+
// }
|
|
742
|
+
// // eslint-disable-next-line no-eval
|
|
743
|
+
// const result = eval(code);
|
|
744
|
+
// return result;
|
|
745
|
+
// } catch (error) {
|
|
746
|
+
// console.error(`Error executing code: ${error}`);
|
|
747
|
+
// return `Error: ${error}`;
|
|
748
|
+
// }
|
|
749
|
+
// },
|
|
659
750
|
/**
|
|
660
751
|
* 获取技能详情
|
|
661
752
|
* @param skillName 技能名称
|
|
@@ -673,6 +764,51 @@ const availableTools = {
|
|
|
673
764
|
GetCommand: async () => {
|
|
674
765
|
return help;
|
|
675
766
|
},
|
|
767
|
+
http_request: async ({ url, method = "GET", body, }) => {
|
|
768
|
+
await validateURL(url);
|
|
769
|
+
const controller = new AbortController();
|
|
770
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
771
|
+
try {
|
|
772
|
+
const res = await fetch(url, {
|
|
773
|
+
method,
|
|
774
|
+
body,
|
|
775
|
+
signal: controller.signal,
|
|
776
|
+
headers: {
|
|
777
|
+
"User-Agent": "AI-Agent/1.0",
|
|
778
|
+
},
|
|
779
|
+
});
|
|
780
|
+
const text = await res.text();
|
|
781
|
+
if (text.length > 1024 * 1024) {
|
|
782
|
+
return "Response too large";
|
|
783
|
+
}
|
|
784
|
+
return text;
|
|
785
|
+
}
|
|
786
|
+
catch (e) {
|
|
787
|
+
return `Request failed: ${e.message}`;
|
|
788
|
+
}
|
|
789
|
+
finally {
|
|
790
|
+
clearTimeout(timeout);
|
|
791
|
+
}
|
|
792
|
+
},
|
|
793
|
+
ping: async ({ host }) => {
|
|
794
|
+
// 禁止直接 IP
|
|
795
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) {
|
|
796
|
+
return "IP address not allowed";
|
|
797
|
+
}
|
|
798
|
+
const ips = await dns.lookup(host, { all: true });
|
|
799
|
+
for (const { address } of ips) {
|
|
800
|
+
if (isPrivateIP(address)) {
|
|
801
|
+
return "Target resolves to private IP";
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return new Promise((resolve) => {
|
|
805
|
+
const child = spawn("ping", ["-c", "3", host]);
|
|
806
|
+
let output = "";
|
|
807
|
+
child.stdout.on("data", (d) => (output += d.toString()));
|
|
808
|
+
child.stderr.on("data", (d) => (output += d.toString()));
|
|
809
|
+
child.on("close", () => resolve(output));
|
|
810
|
+
});
|
|
811
|
+
},
|
|
676
812
|
};
|
|
677
813
|
|
|
678
814
|
export { availableTools, tools };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import dns from 'dns/promises';
|
|
2
|
+
|
|
3
|
+
// security.ts
|
|
4
|
+
const BLOCKED_IP_RANGES = [
|
|
5
|
+
/^127\./,
|
|
6
|
+
/^10\./,
|
|
7
|
+
/^192\.168\./,
|
|
8
|
+
/^172\.(1[6-9]|2\d|3[0-1])\./,
|
|
9
|
+
/^169\.254\./,
|
|
10
|
+
];
|
|
11
|
+
function isPrivateIP(ip) {
|
|
12
|
+
return BLOCKED_IP_RANGES.some((r) => r.test(ip));
|
|
13
|
+
}
|
|
14
|
+
async function validateURL(url) {
|
|
15
|
+
try {
|
|
16
|
+
const u = new URL(url);
|
|
17
|
+
if (!["http:", "https:"].includes(u.protocol)) {
|
|
18
|
+
throw new Error("Only http/https allowed");
|
|
19
|
+
}
|
|
20
|
+
const ips = await dns.lookup(u.hostname, { all: true });
|
|
21
|
+
for (const { address } of ips) {
|
|
22
|
+
if (isPrivateIP(address)) {
|
|
23
|
+
throw new Error("Access to private network is forbidden");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
throw new Error(`Invalid URL: ${e.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { isPrivateIP, validateURL };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
const BASE = "./public/sandbox/workspaces";
|
|
5
|
+
function getWorkspace(userId) {
|
|
6
|
+
const dir = path.join(BASE, userId);
|
|
7
|
+
if (!fs.existsSync(dir)) {
|
|
8
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
return dir;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { getWorkspace };
|
package/lib/config.js
CHANGED
|
@@ -256,6 +256,67 @@ class db {
|
|
|
256
256
|
}
|
|
257
257
|
await this.setAIChatHistory(guid, history);
|
|
258
258
|
}
|
|
259
|
+
/** 存储摘要,全局共享 */
|
|
260
|
+
async setSummary(guid, id, summary) {
|
|
261
|
+
await this.redis.set(`ai:summary:${guid}:${id}`, `${summary}`);
|
|
262
|
+
}
|
|
263
|
+
/** 获取摘要 */
|
|
264
|
+
async getSummary(guid, id) {
|
|
265
|
+
return await this.redis.get(`ai:summary:${guid}:${id}`);
|
|
266
|
+
}
|
|
267
|
+
/** 获取所有摘要时间列表 */
|
|
268
|
+
async getSummaryList() {
|
|
269
|
+
console.log("获取列表");
|
|
270
|
+
const keys = await this.redis.keys(`ai:summary:*`);
|
|
271
|
+
return Promise.all(keys.map(async (key) => {
|
|
272
|
+
const parts = key.split(":");
|
|
273
|
+
const summary = await this.redis.get(key);
|
|
274
|
+
return {
|
|
275
|
+
id: `${parts[2]}:${parts[3]}`,
|
|
276
|
+
summary: summary ?? "",
|
|
277
|
+
};
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
/** 删除摘要 */
|
|
281
|
+
async deleteSummary(guid, id) {
|
|
282
|
+
return await this.redis.del(`ai:summary:${guid}:${id}`);
|
|
283
|
+
}
|
|
284
|
+
/** 存储摘要中的详细内容 */
|
|
285
|
+
async setSummaryDetail(guid, id, detail) {
|
|
286
|
+
await this.redis.set(`ai:summary_detail:${guid}:${id}`, `${detail}`);
|
|
287
|
+
}
|
|
288
|
+
/** 获取摘要中的详细内容 */
|
|
289
|
+
async getSummaryDetail(guid, id) {
|
|
290
|
+
return await this.redis.get(`ai:summary_detail:${guid}:${id}`);
|
|
291
|
+
}
|
|
292
|
+
/** 删除摘要中的详细内容 */
|
|
293
|
+
async deleteSummaryDetail(guid, id) {
|
|
294
|
+
return await this.redis.del(`ai:summary_detail:${guid}:${id}`);
|
|
295
|
+
}
|
|
296
|
+
/** 存储用户数据 */
|
|
297
|
+
async setUserData(userKey, value) {
|
|
298
|
+
await this.redis.set(`ai:user:${userKey}`, value);
|
|
299
|
+
}
|
|
300
|
+
/** 获取用户数据 */
|
|
301
|
+
async getUserData(userKey) {
|
|
302
|
+
return await this.redis.get(`ai:user:${userKey}`);
|
|
303
|
+
}
|
|
304
|
+
/** 删除用户数据 */
|
|
305
|
+
async deleteUserData(userKey) {
|
|
306
|
+
return await this.redis.del(`ai:user:${userKey}`);
|
|
307
|
+
}
|
|
308
|
+
/** 设置会话id */
|
|
309
|
+
async setSessionId(guid, sessionId) {
|
|
310
|
+
await this.redis.set(`ai:session:${guid}`, sessionId);
|
|
311
|
+
}
|
|
312
|
+
/** 获取会话id */
|
|
313
|
+
async getSessionId(guid) {
|
|
314
|
+
return await this.redis.get(`ai:session:${guid}`);
|
|
315
|
+
}
|
|
316
|
+
/** 删除会话id */
|
|
317
|
+
async deleteSessionId(guid) {
|
|
318
|
+
return await this.redis.del(`ai:session:${guid}`);
|
|
319
|
+
}
|
|
259
320
|
/** 获取好感度 */
|
|
260
321
|
async getAffectionLevel(guid, userKey) {
|
|
261
322
|
const levelStr = await this.redis.get(`ai:affection:${guid}:${userKey}`);
|
package/lib/data/help.json.js
CHANGED
package/lib/middleware/mw.js
CHANGED
|
@@ -2,6 +2,7 @@ import { getConfigValue, useClient } from 'alemonjs';
|
|
|
2
2
|
import { API } from '@alemonjs/onebot';
|
|
3
3
|
import redisClient from '../config.js';
|
|
4
4
|
import { CApiReply } from '../response/zreply/capi.js';
|
|
5
|
+
import { RApiReply } from '../response/zreply/rapi.js';
|
|
5
6
|
|
|
6
7
|
const selects = onSelects(["message.create", "private.message.create"]);
|
|
7
8
|
// 中间件
|
|
@@ -268,15 +269,21 @@ var mw = onMiddleware(selects, async (event, next) => {
|
|
|
268
269
|
// 执行ai回复, 方便ai控制后续指令
|
|
269
270
|
const aiconfig = await redisClient.getAIConfig(event.guid);
|
|
270
271
|
if (aiconfig && aiconfig.model && aiconfig.key) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
event.MessageText = ReplyRes.commands[0];
|
|
277
|
-
event["msg"] = ReplyRes.commands[0];
|
|
278
|
-
}
|
|
272
|
+
if (aiconfig.rapi) {
|
|
273
|
+
await RApiReply(event);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
await CApiReply(event);
|
|
279
277
|
}
|
|
278
|
+
// const ReplyRes = await RApiReply(event);
|
|
279
|
+
// console.log("res", ReplyRes);
|
|
280
|
+
// if (ReplyRes) {
|
|
281
|
+
// event["Xianyu"] = ReplyRes.next; // 根据AI回复决定是否继续执行后续的处理函数
|
|
282
|
+
// // if (ReplyRes.commands && ReplyRes.commands.length > 0) {
|
|
283
|
+
// // event.MessageText = ReplyRes.commands[0];
|
|
284
|
+
// // event["msg"] = ReplyRes.commands[0];
|
|
285
|
+
// // }
|
|
286
|
+
// }
|
|
280
287
|
}
|
|
281
288
|
}
|
|
282
289
|
next();
|
package/lib/redis.js
CHANGED
|
@@ -2,10 +2,9 @@ import redisClient from '../../config.js';
|
|
|
2
2
|
import App from '../../image/conponent/AiConfig.js';
|
|
3
3
|
import { useMessage, Image, Text } from 'alemonjs';
|
|
4
4
|
import { renderComponentIsHtmlToBuffer } from 'jsxp';
|
|
5
|
+
import OpenAi from 'openai';
|
|
5
6
|
|
|
6
7
|
const selects = onSelects(["message.create", "private.message.create"]);
|
|
7
|
-
// inline regex used directly in condition checks
|
|
8
|
-
const get = /^(\/|#)get (.+)$/i;
|
|
9
8
|
var res = onResponse(selects, async (e, next) => {
|
|
10
9
|
// 创建
|
|
11
10
|
const [message] = useMessage(e);
|
|
@@ -67,6 +66,7 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
67
66
|
// 清空对话
|
|
68
67
|
if (/^(\/|#)清空对话$/i.test(e.msg)) {
|
|
69
68
|
await redisClient.clearAIChatHistory(e.guid);
|
|
69
|
+
await redisClient.deleteSessionId(e.guid);
|
|
70
70
|
message.send(format(Text("对话已清空")));
|
|
71
71
|
}
|
|
72
72
|
// 清空所有对话
|
|
@@ -75,8 +75,8 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
75
75
|
message.send(format(Text("所有对话已清空")));
|
|
76
76
|
}
|
|
77
77
|
// get请求一个地址
|
|
78
|
-
if (get.test(e.msg)) {
|
|
79
|
-
const match = e.msg.match(get);
|
|
78
|
+
if (/^(\/|#)get (.+)$/i.test(e.msg)) {
|
|
79
|
+
const match = e.msg.match(/^(\/|#)get (.+)$/i);
|
|
80
80
|
if (!match) {
|
|
81
81
|
message.send(format(Text("格式错误,请按照 格式:/get <url> 进行请求")));
|
|
82
82
|
return;
|
|
@@ -91,6 +91,72 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
91
91
|
message.send(format(Text(`请求 ${url} 失败:${error}`)));
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
+
// 新对话
|
|
95
|
+
if (/(\/|#)(开启)?(new|新对话|新会话)$/i.test(e.msg)) {
|
|
96
|
+
const match = e.msg.match(/^(\/|#)(开启)?(new|新对话|新会话)$/i);
|
|
97
|
+
if (!match) {
|
|
98
|
+
message.send(format(Text("格式错误,请按照 格式:/new 进行开启新对话")));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// 压缩当前对话的上下文
|
|
102
|
+
const aiConfig = await redisClient.getAIConfig(e.guid);
|
|
103
|
+
if (aiConfig.model) {
|
|
104
|
+
// 让ai总结当前聊天内容, 将其存储到redis中作为历史对话的一部分
|
|
105
|
+
const openai = new OpenAi({
|
|
106
|
+
baseURL: aiConfig.host,
|
|
107
|
+
apiKey: aiConfig.key,
|
|
108
|
+
timeout: 300000,
|
|
109
|
+
});
|
|
110
|
+
const history = await redisClient.getAIChatHistory(e.guid);
|
|
111
|
+
const FilingID = Date.now();
|
|
112
|
+
const summaryPrompt = `system:请总结一下我们之前的聊天内容,要求总结内容能够涵盖之前聊天的主要信息和细节,字数控制在200字以内。总结内容将被用作未来对话的上下文,请确保总结内容的准确性和完整性。请务必按照特定格式总总结, 格式如下
|
|
113
|
+
|
|
114
|
+
归档ID:${FilingID}
|
|
115
|
+
摘要:<简单描述>
|
|
116
|
+
详细信息:<详细描述>
|
|
117
|
+
|
|
118
|
+
归档id为框架生成
|
|
119
|
+
其中摘要部分要求简洁明了, 详细信息部分要求尽可能全面地涵盖之前聊天的内容, 包括重要的细节和信息点。请确保总结内容能够准确反映之前的聊天内容, 并且格式正确,以便我们在未来的对话中能够正确地使用这些总结内容。`;
|
|
120
|
+
const messages = [
|
|
121
|
+
...history,
|
|
122
|
+
{
|
|
123
|
+
role: "user",
|
|
124
|
+
content: summaryPrompt,
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
console.log("messages", messages);
|
|
128
|
+
try {
|
|
129
|
+
const response = await openai.chat.completions.create({
|
|
130
|
+
model: aiConfig.model,
|
|
131
|
+
messages,
|
|
132
|
+
temperature: 0.8,
|
|
133
|
+
stream: false,
|
|
134
|
+
});
|
|
135
|
+
const summary = response.choices[0].message.content;
|
|
136
|
+
if (summary) {
|
|
137
|
+
const AbstractMatch = summary.match(/摘要:(.*)详细信息:/s);
|
|
138
|
+
const DetailMatch = summary.match(/详细信息:(.*)/s);
|
|
139
|
+
const Abstract = AbstractMatch ? AbstractMatch[1].trim() : "无摘要";
|
|
140
|
+
const Detail = DetailMatch ? DetailMatch[1].trim() : "无详细信息";
|
|
141
|
+
await redisClient.setSummary(e.guid, FilingID.toString(), Abstract);
|
|
142
|
+
await redisClient.setSummaryDetail(e.guid, FilingID.toString(), Detail);
|
|
143
|
+
await redisClient.clearAIChatHistory(e.guid);
|
|
144
|
+
await redisClient.addAIChatHistory(e.guid, {
|
|
145
|
+
role: "user",
|
|
146
|
+
content: `system:上一轮对话小结:${Abstract}\n详细信息:${Detail}`,
|
|
147
|
+
});
|
|
148
|
+
message.send(format(Text("已开启新对话")));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
console.error("对话总结失败:", error);
|
|
153
|
+
message.send(format(Text("新对话开启失败"), Text(`\n报错内容:${error}`)));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
message.send(format(Text("当前无可用的AI配置,请先设置AI配置")));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
94
160
|
next();
|
|
95
161
|
});
|
|
96
162
|
|