alemonjs-aichat 1.0.37 → 1.0.39
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 +414 -52
- package/lib/assets/main.css-Dysn6Zt3.css +1 -0
- package/lib/assets/main.css.js +1 -1
- package/lib/config.js +23 -1
- package/lib/data/help.json.js +13 -1
- package/lib/image/conponent/AiConfig.js +7 -1
- package/lib/middleware/mw.js +3 -0
- package/lib/response/config/res.js +40 -19
- package/lib/response/revoke/res.js +34 -0
- package/lib/response/setting/res.js +48 -0
- package/lib/response/zreply/capi.js +79 -7
- package/lib/response/zreply/getChatConfig.js +43 -36
- package/lib/response/zreply/tools.js +1 -1
- package/lib/routes/commands.js +6 -1
- package/package.json +2 -1
- package/skills/add-skill/SKILL.md +73 -24
package/lib/config.js
CHANGED
|
@@ -5,6 +5,7 @@ import { redis } from './redis.js';
|
|
|
5
5
|
|
|
6
6
|
const TOOL_LOOP_TTL_SECONDS = 600;
|
|
7
7
|
const TOOL_LOOP_STALE_MS = TOOL_LOOP_TTL_SECONDS * 1000;
|
|
8
|
+
const DEFAULT_CAPI_CONTEXT_LIMIT = 256000;
|
|
8
9
|
class db {
|
|
9
10
|
redis;
|
|
10
11
|
systemPrompt = "";
|
|
@@ -207,6 +208,19 @@ class db {
|
|
|
207
208
|
}
|
|
208
209
|
/** --------------------------------------------------------- */
|
|
209
210
|
/** 工具相关配置 */
|
|
211
|
+
/** 获取 CAPI 自动压缩上下文长度 */
|
|
212
|
+
async getCAPIContextLimit(guid) {
|
|
213
|
+
const raw = await this.redis.get(`ai:capi_context_limit:${guid}`);
|
|
214
|
+
const limit = Number(raw);
|
|
215
|
+
return Number.isFinite(limit) && limit > 0
|
|
216
|
+
? Math.floor(limit)
|
|
217
|
+
: DEFAULT_CAPI_CONTEXT_LIMIT;
|
|
218
|
+
}
|
|
219
|
+
/** 设置 CAPI 自动压缩上下文长度 */
|
|
220
|
+
async setCAPIContextLimit(guid, limit) {
|
|
221
|
+
const normalized = Math.max(1, Math.floor(limit));
|
|
222
|
+
await this.redis.set(`ai:capi_context_limit:${guid}`, String(normalized));
|
|
223
|
+
}
|
|
210
224
|
/** 获取工具开关状态 */
|
|
211
225
|
async getToolSwitch(guid) {
|
|
212
226
|
return (await this.redis.get(`ai:tool:switch:${guid}`)) || "1";
|
|
@@ -239,6 +253,14 @@ class db {
|
|
|
239
253
|
async setToolPromptArgsSwitch(guid, enable) {
|
|
240
254
|
await this.redis.set(`ai:tool_prompt_args:switch:${guid}`, enable ? "1" : "0");
|
|
241
255
|
}
|
|
256
|
+
/** 工具调用前是否发送模型返回的 content */
|
|
257
|
+
async getToolCallContentSwitch(guid) {
|
|
258
|
+
return (await this.redis.get(`ai:tool_call_content:switch:${guid}`)) || "0";
|
|
259
|
+
}
|
|
260
|
+
/** 设置工具调用前 content 发送开关状态 */
|
|
261
|
+
async setToolCallContentSwitch(guid, enable) {
|
|
262
|
+
await this.redis.set(`ai:tool_call_content:switch:${guid}`, enable ? "1" : "0");
|
|
263
|
+
}
|
|
242
264
|
/** --------------------------------------------------------- */
|
|
243
265
|
/** 获取复杂输出开关状态 */
|
|
244
266
|
async getComplexOutput(guid) {
|
|
@@ -1189,4 +1211,4 @@ class db {
|
|
|
1189
1211
|
}
|
|
1190
1212
|
const redisClient = new db(redis);
|
|
1191
1213
|
|
|
1192
|
-
export { redisClient as default };
|
|
1214
|
+
export { DEFAULT_CAPI_CONTEXT_LIMIT, 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.39";
|
|
5
5
|
var by = "AlemonJS";
|
|
6
6
|
var list = [
|
|
7
7
|
{
|
|
@@ -136,6 +136,14 @@ var list = [
|
|
|
136
136
|
{
|
|
137
137
|
cmd: "/清空对话",
|
|
138
138
|
desc: "清空当前页面的对话记录。"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
cmd: "/设置AI上下文长度 [256k|1m]",
|
|
142
|
+
desc: "设置 CAPI 模式自动压缩上下文的长度,默认 256k。"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
cmd: "/撤回",
|
|
146
|
+
desc: "引用一条消息发送撤回指令,自动撤回被引用的消息。"
|
|
139
147
|
}
|
|
140
148
|
]
|
|
141
149
|
}
|
|
@@ -200,6 +208,10 @@ var list = [
|
|
|
200
208
|
{
|
|
201
209
|
cmd: "/[开启|关闭]工具提示详情",
|
|
202
210
|
desc: "开启或关闭工具调用提示详情功能。开启后工具调用的提示消息会包含更多的详情信息。"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
cmd: "/[开启|关闭]工具调用内容",
|
|
214
|
+
desc: "开启或关闭工具调用前模型返回内容的发送。开启后会发送工具调用响应中 content 的内容。"
|
|
203
215
|
}
|
|
204
216
|
]
|
|
205
217
|
}
|
|
@@ -9,6 +9,8 @@ const cmds = [
|
|
|
9
9
|
{ cmd: "/切换模型 <模型名称>", desc: "切换当前使用的AI模型" },
|
|
10
10
|
{ cmd: "/<开启|关闭>仅艾特触发", desc: "开启或关闭仅艾特触发功能" },
|
|
11
11
|
{ cmd: "/<开启|关闭>工具", desc: "开启或关闭AI工具" },
|
|
12
|
+
{ cmd: "/<开启|关闭>工具调用内容", desc: "开启或关闭工具调用前内容发送" },
|
|
13
|
+
{ cmd: "/设置AI上下文长度 256k", desc: "设置CAPI自动压缩上下文长度" },
|
|
12
14
|
{ cmd: "/<开启|关闭>复杂输出", desc: "开启或关闭复杂输出" },
|
|
13
15
|
{ cmd: "/<开启|关闭>好感度", desc: "开启或关闭好感度系统" },
|
|
14
16
|
{ cmd: "/清空对话", desc: "清空当前对话历史" },
|
|
@@ -76,7 +78,11 @@ function App(data) {
|
|
|
76
78
|
React.createElement(StatusItem, { label: "\u5DE5\u5177\u603B\u5F00\u5173", value: data.tools }),
|
|
77
79
|
React.createElement(StatusItem, { label: "\u5DE5\u5177\u63D0\u793A", value: data.toolPromptSwitch }),
|
|
78
80
|
React.createElement(StatusItem, { label: "\u5DE5\u5177\u63D0\u793A\u64A4\u56DE", value: data.toolPromptRevokeSwitch }),
|
|
79
|
-
React.createElement(StatusItem, { label: "\u5DE5\u5177\u63D0\u793A\u4F20\u53C2", value: data.toolPromptArgsSwitch })
|
|
81
|
+
React.createElement(StatusItem, { label: "\u5DE5\u5177\u63D0\u793A\u4F20\u53C2", value: data.toolPromptArgsSwitch }),
|
|
82
|
+
React.createElement(StatusItem, { label: "\u5DE5\u5177\u8C03\u7528\u5185\u5BB9", value: data.toolCallContentSwitch }),
|
|
83
|
+
React.createElement("div", { className: "flex items-center justify-between py-2 px-2 rounded-lg bg-white/5" },
|
|
84
|
+
React.createElement("span", { className: " text-white/70" }, "CAPI\u4E0A\u4E0B\u6587\u957F\u5EA6"),
|
|
85
|
+
React.createElement("span", { className: "px-3 py-0.5 rounded-full text-xs font-mono font-medium border bg-cyan-500/20 text-cyan-300 border-cyan-500/30" }, data.capiContextLimit))),
|
|
80
86
|
React.createElement("div", { className: "pt-2 border-t border-white/10" },
|
|
81
87
|
React.createElement("div", { className: "flex items-center justify-between py-2" },
|
|
82
88
|
React.createElement("span", { className: " text-white/60" }, "\u5F53\u524D\u4F7F\u7528\u6A21\u578B"),
|
package/lib/middleware/mw.js
CHANGED
|
@@ -177,6 +177,7 @@ var mw = onMiddleware(selects, async (event, next) => {
|
|
|
177
177
|
// 处理回复消息
|
|
178
178
|
const replyData = event.value.message.find((item) => item.type === "reply");
|
|
179
179
|
if (replyData) {
|
|
180
|
+
event["replyMessageId"] = String(replyData.data.id);
|
|
180
181
|
const msg = await onebotClient.getMsg({
|
|
181
182
|
message_id: Number(replyData.data.id),
|
|
182
183
|
});
|
|
@@ -233,6 +234,7 @@ var mw = onMiddleware(selects, async (event, next) => {
|
|
|
233
234
|
})
|
|
234
235
|
.join("");
|
|
235
236
|
if (event.replyId && event.replyId !== "-1") {
|
|
237
|
+
event["replyMessageId"] = String(event.replyId);
|
|
236
238
|
const replyMsg = await client.getMessage(event.replyId);
|
|
237
239
|
console.log("获取回复", replyMsg);
|
|
238
240
|
if (replyMsg) {
|
|
@@ -276,6 +278,7 @@ var mw = onMiddleware(selects, async (event, next) => {
|
|
|
276
278
|
? [CDN_URL + event.value.fileMeta.url]
|
|
277
279
|
: [];
|
|
278
280
|
if (event.value.replyToId) {
|
|
281
|
+
event["replyMessageId"] = String(event.value.replyToId);
|
|
279
282
|
const channelMessages = await client.request({
|
|
280
283
|
method: "GET",
|
|
281
284
|
url: `/channels/${event.ChannelId}/messages/${event.value.replyToId}`,
|
|
@@ -54,44 +54,60 @@ const archiveCurrentConversation = async (guid, reason, aiConfig) => {
|
|
|
54
54
|
reason,
|
|
55
55
|
});
|
|
56
56
|
};
|
|
57
|
-
const
|
|
57
|
+
const statusNumberFormatter = new Intl.NumberFormat("zh-CN");
|
|
58
|
+
const formatNumberValue = (value) => {
|
|
58
59
|
return typeof value === "number" && Number.isFinite(value)
|
|
59
|
-
?
|
|
60
|
+
? statusNumberFormatter.format(value)
|
|
60
61
|
: "未知";
|
|
61
62
|
};
|
|
62
|
-
const
|
|
63
|
+
const formatTokenValue = (value) => {
|
|
64
|
+
return formatNumberValue(value);
|
|
65
|
+
};
|
|
66
|
+
const formatContextLimit = (tokens) => {
|
|
67
|
+
if (tokens >= 1000000 && tokens % 1000000 === 0) {
|
|
68
|
+
return `${tokens / 1000000}m`;
|
|
69
|
+
}
|
|
70
|
+
if (tokens >= 1000 && tokens % 1000 === 0) {
|
|
71
|
+
return `${tokens / 1000}k`;
|
|
72
|
+
}
|
|
73
|
+
return `${tokens}`;
|
|
74
|
+
};
|
|
75
|
+
const formatAIContextStatus = (stats, capiContextLimit) => {
|
|
63
76
|
if (!stats) {
|
|
64
|
-
return
|
|
77
|
+
return `当前会话还没有 CAPI 上下文统计。\nCAPI上下文长度: ${formatContextLimit(capiContextLimit)}`;
|
|
65
78
|
}
|
|
66
79
|
const updatedAt = new Date(stats.updatedAt).toLocaleString("zh-CN", {
|
|
67
80
|
hour12: false,
|
|
68
81
|
});
|
|
69
82
|
const usageNote = stats.usageAvailable
|
|
70
83
|
? ""
|
|
71
|
-
: "
|
|
84
|
+
: "提示: 最近一次请求未返回 usage, 实际 token 统计可能不完整。";
|
|
85
|
+
const divider = "················";
|
|
72
86
|
return [
|
|
73
87
|
"当前AI状态:",
|
|
88
|
+
divider,
|
|
74
89
|
`模式: ${stats.mode.toUpperCase()}`,
|
|
75
90
|
`模型: ${stats.model || "未知"}`,
|
|
76
91
|
`更新时间: ${updatedAt}`,
|
|
77
|
-
`请求次数: ${stats.requestCount}`,
|
|
78
|
-
|
|
92
|
+
`请求次数: ${formatNumberValue(stats.requestCount)}`,
|
|
93
|
+
divider,
|
|
79
94
|
"最近一次上下文:",
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
`CAPI上下文长度: ${formatContextLimit(capiContextLimit)}`,
|
|
96
|
+
`消息数: ${formatNumberValue(stats.contextMessageCount)}`,
|
|
97
|
+
`上下文字符数: ${formatNumberValue(stats.contextCharLength)}`,
|
|
98
|
+
`上下文估算token: ${formatTokenValue(stats.estimatedContextTokens)}`,
|
|
83
99
|
`输入token: ${formatTokenValue(stats.promptTokens)}`,
|
|
84
100
|
`输出token: ${formatTokenValue(stats.completionTokens)}`,
|
|
85
101
|
`总token: ${formatTokenValue(stats.totalTokens)}`,
|
|
86
102
|
`缓存命中token: ${formatTokenValue(stats.cachedTokens)}`,
|
|
87
103
|
`缓存未命中token: ${formatTokenValue(stats.cacheMissTokens)}`,
|
|
88
|
-
|
|
104
|
+
divider,
|
|
89
105
|
"当前会话累计:",
|
|
90
|
-
`输入token: ${stats.totalPromptTokens}`,
|
|
91
|
-
`输出token: ${stats.totalCompletionTokens}`,
|
|
92
|
-
`总token: ${stats.totalTokensUsed}`,
|
|
93
|
-
`缓存命中token: ${stats.totalCachedTokens}`,
|
|
94
|
-
`缓存未命中token: ${stats.totalCacheMissTokens}`,
|
|
106
|
+
`输入token: ${formatTokenValue(stats.totalPromptTokens)}`,
|
|
107
|
+
`输出token: ${formatTokenValue(stats.totalCompletionTokens)}`,
|
|
108
|
+
`总token: ${formatTokenValue(stats.totalTokensUsed)}`,
|
|
109
|
+
`缓存命中token: ${formatTokenValue(stats.totalCachedTokens)}`,
|
|
110
|
+
`缓存未命中token: ${formatTokenValue(stats.totalCacheMissTokens)}`,
|
|
95
111
|
usageNote,
|
|
96
112
|
]
|
|
97
113
|
.filter((line) => line !== "")
|
|
@@ -104,7 +120,8 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
104
120
|
// 查看AI状态
|
|
105
121
|
if (/^(\/|#)ai状态$/i.test(e.msg)) {
|
|
106
122
|
const stats = await redisClient.getAIContextStats(e.guid);
|
|
107
|
-
|
|
123
|
+
const capiContextLimit = await redisClient.getCAPIContextLimit(e.guid);
|
|
124
|
+
message.send(format(Text(formatAIContextStatus(stats, capiContextLimit))));
|
|
108
125
|
}
|
|
109
126
|
// 查看AI配置
|
|
110
127
|
if (/^(\/|#)(ai配置|当前提示词|查看提示词)$/i.test(e.msg)) {
|
|
@@ -121,6 +138,8 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
121
138
|
const toolPromptSwitch = await redisClient.getToolPromptSwitch(e.guid); // 工具提示开关
|
|
122
139
|
const toolPromptRevokeSwitch = await redisClient.getToolPromptRevokeSwitch(e.guid); // 工具提示撤回开关
|
|
123
140
|
const toolPromptArgsSwitch = await redisClient.getToolPromptArgsSwitch(e.guid); // 工具提示传参开关
|
|
141
|
+
const toolCallContentSwitch = await redisClient.getToolCallContentSwitch(e.guid); // 工具调用内容发送开关
|
|
142
|
+
const capiContextLimit = await redisClient.getCAPIContextLimit(e.guid);
|
|
124
143
|
// 发送消息
|
|
125
144
|
try {
|
|
126
145
|
const scale = await redisClient.getRenderPrecision(e.guid);
|
|
@@ -141,6 +160,8 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
141
160
|
toolPromptSwitch: toolPromptSwitch == "1",
|
|
142
161
|
toolPromptRevokeSwitch: toolPromptRevokeSwitch == "1",
|
|
143
162
|
toolPromptArgsSwitch: toolPromptArgsSwitch == "1",
|
|
163
|
+
toolCallContentSwitch: toolCallContentSwitch == "1",
|
|
164
|
+
capiContextLimit: formatContextLimit(capiContextLimit),
|
|
144
165
|
}, {
|
|
145
166
|
playwright: {
|
|
146
167
|
context: { deviceScaleFactor: scale },
|
|
@@ -150,11 +171,11 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
150
171
|
message.send(format(Image(img)));
|
|
151
172
|
}
|
|
152
173
|
else {
|
|
153
|
-
message.send(format(Text(`当前AI配置:\n`), Text(`总开关: ${config.model != "" ? "开启" : "关闭"}\n`), Text(`工具: ${toolsIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`复杂输出: ${complexResponse == "1" ? "开启" : "关闭"}\n`), Text(`好感度开关: ${affectionIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`TTS回复开关: ${isOpenTTSReply == "1" ? "开启" : "关闭"}\n`), Text(`深度思考: ${deepThinkingIsOpen == "1" ? "默认" : "关闭"}\n`), Text(`仅艾特触发: ${atTriggerSwitch == "1" ? "开启" : "关闭"}\n`), Text(`模型: ${config.model || "未设置"}\n`), Text(`提示词: ${config.systemPrompt || "未设置"}\n`), Text(`当前AI: ${currentAI || "未设置"}\n`), Text(`AI数量: ${aiList.length ? aiList.length : "暂无AI配置"}`)));
|
|
174
|
+
message.send(format(Text(`当前AI配置:\n`), Text(`总开关: ${config.model != "" ? "开启" : "关闭"}\n`), Text(`工具: ${toolsIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`工具调用内容: ${toolCallContentSwitch == "1" ? "开启" : "关闭"}\n`), Text(`CAPI上下文长度: ${formatContextLimit(capiContextLimit)}\n`), Text(`复杂输出: ${complexResponse == "1" ? "开启" : "关闭"}\n`), Text(`好感度开关: ${affectionIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`TTS回复开关: ${isOpenTTSReply == "1" ? "开启" : "关闭"}\n`), Text(`深度思考: ${deepThinkingIsOpen == "1" ? "默认" : "关闭"}\n`), Text(`仅艾特触发: ${atTriggerSwitch == "1" ? "开启" : "关闭"}\n`), Text(`模型: ${config.model || "未设置"}\n`), Text(`提示词: ${config.systemPrompt || "未设置"}\n`), Text(`当前AI: ${currentAI || "未设置"}\n`), Text(`AI数量: ${aiList.length ? aiList.length : "暂无AI配置"}`)));
|
|
154
175
|
}
|
|
155
176
|
}
|
|
156
177
|
catch (error) {
|
|
157
|
-
message.send(format(Text(`当前AI配置:\n`), Text(`总开关: ${config.model != "" ? "开启" : "关闭"}\n`), Text(`工具: ${toolsIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`复杂输出: ${complexResponse == "1" ? "开启" : "关闭"}\n`), Text(`好感度开关: ${affectionIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`TTS回复开关: ${isOpenTTSReply == "1" ? "开启" : "关闭"}\n`), Text(`深度思考: ${deepThinkingIsOpen == "1" ? "默认" : "关闭"}\n`), Text(`仅艾特触发: ${atTriggerSwitch == "1" ? "开启" : "关闭"}\n`), Text(`模型: ${config.model || "未设置"}\n`), Text(`提示词: ${config.systemPrompt || "未设置"}\n`), Text(`当前AI: ${currentAI || "未设置"}\n`), Text(`AI数量: ${aiList.length ? aiList.length : "暂无AI配置"}`)));
|
|
178
|
+
message.send(format(Text(`当前AI配置:\n`), Text(`总开关: ${config.model != "" ? "开启" : "关闭"}\n`), Text(`工具: ${toolsIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`工具调用内容: ${toolCallContentSwitch == "1" ? "开启" : "关闭"}\n`), Text(`CAPI上下文长度: ${formatContextLimit(capiContextLimit)}\n`), Text(`复杂输出: ${complexResponse == "1" ? "开启" : "关闭"}\n`), Text(`好感度开关: ${affectionIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`TTS回复开关: ${isOpenTTSReply == "1" ? "开启" : "关闭"}\n`), Text(`深度思考: ${deepThinkingIsOpen == "1" ? "默认" : "关闭"}\n`), Text(`仅艾特触发: ${atTriggerSwitch == "1" ? "开启" : "关闭"}\n`), Text(`模型: ${config.model || "未设置"}\n`), Text(`提示词: ${config.systemPrompt || "未设置"}\n`), Text(`当前AI: ${currentAI || "未设置"}\n`), Text(`AI数量: ${aiList.length ? aiList.length : "暂无AI配置"}`)));
|
|
158
179
|
}
|
|
159
180
|
}
|
|
160
181
|
// 查看AI列表
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useMessage, Text } from 'alemonjs';
|
|
2
|
+
|
|
3
|
+
const selects = onSelects(["message.create", "private.message.create"]);
|
|
4
|
+
const normalizeMessageId = (value) => {
|
|
5
|
+
if (value === undefined || value === null)
|
|
6
|
+
return undefined;
|
|
7
|
+
const text = String(value).trim();
|
|
8
|
+
return text === "" || text === "-1" ? undefined : text;
|
|
9
|
+
};
|
|
10
|
+
const getReplyMessageId = (e) => {
|
|
11
|
+
const replySegment = Array.isArray(e.value?.message)
|
|
12
|
+
? e.value.message.find((item) => item?.type === "reply")
|
|
13
|
+
: undefined;
|
|
14
|
+
return (normalizeMessageId(e.replyMessageId) ||
|
|
15
|
+
normalizeMessageId(replySegment?.data?.id) ||
|
|
16
|
+
normalizeMessageId(e.replyId) ||
|
|
17
|
+
normalizeMessageId(e.value?.replyToId) ||
|
|
18
|
+
normalizeMessageId(e.reply?.message_id) ||
|
|
19
|
+
normalizeMessageId(e.reply?.id));
|
|
20
|
+
};
|
|
21
|
+
var res = onResponse(selects, async (e) => {
|
|
22
|
+
const [message] = useMessage(e);
|
|
23
|
+
const targetMessageId = getReplyMessageId(e);
|
|
24
|
+
if (!targetMessageId) {
|
|
25
|
+
await message.send(format(Text("请引用一条消息后再发送 /撤回")));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const result = await message.delete({ messageId: targetMessageId });
|
|
29
|
+
if (result.code !== 2000) {
|
|
30
|
+
await message.send(format(Text("撤回失败,可能是平台不支持或机器人没有权限")));
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export { res as default, selects };
|
|
@@ -8,6 +8,21 @@ const value = getConfigValue().aiChat || {};
|
|
|
8
8
|
const selects = onSelects(["message.create", "private.message.create"]);
|
|
9
9
|
// inline regex checks are used in-place; keep `test` as shorthand
|
|
10
10
|
const test = /(\/|#)测试$/i;
|
|
11
|
+
const capiContextLimitCommand = /^(#|\/)设置(AI)?上下文(长度)? ?([0-9]+)(k|m)$/i;
|
|
12
|
+
const formatContextLimit = (tokens) => {
|
|
13
|
+
if (tokens >= 1000000 && tokens % 1000000 === 0) {
|
|
14
|
+
return `${tokens / 1000000}m`;
|
|
15
|
+
}
|
|
16
|
+
if (tokens >= 1000 && tokens % 1000 === 0) {
|
|
17
|
+
return `${tokens / 1000}k`;
|
|
18
|
+
}
|
|
19
|
+
return `${tokens}`;
|
|
20
|
+
};
|
|
21
|
+
const parseContextLimit = (value, unit) => {
|
|
22
|
+
const count = Number(value);
|
|
23
|
+
const multiplier = unit.toLowerCase() === "m" ? 1000000 : 1000;
|
|
24
|
+
return Math.floor(count * multiplier);
|
|
25
|
+
};
|
|
11
26
|
var res = onResponse(selects, async (e, next) => {
|
|
12
27
|
// 创建
|
|
13
28
|
const [message] = useMessage(e);
|
|
@@ -281,6 +296,23 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
281
296
|
message.send(format(Text(`已将当前 AI 的模型修改为 ${modelName.trim()} !`)));
|
|
282
297
|
return;
|
|
283
298
|
}
|
|
299
|
+
// 设置 CAPI 自动压缩上下文长度
|
|
300
|
+
if (capiContextLimitCommand.test(e.msg)) {
|
|
301
|
+
const match = e.msg.match(capiContextLimitCommand);
|
|
302
|
+
if (!match) {
|
|
303
|
+
message.send(format(Text("格式错误,请按照 格式:/设置AI上下文长度 256k 进行设置")));
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const [, , , , value, unit] = match;
|
|
307
|
+
const limit = parseContextLimit(value, unit);
|
|
308
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
309
|
+
message.send(format(Text("上下文长度必须大于 0,例如:/设置AI上下文长度 256k")));
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
await redisClient.setCAPIContextLimit(e.guid, limit);
|
|
313
|
+
message.send(format(Text(`已设置 CAPI 上下文长度为 ${formatContextLimit(limit)},接近 90% 时会自动压缩上下文。`)));
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
284
316
|
// 设置复杂输出开关
|
|
285
317
|
if (/(\/|#)(关闭|开启)复杂(输出|回复)$/i.test(e.msg)) {
|
|
286
318
|
const match = e.msg.match(/(\/|#)(关闭|开启)复杂(输出|回复)$/i);
|
|
@@ -493,6 +525,22 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
493
525
|
message.send(format(Text(`已${enable ? "开启" : "关闭"}工具提示详情功能 !`)));
|
|
494
526
|
return;
|
|
495
527
|
}
|
|
528
|
+
// 控制工具调用前 content 发送开关
|
|
529
|
+
if (/(\/|#)(开启|关闭)工具调用内容$/i.test(e.msg)) {
|
|
530
|
+
if (!e.IsMaster) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const match = e.msg.match(/(\/|#)(开启|关闭)工具调用内容$/i);
|
|
534
|
+
if (!match) {
|
|
535
|
+
message.send(format(Text("格式错误,请按照 格式:/开启工具调用内容 或 /关闭工具调用内容 进行设置")));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const [, , action] = match;
|
|
539
|
+
const enable = action === "开启";
|
|
540
|
+
await redisClient.setToolCallContentSwitch(e.guid, enable);
|
|
541
|
+
message.send(format(Text(`已${enable ? "开启" : "关闭"}工具调用内容发送功能 !`)));
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
496
544
|
// 控制工具开关
|
|
497
545
|
if (/(\/|#)(开启|关闭)工具(.*)$/i.test(e.msg)) {
|
|
498
546
|
if (!e.IsMaster) {
|
|
@@ -3,13 +3,16 @@ import { log } from 'console';
|
|
|
3
3
|
import OpenAi from 'openai';
|
|
4
4
|
import { TTSClient } from '../../api/tts.js';
|
|
5
5
|
import { availableTools } from '../../api/aitools.js';
|
|
6
|
-
import redisClient from '../../config.js';
|
|
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
9
|
import { parseAIReply } from './tools.js';
|
|
10
10
|
|
|
11
11
|
const selects = onSelects(["message.create", "private.message.create"]);
|
|
12
12
|
let currentModel = "派蒙-默认"; // 默认音色
|
|
13
|
+
const CAPI_CONTEXT_COMPRESSION_RATIO = 0.9;
|
|
14
|
+
const CAPI_CONTEXT_RETAIN_RATIO = 0.35;
|
|
15
|
+
const AUTO_SUMMARY_DETAIL_MAX_LENGTH = 2000;
|
|
13
16
|
const sendAIReply = async (reply, cfg, e, message) => {
|
|
14
17
|
const aireply = await parseAIReply(reply, cfg, e, message);
|
|
15
18
|
console.log("aireply", aireply);
|
|
@@ -55,6 +58,69 @@ const getTextLength = (value) => {
|
|
|
55
58
|
}
|
|
56
59
|
return 0;
|
|
57
60
|
};
|
|
61
|
+
const estimateContextTokens = (value) => {
|
|
62
|
+
return Math.ceil(getTextLength(value) / 4);
|
|
63
|
+
};
|
|
64
|
+
const trimText = (text, maxLength) => {
|
|
65
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
66
|
+
if (normalized.length <= maxLength) {
|
|
67
|
+
return normalized;
|
|
68
|
+
}
|
|
69
|
+
return `${normalized.slice(0, maxLength)}...`;
|
|
70
|
+
};
|
|
71
|
+
const takeTailMessagesWithinTokenBudget = (history, tokenBudget) => {
|
|
72
|
+
const kept = [];
|
|
73
|
+
for (let index = history.length - 1; index >= 0; index -= 1) {
|
|
74
|
+
kept.unshift(history[index]);
|
|
75
|
+
if (estimateContextTokens(kept) > tokenBudget) {
|
|
76
|
+
kept.shift();
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return kept;
|
|
81
|
+
};
|
|
82
|
+
const buildAutoSummaryMessage = (record) => {
|
|
83
|
+
const summary = record?.summary || "较早对话已自动归档";
|
|
84
|
+
const detail = trimText(record?.detail || record?.transcript || "无详细摘要", AUTO_SUMMARY_DETAIL_MAX_LENGTH);
|
|
85
|
+
return {
|
|
86
|
+
role: "system",
|
|
87
|
+
content: [
|
|
88
|
+
"【自动压缩上下文】",
|
|
89
|
+
`较早对话已归档为 ${record?.id || "未知ID"},当前上下文只保留摘要和最近消息。`,
|
|
90
|
+
`摘要: ${summary}`,
|
|
91
|
+
`详细信息: ${detail}`,
|
|
92
|
+
"如需更完整内容,可按归档ID或关键词调用 MemoryOperation 检索历史归档。",
|
|
93
|
+
].join("\n"),
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
const prepareCapiMessagesForRequest = async (guid, systemMessage, currentMessages) => {
|
|
97
|
+
const limit = await redisClient.getCAPIContextLimit(guid);
|
|
98
|
+
const threshold = Math.floor(limit * CAPI_CONTEXT_COMPRESSION_RATIO);
|
|
99
|
+
const estimatedTokens = estimateContextTokens(currentMessages);
|
|
100
|
+
if (estimatedTokens < threshold) {
|
|
101
|
+
return currentMessages;
|
|
102
|
+
}
|
|
103
|
+
const history = await redisClient.getAIChatHistory(guid);
|
|
104
|
+
if (history.length === 0) {
|
|
105
|
+
return currentMessages;
|
|
106
|
+
}
|
|
107
|
+
const archived = await redisClient.archiveAIChatHistory(guid, {
|
|
108
|
+
reason: "auto_context_compress",
|
|
109
|
+
});
|
|
110
|
+
if (!archived) {
|
|
111
|
+
return currentMessages;
|
|
112
|
+
}
|
|
113
|
+
const minRetainBudget = Math.min(Math.floor(DEFAULT_CAPI_CONTEXT_LIMIT * 0.05), Math.floor(limit * 0.5));
|
|
114
|
+
const retainBudget = Math.max(Math.floor(limit * CAPI_CONTEXT_RETAIN_RATIO), minRetainBudget);
|
|
115
|
+
const keptHistory = takeTailMessagesWithinTokenBudget(history, retainBudget);
|
|
116
|
+
const compressedHistory = [buildAutoSummaryMessage(archived), ...keptHistory];
|
|
117
|
+
await redisClient.setAIChatHistory(guid, compressedHistory);
|
|
118
|
+
const rebuiltMessages = [systemMessage, ...compressedHistory];
|
|
119
|
+
const pendingMessages = currentMessages.slice(Math.max(1 + history.length, 1));
|
|
120
|
+
rebuiltMessages.push(...pendingMessages);
|
|
121
|
+
log(`CAPI上下文接近上限,已自动压缩:${estimatedTokens}/${limit} tokens,归档ID:${archived.id}`);
|
|
122
|
+
return rebuiltMessages;
|
|
123
|
+
};
|
|
58
124
|
const getNumber = (value) => {
|
|
59
125
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
60
126
|
};
|
|
@@ -84,7 +150,7 @@ const recordCapiContextStats = async (guid, model, messages, completion) => {
|
|
|
84
150
|
model,
|
|
85
151
|
contextMessageCount: messages.length,
|
|
86
152
|
contextCharLength,
|
|
87
|
-
estimatedContextTokens:
|
|
153
|
+
estimatedContextTokens: estimateContextTokens(messages),
|
|
88
154
|
promptTokens,
|
|
89
155
|
completionTokens,
|
|
90
156
|
totalTokens: getNumber(usage?.total_tokens),
|
|
@@ -110,14 +176,16 @@ const CApiReply = async (e) => {
|
|
|
110
176
|
apiKey: cfg.config.key,
|
|
111
177
|
timeout: 300000,
|
|
112
178
|
});
|
|
179
|
+
const systemMessage = {
|
|
180
|
+
role: "system",
|
|
181
|
+
content: cfg.systemPrompt + `\n性格约束:${cfg.config.systemPrompt}`,
|
|
182
|
+
};
|
|
113
183
|
let messages = [
|
|
114
|
-
|
|
115
|
-
role: "system",
|
|
116
|
-
content: cfg.systemPrompt + `\n性格约束:${cfg.config.systemPrompt}`,
|
|
117
|
-
},
|
|
184
|
+
systemMessage,
|
|
118
185
|
...cfg.historyMessages,
|
|
119
186
|
usermessage,
|
|
120
187
|
];
|
|
188
|
+
messages = await prepareCapiMessagesForRequest(e.guid, systemMessage, messages);
|
|
121
189
|
const createParams = {
|
|
122
190
|
model: cfg.config.model,
|
|
123
191
|
messages: messages,
|
|
@@ -165,7 +233,9 @@ const CApiReply = async (e) => {
|
|
|
165
233
|
return;
|
|
166
234
|
}
|
|
167
235
|
await redisClient.addAIChatHistory(e.guid, usermessage);
|
|
168
|
-
if (
|
|
236
|
+
if (cfg.toolConfig.toolCallContentSwitch === "1" &&
|
|
237
|
+
res.choices[0].message?.tool_calls &&
|
|
238
|
+
fullContent.trim() !== "") {
|
|
169
239
|
await sendAIReply(fullContent, cfg, e, message);
|
|
170
240
|
}
|
|
171
241
|
// 检查是否有工具调用需要处理
|
|
@@ -180,6 +250,7 @@ const CApiReply = async (e) => {
|
|
|
180
250
|
if (pendingGuidanceMessages.length === 0) {
|
|
181
251
|
break;
|
|
182
252
|
}
|
|
253
|
+
messages = await prepareCapiMessagesForRequest(e.guid, systemMessage, messages);
|
|
183
254
|
log("工具调用完成后收到补充引导,继续请求模型:", pendingGuidanceMessages.length);
|
|
184
255
|
const params = {
|
|
185
256
|
model: cfg.config.model,
|
|
@@ -284,6 +355,7 @@ const CApiReply = async (e) => {
|
|
|
284
355
|
if (guidanceMessages.length > 0) {
|
|
285
356
|
log("工具调用期间收到补充引导:", guidanceMessages.length);
|
|
286
357
|
}
|
|
358
|
+
messages = await prepareCapiMessagesForRequest(e.guid, systemMessage, messages);
|
|
287
359
|
const params = {
|
|
288
360
|
model: cfg.config.model,
|
|
289
361
|
messages: messages,
|
|
@@ -60,6 +60,8 @@ const getChatConfig = async (e) => {
|
|
|
60
60
|
const atTriggerSwitch = await redisClient.getAtTriggerSwitch(e.guid);
|
|
61
61
|
/** 历史消息 */
|
|
62
62
|
const historyMessages = await redisClient.getAIChatHistory(e.guid);
|
|
63
|
+
/** CAPI 自动压缩上下文长度 */
|
|
64
|
+
const capiContextLimit = await redisClient.getCAPIContextLimit(e.guid);
|
|
63
65
|
/** 好感度信息 */
|
|
64
66
|
const affections = await redisClient.getAffectionLevelAll(e.guid);
|
|
65
67
|
/** 工具相关配置 */
|
|
@@ -75,6 +77,8 @@ const getChatConfig = async (e) => {
|
|
|
75
77
|
toolPromptRevokeSwitch: await redisClient.getToolPromptRevokeSwitch(e.guid),
|
|
76
78
|
/** 工具提示传参开关 */
|
|
77
79
|
toolPromptArgsSwitch: await redisClient.getToolPromptArgsSwitch(e.guid),
|
|
80
|
+
/** 工具调用前 content 发送开关 */
|
|
81
|
+
toolCallContentSwitch: await redisClient.getToolCallContentSwitch(e.guid),
|
|
78
82
|
};
|
|
79
83
|
/** 技能列表 */
|
|
80
84
|
const skills = loadSkills();
|
|
@@ -83,33 +87,34 @@ const getChatConfig = async (e) => {
|
|
|
83
87
|
/** 机器人昵称 */
|
|
84
88
|
const botName = e.bot.nickname || "小咸鱼";
|
|
85
89
|
const memoryPrompt = relevantMemoryContext
|
|
86
|
-
? `
|
|
87
|
-
## 系统预检索记忆
|
|
88
|
-
以下内容是系统根据当前话题自动检索到的相关长期记忆和历史归档, 回答时优先参考与当前话题直接相关的部分; 如果与用户本轮最新表述冲突, 以用户本轮消息为准
|
|
89
|
-
${relevantMemoryContext}
|
|
90
|
+
? `
|
|
91
|
+
## 系统预检索记忆
|
|
92
|
+
以下内容是系统根据当前话题自动检索到的相关长期记忆和历史归档, 回答时优先参考与当前话题直接相关的部分; 如果与用户本轮最新表述冲突, 以用户本轮消息为准
|
|
93
|
+
${relevantMemoryContext}
|
|
90
94
|
`
|
|
91
95
|
: "";
|
|
92
96
|
/** 系统提示词 */
|
|
93
|
-
const systemPrompt = `
|
|
94
|
-
# AI助手回复规范
|
|
95
|
-
请严格按照以下规范进行回复, 不要添加任何不必要的内容, 也不要删除或修改规范中的任何内容, 以确保回复能够被正确解析和处理
|
|
96
|
-
|
|
97
|
-
## 技能
|
|
98
|
-
在遇到用户需要执行特定操作时,先获取对应的技能,如果有可用技能,获取该技能,并严格按照技能文档要求的格式调用, 不要添加任何多余的内容, 也不要删除或修改规范中的任何内容, 以确保技能能够被正确解析和处理
|
|
99
|
-
当没有技能可以应对需求时, 就自行挑选合适的工具函数来完成用户的需求
|
|
100
|
-
如果用户提到本地仓库、知识库、参考项目、之前克隆过的项目,或者你准备说"本地没有"、"仓库里没有"、"没找到"之类的话,必须先检查 public/{guid} 内现有内容,优先搜索 knowledge/ 目录;至少先调用一次 AgentSearchFiles、AgentListFiles 或读取相关技能后再下结论
|
|
101
|
-
如果你不知道之前项目所在的 guid,先调用 AgentListWorkspaces 找到 public/ 下已有工作目录,再继续用 AgentListFiles、AgentSearchFiles 或 AgentReadFileLines;不要用 exec 查 public/{guid},因为 exec 的默认工作目录不是那里
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
97
|
+
const systemPrompt = `
|
|
98
|
+
# AI助手回复规范
|
|
99
|
+
请严格按照以下规范进行回复, 不要添加任何不必要的内容, 也不要删除或修改规范中的任何内容, 以确保回复能够被正确解析和处理
|
|
100
|
+
|
|
101
|
+
## 技能
|
|
102
|
+
在遇到用户需要执行特定操作时,先获取对应的技能,如果有可用技能,获取该技能,并严格按照技能文档要求的格式调用, 不要添加任何多余的内容, 也不要删除或修改规范中的任何内容, 以确保技能能够被正确解析和处理
|
|
103
|
+
当没有技能可以应对需求时, 就自行挑选合适的工具函数来完成用户的需求
|
|
104
|
+
如果用户提到本地仓库、知识库、参考项目、之前克隆过的项目,或者你准备说"本地没有"、"仓库里没有"、"没找到"之类的话,必须先检查 public/{guid} 内现有内容,优先搜索 knowledge/ 目录;至少先调用一次 AgentSearchFiles、AgentListFiles 或读取相关技能后再下结论
|
|
105
|
+
如果你不知道之前项目所在的 guid,先调用 AgentListWorkspaces 找到 public/ 下已有工作目录,再继续用 AgentListFiles、AgentSearchFiles 或 AgentReadFileLines;不要用 exec 查 public/{guid},因为 exec 的默认工作目录不是那里
|
|
106
|
+
如果用户需要直接执行服务器运维指令、查看服务状态、管理 Docker/PM2/systemctl、查看日志或部署服务,使用 RunServerCommand;它会自动审核指令安全,不要改用 exec。调用 RunServerCommand 时 reviewGuid 必填,必须传下方环境信息里的“当前群号(reviewGuid)”,不要传 public/{guid} 工作目录ID
|
|
107
|
+
如果用户要查看分支、确认当前分支、查看远端或切换分支,优先使用 AgentGitOperation,不要自己拼 git commit、push、pull、merge、rebase、reset 等命令
|
|
108
|
+
如果用户在继续之前的话题、问你记不记得、提到上次/刚才/之前聊过的内容,先参考“系统预检索记忆”;如果系统预检索内容还不够,再调用 MemoryOperation,优先使用 chatHistory 的 search:关键词 或 guid:id 去补充历史上下文,然后再回答
|
|
109
|
+
|
|
110
|
+
调用执行脚本类型的工具时, 务必确保脚本安全, 不得执行对服务器有伤害的脚本
|
|
111
|
+
---
|
|
112
|
+
技能列表:
|
|
113
|
+
${skills.map((skill) => `- ${skill.name}: ${skill.description}`).join("\n")}
|
|
114
|
+
---
|
|
115
|
+
${memoryPrompt}
|
|
116
|
+
|
|
117
|
+
## 关于回复格式:
|
|
113
118
|
### 用户发言
|
|
114
119
|
- 格式: [私聊]用户昵称(用户id)(发送时间):消息内容
|
|
115
120
|
当场景为私聊时, 用户昵称前方会出现[私聊]标识
|
|
@@ -158,15 +163,15 @@ const getChatConfig = async (e) => {
|
|
|
158
163
|
#### 基础状态信息:
|
|
159
164
|
这里的信息会实时变化,根据需要进行获取使用
|
|
160
165
|
当前群时间:${getGroupTimeString()}
|
|
161
|
-
|
|
166
|
+
当前群号(reviewGuid):${e.guid}
|
|
162
167
|
当前群名称:${e.GroupName || "无"}
|
|
163
168
|
当前聊天平台:${e.Platform}
|
|
164
169
|
当前框架:alemonjs-aichat
|
|
165
170
|
${e.ClientError ? `当前错误信息:${e.ClientError}` : ""}
|
|
166
171
|
`;
|
|
167
|
-
const RapiSystemPrompt = `
|
|
168
|
-
请严格按照以下规范进行回复, 以确保回复能够被正确解析和处理
|
|
169
|
-
1. 文本消息:
|
|
172
|
+
const RapiSystemPrompt = `
|
|
173
|
+
请严格按照以下规范进行回复, 以确保回复能够被正确解析和处理
|
|
174
|
+
1. 文本消息:
|
|
170
175
|
${botName}::text<<<EOF
|
|
171
176
|
文本内容
|
|
172
177
|
EOF
|
|
@@ -185,14 +190,14 @@ ${botName}::audio voice=音色 内容
|
|
|
185
190
|
=====================
|
|
186
191
|
|
|
187
192
|
- 不允许使用任何其他格式
|
|
188
|
-
- 不允许输出解释说明
|
|
189
|
-
- 不允许夹杂 markdown
|
|
190
|
-
- tool调用前可以先输出一行说明
|
|
191
|
-
- 每一条输出必须独立一行
|
|
192
|
-
- 允许多段输出叠加, 例如:
|
|
193
|
-
${botName}::text 这是第一条消息
|
|
194
|
-
${botName}::text 这是第二条消息
|
|
195
|
-
${memoryPrompt ? `\n【系统预检索记忆】\n${relevantMemoryContext}\n` : ""}
|
|
193
|
+
- 不允许输出解释说明
|
|
194
|
+
- 不允许夹杂 markdown
|
|
195
|
+
- tool调用前可以先输出一行说明
|
|
196
|
+
- 每一条输出必须独立一行
|
|
197
|
+
- 允许多段输出叠加, 例如:
|
|
198
|
+
${botName}::text 这是第一条消息
|
|
199
|
+
${botName}::text 这是第二条消息
|
|
200
|
+
${memoryPrompt ? `\n【系统预检索记忆】\n${relevantMemoryContext}\n` : ""}
|
|
196
201
|
`;
|
|
197
202
|
return {
|
|
198
203
|
/** AI配置 */
|
|
@@ -220,6 +225,8 @@ ${memoryPrompt ? `\n【系统预检索记忆】\n${relevantMemoryContext}\n` : "
|
|
|
220
225
|
skills,
|
|
221
226
|
/** 历史消息 */
|
|
222
227
|
historyMessages,
|
|
228
|
+
/** CAPI 自动压缩上下文长度 */
|
|
229
|
+
capiContextLimit,
|
|
223
230
|
/** 好感度信息 */
|
|
224
231
|
affections,
|
|
225
232
|
/** 工具配置 */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { format, Audio,
|
|
1
|
+
import { format, Audio, ImageURL, ImageFile, Text, 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';
|