alemonjs-aichat 1.0.18 → 1.0.19
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/config.js +7 -0
- package/lib/middleware/mw.js +71 -24
- package/lib/response/zreply/res.js +217 -205
- package/package.json +1 -1
- package/lib/api.js +0 -529
- package/lib/assets/main.css-DlHMK5k-.css +0 -1
package/lib/config.js
CHANGED
|
@@ -286,6 +286,13 @@ class db {
|
|
|
286
286
|
async setProactiveSwitch(guid, enable) {
|
|
287
287
|
await this.redis.set(`ai:proactive:switch:${guid}`, enable ? "1" : "0");
|
|
288
288
|
}
|
|
289
|
+
/** 深度思考 */
|
|
290
|
+
async getDeepThoughtSwitch(guid) {
|
|
291
|
+
return (await this.redis.get(`ai:deep_thought:switch:${guid}`)) || "0";
|
|
292
|
+
}
|
|
293
|
+
async setDeepThoughtSwitch(guid, enable) {
|
|
294
|
+
await this.redis.set(`ai:deep_thought:switch:${guid}`, enable ? "1" : "0");
|
|
295
|
+
}
|
|
289
296
|
}
|
|
290
297
|
const configValue = getConfigValue();
|
|
291
298
|
const aiChatConfig = configValue.aiChat || {};
|
package/lib/middleware/mw.js
CHANGED
|
@@ -7,7 +7,7 @@ const selects = onSelects(["message.create", "private.message.create"]);
|
|
|
7
7
|
// 中间件
|
|
8
8
|
var mw = onMiddleware(selects, async (event, next) => {
|
|
9
9
|
getConfigValue().aiChat || {};
|
|
10
|
-
|
|
10
|
+
console.log("原始事件:", event.value);
|
|
11
11
|
// 新增字段
|
|
12
12
|
event["user_id"] = event.UserId; // 用户QQ号
|
|
13
13
|
event["msg"] = event.MessageText; // 消息内容
|
|
@@ -45,47 +45,94 @@ var mw = onMiddleware(selects, async (event, next) => {
|
|
|
45
45
|
if (event.Platform == "onebot") {
|
|
46
46
|
console.log("onebot");
|
|
47
47
|
// 获取客户端
|
|
48
|
-
const [
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
const [onebotClient] = useClient(event, API);
|
|
49
|
+
try {
|
|
50
|
+
const botInfo = await onebotClient.getLoginInfo();
|
|
51
|
+
if (!botInfo || !botInfo[0] || botInfo[0].code == 4000) {
|
|
52
|
+
event["ClientError"] =
|
|
53
|
+
"onebot平台连接错误, 影响:无法发送语音,无法获取自己的信息,无法获取at用户的信息,这是alemonjs的锅";
|
|
54
|
+
throw new Error("获取机器人信息失败,数据格式不正确");
|
|
55
|
+
}
|
|
56
|
+
event["bot"] = botInfo[0].data ?? {
|
|
57
|
+
nickname: "小喵酱",
|
|
58
|
+
user_id: event.BotId,
|
|
59
|
+
}; // 机器人信息
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.error("获取机器人信息失败,使用默认信息", error);
|
|
63
|
+
event["bot"] = {
|
|
64
|
+
nickname: "小喵酱",
|
|
65
|
+
user_id: event.BotId,
|
|
66
|
+
}; // 机器人信息
|
|
67
|
+
}
|
|
68
|
+
event["self_id"] = event.BotId; // 机器人QQ号
|
|
51
69
|
event["originalMsg"] = event.value.message; // 原始消息内容
|
|
70
|
+
// 判断是否为群管理员
|
|
71
|
+
event["isAdmin"] =
|
|
72
|
+
event.value.sender.role === "admin" ||
|
|
73
|
+
event.value.sender.role === "owner";
|
|
74
|
+
event["nickname"] =
|
|
75
|
+
event.value.sender.role === "admin"
|
|
76
|
+
? "[管理员]" + event["nickname"]
|
|
77
|
+
: event.value.sender.role === "owner"
|
|
78
|
+
? "[群主]" + event["nickname"]
|
|
79
|
+
: event["nickname"]; // 群管理员或群主添加前缀
|
|
52
80
|
// 处理at消息
|
|
53
81
|
event["at"] = await Promise.all(event.value.message
|
|
54
82
|
.filter((item) => item.type === "at")
|
|
55
83
|
.map(async (item) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
console.log("item", item);
|
|
85
|
+
try {
|
|
86
|
+
const atInfo = await onebotClient.send({
|
|
87
|
+
action: "get_stranger_info",
|
|
88
|
+
params: { user_id: item.data.qq, no_cache: true },
|
|
89
|
+
});
|
|
90
|
+
if (!atInfo || !atInfo[0] || atInfo[0].code == 4000) {
|
|
91
|
+
throw new Error("获取QQ昵称失败,数据格式不正确");
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
type: "at",
|
|
95
|
+
data: { qq: item.data.qq, nickname: atInfo[0].data.nickname },
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error(`获取QQ ${item.data.qq} 的昵称失败`, error);
|
|
100
|
+
return {
|
|
101
|
+
type: "at",
|
|
102
|
+
data: {
|
|
103
|
+
qq: item.data.qq,
|
|
104
|
+
nickname: `[${String(item.data.qq) == event.BotId ? event["bot"].nickname : "无法获取昵称"}](${item.data.qq})`,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
64
108
|
}));
|
|
65
109
|
// 是否at机器人
|
|
66
|
-
event["atBot"] = event["at"].find((item) => item.data.qq == event.
|
|
110
|
+
event["atBot"] = event["at"].find((item) => item.data.qq == event.BotId);
|
|
67
111
|
// 处理图片消息
|
|
68
112
|
event["img"] = event.value.message
|
|
69
113
|
.filter((item) => item.type === "image" || item.type === "Image")
|
|
70
114
|
.map((item) => item.data.url);
|
|
71
|
-
// 判断是否为群管理员
|
|
72
|
-
event["isAdmin"] =
|
|
73
|
-
event.value.sender.role === "admin" ||
|
|
74
|
-
event.value.sender.role === "owner";
|
|
75
|
-
event["bot"] = botInfo[0].data; // 机器人信息
|
|
76
115
|
// 处理回复消息
|
|
77
116
|
const replyData = event.value.message.find((item) => item.type === "reply");
|
|
78
117
|
if (replyData) {
|
|
79
|
-
const msg = await
|
|
118
|
+
const msg = await onebotClient.getMsg({
|
|
80
119
|
message_id: Number(replyData.data.id),
|
|
81
120
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
121
|
+
try {
|
|
122
|
+
if (!msg || !msg[0] || msg[0].code == 4000) {
|
|
123
|
+
throw new Error("获取回复消息失败,数据格式不正确");
|
|
124
|
+
}
|
|
125
|
+
console.log("msg[0].data;", JSON.stringify(msg[0].data, null, 2));
|
|
126
|
+
if (msg && msg[0] && msg[0].data) {
|
|
127
|
+
event["reply"] = msg[0].data;
|
|
128
|
+
if (msg[0].data.sender.user_id == event.BotId) {
|
|
129
|
+
event["atBot"] = true;
|
|
130
|
+
}
|
|
87
131
|
}
|
|
88
132
|
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error("获取回复消息失败,数据格式不正确", error);
|
|
135
|
+
}
|
|
89
136
|
}
|
|
90
137
|
}
|
|
91
138
|
// if (event.Platform == "scbbs") {
|
|
@@ -6,7 +6,7 @@ import { log } from 'console';
|
|
|
6
6
|
import OpenAi from 'openai';
|
|
7
7
|
import { TTSClient } from '../../api/tts.js';
|
|
8
8
|
import { getTimeString } from '../../api/format.js';
|
|
9
|
-
import {
|
|
9
|
+
import { tools, availableTools } from '../../api/aitools.js';
|
|
10
10
|
import { loadSkills } from '../../api/loadSkill.js';
|
|
11
11
|
import redisClient from '../../config.js';
|
|
12
12
|
import fileUrl from '../../assets/error.png.js';
|
|
@@ -16,7 +16,7 @@ const selects = onSelects(["message.create", "private.message.create"]);
|
|
|
16
16
|
let ttsClient;
|
|
17
17
|
let currentModel = "派蒙-默认"; // 默认音色
|
|
18
18
|
var res = onResponse(selects, async (e, next) => {
|
|
19
|
-
console.log("e.UserId", e.UserId, e.bot
|
|
19
|
+
console.log("e.UserId", e.UserId, e.bot);
|
|
20
20
|
// 如果是命令则跳过
|
|
21
21
|
if (e.msg.startsWith("/") ||
|
|
22
22
|
e.msg.startsWith("#") ||
|
|
@@ -29,7 +29,8 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
29
29
|
const isOpenTTSReply = await redisClient.getTTSResponseSwitch(e.guid); // TTS回复开关
|
|
30
30
|
const isOpenR18 = await redisClient.getR18Switch(e.guid); // R18开关
|
|
31
31
|
const proactiveSwitch = await redisClient.getProactiveSwitch(e.guid); // 主动搭话开关
|
|
32
|
-
|
|
32
|
+
const deepThoughtSwitch = await redisClient.getDeepThoughtSwitch(e.guid); // 深度思考开关
|
|
33
|
+
console.log("isOpenR18", isOpenR18, "proactiveSwitch", proactiveSwitch, "deepThoughtSwitch", deepThoughtSwitch);
|
|
33
34
|
const skills = loadSkills(); // 加载技能列表
|
|
34
35
|
// 创建消息
|
|
35
36
|
const [message] = useMessage(e);
|
|
@@ -126,6 +127,7 @@ ${botName}:[]
|
|
|
126
127
|
当前群号:${e.guid}
|
|
127
128
|
当前群名称:${e.GroupName || "无"}
|
|
128
129
|
当前聊天平台:${e.Platform}
|
|
130
|
+
${e.ClientError ? `当前错误信息:${e.ClientError}` : ""}
|
|
129
131
|
`;
|
|
130
132
|
// 如果没有配置AI,则不处理
|
|
131
133
|
if (config.host.trim() === "" ||
|
|
@@ -194,7 +196,7 @@ ${botName}:[]
|
|
|
194
196
|
}));
|
|
195
197
|
// 过滤掉下载失败的图片
|
|
196
198
|
const validImageUrls = imageurls.filter((img) => img !== null);
|
|
197
|
-
console.log("validImageUrls", validImageUrls);
|
|
199
|
+
// console.log("validImageUrls", validImageUrls);
|
|
198
200
|
// 处理艾特
|
|
199
201
|
if (e.at && e.at.length > 0) {
|
|
200
202
|
console.log("有艾特");
|
|
@@ -243,228 +245,238 @@ ${botName}:[]
|
|
|
243
245
|
};
|
|
244
246
|
console.log("处理后的消息", JSON.stringify(usermessage, null, 2));
|
|
245
247
|
console.log("好感度列表", affections);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
let toolCalls = [];
|
|
276
|
-
for await (const chunk of stream) {
|
|
277
|
-
const delta = chunk.choices?.[0]?.delta;
|
|
278
|
-
if (!delta)
|
|
279
|
-
continue;
|
|
280
|
-
if (delta.content) {
|
|
281
|
-
fullContent += delta.content;
|
|
248
|
+
try {
|
|
249
|
+
const openai = new OpenAi({
|
|
250
|
+
baseURL: config.host,
|
|
251
|
+
apiKey: config.key,
|
|
252
|
+
timeout: 300000,
|
|
253
|
+
});
|
|
254
|
+
let messages = [
|
|
255
|
+
{
|
|
256
|
+
role: "system",
|
|
257
|
+
content: systemPrompt,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
role: "system",
|
|
261
|
+
content: config.systemPrompt || "",
|
|
262
|
+
},
|
|
263
|
+
...historyMessages,
|
|
264
|
+
{ ...usermessage },
|
|
265
|
+
];
|
|
266
|
+
const isTool = await redisClient.getToolSwitch(e.guid);
|
|
267
|
+
const createParams = {
|
|
268
|
+
model: config.model,
|
|
269
|
+
messages: messages,
|
|
270
|
+
};
|
|
271
|
+
if (isTool === "1") {
|
|
272
|
+
createParams["tools"] = tools;
|
|
273
|
+
createParams["tool_choice"] = "auto";
|
|
274
|
+
}
|
|
275
|
+
if (deepThoughtSwitch === "0") {
|
|
276
|
+
createParams["verbosity"] = "low";
|
|
282
277
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
278
|
+
createParams["stream"] = true;
|
|
279
|
+
const stream = (await openai.chat.completions.create(createParams));
|
|
280
|
+
let fullContent = "";
|
|
281
|
+
let toolCalls = [];
|
|
282
|
+
for await (const chunk of stream) {
|
|
283
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
284
|
+
if (!delta)
|
|
285
|
+
continue;
|
|
286
|
+
if (delta.content) {
|
|
287
|
+
fullContent += delta.content;
|
|
288
|
+
}
|
|
289
|
+
if (delta.tool_calls) {
|
|
290
|
+
for (const tc of delta.tool_calls) {
|
|
291
|
+
if (tc.index !== undefined) {
|
|
292
|
+
if (!toolCalls[tc.index]) {
|
|
293
|
+
toolCalls[tc.index] = {
|
|
294
|
+
id: tc.id || "",
|
|
295
|
+
type: "function",
|
|
296
|
+
function: { name: "", arguments: "" },
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (tc.id)
|
|
300
|
+
toolCalls[tc.index].id = tc.id;
|
|
301
|
+
if (tc.function?.name)
|
|
302
|
+
toolCalls[tc.index].function.name += tc.function.name;
|
|
303
|
+
if (tc.function?.arguments)
|
|
304
|
+
toolCalls[tc.index].function.arguments += tc.function.arguments;
|
|
292
305
|
}
|
|
293
|
-
if (tc.id)
|
|
294
|
-
toolCalls[tc.index].id = tc.id;
|
|
295
|
-
if (tc.function?.name)
|
|
296
|
-
toolCalls[tc.index].function.name += tc.function.name;
|
|
297
|
-
if (tc.function?.arguments)
|
|
298
|
-
toolCalls[tc.index].function.arguments += tc.function.arguments;
|
|
299
306
|
}
|
|
300
307
|
}
|
|
301
308
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
309
|
+
let res = {
|
|
310
|
+
choices: [
|
|
311
|
+
{
|
|
312
|
+
message: {
|
|
313
|
+
role: "assistant",
|
|
314
|
+
content: fullContent || null,
|
|
315
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
316
|
+
},
|
|
310
317
|
},
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
};
|
|
314
|
-
if (!res.choices || res.choices.length === 0) {
|
|
315
|
-
log("AI未返回内容");
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
messages.push(usermessage);
|
|
319
|
-
await redisClient.addAIChatHistory(e.guid, usermessage);
|
|
320
|
-
// 检查是否有工具调用需要处理
|
|
321
|
-
while (res.choices[0].message?.tool_calls?.length) {
|
|
322
|
-
const responseMessage = res.choices[0].message;
|
|
323
|
-
// 先处理对话
|
|
324
|
-
messages.push(responseMessage);
|
|
325
|
-
await redisClient.addAIChatHistory(e.guid, responseMessage);
|
|
326
|
-
console.log("模型决定调用工具:", responseMessage.tool_calls);
|
|
327
|
-
for (const toolCall of responseMessage.tool_calls) {
|
|
328
|
-
const functionName = toolCall.function.name;
|
|
329
|
-
const functionArgs = JSON.parse(toolCall.function.arguments);
|
|
330
|
-
const functionToCall = availableTools[functionName];
|
|
331
|
-
if (!functionToCall) {
|
|
332
|
-
console.error(`未找到函数:${functionName}`);
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
// 执行本地函数
|
|
336
|
-
const functionResult = await functionToCall(functionArgs);
|
|
337
|
-
// 把工具执行结果作为 tool role 消息加回对话
|
|
338
|
-
messages.push({
|
|
339
|
-
role: "tool",
|
|
340
|
-
tool_call_id: toolCall.id,
|
|
341
|
-
name: functionName,
|
|
342
|
-
content: JSON.stringify(functionResult),
|
|
343
|
-
});
|
|
344
|
-
// 记录工具调用结果
|
|
345
|
-
await redisClient.addAIChatHistory(e.guid, {
|
|
346
|
-
role: "tool",
|
|
347
|
-
tool_call_id: toolCall.id,
|
|
348
|
-
name: functionName,
|
|
349
|
-
content: JSON.stringify(functionResult),
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
// 重新请求
|
|
353
|
-
res = await openai.chat.completions.create({
|
|
354
|
-
model: config.model,
|
|
355
|
-
messages: messages,
|
|
356
|
-
tools,
|
|
357
|
-
tool_choice: "auto",
|
|
358
|
-
});
|
|
359
|
-
// 检查是否还有工具调用需要处理
|
|
318
|
+
],
|
|
319
|
+
};
|
|
360
320
|
if (!res.choices || res.choices.length === 0) {
|
|
361
321
|
log("AI未返回内容");
|
|
362
322
|
return;
|
|
363
323
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const ttsRegex = /<(tts|音色)=([^>]+)>/gi;
|
|
398
|
-
let ttsMatch;
|
|
399
|
-
while ((ttsMatch = ttsRegex.exec(replyMessage)) !== null) {
|
|
400
|
-
console.log("检测到语音回复");
|
|
401
|
-
const isTTS = ttsMatch[1].toLowerCase() === "tts";
|
|
402
|
-
const content = ttsMatch[2].trim();
|
|
403
|
-
if (isTTS) {
|
|
404
|
-
ttsMessages.push({ text: content, model: currentModel });
|
|
324
|
+
messages.push(usermessage);
|
|
325
|
+
await redisClient.addAIChatHistory(e.guid, usermessage);
|
|
326
|
+
// 检查是否有工具调用需要处理
|
|
327
|
+
while (res.choices[0].message?.tool_calls?.length) {
|
|
328
|
+
const responseMessage = res.choices[0].message;
|
|
329
|
+
// 先处理对话
|
|
330
|
+
messages.push(responseMessage);
|
|
331
|
+
await redisClient.addAIChatHistory(e.guid, responseMessage);
|
|
332
|
+
console.log("模型决定调用工具:", responseMessage.tool_calls);
|
|
333
|
+
for (const toolCall of responseMessage.tool_calls) {
|
|
334
|
+
const functionName = toolCall.function.name;
|
|
335
|
+
const functionArgs = JSON.parse(toolCall.function.arguments);
|
|
336
|
+
const functionToCall = availableTools[functionName];
|
|
337
|
+
if (!functionToCall) {
|
|
338
|
+
console.error(`未找到函数:${functionName}`);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
// 执行本地函数
|
|
342
|
+
const functionResult = await functionToCall(functionArgs);
|
|
343
|
+
// 把工具执行结果作为 tool role 消息加回对话
|
|
344
|
+
messages.push({
|
|
345
|
+
role: "tool",
|
|
346
|
+
tool_call_id: toolCall.id,
|
|
347
|
+
name: functionName,
|
|
348
|
+
content: JSON.stringify(functionResult),
|
|
349
|
+
});
|
|
350
|
+
// 记录工具调用结果
|
|
351
|
+
await redisClient.addAIChatHistory(e.guid, {
|
|
352
|
+
role: "tool",
|
|
353
|
+
tool_call_id: toolCall.id,
|
|
354
|
+
name: functionName,
|
|
355
|
+
content: JSON.stringify(functionResult),
|
|
356
|
+
});
|
|
405
357
|
}
|
|
406
|
-
|
|
407
|
-
|
|
358
|
+
// 重新请求
|
|
359
|
+
res = await openai.chat.completions.create({
|
|
360
|
+
model: config.model,
|
|
361
|
+
messages: messages,
|
|
362
|
+
tools,
|
|
363
|
+
tool_choice: "auto",
|
|
364
|
+
});
|
|
365
|
+
// 检查是否还有工具调用需要处理
|
|
366
|
+
if (!res.choices || res.choices.length === 0) {
|
|
367
|
+
log("AI未返回内容");
|
|
368
|
+
return;
|
|
408
369
|
}
|
|
409
370
|
}
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
// 记录对话
|
|
417
|
-
await redisClient.addAIChatHistory(e.guid, res.choices?.[0].message);
|
|
418
|
-
// 发送消息
|
|
419
|
-
if (isOpenTTSReply === "1") {
|
|
420
|
-
// 提前发送语音消息避免等待时间过长
|
|
421
|
-
ttsClient = new TTSClient();
|
|
422
|
-
console.log("发送语音", ttsMessages);
|
|
423
|
-
for (const ttsMessage of ttsMessages) {
|
|
424
|
-
sendTTSMessage(ttsMessage.text, e, ttsClient, ttsMessage.model);
|
|
371
|
+
// 如果没有工具调用,处理最终回复
|
|
372
|
+
let reply = res.choices?.[0]?.message?.content?.trim() || `${botName}:[]`;
|
|
373
|
+
if (reply === `${botName}:[]`) {
|
|
374
|
+
log("AI选择不回复");
|
|
375
|
+
return;
|
|
425
376
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
for (const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
377
|
+
console.log("AI回复:\n", reply);
|
|
378
|
+
// 处理消息
|
|
379
|
+
// 提取ai发送的消息有多少条
|
|
380
|
+
const replyMessages = reply.split(new RegExp(`${botName}:`, "g")).slice(1);
|
|
381
|
+
const replymessages = [];
|
|
382
|
+
const ttsMessages = [];
|
|
383
|
+
for (const replyMessage of replyMessages) {
|
|
384
|
+
// 从消息中提取图片
|
|
385
|
+
const imageRegex = /<img=(.*?)>/g;
|
|
386
|
+
let match;
|
|
387
|
+
const imgUrls = [];
|
|
388
|
+
while ((match = imageRegex.exec(replyMessage)) !== null) {
|
|
389
|
+
imgUrls.push(match[1]);
|
|
439
390
|
}
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
391
|
+
// 处理好感度变化(通用和特定用户)
|
|
392
|
+
const affectionRegex = /<([^<>]*?)好感([+-]\d+)>/g;
|
|
393
|
+
let affectionMatch;
|
|
394
|
+
while ((affectionMatch = affectionRegex.exec(replyMessage)) !== null) {
|
|
395
|
+
const nickname = affectionMatch[1] || e.UserName;
|
|
396
|
+
const change = parseInt(affectionMatch[2], 10);
|
|
397
|
+
if (!isNaN(change) && change !== 0) {
|
|
398
|
+
console.log(`检测到对${nickname}的好感变化: ${change}`);
|
|
399
|
+
await redisClient.incrementAffectionLevel(e.guid, nickname, change);
|
|
447
400
|
}
|
|
448
|
-
catch (err) { }
|
|
449
401
|
}
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
402
|
+
// 处理语音回复
|
|
403
|
+
const ttsRegex = /<(tts|音色)=([^>]+)>/gi;
|
|
404
|
+
let ttsMatch;
|
|
405
|
+
while ((ttsMatch = ttsRegex.exec(replyMessage)) !== null) {
|
|
406
|
+
console.log("检测到语音回复");
|
|
407
|
+
const isTTS = ttsMatch[1].toLowerCase() === "tts";
|
|
408
|
+
const content = ttsMatch[2].trim();
|
|
409
|
+
if (isTTS) {
|
|
410
|
+
ttsMessages.push({ text: content, model: currentModel });
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
currentModel = content;
|
|
414
|
+
}
|
|
456
415
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
416
|
+
// 组合消息,添加到replymessages数组中
|
|
417
|
+
replymessages.push({
|
|
418
|
+
text: replyMessage.replace(imageRegex, "").replace(ttsRegex, "").trim(),
|
|
419
|
+
images: imgUrls,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
// 记录对话
|
|
423
|
+
await redisClient.addAIChatHistory(e.guid, res.choices?.[0].message);
|
|
424
|
+
// 发送消息
|
|
425
|
+
if (isOpenTTSReply === "1") {
|
|
426
|
+
// 提前发送语音消息避免等待时间过长
|
|
427
|
+
ttsClient = new TTSClient();
|
|
428
|
+
console.log("发送语音", ttsMessages);
|
|
429
|
+
for (const ttsMessage of ttsMessages) {
|
|
430
|
+
sendTTSMessage(ttsMessage.text, e, ttsClient, ttsMessage.model);
|
|
460
431
|
}
|
|
461
432
|
}
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
433
|
+
// 处理消息
|
|
434
|
+
for (let i = 0; i < replymessages.length; i++) {
|
|
435
|
+
const { text, images } = replymessages[i];
|
|
436
|
+
const components = i === 0 ? [Mention(e.UserId)] : [];
|
|
437
|
+
components.push(Text(text));
|
|
438
|
+
for (const img of images) {
|
|
439
|
+
const isNetworkImage = /^https?:\/\//.test(img);
|
|
440
|
+
let finalImg = img;
|
|
441
|
+
// 👉 scbbs 平台处理(下载再上传)
|
|
442
|
+
if (e.Platform == "scbbs" && isNetworkImage) {
|
|
443
|
+
const uploadedUrl = await uploadImageToR2(img.replace("https://imgen.x.ai", value.xaiImgProxy || "https://imgen.x.ai"));
|
|
444
|
+
finalImg = uploadedUrl || null;
|
|
445
|
+
}
|
|
446
|
+
// 👉 审核逻辑(只在开启时执行)
|
|
447
|
+
if (isOpenR18 !== "1" && finalImg) {
|
|
448
|
+
try {
|
|
449
|
+
if (res?.conclusion === "不合规") {
|
|
450
|
+
components.push(ImageFile(fileUrl));
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
catch (err) { }
|
|
455
|
+
}
|
|
456
|
+
// 👉 正常图片逻辑
|
|
457
|
+
if (finalImg) {
|
|
458
|
+
const imageComponent = isNetworkImage
|
|
459
|
+
? ImageURL(finalImg)
|
|
460
|
+
: ImageFile(finalImg);
|
|
461
|
+
components.push(imageComponent);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
// 上传失败 fallback
|
|
465
|
+
components.push(ImageFile(fileUrl));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// 随机 1-3 秒延迟
|
|
469
|
+
const delay = Math.random() * 2000 + 1000;
|
|
470
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
471
|
+
await message.send(format(...components));
|
|
472
|
+
}
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
console.error("AI回复出错", error);
|
|
477
|
+
message.send(format(Text("AI回复出错了呢~\n" + error)));
|
|
478
|
+
next();
|
|
466
479
|
}
|
|
467
|
-
return;
|
|
468
480
|
});
|
|
469
481
|
async function sendTTSMessage(message, e, ttsClient, modelAndEmotion) {
|
|
470
482
|
const [model, emotion] = modelAndEmotion.split("-");
|
package/package.json
CHANGED
package/lib/api.js
DELETED
|
@@ -1,529 +0,0 @@
|
|
|
1
|
-
import { redis } from './redis.js';
|
|
2
|
-
import { getConfigValue } from 'alemonjs';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import { uploadImageToR2 } from './s3.js';
|
|
5
|
-
import ttsmodels from './data/ttsmodels.json.js';
|
|
6
|
-
|
|
7
|
-
const value = getConfigValue();
|
|
8
|
-
const getTimeString = () => {
|
|
9
|
-
const now = new Date();
|
|
10
|
-
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
11
|
-
const day = String(now.getDate()).padStart(2, "0");
|
|
12
|
-
const hours = String(now.getHours()).padStart(2, "0");
|
|
13
|
-
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
14
|
-
return `${month}-${day} ${hours}:${minutes}`;
|
|
15
|
-
};
|
|
16
|
-
class TTSClient {
|
|
17
|
-
host;
|
|
18
|
-
constructor() {
|
|
19
|
-
this.host = value.ttsServer?.host || "http://localhost:8000";
|
|
20
|
-
}
|
|
21
|
-
async getModels(version = "v4") {
|
|
22
|
-
const res = await fetch(`${this.host}/models/${version}`);
|
|
23
|
-
if (!res.ok) {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
const data = await res.json();
|
|
27
|
-
return data.models;
|
|
28
|
-
}
|
|
29
|
-
async installModel(modelname) {
|
|
30
|
-
// 遍历ttsmodels,查找model对应的下载链接,模糊匹配
|
|
31
|
-
const matchedModel = ttsmodels.find((m) => {
|
|
32
|
-
return m.models.find((md) => md.modelname.includes(modelname));
|
|
33
|
-
});
|
|
34
|
-
if (!matchedModel) {
|
|
35
|
-
return { success: false, message: "未找到对应的模型" };
|
|
36
|
-
}
|
|
37
|
-
const modelInfo = matchedModel.models.find((md) => md.modelname.includes(modelname));
|
|
38
|
-
if (!modelInfo) {
|
|
39
|
-
return { success: false, message: "未找到对应的模型" };
|
|
40
|
-
}
|
|
41
|
-
// console.log("matchedModel", matchedModel);
|
|
42
|
-
console.log("modelInfo", modelInfo);
|
|
43
|
-
// 在这里实现模型安装逻辑
|
|
44
|
-
const res = await fetch(`${this.host}/install_model`, {
|
|
45
|
-
method: "POST",
|
|
46
|
-
headers: {
|
|
47
|
-
"Content-Type": "application/json",
|
|
48
|
-
},
|
|
49
|
-
body: JSON.stringify({
|
|
50
|
-
category: matchedModel.category,
|
|
51
|
-
dl_url: modelInfo.dl_link,
|
|
52
|
-
language: matchedModel.lang,
|
|
53
|
-
model_name: modelInfo.modelname,
|
|
54
|
-
}),
|
|
55
|
-
});
|
|
56
|
-
const data = await res.json();
|
|
57
|
-
if (!res.ok) {
|
|
58
|
-
const err = await res.text();
|
|
59
|
-
console.log(err);
|
|
60
|
-
return { success: false, message: "模型安装失败: " + err };
|
|
61
|
-
}
|
|
62
|
-
return { success: true, message: data.msg };
|
|
63
|
-
}
|
|
64
|
-
async setModel(model, emotion) {
|
|
65
|
-
const models = await this.getModels("v4");
|
|
66
|
-
if (!models) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
let modelInfo = await redis.get("chat:tts:modelInfo");
|
|
70
|
-
if (!modelInfo) {
|
|
71
|
-
modelInfo = {
|
|
72
|
-
version: "v4",
|
|
73
|
-
model_name: "",
|
|
74
|
-
prompt_text_lang: "中文",
|
|
75
|
-
emotion: emotion ?? "默认",
|
|
76
|
-
text_lang: "中文",
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
modelInfo = JSON.parse(modelInfo);
|
|
81
|
-
}
|
|
82
|
-
// model可能只包含模型名称的一部分,需要模糊匹配
|
|
83
|
-
const matchedModel = Object.keys(models).find((m) => m.includes(model));
|
|
84
|
-
if (!matchedModel) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
if (!modelInfo || typeof modelInfo === "string") {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
modelInfo.model_name = matchedModel;
|
|
91
|
-
modelInfo.prompt_text_lang = Object.keys(models[matchedModel])[0];
|
|
92
|
-
modelInfo.emotion =
|
|
93
|
-
emotion || models[matchedModel][modelInfo.prompt_text_lang][0];
|
|
94
|
-
modelInfo.text_lang = modelInfo.prompt_text_lang;
|
|
95
|
-
await redis.set("chat:tts:modelInfo", JSON.stringify(modelInfo));
|
|
96
|
-
return matchedModel;
|
|
97
|
-
}
|
|
98
|
-
async getAudio(text) {
|
|
99
|
-
let modelInfo = await redis.get("chat:tts:modelInfo");
|
|
100
|
-
if (!modelInfo) {
|
|
101
|
-
modelInfo = {
|
|
102
|
-
version: "v4",
|
|
103
|
-
model_name: "原神-中文-芙宁娜_ZH",
|
|
104
|
-
prompt_text_lang: "中文",
|
|
105
|
-
emotion: "默认",
|
|
106
|
-
text_lang: "中文",
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
modelInfo = JSON.parse(modelInfo);
|
|
111
|
-
}
|
|
112
|
-
if (!modelInfo || typeof modelInfo === "string") {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
console.log("modelInfo", modelInfo);
|
|
116
|
-
const res = await fetch(`${this.host}/infer_single`, {
|
|
117
|
-
method: "POST",
|
|
118
|
-
headers: {
|
|
119
|
-
"Content-Type": "application/json",
|
|
120
|
-
},
|
|
121
|
-
body: JSON.stringify({
|
|
122
|
-
dl_url: this.host,
|
|
123
|
-
version: modelInfo.version,
|
|
124
|
-
model_name: modelInfo.model_name,
|
|
125
|
-
prompt_text_lang: modelInfo.prompt_text_lang,
|
|
126
|
-
emotion: modelInfo.emotion,
|
|
127
|
-
text: text,
|
|
128
|
-
text_lang: modelInfo.text_lang,
|
|
129
|
-
top_k: 10,
|
|
130
|
-
top_p: 1,
|
|
131
|
-
temperature: 1,
|
|
132
|
-
text_split_method: "按标点符号切",
|
|
133
|
-
batch_size: 1,
|
|
134
|
-
batch_threshold: 0.75,
|
|
135
|
-
split_bucket: true,
|
|
136
|
-
speed_facter: 1,
|
|
137
|
-
fragment_interval: 0.3,
|
|
138
|
-
media_type: "wav",
|
|
139
|
-
parallel_infer: true,
|
|
140
|
-
repetition_penalty: 1.35,
|
|
141
|
-
seed: -1,
|
|
142
|
-
sample_steps: 16,
|
|
143
|
-
if_sr: false,
|
|
144
|
-
}),
|
|
145
|
-
});
|
|
146
|
-
if (!res.ok) {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
const data = await res.json();
|
|
150
|
-
return data;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
const getPrompt = async (text) => {
|
|
154
|
-
// 检查中文
|
|
155
|
-
const hasChinese = /[\u4e00-\u9fa5]/g.test(text);
|
|
156
|
-
const fanyiApi = "https://api.52vmy.cn/api/query/fanyi/youdao?msg=";
|
|
157
|
-
if (hasChinese) {
|
|
158
|
-
try {
|
|
159
|
-
// 调用后端的 send_get_request 函数
|
|
160
|
-
const responseText = await fetch(fanyiApi + encodeURIComponent(text)).then((res) => res.text());
|
|
161
|
-
// 将返回的 JSON 字符串解析为对象
|
|
162
|
-
const data = JSON.parse(responseText);
|
|
163
|
-
console.log("提示词翻译:", data.data.target);
|
|
164
|
-
return data.data.target;
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
console.error("Error fetching translation:", error);
|
|
168
|
-
return text; // 如果翻译失败,返回原始文本
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
return text;
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
const availableTools = {
|
|
176
|
-
/**
|
|
177
|
-
* 使用 Stable Diffusion 生成图片
|
|
178
|
-
* @param {string} prompt - 正向提示词
|
|
179
|
-
* @param {string} [bad_prompt] - 反向提示词
|
|
180
|
-
* @param {string} [direction] - 方向: landscape, portrait, square
|
|
181
|
-
* @param {boolean} [base64] - 是否返回base64格式的图片
|
|
182
|
-
* @returns {Promise<{type: "image", url: string}[]>} 图片列表
|
|
183
|
-
*/
|
|
184
|
-
StableDiffusionGenerateImage: async ({ prompt, bad_prompt = "(easynegative:1.1), (verybadimagenegative_v1.3:1), (low quality:1.2), (worst quality:1.2)", direction = "portrait", base64 = false, }) => {
|
|
185
|
-
const images = [];
|
|
186
|
-
const sizeMap = {
|
|
187
|
-
landscape: { width: 768, height: 512 },
|
|
188
|
-
portrait: { width: 512, height: 768 },
|
|
189
|
-
square: { width: 640, height: 640 },
|
|
190
|
-
};
|
|
191
|
-
const { width, height } = sizeMap[direction] || sizeMap["portrait"];
|
|
192
|
-
// 检查是否有中文
|
|
193
|
-
const body = {
|
|
194
|
-
enable_hr: true,
|
|
195
|
-
denoising_strength: 0.8,
|
|
196
|
-
hr_scale: 2,
|
|
197
|
-
hr_upscaler: "R-ESRGAN 4x+ Anime6B",
|
|
198
|
-
hr_second_pass_steps: 10,
|
|
199
|
-
prompt: await getPrompt(prompt),
|
|
200
|
-
seed: -1,
|
|
201
|
-
sampler_name: "DPM++ 2M",
|
|
202
|
-
steps: 20,
|
|
203
|
-
cfg_scale: 10,
|
|
204
|
-
width: width,
|
|
205
|
-
height: height,
|
|
206
|
-
negative_prompt: bad_prompt,
|
|
207
|
-
};
|
|
208
|
-
try {
|
|
209
|
-
const response = await fetch("http://datukuai.top:1450/ht2.php", {
|
|
210
|
-
method: "POST",
|
|
211
|
-
headers: {
|
|
212
|
-
"Content-Type": "application/json",
|
|
213
|
-
},
|
|
214
|
-
body: JSON.stringify(body),
|
|
215
|
-
});
|
|
216
|
-
if (!response.ok) {
|
|
217
|
-
return images;
|
|
218
|
-
}
|
|
219
|
-
const data = await response.json();
|
|
220
|
-
// 将图片保存到本地,然后返回本地路径
|
|
221
|
-
const dir = "./public/image_out";
|
|
222
|
-
if (!fs.existsSync(dir)) {
|
|
223
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
224
|
-
}
|
|
225
|
-
const path = `${dir}/${Date.now()}.jpg`;
|
|
226
|
-
const base64Data = data.images[0].replace(/^data:image\/\w+;base64,/, "");
|
|
227
|
-
const buffer = Buffer.from(base64Data, "base64");
|
|
228
|
-
fs.writeFileSync(path, buffer);
|
|
229
|
-
if (base64) {
|
|
230
|
-
return data.images;
|
|
231
|
-
}
|
|
232
|
-
images.push({ type: "image", url: path });
|
|
233
|
-
return images;
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
console.error("Error generating images:", error);
|
|
237
|
-
return images;
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
|
-
/** 使用 Stable Diffusion 将图片转换为提示词 */
|
|
241
|
-
StableDiffusionImageToPrompt: async ({ image_url }) => {
|
|
242
|
-
// 先查看redis中是否有这张图的缓存
|
|
243
|
-
const cacheKey = `image:caption:${image_url}`;
|
|
244
|
-
const cachedCaption = await redis.get(cacheKey);
|
|
245
|
-
let imgBase64 = "";
|
|
246
|
-
if (cachedCaption) {
|
|
247
|
-
// 如果有, 它是本地路径, 获取base64
|
|
248
|
-
const data = fs.readFileSync(cachedCaption, { encoding: "base64" });
|
|
249
|
-
imgBase64 = data;
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
// 如果没有, 下载图片并转换为base64
|
|
253
|
-
const response = await fetch(image_url);
|
|
254
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
255
|
-
const buffer = Buffer.from(arrayBuffer);
|
|
256
|
-
imgBase64 = buffer.toString("base64");
|
|
257
|
-
}
|
|
258
|
-
const api = "http://datukuai.top:1450//ckmf.php";
|
|
259
|
-
const body = {
|
|
260
|
-
fn_index: 280,
|
|
261
|
-
data: [
|
|
262
|
-
"data:image/png;base64," + imgBase64,
|
|
263
|
-
"",
|
|
264
|
-
false,
|
|
265
|
-
"",
|
|
266
|
-
"[name].[output_extension]",
|
|
267
|
-
"ignore",
|
|
268
|
-
false,
|
|
269
|
-
"wd14-vit",
|
|
270
|
-
0.35,
|
|
271
|
-
"",
|
|
272
|
-
"",
|
|
273
|
-
false,
|
|
274
|
-
false,
|
|
275
|
-
true,
|
|
276
|
-
"0_0, (o)_(o), +_+, +_-, ._., <o>_<o>, <|>_<|>, =_=, >_<, 3_3, 6_9, >_o, @_@, ^_^, o_o, u_u, x_x, |_|, ||_||",
|
|
277
|
-
false,
|
|
278
|
-
false,
|
|
279
|
-
],
|
|
280
|
-
session_hash: "0yzsdmhe9br9",
|
|
281
|
-
};
|
|
282
|
-
const res = await fetch(api, {
|
|
283
|
-
method: "POST",
|
|
284
|
-
headers: {
|
|
285
|
-
"Content-Type": "application/json",
|
|
286
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
|
|
287
|
-
},
|
|
288
|
-
body: JSON.stringify(body),
|
|
289
|
-
});
|
|
290
|
-
if (!res.ok) {
|
|
291
|
-
return "无法转为提示词";
|
|
292
|
-
}
|
|
293
|
-
const data = await res.json();
|
|
294
|
-
const prompt = data.data[0];
|
|
295
|
-
console.log("图片提示词:", prompt);
|
|
296
|
-
return prompt;
|
|
297
|
-
},
|
|
298
|
-
/** 搜索survivalcraft(生存战争)论坛的帖子 */
|
|
299
|
-
SearchScbbsPost: async ({ keyword }) => {
|
|
300
|
-
const api = `https://m.suancaixianyu.cn/api/post/list?title=${encodeURIComponent(keyword)}`;
|
|
301
|
-
try {
|
|
302
|
-
const response = await fetch(api);
|
|
303
|
-
if (!response.ok) {
|
|
304
|
-
return [];
|
|
305
|
-
}
|
|
306
|
-
const data = await response.json();
|
|
307
|
-
return data.data.list.map((item) => ({
|
|
308
|
-
viewUrl: `https://test.suancaixianyu.cn/#/postDetails/${item.id}`,
|
|
309
|
-
title: item.title,
|
|
310
|
-
content: item.content,
|
|
311
|
-
userName: item.creator.nickname,
|
|
312
|
-
userPage: `https://test.suancaixianyu.cn/#/user/${item.creatorId}`,
|
|
313
|
-
createdAt: item.createdAt,
|
|
314
|
-
plate: item.plate.name,
|
|
315
|
-
postVersions: item.postVersions.map((version) => ({
|
|
316
|
-
version: version.version,
|
|
317
|
-
versionName: version.title,
|
|
318
|
-
createdAt: version.createdAt,
|
|
319
|
-
files: version.files.map((file) => ({
|
|
320
|
-
name: file.name,
|
|
321
|
-
url: file.url,
|
|
322
|
-
})),
|
|
323
|
-
})),
|
|
324
|
-
}));
|
|
325
|
-
}
|
|
326
|
-
catch (error) {
|
|
327
|
-
console.error("Error searching posts:", error);
|
|
328
|
-
return [];
|
|
329
|
-
}
|
|
330
|
-
},
|
|
331
|
-
/** 使用搜索引擎搜索网页 */
|
|
332
|
-
SearchWeb: async ({ keyword, engine = "baidu", cc = "CN", type = "organic_results", }) => {
|
|
333
|
-
const apikey = value.searchApiKey ||
|
|
334
|
-
"63706fc3e6827a90e8219913297b510ff053d5238b24f64251a066e4ecea7408";
|
|
335
|
-
const api = `https://serpapi.com/search.json?engine=${engine}&q=${encodeURIComponent(keyword)}&cc=${cc}&api_key=${apikey}`;
|
|
336
|
-
try {
|
|
337
|
-
const response = await fetch(api);
|
|
338
|
-
if (!response.ok) {
|
|
339
|
-
return [];
|
|
340
|
-
}
|
|
341
|
-
const data = await response.json();
|
|
342
|
-
console.log("搜索结果", data);
|
|
343
|
-
const results = data[type] || [];
|
|
344
|
-
return type === "organic_results" ? results.slice(0, 7) : results;
|
|
345
|
-
}
|
|
346
|
-
catch (error) {
|
|
347
|
-
console.error("Error searching web:", error);
|
|
348
|
-
return [];
|
|
349
|
-
}
|
|
350
|
-
},
|
|
351
|
-
AIPS: async ({ image_url, instruction }) => {
|
|
352
|
-
// 从aiconfig中查询grok配置
|
|
353
|
-
const aiConfigStr = await redis.get(`ai:list:grok`);
|
|
354
|
-
if (!aiConfigStr) {
|
|
355
|
-
return "未找到AI配置";
|
|
356
|
-
}
|
|
357
|
-
const aiConfig = JSON.parse(aiConfigStr);
|
|
358
|
-
try {
|
|
359
|
-
// 处理单个或多个图片
|
|
360
|
-
const imageUrls = Array.isArray(image_url) ? image_url : [image_url];
|
|
361
|
-
const imgBase64Array = [];
|
|
362
|
-
for (const url of imageUrls) {
|
|
363
|
-
let imgBase64 = "";
|
|
364
|
-
if (url.startsWith("https://imgen.x.ai")) {
|
|
365
|
-
imgBase64 = url;
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
imgBase64 = await uploadImageToR2(url);
|
|
369
|
-
}
|
|
370
|
-
imgBase64Array.push(imgBase64);
|
|
371
|
-
}
|
|
372
|
-
const requestBody = Array.isArray(image_url)
|
|
373
|
-
? {
|
|
374
|
-
model: "grok-imagine-image",
|
|
375
|
-
images: imgBase64Array.map((base64) => ({
|
|
376
|
-
url: base64,
|
|
377
|
-
type: "image_url",
|
|
378
|
-
})),
|
|
379
|
-
prompt: instruction,
|
|
380
|
-
n: 1,
|
|
381
|
-
}
|
|
382
|
-
: {
|
|
383
|
-
model: "grok-imagine-image",
|
|
384
|
-
image: {
|
|
385
|
-
url: imgBase64Array[0],
|
|
386
|
-
type: "image_url",
|
|
387
|
-
},
|
|
388
|
-
prompt: instruction,
|
|
389
|
-
n: 1,
|
|
390
|
-
};
|
|
391
|
-
console.log("最终请求体", requestBody);
|
|
392
|
-
const res = await fetch(aiConfig.host + "/images/edits", {
|
|
393
|
-
method: "POST",
|
|
394
|
-
headers: {
|
|
395
|
-
"Content-Type": "application/json",
|
|
396
|
-
Authorization: `Bearer ${aiConfig.key}`,
|
|
397
|
-
},
|
|
398
|
-
body: JSON.stringify(requestBody),
|
|
399
|
-
});
|
|
400
|
-
if (!res.ok) {
|
|
401
|
-
const err = await res.text();
|
|
402
|
-
console.log(err);
|
|
403
|
-
return err;
|
|
404
|
-
}
|
|
405
|
-
const image = await res.json();
|
|
406
|
-
console.log("画好了", image);
|
|
407
|
-
return image.data;
|
|
408
|
-
}
|
|
409
|
-
catch (error) {
|
|
410
|
-
console.error("Error calling AIPS:", error);
|
|
411
|
-
return "调用接口失败" + error;
|
|
412
|
-
}
|
|
413
|
-
},
|
|
414
|
-
AIVideos: async ({ image_url, instruction }) => {
|
|
415
|
-
// 从aiconfig中查询grok配置
|
|
416
|
-
const aiConfigStr = await redis.get(`ai:list:grok`);
|
|
417
|
-
if (!aiConfigStr) {
|
|
418
|
-
return "未找到AI配置";
|
|
419
|
-
}
|
|
420
|
-
const aiConfig = JSON.parse(aiConfigStr);
|
|
421
|
-
// 处理单个或多个图片
|
|
422
|
-
const imageUrls = Array.isArray(image_url) ? image_url : [image_url];
|
|
423
|
-
const imgBase64Array = [];
|
|
424
|
-
for (const url of imageUrls) {
|
|
425
|
-
let imgBase64 = "";
|
|
426
|
-
if (url) {
|
|
427
|
-
if (url.startsWith("https://imgen.x.ai")) {
|
|
428
|
-
imgBase64 = url;
|
|
429
|
-
}
|
|
430
|
-
else {
|
|
431
|
-
imgBase64 = await uploadImageToR2(url);
|
|
432
|
-
}
|
|
433
|
-
imgBase64Array.push(imgBase64);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
try {
|
|
437
|
-
const requestBody = imgBase64Array.length === 0
|
|
438
|
-
? {
|
|
439
|
-
model: "grok-imagine-video",
|
|
440
|
-
prompt: instruction,
|
|
441
|
-
aspect_ratio: "16:9",
|
|
442
|
-
resolution: "720p",
|
|
443
|
-
}
|
|
444
|
-
: Array.isArray(image_url)
|
|
445
|
-
? {
|
|
446
|
-
model: "grok-imagine-video",
|
|
447
|
-
images: imgBase64Array.map((base64) => ({
|
|
448
|
-
url: base64,
|
|
449
|
-
type: "image_url",
|
|
450
|
-
})),
|
|
451
|
-
prompt: instruction,
|
|
452
|
-
resolution: "720p",
|
|
453
|
-
}
|
|
454
|
-
: {
|
|
455
|
-
model: "grok-imagine-video",
|
|
456
|
-
image: {
|
|
457
|
-
url: imgBase64Array[0],
|
|
458
|
-
type: "image_url",
|
|
459
|
-
},
|
|
460
|
-
prompt: instruction,
|
|
461
|
-
resolution: "720p",
|
|
462
|
-
};
|
|
463
|
-
console.log("最终请求体", requestBody);
|
|
464
|
-
const res = await fetch(aiConfig.host + "/videos/generations", {
|
|
465
|
-
method: "POST",
|
|
466
|
-
headers: {
|
|
467
|
-
"Content-Type": "application/json",
|
|
468
|
-
Authorization: `Bearer ${aiConfig.key}`,
|
|
469
|
-
},
|
|
470
|
-
body: JSON.stringify(requestBody),
|
|
471
|
-
});
|
|
472
|
-
if (!res.ok) {
|
|
473
|
-
const err = await res.text();
|
|
474
|
-
console.log(err);
|
|
475
|
-
return err;
|
|
476
|
-
}
|
|
477
|
-
const video = await res.json();
|
|
478
|
-
console.log("画好了", video);
|
|
479
|
-
return {
|
|
480
|
-
data: video,
|
|
481
|
-
msg: "生成任务已提交,稍后会自动发送生成结果,无需继续等待~,将request_id提供给用户,方便查询生成状态",
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
catch (error) {
|
|
485
|
-
console.error("Error calling AIPS:", error);
|
|
486
|
-
return "调用接口失败" + error;
|
|
487
|
-
}
|
|
488
|
-
},
|
|
489
|
-
AIVideoResult: async ({ request_id }) => {
|
|
490
|
-
// 从aiconfig中查询grok配置
|
|
491
|
-
const aiConfigStr = await redis.get(`ai:list:grok`);
|
|
492
|
-
if (!aiConfigStr) {
|
|
493
|
-
return "未找到AI配置";
|
|
494
|
-
}
|
|
495
|
-
const aiConfig = JSON.parse(aiConfigStr);
|
|
496
|
-
try {
|
|
497
|
-
const res = await fetch(aiConfig.host + `/videos/${request_id}`, {
|
|
498
|
-
method: "GET",
|
|
499
|
-
headers: {
|
|
500
|
-
"Content-Type": "application/json",
|
|
501
|
-
Authorization: `Bearer ${aiConfig.key}`,
|
|
502
|
-
},
|
|
503
|
-
});
|
|
504
|
-
if (!res.ok) {
|
|
505
|
-
const err = await res.text();
|
|
506
|
-
console.log(err);
|
|
507
|
-
return err;
|
|
508
|
-
}
|
|
509
|
-
const video = await res.json();
|
|
510
|
-
console.log("视频生成结果", video);
|
|
511
|
-
return video;
|
|
512
|
-
}
|
|
513
|
-
catch (error) {
|
|
514
|
-
console.error("Error calling AIVideoResult:", error);
|
|
515
|
-
return "调用接口失败" + error;
|
|
516
|
-
}
|
|
517
|
-
},
|
|
518
|
-
/**
|
|
519
|
-
* 获取TTS模型列表
|
|
520
|
-
* @returns TTS模型列表
|
|
521
|
-
*/
|
|
522
|
-
GetTTSModels: async () => {
|
|
523
|
-
const ttsClient = new TTSClient();
|
|
524
|
-
const models = await ttsClient.getModels("v4");
|
|
525
|
-
return models;
|
|
526
|
-
},
|
|
527
|
-
};
|
|
528
|
-
|
|
529
|
-
export { TTSClient, availableTools, getTimeString };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.mb-4{margin-bottom:1rem}.box-border{box-sizing:border-box}.flex{display:flex}.w-1\/3{width:33.333333%}.flex-wrap{flex-wrap:wrap}.gap-2{gap:.5rem}.overflow-hidden{overflow:hidden}.rounded-lg{border-radius:.5rem}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-white\/20{border-color:hsla(0,0%,100%,.2)}.bg-black\/30{background-color:rgba(0,0,0,.3)}.bg-black\/60{background-color:rgba(0,0,0,.6)}.bg-cover{background-size:cover}.p-2{padding:.5rem}.p-4{padding:1rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.italic{font-style:italic}.text-\[\#B8AE8E\]{--tw-text-opacity:1;color:rgb(184 174 142/var(--tw-text-opacity,1))}.text-\[\#e8deba\]{--tw-text-opacity:1;color:rgb(232 222 186/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.drop-shadow-md{--tw-drop-shadow:drop-shadow(0 4px 3px rgba(0,0,0,.07)) drop-shadow(0 2px 2px rgba(0,0,0,.06));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}body{display:flex;flex-direction:column;margin:0;padding:0}.backdrop-blur-xs{backdrop-filter:blur(4px)}.last\:border-0:last-child{border-width:0}
|