mioku-plugin-chat 3.0.6 → 3.1.1
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/context.ts +5 -5
- package/core/base.ts +20 -20
- package/core/chat-engine.ts +14 -10
- package/core/chat-turn.ts +109 -37
- package/core/media/history-media.ts +14 -14
- package/core/media/image-compress.ts +38 -27
- package/core/media/segment.ts +2 -2
- package/core/prompt.ts +1 -1
- package/core/tools/info.ts +14 -7
- package/core/tools/web.ts +6 -9
- package/db.ts +13 -13
- package/handlers/idle-debug.ts +7 -7
- package/handlers/message.ts +6 -8
- package/handlers/poke.ts +15 -14
- package/humanize/emotion-agent.ts +5 -5
- package/humanize/expression.ts +6 -6
- package/humanize/index.ts +1 -1
- package/humanize/memory.ts +1 -1
- package/index.ts +52 -28
- package/manage/cooldown.ts +8 -8
- package/manage/group-structured-history.ts +2 -2
- package/manage/idle-check.ts +12 -11
- package/manage/queue-processor.ts +9 -8
- package/manage/rate-limit-guard.ts +2 -2
- package/manage/rate-limiter.ts +21 -21
- package/manage/session.ts +1 -1
- package/manage/types.ts +16 -16
- package/package.json +1 -1
- package/runtime/chat-runtime.ts +4 -4
- package/types.ts +11 -11
- package/utils/config.ts +4 -4
- package/utils/group-config.ts +3 -4
- package/utils/index.ts +2 -0
- package/utils/message.ts +112 -47
- package/utils/model.ts +31 -0
- package/utils/queue.ts +12 -11
package/core/tools/info.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {logger} from "mioku";
|
|
|
2
2
|
import type { AITool } from "mioku";
|
|
3
3
|
import { TOOL_RESULT_FOLLOWUP_KEY } from "mioku";
|
|
4
4
|
import type { ToolContext } from "../../types";
|
|
5
|
+
import { mainModelSupportsVision } from "../../utils/model";
|
|
5
6
|
|
|
6
7
|
async function createImageFollowupResult(
|
|
7
8
|
imageUrl: string,
|
|
@@ -67,7 +68,11 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
|
|
|
67
68
|
parameters: {
|
|
68
69
|
type: "object",
|
|
69
70
|
properties: {
|
|
70
|
-
user_id: {
|
|
71
|
+
user_id: {
|
|
72
|
+
type: "string",
|
|
73
|
+
description:
|
|
74
|
+
"Platform user id of the member (QQ number, or openid on QQ official)",
|
|
75
|
+
},
|
|
71
76
|
},
|
|
72
77
|
required: ["user_id"],
|
|
73
78
|
},
|
|
@@ -137,7 +142,7 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
|
|
|
137
142
|
type: "object",
|
|
138
143
|
properties: {
|
|
139
144
|
message_id: {
|
|
140
|
-
type: "
|
|
145
|
+
type: "string",
|
|
141
146
|
description:
|
|
142
147
|
"The message ID (message_id) of the image or video. You can get this from the original message that contains the media.",
|
|
143
148
|
},
|
|
@@ -154,7 +159,7 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
|
|
|
154
159
|
}
|
|
155
160
|
|
|
156
161
|
if (media.kind === "image") {
|
|
157
|
-
if (toolCtx.config.
|
|
162
|
+
if (mainModelSupportsVision(toolCtx.aiService, toolCtx.config.model)) {
|
|
158
163
|
return await createImageFollowupResult(
|
|
159
164
|
media.url,
|
|
160
165
|
`The image from message #${args.message_id} is attached. Inspect it directly and answer the user's question from the visual content.`,
|
|
@@ -201,7 +206,8 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
|
|
|
201
206
|
|
|
202
207
|
try {
|
|
203
208
|
const attachFullVideo =
|
|
204
|
-
toolCtx.config.
|
|
209
|
+
mainModelSupportsVision(toolCtx.aiService, toolCtx.config.model) &&
|
|
210
|
+
videoFile.byteSize <= VIDEO_FULL_UPLOAD_MAX_BYTES;
|
|
205
211
|
|
|
206
212
|
if (attachFullVideo) {
|
|
207
213
|
const fs = await import("fs/promises");
|
|
@@ -250,8 +256,9 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
|
|
|
250
256
|
type: "object",
|
|
251
257
|
properties: {
|
|
252
258
|
user_id: {
|
|
253
|
-
type: "
|
|
254
|
-
description:
|
|
259
|
+
type: "string",
|
|
260
|
+
description:
|
|
261
|
+
"Platform user id of the member whose avatar you want to view",
|
|
255
262
|
},
|
|
256
263
|
},
|
|
257
264
|
required: ["user_id"],
|
|
@@ -261,7 +268,7 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
|
|
|
261
268
|
const avatarUrl = `https://q1.qlogo.cn/g?b=qq&nk=${args.user_id}&s=640`;
|
|
262
269
|
logger.info(`[view_member_avatar] Analyzing avatar: ${avatarUrl}`);
|
|
263
270
|
|
|
264
|
-
if (toolCtx.config.
|
|
271
|
+
if (mainModelSupportsVision(toolCtx.aiService, toolCtx.config.model)) {
|
|
265
272
|
return await createImageFollowupResult(
|
|
266
273
|
avatarUrl,
|
|
267
274
|
`User ${args.user_id}'s QQ avatar is attached. Inspect it directly and answer the user's question from the visual content.`,
|
package/core/tools/web.ts
CHANGED
|
@@ -23,14 +23,11 @@ function resolveUserHistoryLimit(value: unknown): number {
|
|
|
23
23
|
|
|
24
24
|
function extractTargetUserIdsFromQuestion(
|
|
25
25
|
question: string,
|
|
26
|
-
requesterUserId:
|
|
27
|
-
):
|
|
26
|
+
requesterUserId: string,
|
|
27
|
+
): string[] {
|
|
28
28
|
const matches = question.match(/\b\d{5,12}\b/g) || [];
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
.filter((item) => Number.isFinite(item) && item > 0);
|
|
32
|
-
const ids = [requesterUserId, ...parsed].filter(
|
|
33
|
-
(item) => Number.isFinite(item) && item > 0,
|
|
29
|
+
const ids = [String(requesterUserId ?? "").trim(), ...matches].filter(
|
|
30
|
+
(item) => item.length > 0,
|
|
34
31
|
);
|
|
35
32
|
return [...new Set(ids)].slice(0, 3);
|
|
36
33
|
}
|
|
@@ -138,8 +135,8 @@ async function fetchGroupHistoryByMessageIdPaging(
|
|
|
138
135
|
sessionId: toolCtx.sessionId,
|
|
139
136
|
role: String(raw.user_id) === String(bot.bot_id) ? "assistant" : "user",
|
|
140
137
|
content,
|
|
141
|
-
userId:
|
|
142
|
-
userName: raw.nickname || String(raw.user_id
|
|
138
|
+
userId: String(raw.user_id ?? "").trim(),
|
|
139
|
+
userName: raw.nickname || String(raw.user_id ?? "unknown"),
|
|
143
140
|
userRole: "member",
|
|
144
141
|
groupId: toolCtx.groupId,
|
|
145
142
|
timestamp: ts,
|
package/db.ts
CHANGED
|
@@ -69,14 +69,14 @@ export interface ChatDatabase {
|
|
|
69
69
|
before?: number,
|
|
70
70
|
): ChatMessage[];
|
|
71
71
|
// 获取 bot 发送的消息
|
|
72
|
-
getBotMessages(groupId:
|
|
73
|
-
getStoredGroupNoticeMessages(groupId:
|
|
72
|
+
getBotMessages(groupId: string, limit?: number): ChatMessage[];
|
|
73
|
+
getStoredGroupNoticeMessages(groupId: string, limit?: number): ChatMessage[];
|
|
74
74
|
getMessagesByUser(
|
|
75
|
-
userId:
|
|
75
|
+
userId: string,
|
|
76
76
|
sessionId?: string,
|
|
77
77
|
limit?: number,
|
|
78
78
|
): ChatMessage[];
|
|
79
|
-
getAllMessagesByUser(userId:
|
|
79
|
+
getAllMessagesByUser(userId: string, sessionId?: string): ChatMessage[];
|
|
80
80
|
getMessagesByTimeRange(
|
|
81
81
|
sessionId: string,
|
|
82
82
|
startTimestamp: number,
|
|
@@ -108,9 +108,9 @@ export interface ChatDatabase {
|
|
|
108
108
|
// 表达学习
|
|
109
109
|
saveExpression(expr: ExpressionRecord): void;
|
|
110
110
|
getExpressions(sessionId: string, limit?: number): ExpressionRecord[];
|
|
111
|
-
getExpressionsByUser(userId:
|
|
111
|
+
getExpressionsByUser(userId: string, limit?: number): ExpressionRecord[];
|
|
112
112
|
replaceExpressionsByUser(
|
|
113
|
-
userId:
|
|
113
|
+
userId: string,
|
|
114
114
|
userName: string,
|
|
115
115
|
expressions: Array<
|
|
116
116
|
Pick<ExpressionRecord, "situation" | "style" | "example">
|
|
@@ -473,7 +473,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
|
|
|
473
473
|
.reverse(); // 按时间正序
|
|
474
474
|
},
|
|
475
475
|
|
|
476
|
-
getBotMessages(groupId:
|
|
476
|
+
getBotMessages(groupId: string, limit: number = 50): ChatMessage[] {
|
|
477
477
|
const rows = stmts.getBotMessages.all({ $groupId: groupId, $limit: limit }) as any[];
|
|
478
478
|
return rows
|
|
479
479
|
.map(toChatMessage)
|
|
@@ -481,7 +481,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
|
|
|
481
481
|
},
|
|
482
482
|
|
|
483
483
|
getStoredGroupNoticeMessages(
|
|
484
|
-
groupId:
|
|
484
|
+
groupId: string,
|
|
485
485
|
limit: number = 20,
|
|
486
486
|
): ChatMessage[] {
|
|
487
487
|
const rows = stmts.getStoredGroupNoticeMessages.all(
|
|
@@ -493,7 +493,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
|
|
|
493
493
|
},
|
|
494
494
|
|
|
495
495
|
getMessagesByUser(
|
|
496
|
-
userId:
|
|
496
|
+
userId: string,
|
|
497
497
|
sessionId?: string,
|
|
498
498
|
limit: number = 20,
|
|
499
499
|
): ChatMessage[] {
|
|
@@ -510,7 +510,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
|
|
|
510
510
|
.reverse();
|
|
511
511
|
},
|
|
512
512
|
|
|
513
|
-
getAllMessagesByUser(userId:
|
|
513
|
+
getAllMessagesByUser(userId: string, sessionId?: string): ChatMessage[] {
|
|
514
514
|
const rows = sessionId
|
|
515
515
|
? (stmts.getAllMessagesByUserInSession.all({ $userId: userId, $sessionId: sessionId }) as any[])
|
|
516
516
|
: (stmts.getAllMessagesByUser.all({ $userId: userId }) as any[]);
|
|
@@ -633,7 +633,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
|
|
|
633
633
|
},
|
|
634
634
|
|
|
635
635
|
getExpressionsByUser(
|
|
636
|
-
userId:
|
|
636
|
+
userId: string,
|
|
637
637
|
limit: number = 50,
|
|
638
638
|
): ExpressionRecord[] {
|
|
639
639
|
const rows = stmts.getExpressionsByUser.all({ $userId: userId, $limit: limit }) as any[];
|
|
@@ -641,7 +641,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
|
|
|
641
641
|
},
|
|
642
642
|
|
|
643
643
|
replaceExpressionsByUser(
|
|
644
|
-
userId:
|
|
644
|
+
userId: string,
|
|
645
645
|
userName: string,
|
|
646
646
|
expressions: Array<
|
|
647
647
|
Pick<ExpressionRecord, "situation" | "style" | "example">
|
|
@@ -650,7 +650,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
|
|
|
650
650
|
const now = Date.now();
|
|
651
651
|
const tx = db.transaction(
|
|
652
652
|
(
|
|
653
|
-
targetUserId:
|
|
653
|
+
targetUserId: string,
|
|
654
654
|
targetUserName: string,
|
|
655
655
|
rows: Array<
|
|
656
656
|
Pick<ExpressionRecord, "situation" | "style" | "example">
|
package/handlers/idle-debug.ts
CHANGED
|
@@ -17,10 +17,10 @@ export async function handleIdleCheckDebug(
|
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
19
|
const groupIdStr =
|
|
20
|
-
e.message[0]?.text?.replace(
|
|
21
|
-
const targetGroupId =
|
|
20
|
+
e.message[0]?.text?.replace(/^[./]空闲检查/, "")?.trim() || "";
|
|
21
|
+
const targetGroupId = String(groupIdStr).trim();
|
|
22
22
|
if (!targetGroupId) {
|
|
23
|
-
await e.reply("
|
|
23
|
+
await e.reply("请指定群 ID,如:.空闲检查 123456");
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -61,7 +61,7 @@ export async function handleIdleCheckDebug(
|
|
|
61
61
|
|
|
62
62
|
const targetMessage: TargetMessage = {
|
|
63
63
|
userName: "系统",
|
|
64
|
-
userId:
|
|
64
|
+
userId: "",
|
|
65
65
|
userRole: "member",
|
|
66
66
|
content: "[No one in the group is talking? I'll answer!]",
|
|
67
67
|
messageId: "",
|
|
@@ -73,7 +73,7 @@ export async function handleIdleCheckDebug(
|
|
|
73
73
|
event: null,
|
|
74
74
|
groupSessionId,
|
|
75
75
|
groupId: targetGroupId,
|
|
76
|
-
userId:
|
|
76
|
+
userId: "",
|
|
77
77
|
config: cfg,
|
|
78
78
|
aiService: ctx.services!.ai as AIService,
|
|
79
79
|
db: pluginCtx.db,
|
|
@@ -117,8 +117,8 @@ export async function handleIdleCheckDebug(
|
|
|
117
117
|
result,
|
|
118
118
|
groupId: targetGroupId,
|
|
119
119
|
groupSessionId,
|
|
120
|
-
userId:
|
|
121
|
-
selfId: e.self_id,
|
|
120
|
+
userId: "",
|
|
121
|
+
selfId: String(e.self_id ?? "").trim(),
|
|
122
122
|
toolCtx,
|
|
123
123
|
send: true,
|
|
124
124
|
isLive: true,
|
package/handlers/message.ts
CHANGED
|
@@ -35,8 +35,8 @@ export function createMessageHandler(
|
|
|
35
35
|
|
|
36
36
|
return async (e: MessageEvent) => {
|
|
37
37
|
const isGroup = e.message_type === "group";
|
|
38
|
-
const groupId:
|
|
39
|
-
?
|
|
38
|
+
const groupId: string | undefined = isGroup
|
|
39
|
+
? String(e.group_id ?? "").trim() || undefined
|
|
40
40
|
: undefined;
|
|
41
41
|
const cfg = await getConfig(groupId);
|
|
42
42
|
if (!isGroup && cfg.ignorePrivateChat) return;
|
|
@@ -44,13 +44,13 @@ export function createMessageHandler(
|
|
|
44
44
|
if (!e?.message || !Array.isArray(e.message)) return;
|
|
45
45
|
|
|
46
46
|
const text = ctx.text(e) || "";
|
|
47
|
-
const userId:
|
|
47
|
+
const userId: string = String(e.user_id ?? e.sender?.user_id ?? "").trim();
|
|
48
48
|
|
|
49
|
-
if (userId ===
|
|
49
|
+
if (!userId || userId === String(e.self_id ?? "").trim()) return;
|
|
50
50
|
|
|
51
51
|
if (matchMessageCommands && matchMessageCommands(text).length > 0) return;
|
|
52
52
|
|
|
53
|
-
if (text
|
|
53
|
+
if (/^[./]空闲检查\s/.test(text)) {
|
|
54
54
|
await handleIdleCheckDebug(pluginCtx, e, cfg);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
@@ -204,9 +204,7 @@ export function createMessageHandler(
|
|
|
204
204
|
|
|
205
205
|
const atBot = shouldTrigger(e, text, cfg, ctx);
|
|
206
206
|
const replyBot = ctx.pickReplyBot(e);
|
|
207
|
-
const actorSelfId = replyBot
|
|
208
|
-
? Number(replyBot.bot_id)
|
|
209
|
-
: Number(e.self_id || 0);
|
|
207
|
+
const actorSelfId = String(replyBot?.bot_id ?? e.self_id ?? "").trim();
|
|
210
208
|
const quotedBot = isGroup ? await isQuotingBot(e, ctx) : null;
|
|
211
209
|
const mentionedNickname =
|
|
212
210
|
cfg.nicknames.length > 0 &&
|
package/handlers/poke.ts
CHANGED
|
@@ -15,12 +15,13 @@ export function createPokeHandler(
|
|
|
15
15
|
const { getConfig, runtimeState, pokeCooldowns } = state;
|
|
16
16
|
|
|
17
17
|
return async (e: NoticeEvent) => {
|
|
18
|
-
const selfId =
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
const selfId = String(e.self_id ?? "").trim();
|
|
19
|
+
const raw = e.raw as { target_id?: unknown; user_id?: unknown } | undefined;
|
|
20
|
+
const targetId = String(
|
|
21
|
+
raw?.target_id ?? raw?.user_id ?? e.user_id ?? "",
|
|
22
|
+
).trim();
|
|
23
|
+
if (!targetId || targetId !== selfId) return;
|
|
24
|
+
const groupId = String(e.group_id ?? "").trim();
|
|
24
25
|
const cfg = groupId ? await getConfig(groupId) : await getConfig();
|
|
25
26
|
if (!cfg.model && !cfg.apiKey) return;
|
|
26
27
|
if (!groupId || !isGroupAllowed(groupId, cfg)) return;
|
|
@@ -42,10 +43,9 @@ export function createPokeHandler(
|
|
|
42
43
|
"group",
|
|
43
44
|
groupId,
|
|
44
45
|
);
|
|
45
|
-
const userId =
|
|
46
|
+
const userId = String(e.user_id ?? "").trim();
|
|
46
47
|
const botRole = await getBotRole(groupId, ctx, selfId);
|
|
47
|
-
const botNickname =
|
|
48
|
-
cfg.nicknames[0] || e.bot?.nickname || "Bot";
|
|
48
|
+
const botNickname = cfg.nicknames[0] || e.bot?.nickname || "Bot";
|
|
49
49
|
|
|
50
50
|
let senderName = String(userId);
|
|
51
51
|
try {
|
|
@@ -54,9 +54,7 @@ export function createPokeHandler(
|
|
|
54
54
|
? await bot.getMemberInfo(groupId, userId)
|
|
55
55
|
: undefined;
|
|
56
56
|
senderName =
|
|
57
|
-
memberInfo?.card ||
|
|
58
|
-
memberInfo?.nickname ||
|
|
59
|
-
String(userId);
|
|
57
|
+
memberInfo?.card || memberInfo?.nickname || String(userId);
|
|
60
58
|
} catch {}
|
|
61
59
|
|
|
62
60
|
const targetMessage: TargetMessage = {
|
|
@@ -76,8 +74,11 @@ export function createPokeHandler(
|
|
|
76
74
|
selfId,
|
|
77
75
|
pluginCtx.buildHistoryMediaOptions(pluginCtx.aiInstance, cfg),
|
|
78
76
|
);
|
|
79
|
-
const { groupName, memberCount } =
|
|
80
|
-
|
|
77
|
+
const { groupName, memberCount } = await pluginCtx.getGroupInfoData(
|
|
78
|
+
ctx,
|
|
79
|
+
groupId,
|
|
80
|
+
selfId,
|
|
81
|
+
);
|
|
81
82
|
|
|
82
83
|
const toolCtx = pluginCtx.buildToolContext({
|
|
83
84
|
ctx,
|
|
@@ -38,7 +38,7 @@ export class EmotionAgent {
|
|
|
38
38
|
return state;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
getAvailableEmotions(groupId?:
|
|
41
|
+
getAvailableEmotions(groupId?: string): string[] {
|
|
42
42
|
const cfg = this.getConfig(groupId);
|
|
43
43
|
const emotions = Object.keys(cfg.emotion?.emotions || {})
|
|
44
44
|
.map((name) => name.trim().toLowerCase())
|
|
@@ -46,7 +46,7 @@ export class EmotionAgent {
|
|
|
46
46
|
return Array.from(new Set(["default", ...emotions]));
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
getReferenceExamples(emotion: string, groupId?:
|
|
49
|
+
getReferenceExamples(emotion: string, groupId?: string): string[] {
|
|
50
50
|
const cfg = this.getConfig(groupId);
|
|
51
51
|
const normalized = this.normalizeEmotionName(emotion);
|
|
52
52
|
const emotions = cfg.emotion?.emotions || {};
|
|
@@ -55,7 +55,7 @@ export class EmotionAgent {
|
|
|
55
55
|
return this.normalizeExamples(emotions[this.getDefaultEmotion(groupId)]?.examples);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
setEmotion(sessionId: string, emotion: string, groupId?:
|
|
58
|
+
setEmotion(sessionId: string, emotion: string, groupId?: string): EmotionState {
|
|
59
59
|
const current = this.resolveEmotion(emotion, groupId);
|
|
60
60
|
const state = { current, updatedAt: Date.now() };
|
|
61
61
|
this.states.set(sessionId, state);
|
|
@@ -144,14 +144,14 @@ ${input.targetMessage.userName}: ${input.targetMessage.content}`;
|
|
|
144
144
|
return this.resolveEmotion(parsed?.emotion, extractGroupIdFromSession(input.sessionId));
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
private resolveEmotion(emotion: unknown, groupId?:
|
|
147
|
+
private resolveEmotion(emotion: unknown, groupId?: string): string {
|
|
148
148
|
const normalized = this.normalizeEmotionName(emotion);
|
|
149
149
|
const available = this.getAvailableEmotions(groupId);
|
|
150
150
|
if (available.includes(normalized)) return normalized;
|
|
151
151
|
return this.getDefaultEmotion(groupId);
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
private getDefaultEmotion(groupId?:
|
|
154
|
+
private getDefaultEmotion(groupId?: string): string {
|
|
155
155
|
const cfg = this.getConfig(groupId);
|
|
156
156
|
const configured = this.normalizeEmotionName(cfg.emotion?.defaultEmotion);
|
|
157
157
|
const available = this.getAvailableEmotions(groupId);
|
package/humanize/expression.ts
CHANGED
|
@@ -9,8 +9,8 @@ export class ExpressionLearner {
|
|
|
9
9
|
private ai: AIInstance;
|
|
10
10
|
private getConfig: ChatConfigProvider;
|
|
11
11
|
private db: ChatDatabase;
|
|
12
|
-
private pendingMessagesByUser: Map<
|
|
13
|
-
private learningUsers: Set<
|
|
12
|
+
private pendingMessagesByUser: Map<string, ChatMessage[]> = new Map();
|
|
13
|
+
private learningUsers: Set<string> = new Set();
|
|
14
14
|
|
|
15
15
|
constructor(ai: AIInstance, configProvider: ChatConfigProvider, db: ChatDatabase) {
|
|
16
16
|
this.ai = ai;
|
|
@@ -33,9 +33,9 @@ export class ExpressionLearner {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
getExpressionContextForUser(
|
|
36
|
-
userId:
|
|
36
|
+
userId: string,
|
|
37
37
|
userName: string,
|
|
38
|
-
groupId?:
|
|
38
|
+
groupId?: string,
|
|
39
39
|
): string {
|
|
40
40
|
const cfg = this.getConfig(groupId);
|
|
41
41
|
if (!cfg.expression?.enabled) return "";
|
|
@@ -62,7 +62,7 @@ export class ExpressionLearner {
|
|
|
62
62
|
return context;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
private async tryLearn(userId:
|
|
65
|
+
private async tryLearn(userId: string, cfg = this.getConfig()): Promise<void> {
|
|
66
66
|
if (this.learningUsers.has(userId)) return;
|
|
67
67
|
|
|
68
68
|
const threshold = Math.max(
|
|
@@ -95,7 +95,7 @@ export class ExpressionLearner {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
private async learnForUser(
|
|
98
|
-
userId:
|
|
98
|
+
userId: string,
|
|
99
99
|
messages: ChatMessage[],
|
|
100
100
|
cfg = this.getConfig(),
|
|
101
101
|
): Promise<void> {
|
package/humanize/index.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { TopicTracker } from "./topic";
|
|
|
16
16
|
import { ActionPlanner } from "./planner";
|
|
17
17
|
import { ExpressionLearner } from "./expression";
|
|
18
18
|
|
|
19
|
-
export type ChatConfigProvider = (groupId?:
|
|
19
|
+
export type ChatConfigProvider = (groupId?: string) => ChatConfig;
|
|
20
20
|
|
|
21
21
|
export class HumanizeEngine {
|
|
22
22
|
readonly memoryRetrieval: MemoryRetrieval;
|
package/humanize/memory.ts
CHANGED
package/index.ts
CHANGED
|
@@ -48,6 +48,7 @@ import { buildHistoryMediaOptions } from "./core/media/segment";
|
|
|
48
48
|
import type { ChatConfigProvider } from "./humanize";
|
|
49
49
|
import type { ChatPluginContext, ChatHandlerState } from "./context";
|
|
50
50
|
import { mergeGroupOverrides } from "./utils/group-config";
|
|
51
|
+
import { mainModelSupportsVision } from "./utils/model";
|
|
51
52
|
import {
|
|
52
53
|
mergeChatConfig,
|
|
53
54
|
normalizeIdList,
|
|
@@ -95,9 +96,20 @@ function resolveRoleInstances(aiService: AIService): {
|
|
|
95
96
|
const visionModel = findModel("vision", vision) || workingModel;
|
|
96
97
|
|
|
97
98
|
const models = aiService.listModels?.() ?? [];
|
|
98
|
-
const
|
|
99
|
-
models.find((item) => item.id === bindings
|
|
100
|
-
|
|
99
|
+
const findModelDesc = (role: AIModelRole, modelId: string) => {
|
|
100
|
+
const exact = models.find((item) => item.id === bindings[role]);
|
|
101
|
+
if (exact) return exact;
|
|
102
|
+
const providerId = instances.find(
|
|
103
|
+
(info) => info.role === role || info.name === role,
|
|
104
|
+
)?.providerId;
|
|
105
|
+
return (
|
|
106
|
+
models.find(
|
|
107
|
+
(item) => item.modelId === modelId && item.providerId === providerId,
|
|
108
|
+
) || models.find((item) => item.modelId === modelId)
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const visionDesc = findModelDesc("vision", visionModel);
|
|
101
113
|
const isMultimodal =
|
|
102
114
|
visionDesc?.capabilities?.includes("vision") ?? Boolean(visionModel);
|
|
103
115
|
|
|
@@ -261,7 +273,7 @@ export default definePlugin({
|
|
|
261
273
|
});
|
|
262
274
|
}
|
|
263
275
|
|
|
264
|
-
const configProvider: ChatConfigProvider = (groupId?:
|
|
276
|
+
const configProvider: ChatConfigProvider = (groupId?: string) => {
|
|
265
277
|
const base = cachedBaseConfig;
|
|
266
278
|
if (!base) return PERSONALIZATION_CONFIG as unknown as ChatConfig;
|
|
267
279
|
if (groupId === undefined) return base;
|
|
@@ -269,7 +281,7 @@ export default definePlugin({
|
|
|
269
281
|
return mergeGroupOverrides(base, overrides);
|
|
270
282
|
};
|
|
271
283
|
|
|
272
|
-
const getConfig = async (groupId?:
|
|
284
|
+
const getConfig = async (groupId?: string): Promise<ChatConfig> => {
|
|
273
285
|
if (!cachedBaseConfig) await refreshBaseCache();
|
|
274
286
|
return configProvider(groupId);
|
|
275
287
|
};
|
|
@@ -341,7 +353,7 @@ export default definePlugin({
|
|
|
341
353
|
return;
|
|
342
354
|
}
|
|
343
355
|
|
|
344
|
-
roleModels = resolved.models;
|
|
356
|
+
roleModels = resolved.models;
|
|
345
357
|
roleIsMultimodal = resolved.isMultimodal;
|
|
346
358
|
await refreshBaseCache();
|
|
347
359
|
|
|
@@ -380,14 +392,14 @@ roleModels = resolved.models;
|
|
|
380
392
|
rateLimiter.setConfigProvider(configProvider);
|
|
381
393
|
const skillManager = new SkillSessionManager();
|
|
382
394
|
|
|
383
|
-
rateLimiter.setQueueLengthGetter((groupId:
|
|
395
|
+
rateLimiter.setQueueLengthGetter((groupId: string) =>
|
|
384
396
|
queueManager.getQueueLength(`group:${groupId}`),
|
|
385
397
|
);
|
|
386
398
|
|
|
387
399
|
const humanize = new HumanizeEngine(workAIInstance, db, configProvider);
|
|
388
400
|
await humanize.init();
|
|
389
401
|
|
|
390
|
-
const pokeCooldowns = new Map<
|
|
402
|
+
const pokeCooldowns = new Map<string, number>();
|
|
391
403
|
const processingSet = new Set<string>();
|
|
392
404
|
const sessionTurnScheduler = new SessionTurnScheduler();
|
|
393
405
|
const groupStructuredHistory = new GroupStructuredHistoryManager();
|
|
@@ -442,8 +454,8 @@ roleModels = resolved.models;
|
|
|
442
454
|
audioService,
|
|
443
455
|
startCooldownTimer: (
|
|
444
456
|
groupSessionId: string,
|
|
445
|
-
groupId:
|
|
446
|
-
selfId:
|
|
457
|
+
groupId: string,
|
|
458
|
+
selfId: string,
|
|
447
459
|
) => cooldownManager.startCooldownTimer(groupSessionId, groupId, selfId),
|
|
448
460
|
async recordGroupMessageForLearning(
|
|
449
461
|
userMsg: ChatMessage,
|
|
@@ -511,20 +523,25 @@ roleModels = resolved.models;
|
|
|
511
523
|
aiService.registerChatRuntime(runtime);
|
|
512
524
|
|
|
513
525
|
ctx.command({
|
|
514
|
-
name: "
|
|
515
|
-
|
|
516
|
-
|
|
526
|
+
name: "重置会话",
|
|
527
|
+
prefixes: ["/", "."],
|
|
528
|
+
match: /^重置会话$/,
|
|
517
529
|
description: "重置自己的AI聊天记录",
|
|
530
|
+
usage: ".重置会话",
|
|
518
531
|
handler: async ({ event }) => {
|
|
519
|
-
if (event.user_id === event.self_id) return;
|
|
532
|
+
if (String(event.user_id ?? "") === String(event.self_id ?? "")) return;
|
|
520
533
|
const groupId =
|
|
521
|
-
event.message_type === "group"
|
|
534
|
+
event.message_type === "group"
|
|
535
|
+
? String(event.group_id ?? "").trim() || undefined
|
|
536
|
+
: undefined;
|
|
522
537
|
if (groupId) {
|
|
523
538
|
pluginCtx.sessionManager.resetBotMessages(`group:${groupId}`);
|
|
524
539
|
pluginCtx.groupStructuredHistory.clear(`group:${groupId}`);
|
|
525
540
|
await event.reply("已清除本群会话中 AI 发送的消息~");
|
|
526
541
|
} else {
|
|
527
|
-
const userId =
|
|
542
|
+
const userId = String(
|
|
543
|
+
event.user_id ?? event.sender?.user_id ?? "",
|
|
544
|
+
).trim();
|
|
528
545
|
pluginCtx.sessionManager.resetBotMessages(`personal:${userId}`);
|
|
529
546
|
pluginCtx.groupStructuredHistory.clear(`personal:${userId}`);
|
|
530
547
|
await event.reply("已清除你的个人会话中 AI 发送的消息~");
|
|
@@ -532,15 +549,18 @@ roleModels = resolved.models;
|
|
|
532
549
|
},
|
|
533
550
|
});
|
|
534
551
|
ctx.command({
|
|
535
|
-
name: "
|
|
536
|
-
|
|
537
|
-
|
|
552
|
+
name: "重置群会话",
|
|
553
|
+
prefixes: ["/", "."],
|
|
554
|
+
match: /^重置群会话$/,
|
|
538
555
|
permission: "admin",
|
|
539
556
|
description: "重置当前群的AI聊天记录",
|
|
557
|
+
usage: ".重置群会话",
|
|
540
558
|
handler: async ({ event }) => {
|
|
541
|
-
if (event.user_id === event.self_id) return;
|
|
559
|
+
if (String(event.user_id ?? "") === String(event.self_id ?? "")) return;
|
|
542
560
|
const groupId =
|
|
543
|
-
event.message_type === "group"
|
|
561
|
+
event.message_type === "group"
|
|
562
|
+
? String(event.group_id ?? "").trim() || undefined
|
|
563
|
+
: undefined;
|
|
544
564
|
if (!groupId) {
|
|
545
565
|
await event.reply("在群里使用试试看吧~", true);
|
|
546
566
|
return;
|
|
@@ -551,15 +571,19 @@ roleModels = resolved.models;
|
|
|
551
571
|
},
|
|
552
572
|
});
|
|
553
573
|
ctx.command({
|
|
554
|
-
name: "
|
|
555
|
-
|
|
556
|
-
|
|
574
|
+
name: "tts",
|
|
575
|
+
prefixes: ["/", "."],
|
|
576
|
+
match: /^tts\s*(.*)$/,
|
|
557
577
|
permission: "owner",
|
|
558
578
|
description: "TTS 推理",
|
|
559
|
-
usage: "
|
|
579
|
+
usage: ".tts <文本>",
|
|
560
580
|
handler: async ({ event, match }) => {
|
|
561
|
-
if (event.user_id === event.self_id) return;
|
|
562
|
-
await handleTtsCommand(
|
|
581
|
+
if (String(event.user_id ?? "") === String(event.self_id ?? "")) return;
|
|
582
|
+
await handleTtsCommand(
|
|
583
|
+
pluginCtx,
|
|
584
|
+
event,
|
|
585
|
+
String(match?.[1] ?? "").trim(),
|
|
586
|
+
);
|
|
563
587
|
},
|
|
564
588
|
});
|
|
565
589
|
|
|
@@ -567,7 +591,7 @@ roleModels = resolved.models;
|
|
|
567
591
|
ctx.handle("notice.group.poke", createPokeHandler(pluginCtx, handlerState));
|
|
568
592
|
|
|
569
593
|
ctx.logger.info(
|
|
570
|
-
`聊天插件加载成功 (main=${roleModels.main || "?"}, work=${roleModels.working || "?"}, vision=${roleModels.vision || "?"})`,
|
|
594
|
+
`聊天插件加载成功 (main=${roleModels.main || "?"}${mainModelSupportsVision(aiService, roleModels.main) ? "" : " 无视觉,图片交给视觉模型描述"}, work=${roleModels.working || "?"}, vision=${roleModels.vision || "?"})`,
|
|
571
595
|
);
|
|
572
596
|
|
|
573
597
|
return () => {
|
package/manage/cooldown.ts
CHANGED
|
@@ -26,7 +26,7 @@ interface CooldownMessage {
|
|
|
26
26
|
event: any;
|
|
27
27
|
content: string;
|
|
28
28
|
userName: string;
|
|
29
|
-
userId:
|
|
29
|
+
userId: string;
|
|
30
30
|
messageId: string;
|
|
31
31
|
timestamp: number;
|
|
32
32
|
isDirectAt: boolean;
|
|
@@ -86,8 +86,8 @@ export class CooldownManager {
|
|
|
86
86
|
|
|
87
87
|
startCooldownTimer(
|
|
88
88
|
groupSessionId: string,
|
|
89
|
-
groupId:
|
|
90
|
-
selfId:
|
|
89
|
+
groupId: string,
|
|
90
|
+
selfId: string,
|
|
91
91
|
): void {
|
|
92
92
|
const existingTimer = this.cooldownTimeoutIds.get(groupSessionId);
|
|
93
93
|
if (existingTimer) clearTimeout(existingTimer);
|
|
@@ -142,7 +142,7 @@ export class CooldownManager {
|
|
|
142
142
|
|
|
143
143
|
collectMessage(
|
|
144
144
|
groupSessionId: string,
|
|
145
|
-
groupId:
|
|
145
|
+
groupId: string,
|
|
146
146
|
event: any,
|
|
147
147
|
content: string,
|
|
148
148
|
isDirectAt: boolean,
|
|
@@ -175,9 +175,9 @@ export class CooldownManager {
|
|
|
175
175
|
|
|
176
176
|
private async processReviewMessages(
|
|
177
177
|
groupSessionId: string,
|
|
178
|
-
groupId:
|
|
178
|
+
groupId: string,
|
|
179
179
|
collected: CooldownMessage[],
|
|
180
|
-
selfId:
|
|
180
|
+
selfId: string,
|
|
181
181
|
): Promise<void> {
|
|
182
182
|
const mergedContents = collected.map((m) => m.content);
|
|
183
183
|
const userNames = collected.map((m) => m.userName);
|
|
@@ -297,9 +297,9 @@ export class CooldownManager {
|
|
|
297
297
|
|
|
298
298
|
private async processCooldownWithPlanner(
|
|
299
299
|
groupSessionId: string,
|
|
300
|
-
groupId:
|
|
300
|
+
groupId: string,
|
|
301
301
|
collected: CooldownMessage[],
|
|
302
|
-
selfId:
|
|
302
|
+
selfId: string,
|
|
303
303
|
): Promise<void> {
|
|
304
304
|
const mergedContent = collected.map((m) => m.content).join("\n");
|
|
305
305
|
const firstMsg = collected[0];
|