koishi-plugin-chatluna-character 0.0.75 → 0.0.76
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/index.cjs +367 -301
- package/lib/index.d.ts +29 -1
- package/lib/index.mjs +308 -242
- package/package.json +3 -2
package/lib/index.cjs
CHANGED
|
@@ -37,200 +37,17 @@ __export(src_exports, {
|
|
|
37
37
|
name: () => name
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(src_exports);
|
|
40
|
-
var
|
|
40
|
+
var import_koishi7 = require("koishi");
|
|
41
41
|
|
|
42
42
|
// src/plugins/chat.ts
|
|
43
43
|
var import_messages = require("@langchain/core/messages");
|
|
44
|
-
var
|
|
44
|
+
var import_koishi2 = require("koishi");
|
|
45
45
|
var import_count_tokens = require("koishi-plugin-chatluna/llm-core/utils/count_tokens");
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
logger = service.logger;
|
|
52
|
-
const modelPool = {};
|
|
53
|
-
const [platform, modelName] = (0, import_count_tokens.parseRawModelName)(config.model);
|
|
54
|
-
await ctx.chatluna.awaitLoadPlatform(platform);
|
|
55
|
-
const globalModel = await ctx.chatluna.createChatModel(
|
|
56
|
-
platform,
|
|
57
|
-
modelName
|
|
58
|
-
);
|
|
59
|
-
logger.info("global model loaded %c", config.model);
|
|
60
|
-
if (config.modelOverride?.length > 0) {
|
|
61
|
-
for (const override of config.modelOverride) {
|
|
62
|
-
modelPool[override.groupId] = (async () => {
|
|
63
|
-
const [platform2, modelName2] = (0, import_count_tokens.parseRawModelName)(override.model);
|
|
64
|
-
await ctx.chatluna.awaitLoadPlatform(platform2);
|
|
65
|
-
const loadedModel = await ctx.chatluna.createChatModel(
|
|
66
|
-
platform2,
|
|
67
|
-
modelName2
|
|
68
|
-
);
|
|
69
|
-
logger.info(
|
|
70
|
-
"override model loaded %c for group %c",
|
|
71
|
-
override.model,
|
|
72
|
-
override.groupId
|
|
73
|
-
);
|
|
74
|
-
modelPool[override.groupId] = Promise.resolve(loadedModel);
|
|
75
|
-
return loadedModel;
|
|
76
|
-
})();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
let globalPreset = preset.getPresetForCache(config.defaultPreset);
|
|
80
|
-
let presetPool = {};
|
|
81
|
-
ctx.on("chatluna_character/preset_updated", () => {
|
|
82
|
-
globalPreset = preset.getPresetForCache(config.defaultPreset);
|
|
83
|
-
presetPool = {};
|
|
84
|
-
});
|
|
85
|
-
service.collect(async (session, messages) => {
|
|
86
|
-
const guildId = session.event.guild?.id ?? session.guildId;
|
|
87
|
-
const model = await (modelPool[guildId] ?? Promise.resolve(globalModel));
|
|
88
|
-
const currentGuildConfig = config.configs[guildId];
|
|
89
|
-
let copyOfConfig = Object.assign({}, config);
|
|
90
|
-
let currentPreset = globalPreset;
|
|
91
|
-
if (currentGuildConfig != null) {
|
|
92
|
-
copyOfConfig = Object.assign({}, copyOfConfig, currentGuildConfig);
|
|
93
|
-
currentPreset = presetPool[guildId] ?? await (async () => {
|
|
94
|
-
const template = preset.getPresetForCache(
|
|
95
|
-
currentGuildConfig.preset
|
|
96
|
-
);
|
|
97
|
-
presetPool[guildId] = template;
|
|
98
|
-
return template;
|
|
99
|
-
})();
|
|
100
|
-
}
|
|
101
|
-
const [recentMessage, lastMessage] = await formatMessage(
|
|
102
|
-
messages,
|
|
103
|
-
copyOfConfig,
|
|
104
|
-
model,
|
|
105
|
-
currentPreset.system.template,
|
|
106
|
-
currentPreset.system.template
|
|
107
|
-
);
|
|
108
|
-
const temp = await service.getTemp(session);
|
|
109
|
-
const formattedSystemPrompt = await currentPreset.system.format({
|
|
110
|
-
time: (/* @__PURE__ */ new Date()).toLocaleString(),
|
|
111
|
-
status: temp.status ?? currentPreset.status ?? "",
|
|
112
|
-
stickers: JSON.stringify(stickerService.getAllStickTypes())
|
|
113
|
-
});
|
|
114
|
-
logger.debug("messages_new: " + JSON.stringify(recentMessage));
|
|
115
|
-
logger.debug("messages_last: " + JSON.stringify(lastMessage));
|
|
116
|
-
const humanMessage = new import_messages.HumanMessage(
|
|
117
|
-
await currentPreset.input.format({
|
|
118
|
-
history_new: recentMessage,
|
|
119
|
-
history_last: lastMessage,
|
|
120
|
-
time: (/* @__PURE__ */ new Date()).toLocaleString(),
|
|
121
|
-
stickers: JSON.stringify(stickerService.getAllStickTypes()),
|
|
122
|
-
status: temp.status ?? currentPreset.status ?? ""
|
|
123
|
-
})
|
|
124
|
-
);
|
|
125
|
-
const completionMessages = await formatCompletionMessages(
|
|
126
|
-
[new import_messages.SystemMessage(formattedSystemPrompt)].concat(
|
|
127
|
-
temp.completionMessages
|
|
128
|
-
),
|
|
129
|
-
humanMessage,
|
|
130
|
-
copyOfConfig,
|
|
131
|
-
model
|
|
132
|
-
);
|
|
133
|
-
logger.debug(
|
|
134
|
-
"completion message: " + JSON.stringify(completionMessages.map((it) => it.content))
|
|
135
|
-
);
|
|
136
|
-
let responseMessage;
|
|
137
|
-
let parsedResponse;
|
|
138
|
-
let retryCount = 0;
|
|
139
|
-
while (retryCount < 3) {
|
|
140
|
-
retryCount++;
|
|
141
|
-
try {
|
|
142
|
-
responseMessage = await model.invoke(completionMessages);
|
|
143
|
-
} catch (e) {
|
|
144
|
-
logger.error("model requests failed", e);
|
|
145
|
-
retryCount = 3;
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
try {
|
|
149
|
-
logger.debug("model response: " + responseMessage.content);
|
|
150
|
-
parsedResponse = parseResponse(
|
|
151
|
-
responseMessage.content,
|
|
152
|
-
copyOfConfig.isAt
|
|
153
|
-
);
|
|
154
|
-
break;
|
|
155
|
-
} catch (e) {
|
|
156
|
-
await (0, import_koishi.sleep)(3e3);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (retryCount >= 3) {
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
temp.status = parsedResponse.status;
|
|
163
|
-
if (parsedResponse.elements.length < 1) {
|
|
164
|
-
service.mute(session, copyOfConfig.muteTime);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
temp.completionMessages.push(humanMessage, responseMessage);
|
|
168
|
-
if (temp.completionMessages.length > 5) {
|
|
169
|
-
temp.completionMessages.length = 0;
|
|
170
|
-
}
|
|
171
|
-
const random = new import_koishi.Random();
|
|
172
|
-
for (const elements of parsedResponse.elements) {
|
|
173
|
-
const text = elements.map((element) => element.attrs.content ?? "").join("");
|
|
174
|
-
const emoticonStatement = isEmoticonStatement(text, elements);
|
|
175
|
-
if (elements.length < 1) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
let maxTime = text.length * copyOfConfig.typingTime + 100;
|
|
179
|
-
if (parsedResponse.messageType === "voice" && emoticonStatement !== "text") {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
if (config.splitVoice !== true && parsedResponse.messageType === "voice") {
|
|
183
|
-
maxTime = parsedResponse.rawMessage.length * copyOfConfig.typingTime + 100;
|
|
184
|
-
await (0, import_koishi.sleep)(random.int(maxTime / 4, maxTime / 2));
|
|
185
|
-
try {
|
|
186
|
-
await session.send(
|
|
187
|
-
await ctx.vits.say({ input: parsedResponse.rawMessage })
|
|
188
|
-
);
|
|
189
|
-
} catch (e) {
|
|
190
|
-
logger.error(e);
|
|
191
|
-
await session.send(elements);
|
|
192
|
-
}
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
if (emoticonStatement !== "emo") {
|
|
197
|
-
await (0, import_koishi.sleep)(random.int(maxTime / 2, maxTime));
|
|
198
|
-
} else {
|
|
199
|
-
await (0, import_koishi.sleep)(random.int(maxTime / 8, maxTime / 2));
|
|
200
|
-
}
|
|
201
|
-
switch (parsedResponse.messageType) {
|
|
202
|
-
case "text":
|
|
203
|
-
await session.send(elements);
|
|
204
|
-
break;
|
|
205
|
-
case "voice":
|
|
206
|
-
await session.send(
|
|
207
|
-
await ctx.vits.say({
|
|
208
|
-
input: text
|
|
209
|
-
})
|
|
210
|
-
);
|
|
211
|
-
break;
|
|
212
|
-
default:
|
|
213
|
-
await session.send(elements);
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
} catch (e) {
|
|
217
|
-
logger.error(e);
|
|
218
|
-
await session.send(elements);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
const randomNumber = Math.random();
|
|
222
|
-
if (randomNumber < config.sendStickerProbability) {
|
|
223
|
-
const sticker = await stickerService.randomStickByType(
|
|
224
|
-
parsedResponse.sticker
|
|
225
|
-
);
|
|
226
|
-
await (0, import_koishi.sleep)(random.int(500, 2e3));
|
|
227
|
-
await session.send(sticker);
|
|
228
|
-
}
|
|
229
|
-
service.mute(session, copyOfConfig.coolDownTime * 1e3);
|
|
230
|
-
await service.broadcastOnBot(session, parsedResponse.elements.flat());
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
__name(apply, "apply");
|
|
46
|
+
|
|
47
|
+
// src/utils.ts
|
|
48
|
+
var import_koishi = require("koishi");
|
|
49
|
+
var import_marked = require("marked");
|
|
50
|
+
var import_he = __toESM(require("he"), 1);
|
|
234
51
|
function isEmoticonStatement(text, elements) {
|
|
235
52
|
if (elements.length === 1 && elements[0].attrs["emo"]) {
|
|
236
53
|
return "emo";
|
|
@@ -281,7 +98,7 @@ function parseResponse(response, useAt = true) {
|
|
|
281
98
|
const before = rawMessage.substring(lastAtIndex, at.start);
|
|
282
99
|
if (before.length > 0) {
|
|
283
100
|
parsedMessage += before;
|
|
284
|
-
currentElements.push(...
|
|
101
|
+
currentElements.push(...transform(before));
|
|
285
102
|
}
|
|
286
103
|
if (useAt) {
|
|
287
104
|
currentElements.push(import_koishi.h.at(at.at));
|
|
@@ -291,11 +108,11 @@ function parseResponse(response, useAt = true) {
|
|
|
291
108
|
const after = rawMessage.substring(lastAtIndex);
|
|
292
109
|
if (after.length > 0) {
|
|
293
110
|
parsedMessage += after;
|
|
294
|
-
currentElements.push(...
|
|
111
|
+
currentElements.push(...transform(after));
|
|
295
112
|
}
|
|
296
113
|
} else {
|
|
297
114
|
parsedMessage = rawMessage;
|
|
298
|
-
currentElements.push(...
|
|
115
|
+
currentElements.push(...transform(rawMessage));
|
|
299
116
|
}
|
|
300
117
|
const forEachElement = /* @__PURE__ */ __name((elements) => {
|
|
301
118
|
for (let i = 0; i < elements.length; i++) {
|
|
@@ -313,7 +130,12 @@ function parseResponse(response, useAt = true) {
|
|
|
313
130
|
continue;
|
|
314
131
|
}
|
|
315
132
|
}
|
|
316
|
-
|
|
133
|
+
console.log(element);
|
|
134
|
+
if (element.attrs["code"]) {
|
|
135
|
+
resultElements.push([element]);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const matchArray = splitSentence(import_he.default.decode(text)).filter(
|
|
317
139
|
(x) => x.length > 0
|
|
318
140
|
);
|
|
319
141
|
for (const match of matchArray) {
|
|
@@ -342,32 +164,6 @@ function parseResponse(response, useAt = true) {
|
|
|
342
164
|
};
|
|
343
165
|
}
|
|
344
166
|
__name(parseResponse, "parseResponse");
|
|
345
|
-
function parseXmlToObject(xml) {
|
|
346
|
-
const messageRegex = /<message\s+(.*?)>(.*?)<\/message>/s;
|
|
347
|
-
const match = xml.match(messageRegex);
|
|
348
|
-
if (!match) {
|
|
349
|
-
throw new Error("Failed to parse response: " + xml);
|
|
350
|
-
}
|
|
351
|
-
const [, attributes, content] = match;
|
|
352
|
-
const getAttr = /* @__PURE__ */ __name((name3) => {
|
|
353
|
-
const attrRegex = new RegExp(`${name3}=['"]?([^'"]+)['"]?`);
|
|
354
|
-
const attrMatch = attributes.match(attrRegex);
|
|
355
|
-
if (!attrMatch) {
|
|
356
|
-
logger.warn(`Failed to parse ${name3} attribute: ${xml}`);
|
|
357
|
-
return "";
|
|
358
|
-
}
|
|
359
|
-
return attrMatch[1];
|
|
360
|
-
}, "getAttr");
|
|
361
|
-
const name2 = getAttr("name");
|
|
362
|
-
const id = getAttr("id");
|
|
363
|
-
const type = getAttr("type") || "text";
|
|
364
|
-
const sticker = getAttr("sticker");
|
|
365
|
-
if (content === void 0) {
|
|
366
|
-
throw new Error("Failed to parse content: " + xml);
|
|
367
|
-
}
|
|
368
|
-
return { name: name2, id, type, sticker, content };
|
|
369
|
-
}
|
|
370
|
-
__name(parseXmlToObject, "parseXmlToObject");
|
|
371
167
|
function splitSentence(text) {
|
|
372
168
|
if (isOnlyPunctuation(text)) {
|
|
373
169
|
return [text];
|
|
@@ -459,6 +255,33 @@ function matchAt(str) {
|
|
|
459
255
|
});
|
|
460
256
|
}
|
|
461
257
|
__name(matchAt, "matchAt");
|
|
258
|
+
async function formatMessage(messages, config, model, systemPrompt, historyPrompt) {
|
|
259
|
+
const maxTokens = config.maxTokens - 300;
|
|
260
|
+
let currentTokens = 0;
|
|
261
|
+
currentTokens += await model.getNumTokens(systemPrompt);
|
|
262
|
+
currentTokens += await model.getNumTokens(historyPrompt);
|
|
263
|
+
const calculatedMessages = [];
|
|
264
|
+
const random = new import_koishi.Random();
|
|
265
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
266
|
+
const message = messages[i];
|
|
267
|
+
const voiceProbability = random.int(1, 10) > 8 ? "voice" : "text";
|
|
268
|
+
const xmlMessage = `<message type='${voiceProbability}' name='${message.name}' id='${message.id}'>${message.content}</message>`;
|
|
269
|
+
const xmlMessageToken = await model.getNumTokens(xmlMessage);
|
|
270
|
+
if (currentTokens + xmlMessageToken > maxTokens - 4) {
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
currentTokens += xmlMessageToken;
|
|
274
|
+
calculatedMessages.unshift(xmlMessage);
|
|
275
|
+
}
|
|
276
|
+
const lastMessage = calculatedMessages.pop();
|
|
277
|
+
if (lastMessage === void 0) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
"lastMessage is undefined, please set the max token to be bigger"
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return [calculatedMessages, lastMessage];
|
|
283
|
+
}
|
|
284
|
+
__name(formatMessage, "formatMessage");
|
|
462
285
|
async function formatCompletionMessages(messages, humanMessage, config, model) {
|
|
463
286
|
const maxTokens = config.maxTokens - 600;
|
|
464
287
|
const systemMessage = messages.shift();
|
|
@@ -483,34 +306,277 @@ async function formatCompletionMessages(messages, humanMessage, config, model) {
|
|
|
483
306
|
return result;
|
|
484
307
|
}
|
|
485
308
|
__name(formatCompletionMessages, "formatCompletionMessages");
|
|
486
|
-
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
break;
|
|
309
|
+
function parseXmlToObject(xml) {
|
|
310
|
+
const messageRegex = /<message\s+(.*?)>(.*?)<\/message>/s;
|
|
311
|
+
const match = xml.match(messageRegex);
|
|
312
|
+
if (!match) {
|
|
313
|
+
throw new Error("Failed to parse response: " + xml);
|
|
314
|
+
}
|
|
315
|
+
const [, attributes, content] = match;
|
|
316
|
+
const getAttr = /* @__PURE__ */ __name((name3) => {
|
|
317
|
+
const attrRegex = new RegExp(`${name3}=['"]?([^'"]+)['"]?`);
|
|
318
|
+
const attrMatch = attributes.match(attrRegex);
|
|
319
|
+
if (!attrMatch) {
|
|
320
|
+
logger.warn(`Failed to parse ${name3} attribute: ${xml}`);
|
|
321
|
+
return "";
|
|
500
322
|
}
|
|
501
|
-
|
|
502
|
-
|
|
323
|
+
return attrMatch[1];
|
|
324
|
+
}, "getAttr");
|
|
325
|
+
const name2 = getAttr("name");
|
|
326
|
+
const id = getAttr("id");
|
|
327
|
+
const type = getAttr("type") || "text";
|
|
328
|
+
const sticker = getAttr("sticker");
|
|
329
|
+
if (content === void 0) {
|
|
330
|
+
throw new Error("Failed to parse content: " + xml);
|
|
503
331
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
332
|
+
return { name: name2, id, type, sticker, content };
|
|
333
|
+
}
|
|
334
|
+
__name(parseXmlToObject, "parseXmlToObject");
|
|
335
|
+
var tagRegExp = /^<(\/?)([^!\s>/]+)([^>]*?)\s*(\/?)>$/;
|
|
336
|
+
function renderToken(token) {
|
|
337
|
+
if (token.type === "code") {
|
|
338
|
+
return (0, import_koishi.h)("text", { code: true, content: token.text + "\n" });
|
|
339
|
+
} else if (token.type === "paragraph") {
|
|
340
|
+
return (0, import_koishi.h)("p", render(token.tokens));
|
|
341
|
+
} else if (token.type === "image") {
|
|
342
|
+
return import_koishi.h.image(token.href);
|
|
343
|
+
} else if (token.type === "blockquote") {
|
|
344
|
+
return (0, import_koishi.h)("text", { content: token.text + "\n" });
|
|
345
|
+
} else if (token.type === "text") {
|
|
346
|
+
return (0, import_koishi.h)("text", { content: token.text });
|
|
347
|
+
} else if (token.type === "em") {
|
|
348
|
+
return (0, import_koishi.h)("em", render(token.tokens));
|
|
349
|
+
} else if (token.type === "strong") {
|
|
350
|
+
return (0, import_koishi.h)("strong", render(token.tokens));
|
|
351
|
+
} else if (token.type === "del") {
|
|
352
|
+
return (0, import_koishi.h)("del", render(token.tokens));
|
|
353
|
+
} else if (token.type === "link") {
|
|
354
|
+
return (0, import_koishi.h)("a", { href: token.href }, render(token.tokens));
|
|
355
|
+
} else if (token.type === "html") {
|
|
356
|
+
const cap = tagRegExp.exec(token.text);
|
|
357
|
+
if (!cap) {
|
|
358
|
+
return (0, import_koishi.h)("text", { content: token.text });
|
|
359
|
+
}
|
|
360
|
+
if (cap[2] === "img") {
|
|
361
|
+
if (cap[1]) return;
|
|
362
|
+
const src = cap[3].match(/src="([^"]+)"/);
|
|
363
|
+
if (src) return import_koishi.h.image(src[1]);
|
|
364
|
+
}
|
|
509
365
|
}
|
|
510
|
-
return
|
|
366
|
+
return (0, import_koishi.h)("text", { content: token.raw });
|
|
511
367
|
}
|
|
512
|
-
__name(
|
|
368
|
+
__name(renderToken, "renderToken");
|
|
369
|
+
function render(tokens) {
|
|
370
|
+
return tokens.map(renderToken).filter(Boolean);
|
|
371
|
+
}
|
|
372
|
+
__name(render, "render");
|
|
373
|
+
function transform(source, ...args) {
|
|
374
|
+
if (!source) return [];
|
|
375
|
+
if (Array.isArray(source)) {
|
|
376
|
+
source = args.map((arg, index) => source[index] + arg).join("") + source[args.length];
|
|
377
|
+
}
|
|
378
|
+
return render(import_marked.marked.lexer(source));
|
|
379
|
+
}
|
|
380
|
+
__name(transform, "transform");
|
|
513
381
|
var logger;
|
|
382
|
+
function setLogger(setLogger2) {
|
|
383
|
+
logger = setLogger2;
|
|
384
|
+
}
|
|
385
|
+
__name(setLogger, "setLogger");
|
|
386
|
+
|
|
387
|
+
// src/plugins/chat.ts
|
|
388
|
+
async function apply(ctx, config) {
|
|
389
|
+
const service = ctx.chatluna_character;
|
|
390
|
+
const preset = service.preset;
|
|
391
|
+
const stickerService = service.stickerService;
|
|
392
|
+
logger2 = service.logger;
|
|
393
|
+
setLogger(logger2);
|
|
394
|
+
const modelPool = {};
|
|
395
|
+
const [platform, modelName] = (0, import_count_tokens.parseRawModelName)(config.model);
|
|
396
|
+
await ctx.chatluna.awaitLoadPlatform(platform);
|
|
397
|
+
const globalModel = await ctx.chatluna.createChatModel(
|
|
398
|
+
platform,
|
|
399
|
+
modelName
|
|
400
|
+
);
|
|
401
|
+
logger2.info("global model loaded %c", config.model);
|
|
402
|
+
if (config.modelOverride?.length > 0) {
|
|
403
|
+
for (const override of config.modelOverride) {
|
|
404
|
+
modelPool[override.groupId] = (async () => {
|
|
405
|
+
const [platform2, modelName2] = (0, import_count_tokens.parseRawModelName)(override.model);
|
|
406
|
+
await ctx.chatluna.awaitLoadPlatform(platform2);
|
|
407
|
+
const loadedModel = await ctx.chatluna.createChatModel(
|
|
408
|
+
platform2,
|
|
409
|
+
modelName2
|
|
410
|
+
);
|
|
411
|
+
logger2.info(
|
|
412
|
+
"override model loaded %c for group %c",
|
|
413
|
+
override.model,
|
|
414
|
+
override.groupId
|
|
415
|
+
);
|
|
416
|
+
modelPool[override.groupId] = Promise.resolve(loadedModel);
|
|
417
|
+
return loadedModel;
|
|
418
|
+
})();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
let globalPreset = preset.getPresetForCache(config.defaultPreset);
|
|
422
|
+
let presetPool = {};
|
|
423
|
+
ctx.on("chatluna_character/preset_updated", () => {
|
|
424
|
+
globalPreset = preset.getPresetForCache(config.defaultPreset);
|
|
425
|
+
presetPool = {};
|
|
426
|
+
});
|
|
427
|
+
service.collect(async (session, messages) => {
|
|
428
|
+
const guildId = session.event.guild?.id ?? session.guildId;
|
|
429
|
+
const model = await (modelPool[guildId] ?? Promise.resolve(globalModel));
|
|
430
|
+
const currentGuildConfig = config.configs[guildId];
|
|
431
|
+
let copyOfConfig = Object.assign({}, config);
|
|
432
|
+
let currentPreset = globalPreset;
|
|
433
|
+
if (currentGuildConfig != null) {
|
|
434
|
+
copyOfConfig = Object.assign({}, copyOfConfig, currentGuildConfig);
|
|
435
|
+
currentPreset = presetPool[guildId] ?? await (async () => {
|
|
436
|
+
const template = preset.getPresetForCache(
|
|
437
|
+
currentGuildConfig.preset
|
|
438
|
+
);
|
|
439
|
+
presetPool[guildId] = template;
|
|
440
|
+
return template;
|
|
441
|
+
})();
|
|
442
|
+
}
|
|
443
|
+
const [recentMessage, lastMessage] = await formatMessage(
|
|
444
|
+
messages,
|
|
445
|
+
copyOfConfig,
|
|
446
|
+
model,
|
|
447
|
+
currentPreset.system.template,
|
|
448
|
+
currentPreset.system.template
|
|
449
|
+
);
|
|
450
|
+
const temp = await service.getTemp(session);
|
|
451
|
+
const formattedSystemPrompt = await currentPreset.system.format({
|
|
452
|
+
time: (/* @__PURE__ */ new Date()).toLocaleString(),
|
|
453
|
+
status: temp.status ?? currentPreset.status ?? "",
|
|
454
|
+
stickers: JSON.stringify(stickerService.getAllStickTypes())
|
|
455
|
+
});
|
|
456
|
+
logger2.debug("messages_new: " + JSON.stringify(recentMessage));
|
|
457
|
+
logger2.debug("messages_last: " + JSON.stringify(lastMessage));
|
|
458
|
+
const humanMessage = new import_messages.HumanMessage(
|
|
459
|
+
await currentPreset.input.format({
|
|
460
|
+
history_new: recentMessage,
|
|
461
|
+
history_last: lastMessage,
|
|
462
|
+
time: (/* @__PURE__ */ new Date()).toLocaleString(),
|
|
463
|
+
stickers: JSON.stringify(stickerService.getAllStickTypes()),
|
|
464
|
+
status: temp.status ?? currentPreset.status ?? ""
|
|
465
|
+
})
|
|
466
|
+
);
|
|
467
|
+
const completionMessages = await formatCompletionMessages(
|
|
468
|
+
[new import_messages.SystemMessage(formattedSystemPrompt)].concat(
|
|
469
|
+
temp.completionMessages
|
|
470
|
+
),
|
|
471
|
+
humanMessage,
|
|
472
|
+
copyOfConfig,
|
|
473
|
+
model
|
|
474
|
+
);
|
|
475
|
+
logger2.debug(
|
|
476
|
+
"completion message: " + JSON.stringify(completionMessages.map((it) => it.content))
|
|
477
|
+
);
|
|
478
|
+
let responseMessage;
|
|
479
|
+
let parsedResponse;
|
|
480
|
+
let retryCount = 0;
|
|
481
|
+
while (retryCount < 3) {
|
|
482
|
+
retryCount++;
|
|
483
|
+
try {
|
|
484
|
+
responseMessage = await model.invoke(completionMessages);
|
|
485
|
+
} catch (e) {
|
|
486
|
+
logger2.error("model requests failed", e);
|
|
487
|
+
retryCount = 3;
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
try {
|
|
491
|
+
logger2.debug("model response: " + responseMessage.content);
|
|
492
|
+
parsedResponse = parseResponse(
|
|
493
|
+
responseMessage.content,
|
|
494
|
+
copyOfConfig.isAt
|
|
495
|
+
);
|
|
496
|
+
break;
|
|
497
|
+
} catch (e) {
|
|
498
|
+
await (0, import_koishi2.sleep)(3e3);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (retryCount >= 3) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
temp.status = parsedResponse.status;
|
|
505
|
+
if (parsedResponse.elements.length < 1) {
|
|
506
|
+
service.mute(session, copyOfConfig.muteTime);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
temp.completionMessages.push(humanMessage, responseMessage);
|
|
510
|
+
if (temp.completionMessages.length > 5) {
|
|
511
|
+
temp.completionMessages.length = 0;
|
|
512
|
+
}
|
|
513
|
+
const random = new import_koishi2.Random();
|
|
514
|
+
for (const elements of parsedResponse.elements) {
|
|
515
|
+
const text = elements.map((element) => element.attrs.content ?? "").join("");
|
|
516
|
+
const emoticonStatement = isEmoticonStatement(text, elements);
|
|
517
|
+
if (elements.length < 1) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
let maxTime = text.length * copyOfConfig.typingTime + 100;
|
|
521
|
+
if (elements.length === 1 && elements[0].attrs["code"] === true) {
|
|
522
|
+
maxTime = 10;
|
|
523
|
+
}
|
|
524
|
+
if (parsedResponse.messageType === "voice" && emoticonStatement !== "text") {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (config.splitVoice !== true && parsedResponse.messageType === "voice") {
|
|
528
|
+
maxTime = parsedResponse.rawMessage.length * copyOfConfig.typingTime + 100;
|
|
529
|
+
await (0, import_koishi2.sleep)(random.int(maxTime / 4, maxTime / 2));
|
|
530
|
+
try {
|
|
531
|
+
await session.send(
|
|
532
|
+
await ctx.vits.say({ input: parsedResponse.rawMessage })
|
|
533
|
+
);
|
|
534
|
+
} catch (e) {
|
|
535
|
+
logger2.error(e);
|
|
536
|
+
await session.send(elements);
|
|
537
|
+
}
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
if (emoticonStatement !== "emo") {
|
|
542
|
+
await (0, import_koishi2.sleep)(random.int(maxTime / 2, maxTime));
|
|
543
|
+
} else {
|
|
544
|
+
await (0, import_koishi2.sleep)(random.int(maxTime / 8, maxTime / 2));
|
|
545
|
+
}
|
|
546
|
+
switch (parsedResponse.messageType) {
|
|
547
|
+
case "text":
|
|
548
|
+
await session.send(elements);
|
|
549
|
+
break;
|
|
550
|
+
case "voice":
|
|
551
|
+
await session.send(
|
|
552
|
+
await ctx.vits.say({
|
|
553
|
+
input: text
|
|
554
|
+
})
|
|
555
|
+
);
|
|
556
|
+
break;
|
|
557
|
+
default:
|
|
558
|
+
await session.send(elements);
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
} catch (e) {
|
|
562
|
+
logger2.error(e);
|
|
563
|
+
await session.send(elements);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
const randomNumber = Math.random();
|
|
567
|
+
if (randomNumber < config.sendStickerProbability) {
|
|
568
|
+
const sticker = await stickerService.randomStickByType(
|
|
569
|
+
parsedResponse.sticker
|
|
570
|
+
);
|
|
571
|
+
await (0, import_koishi2.sleep)(random.int(500, 2e3));
|
|
572
|
+
await session.send(sticker);
|
|
573
|
+
}
|
|
574
|
+
service.mute(session, copyOfConfig.coolDownTime * 1e3);
|
|
575
|
+
await service.broadcastOnBot(session, parsedResponse.elements.flat());
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
__name(apply, "apply");
|
|
579
|
+
var logger2;
|
|
514
580
|
|
|
515
581
|
// src/plugins/filter.ts
|
|
516
582
|
var groupInfos = {};
|
|
@@ -518,7 +584,7 @@ async function apply2(ctx, config) {
|
|
|
518
584
|
const maxMessages = config.messageInterval;
|
|
519
585
|
const service = ctx.chatluna_character;
|
|
520
586
|
const preset = service.preset;
|
|
521
|
-
const
|
|
587
|
+
const logger3 = service.logger;
|
|
522
588
|
const globalPreset = await preset.getPreset(config.defaultPreset);
|
|
523
589
|
const presetPool = {};
|
|
524
590
|
service.addFilter((session, message) => {
|
|
@@ -541,7 +607,7 @@ async function apply2(ctx, config) {
|
|
|
541
607
|
})();
|
|
542
608
|
}
|
|
543
609
|
let { messageCount, messageSendProbability } = info;
|
|
544
|
-
|
|
610
|
+
logger3.debug(
|
|
545
611
|
`messageCount: ${messageCount}, messageSendProbability: ${messageSendProbability}. content: ${JSON.stringify(
|
|
546
612
|
message
|
|
547
613
|
)}`
|
|
@@ -569,7 +635,7 @@ async function apply2(ctx, config) {
|
|
|
569
635
|
(value) => message.content.includes(value)
|
|
570
636
|
);
|
|
571
637
|
if (needMute) {
|
|
572
|
-
|
|
638
|
+
logger3.debug(`mute content: ${message.content}`);
|
|
573
639
|
service.mute(session, config.muteTime);
|
|
574
640
|
}
|
|
575
641
|
}
|
|
@@ -625,20 +691,20 @@ function apply3(ctx, config) {
|
|
|
625
691
|
__name(apply3, "apply");
|
|
626
692
|
|
|
627
693
|
// src/plugins/config.ts
|
|
628
|
-
var
|
|
694
|
+
var import_koishi3 = require("koishi");
|
|
629
695
|
var import_types = require("koishi-plugin-chatluna/llm-core/platform/types");
|
|
630
696
|
async function apply4(ctx, config) {
|
|
631
697
|
ctx.on("chatluna/model-added", (service) => {
|
|
632
|
-
ctx.schema.set("model",
|
|
698
|
+
ctx.schema.set("model", import_koishi3.Schema.union(getModelNames(service)));
|
|
633
699
|
});
|
|
634
700
|
ctx.on("chatluna/model-removed", (service) => {
|
|
635
|
-
ctx.schema.set("model",
|
|
701
|
+
ctx.schema.set("model", import_koishi3.Schema.union(getModelNames(service)));
|
|
636
702
|
});
|
|
637
|
-
ctx.schema.set("model",
|
|
703
|
+
ctx.schema.set("model", import_koishi3.Schema.union(getModelNames(ctx.chatluna.platform)));
|
|
638
704
|
}
|
|
639
705
|
__name(apply4, "apply");
|
|
640
706
|
function getModelNames(service) {
|
|
641
|
-
return service.getAllModels(import_types.ModelType.llm).map((m) =>
|
|
707
|
+
return service.getAllModels(import_types.ModelType.llm).map((m) => import_koishi3.Schema.const(m));
|
|
642
708
|
}
|
|
643
709
|
__name(getModelNames, "getModelNames");
|
|
644
710
|
|
|
@@ -686,14 +752,14 @@ __name(plugins, "plugins");
|
|
|
686
752
|
|
|
687
753
|
// src/service/message.ts
|
|
688
754
|
var import_events = __toESM(require("events"), 1);
|
|
689
|
-
var
|
|
755
|
+
var import_koishi6 = require("koishi");
|
|
690
756
|
var import_logger = require("koishi-plugin-chatluna/utils/logger");
|
|
691
757
|
|
|
692
758
|
// src/preset.ts
|
|
693
759
|
var import_prompts = require("@langchain/core/prompts");
|
|
694
760
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
695
761
|
var import_js_yaml = require("js-yaml");
|
|
696
|
-
var
|
|
762
|
+
var import_koishi4 = require("koishi");
|
|
697
763
|
var import_error = require("koishi-plugin-chatluna/utils/error");
|
|
698
764
|
var import_path = __toESM(require("path"), 1);
|
|
699
765
|
var import_url = require("url");
|
|
@@ -731,8 +797,8 @@ var Preset = class {
|
|
|
731
797
|
}
|
|
732
798
|
this.ctx.schema.set(
|
|
733
799
|
"character-preset",
|
|
734
|
-
|
|
735
|
-
this._presets.map((preset) => preset.name).concat("无").map((name2) =>
|
|
800
|
+
import_koishi4.Schema.union(
|
|
801
|
+
this._presets.map((preset) => preset.name).concat("无").map((name2) => import_koishi4.Schema.const(name2))
|
|
736
802
|
)
|
|
737
803
|
);
|
|
738
804
|
this.ctx.emit("chatluna_character/preset_updated");
|
|
@@ -859,7 +925,7 @@ function loadPreset(text) {
|
|
|
859
925
|
__name(loadPreset, "loadPreset");
|
|
860
926
|
|
|
861
927
|
// src/service/sticker.ts
|
|
862
|
-
var
|
|
928
|
+
var import_koishi5 = require("koishi");
|
|
863
929
|
var import_path2 = __toESM(require("path"), 1);
|
|
864
930
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
865
931
|
var import_url2 = require("url");
|
|
@@ -919,7 +985,7 @@ var StickerService = class {
|
|
|
919
985
|
this._ctx.root.chatluna_character.logger.debug(
|
|
920
986
|
`send sticker: ${sticker}`
|
|
921
987
|
);
|
|
922
|
-
return
|
|
988
|
+
return import_koishi5.h.image(await (0, import_promises2.readFile)(sticker), `image/${getFileType(sticker)}`);
|
|
923
989
|
}
|
|
924
990
|
async randomStick() {
|
|
925
991
|
const allStickers = Object.values(this._stickers).flat();
|
|
@@ -931,7 +997,7 @@ var StickerService = class {
|
|
|
931
997
|
this._ctx.root.chatluna_character.logger.debug(
|
|
932
998
|
`send sticker: ${sticker}`
|
|
933
999
|
);
|
|
934
|
-
return
|
|
1000
|
+
return import_koishi5.h.image(await (0, import_promises2.readFile)(sticker), `image/${getFileType(sticker)}`);
|
|
935
1001
|
}
|
|
936
1002
|
};
|
|
937
1003
|
function getFileType(path3) {
|
|
@@ -944,7 +1010,7 @@ function getFileType(path3) {
|
|
|
944
1010
|
__name(getFileType, "getFileType");
|
|
945
1011
|
|
|
946
1012
|
// src/service/message.ts
|
|
947
|
-
var MessageCollector = class extends
|
|
1013
|
+
var MessageCollector = class extends import_koishi6.Service {
|
|
948
1014
|
constructor(ctx, _config) {
|
|
949
1015
|
super(ctx, "chatluna_character");
|
|
950
1016
|
this.ctx = ctx;
|
|
@@ -1080,7 +1146,7 @@ var MessageCollector = class extends import_koishi5.Service {
|
|
|
1080
1146
|
const groupId = session.guildId;
|
|
1081
1147
|
const maxMessageSize = this._config.maxMessages;
|
|
1082
1148
|
const groupArray = this._messages[groupId] ? this._messages[groupId] : [];
|
|
1083
|
-
const elements = session.elements ? session.elements : [
|
|
1149
|
+
const elements = session.elements ? session.elements : [import_koishi6.h.text(session.content)];
|
|
1084
1150
|
const content = mapElementToString(session, session.content, elements);
|
|
1085
1151
|
if (content.length < 1) {
|
|
1086
1152
|
await this._unlock(session);
|
|
@@ -1101,7 +1167,7 @@ var MessageCollector = class extends import_koishi5.Service {
|
|
|
1101
1167
|
session,
|
|
1102
1168
|
session.quote.content,
|
|
1103
1169
|
session.quote.elements ?? [
|
|
1104
|
-
|
|
1170
|
+
import_koishi6.h.text(session.quote.content)
|
|
1105
1171
|
]
|
|
1106
1172
|
),
|
|
1107
1173
|
name: session.quote.user.name,
|
|
@@ -1223,57 +1289,57 @@ var inject2 = {
|
|
|
1223
1289
|
required: false
|
|
1224
1290
|
}
|
|
1225
1291
|
};
|
|
1226
|
-
var Config =
|
|
1227
|
-
|
|
1228
|
-
applyGroup:
|
|
1229
|
-
maxMessages:
|
|
1230
|
-
disableChatLuna:
|
|
1231
|
-
whiteListDisableChatLuna:
|
|
1292
|
+
var Config = import_koishi7.Schema.intersect([
|
|
1293
|
+
import_koishi7.Schema.object({
|
|
1294
|
+
applyGroup: import_koishi7.Schema.array(import_koishi7.Schema.string()).description("应用到的群组"),
|
|
1295
|
+
maxMessages: import_koishi7.Schema.number().description("存储在内存里的最大消息数量").default(10).min(3).role("slider").max(100),
|
|
1296
|
+
disableChatLuna: import_koishi7.Schema.boolean().default(true).description("在使用此插件的群聊里,是否禁用 ChatLuna 主功能"),
|
|
1297
|
+
whiteListDisableChatLuna: import_koishi7.Schema.array(import_koishi7.Schema.string()).description(
|
|
1232
1298
|
"在使用此插件时,不禁用 ChatLuna 主功能的群聊列表"
|
|
1233
1299
|
)
|
|
1234
1300
|
}).description("基础配置"),
|
|
1235
|
-
|
|
1236
|
-
model:
|
|
1237
|
-
modelOverride:
|
|
1238
|
-
|
|
1239
|
-
groupId:
|
|
1240
|
-
model:
|
|
1301
|
+
import_koishi7.Schema.object({
|
|
1302
|
+
model: import_koishi7.Schema.dynamic("model").description("使用的模型"),
|
|
1303
|
+
modelOverride: import_koishi7.Schema.array(
|
|
1304
|
+
import_koishi7.Schema.object({
|
|
1305
|
+
groupId: import_koishi7.Schema.string().required().description("群组 ID"),
|
|
1306
|
+
model: import_koishi7.Schema.dynamic("model").description("模型")
|
|
1241
1307
|
})
|
|
1242
1308
|
).description("针对某个群的模型设置,会覆盖上面的配置"),
|
|
1243
|
-
maxTokens:
|
|
1309
|
+
maxTokens: import_koishi7.Schema.number().default(5e3).min(1024).max(32e3).description("使用聊天的最大 token 数")
|
|
1244
1310
|
}).description("模型配置"),
|
|
1245
|
-
|
|
1246
|
-
isNickname:
|
|
1247
|
-
isForceMute:
|
|
1311
|
+
import_koishi7.Schema.object({
|
|
1312
|
+
isNickname: import_koishi7.Schema.boolean().description("允许 bot 配置中的昵称引发回复").default(true),
|
|
1313
|
+
isForceMute: import_koishi7.Schema.boolean().description(
|
|
1248
1314
|
"是否启用强制禁言(当聊天涉及到关键词时则会禁言,关键词需要在预设文件里配置)"
|
|
1249
1315
|
).default(true),
|
|
1250
|
-
isAt:
|
|
1251
|
-
splitVoice:
|
|
1252
|
-
messageInterval:
|
|
1253
|
-
messageProbability:
|
|
1254
|
-
coolDownTime:
|
|
1255
|
-
typingTime:
|
|
1256
|
-
muteTime:
|
|
1257
|
-
sendStickerProbability:
|
|
1258
|
-
defaultPreset:
|
|
1316
|
+
isAt: import_koishi7.Schema.boolean().description("是否允许 bot 艾特他人").default(true),
|
|
1317
|
+
splitVoice: import_koishi7.Schema.boolean().description("是否分段发送语言").default(false),
|
|
1318
|
+
messageInterval: import_koishi7.Schema.number().default(14).min(0).role("slider").max(100).description("随机发送消息的间隔"),
|
|
1319
|
+
messageProbability: import_koishi7.Schema.number().default(0.1).min(0).max(4).role("slider").step(1e-5).description("发送消息的叠加概率(线性增长)"),
|
|
1320
|
+
coolDownTime: import_koishi7.Schema.number().default(10).min(1).max(60 * 24).description("冷却发言时间(秒)"),
|
|
1321
|
+
typingTime: import_koishi7.Schema.number().default(440).min(100).role("slider").max(1500).description("模拟打字时的间隔(毫秒)"),
|
|
1322
|
+
muteTime: import_koishi7.Schema.number().default(1e3 * 60).min(1e3).max(1e3 * 60 * 10 * 10).description("闭嘴时的禁言时间(毫秒)"),
|
|
1323
|
+
sendStickerProbability: import_koishi7.Schema.number().default(0.6).min(0).max(1).role("slider").step(0.01).description("发送表情的概率"),
|
|
1324
|
+
defaultPreset: import_koishi7.Schema.dynamic("character-preset").description("使用的伪装预设").default("旧梦旧念")
|
|
1259
1325
|
}).description("对话设置"),
|
|
1260
|
-
|
|
1261
|
-
configs:
|
|
1262
|
-
|
|
1263
|
-
maxTokens:
|
|
1264
|
-
isAt:
|
|
1265
|
-
splitVoice:
|
|
1266
|
-
isNickname:
|
|
1267
|
-
isForceMute:
|
|
1326
|
+
import_koishi7.Schema.object({
|
|
1327
|
+
configs: import_koishi7.Schema.dict(
|
|
1328
|
+
import_koishi7.Schema.object({
|
|
1329
|
+
maxTokens: import_koishi7.Schema.number().default(4e3).min(1024).max(2e4).description("使用聊天的最大 token 数"),
|
|
1330
|
+
isAt: import_koishi7.Schema.boolean().description("是否启用@").default(true),
|
|
1331
|
+
splitVoice: import_koishi7.Schema.boolean().description("是否分段发送语言").default(false),
|
|
1332
|
+
isNickname: import_koishi7.Schema.boolean().description("允许 bot 配置中的昵称引发回复").default(true),
|
|
1333
|
+
isForceMute: import_koishi7.Schema.boolean().description(
|
|
1268
1334
|
"是否启用强制禁言(当聊天涉及到关键词时则会禁言,关键词需要在预设文件里配置)"
|
|
1269
1335
|
).default(true),
|
|
1270
|
-
messageInterval:
|
|
1271
|
-
messageProbability:
|
|
1272
|
-
coolDownTime:
|
|
1273
|
-
typingTime:
|
|
1274
|
-
muteTime:
|
|
1275
|
-
sendStickerProbability:
|
|
1276
|
-
preset:
|
|
1336
|
+
messageInterval: import_koishi7.Schema.number().default(10).min(0).role("slider").max(50).description("随机发送消息的间隔"),
|
|
1337
|
+
messageProbability: import_koishi7.Schema.number().default(0.1).min(0).max(1).role("slider").step(1e-5).description("发送消息的叠加概率(线性增长)"),
|
|
1338
|
+
coolDownTime: import_koishi7.Schema.number().default(10).min(1).max(60 * 24 * 24).description("冷却发言时间(秒)"),
|
|
1339
|
+
typingTime: import_koishi7.Schema.number().default(440).min(100).role("slider").max(1700).description("模拟打字时的间隔(毫秒)"),
|
|
1340
|
+
muteTime: import_koishi7.Schema.number().default(1e3 * 60).min(1e3).max(1e3 * 60 * 10 * 10).description("闭嘴时的禁言时间(毫秒)"),
|
|
1341
|
+
sendStickerProbability: import_koishi7.Schema.number().default(0.2).min(0).max(1).role("slider").step(0.01).description("发送表情的概率"),
|
|
1342
|
+
preset: import_koishi7.Schema.dynamic("character-preset").description("使用的伪装预设").default("煕")
|
|
1277
1343
|
})
|
|
1278
1344
|
).role("table").description("分群配置,会覆盖上面的默认配置(键填写群号)")
|
|
1279
1345
|
}).description("分群配置")
|