alemonjs-aichat 1.0.31-beta.3 → 1.0.32
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 +113 -22
- package/lib/api/format.js +12 -2
- package/lib/config.js +238 -14
- package/lib/data/help.json.js +29 -1
- package/lib/image/conponent/AiConfig.js +1 -1
- package/lib/middleware/mw.js +15 -1
- package/lib/redis.js +79 -7
- package/lib/remoteNodes.js +323 -0
- package/lib/response/config/res.js +5 -1
- package/lib/response/env/res.js +307 -0
- package/lib/response/help/res.js +10 -1
- package/lib/response/setting/res.js +156 -5
- package/lib/response/tools/res.js +2 -2
- package/lib/response/zreply/capi.js +67 -61
- package/lib/response/zreply/getChatConfig.js +66 -9
- package/lib/response/zreply/rapi.js +1 -1
- package/lib/response/zreply/res.js +22 -16
- package/lib/response/zreply/tools.js +9 -8
- package/lib/routes/commands.js +1 -1
- package/lib/userEnv.js +136 -0
- package/package.json +57 -54
- package/skills/send-media/SKILL.md +2 -2
package/lib/api/aitools.js
CHANGED
|
@@ -10,6 +10,8 @@ import { isPrivateIP, validateURL } from './security.js';
|
|
|
10
10
|
import { spawn } from 'child_process';
|
|
11
11
|
import * as dns from 'node:dns/promises';
|
|
12
12
|
import { getWorkspace } from './workspace.js';
|
|
13
|
+
import { getExecCommandEnv } from '../userEnv.js';
|
|
14
|
+
import { remoteNodeManager } from '../remoteNodes.js';
|
|
13
15
|
|
|
14
16
|
const value = getConfigValue().aiChat || {};
|
|
15
17
|
const getPrompt = async (text) => {
|
|
@@ -168,7 +170,7 @@ const tools = [
|
|
|
168
170
|
type: "function",
|
|
169
171
|
function: {
|
|
170
172
|
name: "exec",
|
|
171
|
-
description: "
|
|
173
|
+
description: "执行终端命令. 你可以使用这个工具来执行一些简单的命令, 例如查看当前目录下的文件ls, 查看系统状态top等. 当你想要执行一个命令时, 请提供具体的命令文本, 例如'ls -la'或'top -n 1'. 执行结果将会返回给你, 但请注意, 由于安全限制, 某些命令可能无法执行或返回受限的结果",
|
|
172
174
|
parameters: {
|
|
173
175
|
type: "object",
|
|
174
176
|
properties: {
|
|
@@ -176,12 +178,16 @@ const tools = [
|
|
|
176
178
|
type: "string",
|
|
177
179
|
description: "执行命令的用户ID",
|
|
178
180
|
},
|
|
181
|
+
groupId: {
|
|
182
|
+
type: "string",
|
|
183
|
+
description: "当前群聊ID,私聊时传'private'",
|
|
184
|
+
},
|
|
179
185
|
command: {
|
|
180
186
|
type: "string",
|
|
181
187
|
description: "要执行的终端命令",
|
|
182
188
|
},
|
|
183
189
|
},
|
|
184
|
-
required: ["userId", "command"],
|
|
190
|
+
required: ["userId", "groupId", "command"],
|
|
185
191
|
},
|
|
186
192
|
},
|
|
187
193
|
},
|
|
@@ -234,7 +240,7 @@ const tools = [
|
|
|
234
240
|
type: "function",
|
|
235
241
|
function: {
|
|
236
242
|
name: "MemoryOperation",
|
|
237
|
-
description: "这个工具用于获取或修改对于用户的记忆数据, 包括用户形象, 用户偏好, 历史对话等. 你可以使用这个工具来存储一些需要长期记忆的信息, 例如用户的兴趣爱好,
|
|
243
|
+
description: "这个工具用于获取或修改对于用户的记忆数据, 包括用户形象, 用户偏好, 历史对话等. 你可以使用这个工具来存储一些需要长期记忆的信息, 例如用户的兴趣爱好, 重要事件等, 在不认识对方时可以尝试一下获取. 当你想要获取或修改这些信息时, 可以调用这个工具并提供具体的操作指令和数据. 例如, 在聊天过程中他对你的态度,以及他喜欢的东西等,可以通过这个工具及时记录下来, 也可以通过这个工具获取之前的对话记录,便于继续之前的话题",
|
|
238
244
|
parameters: {
|
|
239
245
|
type: "object",
|
|
240
246
|
properties: {
|
|
@@ -335,6 +341,51 @@ const tools = [
|
|
|
335
341
|
},
|
|
336
342
|
},
|
|
337
343
|
];
|
|
344
|
+
/**
|
|
345
|
+
* 确保 Docker 容器已创建并运行
|
|
346
|
+
*/
|
|
347
|
+
async function ensureContainerRunning(config) {
|
|
348
|
+
const { containerName, image, memory, cpus, pidsLimit, networkMode, workspacePath } = config;
|
|
349
|
+
if (!containerName)
|
|
350
|
+
return;
|
|
351
|
+
return new Promise((resolve) => {
|
|
352
|
+
const check = spawn("docker", ["inspect", "-f", "{{.State.Running}}", containerName]);
|
|
353
|
+
let stdout = "";
|
|
354
|
+
check.stdout.on("data", (d) => (stdout += d.toString()));
|
|
355
|
+
check.on("close", (code) => {
|
|
356
|
+
if (code === 0 && stdout.trim() === "true") {
|
|
357
|
+
resolve();
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (code === 0 && stdout.trim() === "false") {
|
|
361
|
+
const start = spawn("docker", ["start", containerName]);
|
|
362
|
+
start.on("close", () => resolve());
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const args = [
|
|
366
|
+
"run", "-d",
|
|
367
|
+
"--name", containerName,
|
|
368
|
+
"--memory", memory || "256m",
|
|
369
|
+
"--cpus", cpus || "0.5",
|
|
370
|
+
"--pids-limit", String(pidsLimit || 64),
|
|
371
|
+
"--network", networkMode || "none",
|
|
372
|
+
];
|
|
373
|
+
if (workspacePath && fs.existsSync(workspacePath)) {
|
|
374
|
+
args.push("-v", `${workspacePath}:/workspace`, "-w", "/workspace");
|
|
375
|
+
}
|
|
376
|
+
args.push(image || "ubuntu:latest", "sleep", "infinity");
|
|
377
|
+
const create = spawn("docker", args);
|
|
378
|
+
let createStderr = "";
|
|
379
|
+
create.stderr.on("data", (d) => (createStderr += d.toString()));
|
|
380
|
+
create.on("close", (code) => {
|
|
381
|
+
if (code !== 0) {
|
|
382
|
+
console.error(`[Docker] 容器创建失败: ${containerName}`, createStderr);
|
|
383
|
+
}
|
|
384
|
+
resolve();
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
}
|
|
338
389
|
const availableTools = {
|
|
339
390
|
getAlemonjsConfig: async ({ key, value }) => {
|
|
340
391
|
if (key == "ailist") {
|
|
@@ -726,30 +777,70 @@ const availableTools = {
|
|
|
726
777
|
},
|
|
727
778
|
/**
|
|
728
779
|
* 运行cmd命令
|
|
729
|
-
* @param
|
|
780
|
+
* @param userId 用户ID
|
|
781
|
+
* @param groupId 群聊ID,私聊时为"private"
|
|
782
|
+
* @param command 命令字符串
|
|
730
783
|
* @returns 命令执行结果
|
|
731
784
|
*/
|
|
732
|
-
exec: async ({ userId, command }) => {
|
|
785
|
+
exec: async ({ userId, groupId, command }) => {
|
|
733
786
|
const workspace = getWorkspace(userId);
|
|
787
|
+
// 使用新的环境管理获取执行环境
|
|
788
|
+
const envInfo = await getExecCommandEnv(groupId || "private", userId);
|
|
789
|
+
// 远程节点环境:通过 SSH 执行
|
|
790
|
+
if (envInfo.type === "remote" && envInfo.nodeName) {
|
|
791
|
+
try {
|
|
792
|
+
const result = await remoteNodeManager.execute(envInfo.nodeName, command);
|
|
793
|
+
return result || "命令执行完成,无输出";
|
|
794
|
+
}
|
|
795
|
+
catch (err) {
|
|
796
|
+
return `远程执行失败: ${err.message}`;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
// Singularity 容器环境:通过远程节点的 Singularity 执行
|
|
800
|
+
if (envInfo.type === "singularity" && envInfo.nodeName) {
|
|
801
|
+
try {
|
|
802
|
+
const node = remoteNodeManager.getNode(envInfo.nodeName);
|
|
803
|
+
if (!node) {
|
|
804
|
+
return `未找到远程节点: ${envInfo.nodeName}`;
|
|
805
|
+
}
|
|
806
|
+
const image = envInfo.singularityImage || node.defaultImage;
|
|
807
|
+
if (!image) {
|
|
808
|
+
return "未指定 Singularity 镜像,请在切换环境时指定或配置 defaultImage";
|
|
809
|
+
}
|
|
810
|
+
// 构建 singularity exec 命令
|
|
811
|
+
const singularityCmd = `singularity exec ${image} sh -c '${command.replace(/'/g, "'\''")}'`;
|
|
812
|
+
const result = await remoteNodeManager.execute(envInfo.nodeName, singularityCmd);
|
|
813
|
+
return result || "命令执行完成,无输出";
|
|
814
|
+
}
|
|
815
|
+
catch (err) {
|
|
816
|
+
return `Singularity 执行失败: ${err.message}`;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
// 系统环境:直接在宿主机执行
|
|
820
|
+
if (envInfo.type === "system") {
|
|
821
|
+
return new Promise((resolve) => {
|
|
822
|
+
let output = "";
|
|
823
|
+
const child = spawn("sh", ["-c", command], { cwd: workspace });
|
|
824
|
+
child.stdout.on("data", (d) => (output += d.toString()));
|
|
825
|
+
child.stderr.on("data", (d) => (output += d.toString()));
|
|
826
|
+
child.on("close", () => resolve(output));
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
// 容器环境(group/user):确保容器存在并启动,然后 docker exec
|
|
830
|
+
const envConfig = await redisClient.getExecEnv(groupId || "private", userId);
|
|
831
|
+
const containerName = envConfig?.containerName;
|
|
832
|
+
if (!containerName) {
|
|
833
|
+
return "错误:未找到执行环境配置,请先切换执行环境";
|
|
834
|
+
}
|
|
835
|
+
await ensureContainerRunning(envConfig);
|
|
734
836
|
return new Promise((resolve) => {
|
|
735
|
-
const child = spawn("docker", [
|
|
736
|
-
"run",
|
|
737
|
-
"--rm",
|
|
738
|
-
"--memory=256m",
|
|
739
|
-
"--cpus=0.5",
|
|
740
|
-
"--pids-limit=64",
|
|
741
|
-
"--network=none",
|
|
742
|
-
"--read-only",
|
|
743
|
-
"-v",
|
|
744
|
-
`${workspace}:/workspace`,
|
|
745
|
-
"-w",
|
|
746
|
-
"/workspace",
|
|
747
|
-
"alpine",
|
|
748
|
-
"sh",
|
|
749
|
-
"-c",
|
|
750
|
-
command,
|
|
751
|
-
]);
|
|
752
837
|
let output = "";
|
|
838
|
+
const args = ["exec"];
|
|
839
|
+
if (envConfig.workspacePath) {
|
|
840
|
+
args.push("-w", "/workspace");
|
|
841
|
+
}
|
|
842
|
+
args.push(containerName, "sh", "-c", command);
|
|
843
|
+
const child = spawn("docker", args);
|
|
753
844
|
child.stdout.on("data", (d) => (output += d.toString()));
|
|
754
845
|
child.stderr.on("data", (d) => (output += d.toString()));
|
|
755
846
|
child.on("close", () => resolve(output));
|
package/lib/api/format.js
CHANGED
|
@@ -2,12 +2,22 @@ import { getConfigValue } from 'alemonjs';
|
|
|
2
2
|
|
|
3
3
|
getConfigValue().aiChat || {};
|
|
4
4
|
const getTimeString = () => {
|
|
5
|
+
return getMessageTimeString();
|
|
6
|
+
};
|
|
7
|
+
const getGroupTimeString = () => {
|
|
8
|
+
const now = new Date();
|
|
9
|
+
const year = now.getFullYear();
|
|
10
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
11
|
+
return `${year}/${month}`;
|
|
12
|
+
};
|
|
13
|
+
const getMessageTimeString = () => {
|
|
5
14
|
const now = new Date();
|
|
15
|
+
const year = now.getFullYear();
|
|
6
16
|
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
7
17
|
const day = String(now.getDate()).padStart(2, "0");
|
|
8
18
|
const hours = String(now.getHours()).padStart(2, "0");
|
|
9
19
|
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
10
|
-
return `${month}
|
|
20
|
+
return `${year}/${month}/${day} ${hours}:${minutes}`;
|
|
11
21
|
};
|
|
12
22
|
|
|
13
|
-
export { getTimeString };
|
|
23
|
+
export { getGroupTimeString, getMessageTimeString, getTimeString };
|
package/lib/config.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import Redis from 'ioredis';
|
|
2
1
|
import { getConfigValue } from 'alemonjs';
|
|
3
|
-
import
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { redis } from './redis.js';
|
|
4
5
|
|
|
5
6
|
class db {
|
|
6
7
|
redis;
|
|
7
8
|
systemPrompt = "";
|
|
9
|
+
async getToolDefinitions() {
|
|
10
|
+
const { tools } = await import('./api/aitools.js');
|
|
11
|
+
return tools;
|
|
12
|
+
}
|
|
8
13
|
static defaultAIConfig = {
|
|
9
14
|
host: "",
|
|
10
15
|
key: "",
|
|
@@ -14,11 +19,8 @@ class db {
|
|
|
14
19
|
systemPrompt: "",
|
|
15
20
|
rapi: false,
|
|
16
21
|
};
|
|
17
|
-
constructor(
|
|
18
|
-
this.redis =
|
|
19
|
-
redisConfig && Object.keys(redisConfig).length > 0
|
|
20
|
-
? new Redis(redisConfig)
|
|
21
|
-
: new Redis(); // 默认配置
|
|
22
|
+
constructor(redisInstance) {
|
|
23
|
+
this.redis = redisInstance || redis;
|
|
22
24
|
}
|
|
23
25
|
/** 获取提示词优化内容 */
|
|
24
26
|
async getPromptOptimization(guid) {
|
|
@@ -408,6 +410,218 @@ class db {
|
|
|
408
410
|
async setAtTriggerSwitch(guid, enable) {
|
|
409
411
|
await this.redis.set(`ai:at_trigger:switch:${guid}`, enable ? "1" : "0");
|
|
410
412
|
}
|
|
413
|
+
/** --------------------------------------------------------- */
|
|
414
|
+
/** 正则触发规则 */
|
|
415
|
+
/** 获取正则触发规则列表 */
|
|
416
|
+
async getRegexRules(guid) {
|
|
417
|
+
const rulesStr = await this.redis.get(`ai:regex_rules:${guid}`);
|
|
418
|
+
if (rulesStr) {
|
|
419
|
+
return JSON.parse(rulesStr);
|
|
420
|
+
}
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
/** 设置正则触发规则列表 */
|
|
424
|
+
async setRegexRules(guid, rules) {
|
|
425
|
+
await this.redis.set(`ai:regex_rules:${guid}`, JSON.stringify(rules));
|
|
426
|
+
}
|
|
427
|
+
/** 添加正则触发规则 */
|
|
428
|
+
async addRegexRule(guid, rule) {
|
|
429
|
+
// 验证正则表达式是否合法
|
|
430
|
+
try {
|
|
431
|
+
new RegExp(rule);
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
const rules = await this.getRegexRules(guid);
|
|
437
|
+
if (rules.includes(rule)) {
|
|
438
|
+
return false; // 规则已存在
|
|
439
|
+
}
|
|
440
|
+
rules.push(rule);
|
|
441
|
+
await this.setRegexRules(guid, rules);
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
/** 删除正则触发规则 */
|
|
445
|
+
async removeRegexRule(guid, ruleOrIndex) {
|
|
446
|
+
const rules = await this.getRegexRules(guid);
|
|
447
|
+
// 尝试按索引删除
|
|
448
|
+
const index = parseInt(ruleOrIndex, 10);
|
|
449
|
+
if (!isNaN(index) && index >= 0 && index < rules.length) {
|
|
450
|
+
rules.splice(index, 1);
|
|
451
|
+
await this.setRegexRules(guid, rules);
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
// 按规则内容删除
|
|
455
|
+
const ruleIndex = rules.indexOf(ruleOrIndex);
|
|
456
|
+
if (ruleIndex === -1) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
rules.splice(ruleIndex, 1);
|
|
460
|
+
await this.setRegexRules(guid, rules);
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
/** 检查消息是否匹配正则触发规则 */
|
|
464
|
+
async matchRegexRule(guid, message) {
|
|
465
|
+
const rules = await this.getRegexRules(guid);
|
|
466
|
+
for (const rule of rules) {
|
|
467
|
+
try {
|
|
468
|
+
const regex = new RegExp(rule);
|
|
469
|
+
if (regex.test(message)) {
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
// 跳过无效的正则
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
/** 清空正则触发规则 */
|
|
480
|
+
async clearRegexRules(guid) {
|
|
481
|
+
await this.redis.del(`ai:regex_rules:${guid}`);
|
|
482
|
+
}
|
|
483
|
+
/** --------------------------------------------------------- */
|
|
484
|
+
/** 容器管理 */
|
|
485
|
+
/** 获取群容器配置 */
|
|
486
|
+
async getGroupContainer(groupId) {
|
|
487
|
+
const str = await this.redis.get(`ai:container:group:${groupId}`);
|
|
488
|
+
return str ? JSON.parse(str) : null;
|
|
489
|
+
}
|
|
490
|
+
/** 设置群容器配置 */
|
|
491
|
+
async setGroupContainer(groupId, config) {
|
|
492
|
+
await this.redis.set(`ai:container:group:${groupId}`, JSON.stringify(config));
|
|
493
|
+
}
|
|
494
|
+
/** 获取用户容器配置 */
|
|
495
|
+
async getUserContainer(userId) {
|
|
496
|
+
const str = await this.redis.get(`ai:container:user:${userId}`);
|
|
497
|
+
return str ? JSON.parse(str) : null;
|
|
498
|
+
}
|
|
499
|
+
/** 设置用户容器配置 */
|
|
500
|
+
async setUserContainer(userId, config) {
|
|
501
|
+
await this.redis.set(`ai:container:user:${userId}`, JSON.stringify(config));
|
|
502
|
+
}
|
|
503
|
+
/** 获取用户当前 exec 环境配置(按群隔离) */
|
|
504
|
+
async getExecEnv(groupId, userId) {
|
|
505
|
+
const key = groupId === "private"
|
|
506
|
+
? `ai:container:env:private:${userId}`
|
|
507
|
+
: `ai:container:env:${groupId}:${userId}`;
|
|
508
|
+
const str = await this.redis.get(key);
|
|
509
|
+
return str ? JSON.parse(str) : null;
|
|
510
|
+
}
|
|
511
|
+
/** 设置用户当前 exec 环境配置(按群隔离) */
|
|
512
|
+
async setExecEnv(groupId, userId, config) {
|
|
513
|
+
const key = groupId === "private"
|
|
514
|
+
? `ai:container:env:private:${userId}`
|
|
515
|
+
: `ai:container:env:${groupId}:${userId}`;
|
|
516
|
+
await this.redis.set(key, JSON.stringify(config));
|
|
517
|
+
}
|
|
518
|
+
/** 创建群容器(首次触发时自动创建) */
|
|
519
|
+
async ensureGroupContainer(groupId) {
|
|
520
|
+
const existing = await this.getGroupContainer(groupId);
|
|
521
|
+
if (existing)
|
|
522
|
+
return existing;
|
|
523
|
+
const configValue = getConfigValue();
|
|
524
|
+
const containerConf = configValue.aiChat?.groupContainer || {};
|
|
525
|
+
const image = containerConf.image || "ubuntu:latest";
|
|
526
|
+
const containerName = `aichat_grp_${groupId}`;
|
|
527
|
+
const workspacePath = path.resolve(process.cwd(), `public/sandbox/workspaces/groups/${groupId}`);
|
|
528
|
+
if (!fs.existsSync(workspacePath)) {
|
|
529
|
+
fs.mkdirSync(workspacePath, { recursive: true });
|
|
530
|
+
}
|
|
531
|
+
const config = {
|
|
532
|
+
containerName,
|
|
533
|
+
image,
|
|
534
|
+
memory: containerConf.memory || "256m",
|
|
535
|
+
cpus: containerConf.cpus || "0.5",
|
|
536
|
+
pidsLimit: containerConf.pidsLimit || 64,
|
|
537
|
+
networkMode: containerConf.networkMode || "none",
|
|
538
|
+
workspacePath,
|
|
539
|
+
};
|
|
540
|
+
await this.setGroupContainer(groupId, config);
|
|
541
|
+
return config;
|
|
542
|
+
}
|
|
543
|
+
/** 创建用户容器(手动切换或私聊首次触发时创建) */
|
|
544
|
+
async ensureUserContainer(userId) {
|
|
545
|
+
const existing = await this.getUserContainer(userId);
|
|
546
|
+
if (existing)
|
|
547
|
+
return existing;
|
|
548
|
+
const configValue = getConfigValue();
|
|
549
|
+
const containerConf = configValue.aiChat?.userContainer || {};
|
|
550
|
+
const image = containerConf.image || "ubuntu:latest";
|
|
551
|
+
const containerName = `aichat_usr_${userId}`;
|
|
552
|
+
const workspacePath = path.resolve(process.cwd(), `public/sandbox/workspaces/${userId}`);
|
|
553
|
+
if (!fs.existsSync(workspacePath)) {
|
|
554
|
+
fs.mkdirSync(workspacePath, { recursive: true });
|
|
555
|
+
}
|
|
556
|
+
const config = {
|
|
557
|
+
containerName,
|
|
558
|
+
image,
|
|
559
|
+
memory: containerConf.memory || "128m",
|
|
560
|
+
cpus: containerConf.cpus || "0.25",
|
|
561
|
+
pidsLimit: containerConf.pidsLimit || 32,
|
|
562
|
+
networkMode: containerConf.networkMode || "none",
|
|
563
|
+
workspacePath,
|
|
564
|
+
};
|
|
565
|
+
await this.setUserContainer(userId, config);
|
|
566
|
+
return config;
|
|
567
|
+
}
|
|
568
|
+
/** 初始化用户 exec 环境(群聊默认群容器,私聊默认用户容器) */
|
|
569
|
+
async initExecEnv(groupId, userId) {
|
|
570
|
+
const existing = await this.getExecEnv(groupId, userId);
|
|
571
|
+
if (existing)
|
|
572
|
+
return existing;
|
|
573
|
+
if (groupId === "private") {
|
|
574
|
+
const userConfig = await this.ensureUserContainer(userId);
|
|
575
|
+
await this.setExecEnv("private", userId, { ...userConfig, type: "user" });
|
|
576
|
+
return userConfig;
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
const groupConfig = await this.ensureGroupContainer(groupId);
|
|
580
|
+
await this.setExecEnv(groupId, userId, { ...groupConfig, type: "group" });
|
|
581
|
+
return groupConfig;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/** 切换到群容器 */
|
|
585
|
+
async switchToGroupContainer(groupId, userId) {
|
|
586
|
+
const groupConfig = await this.ensureGroupContainer(groupId);
|
|
587
|
+
await this.setExecEnv(groupId, userId, { ...groupConfig, type: "group" });
|
|
588
|
+
return groupConfig;
|
|
589
|
+
}
|
|
590
|
+
/** 切换到用户容器 */
|
|
591
|
+
async switchToUserContainer(groupId, userId) {
|
|
592
|
+
const userConfig = await this.ensureUserContainer(userId);
|
|
593
|
+
await this.setExecEnv(groupId, userId, { ...userConfig, type: "user" });
|
|
594
|
+
return userConfig;
|
|
595
|
+
}
|
|
596
|
+
/** 切换到系统环境 */
|
|
597
|
+
async switchToSystemEnv(groupId, userId) {
|
|
598
|
+
await this.setExecEnv(groupId, userId, { type: "system" });
|
|
599
|
+
}
|
|
600
|
+
/** 切换到远程节点环境 */
|
|
601
|
+
async switchToRemoteEnv(groupId, userId, nodeName) {
|
|
602
|
+
await this.setExecEnv(groupId, userId, { type: "remote", nodeName });
|
|
603
|
+
}
|
|
604
|
+
/** 切换到 Singularity 环境 */
|
|
605
|
+
async switchToSingularityEnv(groupId, userId, nodeName, image) {
|
|
606
|
+
await this.setExecEnv(groupId, userId, { type: "singularity", nodeName, singularityImage: image });
|
|
607
|
+
}
|
|
608
|
+
/** 判断是否为超级管理员 */
|
|
609
|
+
isSuperAdmin(userId) {
|
|
610
|
+
const configValue = getConfigValue();
|
|
611
|
+
const superAdmins = configValue.aiChat?.superAdmin || [];
|
|
612
|
+
return superAdmins.includes(userId);
|
|
613
|
+
}
|
|
614
|
+
/** 获取所有容器配置 key */
|
|
615
|
+
async getAllContainerKeys() {
|
|
616
|
+
const groupKeys = await this.redis.keys("ai:container:group:*");
|
|
617
|
+
const userKeys = await this.redis.keys("ai:container:user:*");
|
|
618
|
+
return [...groupKeys, ...userKeys];
|
|
619
|
+
}
|
|
620
|
+
/** 删除指定容器配置 */
|
|
621
|
+
async deleteContainer(key) {
|
|
622
|
+
await this.redis.del(key);
|
|
623
|
+
}
|
|
624
|
+
/** --------------------------------------------------------- */
|
|
411
625
|
/** 禁用工具 */
|
|
412
626
|
async disableTool(guid, toolName) {
|
|
413
627
|
await this.redis.set(`ai:tool:disable:${guid}:${toolName}`, "1");
|
|
@@ -423,6 +637,7 @@ class db {
|
|
|
423
637
|
}
|
|
424
638
|
/** 获取所有工具状态 */
|
|
425
639
|
async getAllToolStatus(guid) {
|
|
640
|
+
const tools = await this.getToolDefinitions();
|
|
426
641
|
const status = {};
|
|
427
642
|
for (const tool of tools) {
|
|
428
643
|
const toolName = tool.type === "function" ? tool.function.name : "";
|
|
@@ -432,6 +647,7 @@ class db {
|
|
|
432
647
|
}
|
|
433
648
|
// 获取所有可用工具,返回完整工具
|
|
434
649
|
async getAvailableTools(guid) {
|
|
650
|
+
const tools = await this.getToolDefinitions();
|
|
435
651
|
const toolStatus = await this.getAllToolStatus(guid);
|
|
436
652
|
const availableTools = tools.filter((tool) => {
|
|
437
653
|
const toolName = tool.type === "function" ? tool.function.name : "";
|
|
@@ -439,8 +655,22 @@ class db {
|
|
|
439
655
|
});
|
|
440
656
|
return availableTools;
|
|
441
657
|
}
|
|
658
|
+
/** 获取渲染精度 (中:80%, 高:100%, 超高:120%) */
|
|
659
|
+
async getRenderPrecision(guid) {
|
|
660
|
+
const val = await this.redis.get(`ai:render_precision:${guid}`);
|
|
661
|
+
if (val === "中")
|
|
662
|
+
return 0.8;
|
|
663
|
+
if (val === "超高")
|
|
664
|
+
return 1.2;
|
|
665
|
+
return 1.0;
|
|
666
|
+
}
|
|
667
|
+
/** 设置渲染精度 */
|
|
668
|
+
async setRenderPrecision(guid, level) {
|
|
669
|
+
await this.redis.set(`ai:render_precision:${guid}`, level);
|
|
670
|
+
}
|
|
442
671
|
/** 装载工具 */
|
|
443
672
|
async loadTools(guid) {
|
|
673
|
+
const tools = await this.getToolDefinitions();
|
|
444
674
|
const toolStatus = await this.getAllToolStatus(guid);
|
|
445
675
|
const allTools = tools.map((tool) => tool.type === "function" ? tool.function.name : "");
|
|
446
676
|
// 默认禁用的危险工具
|
|
@@ -463,12 +693,6 @@ class db {
|
|
|
463
693
|
return toolsWithStatus.map((tool) => tool.name);
|
|
464
694
|
}
|
|
465
695
|
}
|
|
466
|
-
const
|
|
467
|
-
const aiChatConfig = configValue.aiChat || {};
|
|
468
|
-
const redisClient = new db({
|
|
469
|
-
host: aiChatConfig.redis?.host ?? "localhost",
|
|
470
|
-
port: aiChatConfig.redis?.port ?? 6379,
|
|
471
|
-
password: aiChatConfig.redis?.password ?? "",
|
|
472
|
-
});
|
|
696
|
+
const redisClient = new db(redis);
|
|
473
697
|
|
|
474
698
|
export { redisClient as default };
|
package/lib/data/help.json.js
CHANGED
|
@@ -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.
|
|
4
|
+
var version = "v1.0.32";
|
|
5
5
|
var by = "AlemonJS";
|
|
6
6
|
var list = [
|
|
7
7
|
{
|
|
@@ -85,6 +85,34 @@ var list = [
|
|
|
85
85
|
cmd: "/[开启|关闭]仅艾特触发",
|
|
86
86
|
desc: "开启或关闭仅艾特触发功能, 避免群内有多个AI时互相干扰。"
|
|
87
87
|
},
|
|
88
|
+
{
|
|
89
|
+
cmd: "/添加正则规则 [表达式]",
|
|
90
|
+
desc: "添加正则触发规则,匹配的消息将自动触发AI回复,无需艾特。"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
cmd: "/删除正则规则 [序号或内容]",
|
|
94
|
+
desc: "删除指定的正则触发规则。"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
cmd: "/正则规则列表",
|
|
98
|
+
desc: "查看当前所有正则触发规则。"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
cmd: "/清空正则规则",
|
|
102
|
+
desc: "清空所有正则触发规则。"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
cmd: "/切换群容器",
|
|
106
|
+
desc: "切换 exec 指令指向当前群的 Docker 容器。"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
cmd: "/切换用户容器",
|
|
110
|
+
desc: "切换 exec 指令指向自己的 Docker 容器。"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
cmd: "/切换系统环境",
|
|
114
|
+
desc: "[仅超级管理员私聊]切换 exec 指令指向宿主机。"
|
|
115
|
+
},
|
|
88
116
|
{
|
|
89
117
|
cmd: "/[开启|关闭]好感度",
|
|
90
118
|
desc: "开启或关闭好感度功能。"
|
|
@@ -35,7 +35,7 @@ function App(data) {
|
|
|
35
35
|
return (React.createElement("html", null,
|
|
36
36
|
React.createElement("head", null,
|
|
37
37
|
React.createElement(LinkStyleSheet, { src: fileUrl })),
|
|
38
|
-
React.createElement("body", { className: "bg-cover p-4", style: { backgroundImage: `url(${fileUrl$1})
|
|
38
|
+
React.createElement("body", { className: "bg-cover p-4", style: { backgroundImage: `url(${fileUrl$1})`, width: "800px" } },
|
|
39
39
|
React.createElement("div", { className: "relative mb-6" },
|
|
40
40
|
React.createElement("div", { className: "absolute -left-4 -top-4 w-24 h-24 bg-purple-400/20 rounded-full blur-2xl -z-0" }),
|
|
41
41
|
React.createElement("h2", { className: "relative text-3xl font-bold bg-gradient-to-r from-amber-300 via-yellow-200 to-amber-300 bg-clip-text text-transparent drop-shadow-lg tracking-wide" }, "AI\u914D\u7F6E\u8BE6\u60C5"),
|
package/lib/middleware/mw.js
CHANGED
|
@@ -16,6 +16,13 @@ var mw = onMiddleware(selects, async (event, next) => {
|
|
|
16
16
|
event["uidkey"] = `${event["guid"]}:${event.UserName}(${event.UserId})`; // 唯一标识
|
|
17
17
|
event["nickname"] =
|
|
18
18
|
`${event.name === "private.message.create" ? "[私信]" : ""}${event.UserName}(${event.UserId})`; // 昵称(用户名+QQ号)
|
|
19
|
+
// 初始化 exec 环境(群容器为默认)
|
|
20
|
+
if (event.name === "private.message.create") {
|
|
21
|
+
await redisClient.initExecEnv("private", event.UserId);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
await redisClient.initExecEnv(event.guid, event.UserId);
|
|
25
|
+
}
|
|
19
26
|
if (event.Platform == "testone") {
|
|
20
27
|
// 处理图片消息
|
|
21
28
|
event["img"] = event.value
|
|
@@ -261,7 +268,14 @@ var mw = onMiddleware(selects, async (event, next) => {
|
|
|
261
268
|
const atTriggerSwitch = await redisClient.getAtTriggerSwitch(event.ChannelId ?? event.UserId);
|
|
262
269
|
if (atTriggerSwitch === "1" && event.Platform !== "testone") {
|
|
263
270
|
if (!event["atBot"] && event.name !== "private.message.create") {
|
|
264
|
-
|
|
271
|
+
// 检查是否匹配正则触发规则
|
|
272
|
+
const regexMatched = await redisClient.matchRegexRule(event.ChannelId ?? event.UserId, event.msg || "");
|
|
273
|
+
if (regexMatched) {
|
|
274
|
+
event["regexTriggered"] = true; // 标记为正则触发
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
event["Xianyu"] = false; // 跳过所有指令,继续执行后续的处理函数
|
|
278
|
+
}
|
|
265
279
|
}
|
|
266
280
|
}
|
|
267
281
|
if (event["Xianyu"]) {
|