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.
@@ -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: "EvalCode",
200
- description: "执行JavaScript代码",
236
+ name: "MemoryOperation",
237
+ description: "这个工具用于获取或修改对于用户的记忆数据, 包括用户形象, 用户偏好, 历史对话等. 你可以使用这个工具来存储一些需要长期记忆的信息, 例如用户的兴趣爱好, 重要事件等. 当你想要获取或修改这些信息时, 可以调用这个工具并提供具体的操作指令和数据. 例如, 在聊天过程中他对你的态度,以及他喜欢的东西等,可以通过这个工具及时记录下来, 也可以通过这个工具获取之前的对话记录,便于继续之前的话题",
201
238
  parameters: {
202
239
  type: "object",
203
240
  properties: {
204
- code: {
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: "要执行的JavaScript代码",
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: "GetCommand",
217
- description: "获取当前框架可用的指令, 查看当前框架每个命令的使用方法和说明. 当用户需要获取指令列表时, 可以调用这个工具来获取当前框架可用的指令列表. 例如, 当用户输入'帮助'或'指令列表'等类似的关键词时, 你可以调用这个工具来获取指令列表并回复给用户. 指令列表会包含每个指令的文本和说明, 例如: [{ command: '帮助', description: '获取帮助信息' }, { command: '看看配置', description: '查看当前AI配置' }]",
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 { exec } = await import('child_process');
706
+ exec: async ({ userId, command }) => {
707
+ const workspace = getWorkspace(userId);
626
708
  return new Promise((resolve) => {
627
- exec(command, (error, stdout, stderr) => {
628
- if (error) {
629
- console.error(`Error executing command: ${error}`);
630
- resolve(`Error: ${error.message}`);
631
- return;
632
- }
633
- if (stderr) {
634
- console.error(`Command stderr: ${stderr}`);
635
- resolve(`Error: ${stderr}`);
636
- return;
637
- }
638
- console.log(`Command stdout: ${stdout}`);
639
- resolve(stdout);
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
- * 执行eval
645
- * @param code 代码字符串
646
- * @returns 执行结果
647
- */
648
- EvalCode: async ({ code }) => {
649
- try {
650
- // eslint-disable-next-line no-eval
651
- const result = eval(code);
652
- return result;
653
- }
654
- catch (error) {
655
- console.error(`Error executing code: ${error}`);
656
- return `Error: ${error}`;
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}`);
@@ -1,7 +1,7 @@
1
1
  var title = "IA聊天插件";
2
2
  var desc = "一个基于AI的聊天插件,支持多种AI模型和丰富的功能配置,适用于各种聊天场景。";
3
3
  var name = "alemonjs-aichat";
4
- var version = "v1.0.28";
4
+ var version = "v1.0.29";
5
5
  var by = "AlemonJS";
6
6
  var list = [
7
7
  {
@@ -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
- const ReplyRes = await CApiReply(event);
272
- console.log("res", ReplyRes);
273
- if (ReplyRes) {
274
- event["Xianyu"] = ReplyRes.next; // 根据AI回复决定是否继续执行后续的处理函数
275
- if (ReplyRes.commands && ReplyRes.commands.length > 0) {
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
@@ -6,6 +6,7 @@ const redisConfig = {
6
6
  host: value.redis?.host ?? "localhost",
7
7
  port: value.redis?.port ?? 6379,
8
8
  password: value.redis?.password ?? "",
9
+ username: value.redis?.user ?? "",
9
10
  };
10
11
  const redis = new Redis(redisConfig);
11
12
 
@@ -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