koishi-plugin-chatluna-character 0.0.30
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/LICENSE +661 -0
- package/lib/index.d.ts +29 -0
- package/lib/index.js +102 -0
- package/lib/plugin.d.ts +3 -0
- package/lib/plugin.js +21 -0
- package/lib/plugins/chat.d.ts +3 -0
- package/lib/plugins/chat.js +268 -0
- package/lib/plugins/commands.d.ts +3 -0
- package/lib/plugins/commands.js +26 -0
- package/lib/plugins/config.d.ts +3 -0
- package/lib/plugins/config.js +17 -0
- package/lib/plugins/filter.d.ts +8 -0
- package/lib/plugins/filter.js +52 -0
- package/lib/plugins/interception.d.ts +3 -0
- package/lib/plugins/interception.js +12 -0
- package/lib/preset.d.ts +16 -0
- package/lib/preset.js +122 -0
- package/lib/service/message.d.ts +27 -0
- package/lib/service/message.js +201 -0
- package/lib/service/sticker.d.ts +10 -0
- package/lib/service/sticker.js +78 -0
- package/lib/types.d.ts +23 -0
- package/lib/types.js +2 -0
- package/package.json +76 -0
- package/readme.md +24 -0
- package/resources/presets/default.yml +98 -0
- package/resources/sticker/DSXU1341.GIF +0 -0
- package/resources/sticker/FACR7741.GIF +0 -0
- package/resources/sticker/FXMX5960.GIF +0 -0
- package/resources/sticker/FXUU7881.GIF +0 -0
- package/resources/sticker/HAPT6605.GIF +0 -0
- package/resources/sticker/HGIU2686.GIF +0 -0
- package/resources/sticker/KPWR7742.GIF +0 -0
- package/resources/sticker/NNUJ4070.GIF +0 -0
- package/resources/sticker/OUWC3312.GIF +0 -0
- package/resources/sticker/PNER5039.GIF +0 -0
- package/resources/sticker/ROEW6722.GIF +0 -0
- package/resources/sticker/UARJ9628.GIF +0 -0
- package/resources/sticker/UQSM5271.GIF +0 -0
- package/resources/sticker/WALC6593.GIF +0 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Context, Logger, Schema } from 'koishi';
|
|
2
|
+
import { ChatLunaPlugin } from 'koishi-plugin-chatluna/lib/services/chat';
|
|
3
|
+
import { MessageCollector } from './service/message';
|
|
4
|
+
import { StickerService } from './service/sticker';
|
|
5
|
+
import { Preset } from './preset';
|
|
6
|
+
export declare let service: MessageCollector;
|
|
7
|
+
export declare let stickerService: StickerService;
|
|
8
|
+
export declare let preset: Preset;
|
|
9
|
+
export declare let logger: Logger;
|
|
10
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
11
|
+
export interface Config extends ChatLunaPlugin.Config {
|
|
12
|
+
model: string;
|
|
13
|
+
maxMessages: number;
|
|
14
|
+
messageInterval: number;
|
|
15
|
+
checkPromptInject: boolean;
|
|
16
|
+
maxTokens: number;
|
|
17
|
+
applyGroup: string[];
|
|
18
|
+
defaultPreset: string;
|
|
19
|
+
isNickname: boolean;
|
|
20
|
+
isForceMute: boolean;
|
|
21
|
+
sendStickerProbability: number;
|
|
22
|
+
coolDownTime: number;
|
|
23
|
+
typingTime: number;
|
|
24
|
+
muteTime: number;
|
|
25
|
+
disableChatHub: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare const Config: Schema<Config>;
|
|
28
|
+
export declare const inject: string[];
|
|
29
|
+
export declare const name = "chatluna-character";
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.name = exports.inject = exports.Config = exports.apply = exports.logger = exports.preset = exports.stickerService = exports.service = void 0;
|
|
4
|
+
/* eslint-disable max-len */
|
|
5
|
+
const koishi_1 = require("koishi");
|
|
6
|
+
const plugin_1 = require("./plugin");
|
|
7
|
+
const message_1 = require("./service/message");
|
|
8
|
+
const sticker_1 = require("./service/sticker");
|
|
9
|
+
const preset_1 = require("./preset");
|
|
10
|
+
const logger_1 = require("koishi-plugin-chatluna/lib/utils/logger");
|
|
11
|
+
function apply(ctx, config) {
|
|
12
|
+
exports.logger = (0, logger_1.createLogger)(ctx, 'chathub-character');
|
|
13
|
+
ctx.on('ready', async () => {
|
|
14
|
+
exports.service = new message_1.MessageCollector(config);
|
|
15
|
+
exports.stickerService = new sticker_1.StickerService(ctx, config);
|
|
16
|
+
exports.preset = new preset_1.Preset(ctx);
|
|
17
|
+
await exports.stickerService.init();
|
|
18
|
+
await exports.preset.loadAllPreset();
|
|
19
|
+
await (0, plugin_1.plugins)(ctx, config);
|
|
20
|
+
});
|
|
21
|
+
ctx.on('message', async (session) => {
|
|
22
|
+
if (!exports.service) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!session.isDirect &&
|
|
26
|
+
config.applyGroup.some((group) => group === session.guildId)) {
|
|
27
|
+
await exports.service.broadcast(session);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
ctx.on('dispose', async () => {
|
|
31
|
+
exports.service = null;
|
|
32
|
+
exports.stickerService = null;
|
|
33
|
+
exports.preset = null;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
exports.apply = apply;
|
|
37
|
+
exports.Config = koishi_1.Schema.intersect([
|
|
38
|
+
koishi_1.Schema.object({
|
|
39
|
+
applyGroup: koishi_1.Schema.array(koishi_1.Schema.string()).description('应用到的群组'),
|
|
40
|
+
maxMessages: koishi_1.Schema.number()
|
|
41
|
+
.description('存储在内存里的最大消息数量')
|
|
42
|
+
.default(10)
|
|
43
|
+
.min(7)
|
|
44
|
+
.role('slider')
|
|
45
|
+
.max(40),
|
|
46
|
+
disableChatHub: koishi_1.Schema.boolean()
|
|
47
|
+
.default(true)
|
|
48
|
+
.description('在使用此插件时,是否禁用 chathub 的功能')
|
|
49
|
+
}).description('基础配置'),
|
|
50
|
+
koishi_1.Schema.object({
|
|
51
|
+
model: koishi_1.Schema.dynamic('model').description('使用的模型'),
|
|
52
|
+
maxTokens: koishi_1.Schema.number()
|
|
53
|
+
.default(2048)
|
|
54
|
+
.min(1024)
|
|
55
|
+
.max(8072)
|
|
56
|
+
.description('使用聊天的最大 token 数')
|
|
57
|
+
}).description('模型配置'),
|
|
58
|
+
koishi_1.Schema.object({
|
|
59
|
+
isNickname: koishi_1.Schema.boolean()
|
|
60
|
+
.description('允许 bot 配置中的昵称引发回复')
|
|
61
|
+
.default(true),
|
|
62
|
+
isForceMute: koishi_1.Schema.boolean()
|
|
63
|
+
.description('是否启用强制禁言(当聊天涉及到关键词时则会禁言,关键词需要在预设文件里配置)')
|
|
64
|
+
.default(true),
|
|
65
|
+
messageInterval: koishi_1.Schema.number()
|
|
66
|
+
.default(14)
|
|
67
|
+
.min(0)
|
|
68
|
+
.role('slider')
|
|
69
|
+
.max(30)
|
|
70
|
+
.description('随机发送消息的间隔'),
|
|
71
|
+
coolDownTime: koishi_1.Schema.number()
|
|
72
|
+
.default(10)
|
|
73
|
+
.min(1)
|
|
74
|
+
.max(60 * 24)
|
|
75
|
+
.description('冷却发言时间(秒)'),
|
|
76
|
+
typingTime: koishi_1.Schema.number()
|
|
77
|
+
.default(440)
|
|
78
|
+
.min(100)
|
|
79
|
+
.role('slider')
|
|
80
|
+
.max(1000)
|
|
81
|
+
.description('模拟打字时的间隔(毫秒)'),
|
|
82
|
+
muteTime: koishi_1.Schema.number()
|
|
83
|
+
.default(1000 * 60)
|
|
84
|
+
.min(1000)
|
|
85
|
+
.max(1000 * 60 * 10 * 10)
|
|
86
|
+
.description('闭嘴时的禁言时间(毫秒)'),
|
|
87
|
+
sendStickerProbability: koishi_1.Schema.number()
|
|
88
|
+
.default(0.6)
|
|
89
|
+
.min(0)
|
|
90
|
+
.max(1)
|
|
91
|
+
.role('slider')
|
|
92
|
+
.step(0.01)
|
|
93
|
+
.description('发送表情的概率')
|
|
94
|
+
}).description('对话设置'),
|
|
95
|
+
koishi_1.Schema.object({
|
|
96
|
+
defaultPreset: koishi_1.Schema.dynamic('character-preset')
|
|
97
|
+
.description('使用的伪装预设')
|
|
98
|
+
.default('旧梦旧念')
|
|
99
|
+
})
|
|
100
|
+
]);
|
|
101
|
+
exports.inject = ['chatluna'];
|
|
102
|
+
exports.name = 'chatluna-character';
|
package/lib/plugin.d.ts
ADDED
package/lib/plugin.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.plugins = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function plugins(ctx, config) {
|
|
10
|
+
const list = await promises_1.default.readdir(path_1.default.join(__dirname, 'plugins'));
|
|
11
|
+
for (const file of list) {
|
|
12
|
+
if (file.endsWith('.d.ts')) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const command = await require(`./plugins/${file}`);
|
|
16
|
+
if (command.apply) {
|
|
17
|
+
await command.apply(ctx, config);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.plugins = plugins;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.apply = void 0;
|
|
4
|
+
const koishi_1 = require("koishi");
|
|
5
|
+
const __1 = require("..");
|
|
6
|
+
const count_tokens_1 = require("koishi-plugin-chatluna/lib/llm-core/utils/count_tokens");
|
|
7
|
+
const schema_1 = require("langchain/schema");
|
|
8
|
+
async function apply(ctx, config) {
|
|
9
|
+
const [platform, modelName] = (0, count_tokens_1.parseRawModelName)(config.model);
|
|
10
|
+
const model = (await ctx.chatluna.createChatModel(platform, modelName));
|
|
11
|
+
const selectedPreset = await __1.preset.getPreset(config.defaultPreset);
|
|
12
|
+
const systemPrompt = selectedPreset.system;
|
|
13
|
+
const completionPrompt = selectedPreset.input;
|
|
14
|
+
__1.service.collect(async (session, messages) => {
|
|
15
|
+
const [recentMessage, lastMessage] = await formatMessage(messages, config, model, systemPrompt.template, completionPrompt.template);
|
|
16
|
+
const temp = await __1.service.getTemp(session);
|
|
17
|
+
const formattedSystemPrompt = await systemPrompt.format({
|
|
18
|
+
time: new Date().toLocaleString()
|
|
19
|
+
});
|
|
20
|
+
__1.logger.debug('messages_new: ' + JSON.stringify(recentMessage));
|
|
21
|
+
__1.logger.debug('messages_last: ' + JSON.stringify(lastMessage));
|
|
22
|
+
const humanMessage = new schema_1.HumanMessage(await completionPrompt.format({
|
|
23
|
+
history_new: recentMessage,
|
|
24
|
+
history_last: lastMessage,
|
|
25
|
+
time: new Date().toLocaleString()
|
|
26
|
+
}));
|
|
27
|
+
const completionMessages = await formatCompletionMessages([new schema_1.SystemMessage(formattedSystemPrompt)].concat(temp.completionMessages), humanMessage, config, model);
|
|
28
|
+
__1.logger.debug('completion message: ' +
|
|
29
|
+
JSON.stringify(completionMessages.map((it) => it.content)));
|
|
30
|
+
let responseMessage;
|
|
31
|
+
for (let i = 0; i < 3; i++) {
|
|
32
|
+
try {
|
|
33
|
+
responseMessage = await model.call(completionMessages);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
__1.logger.error(e);
|
|
38
|
+
await (0, koishi_1.sleep)(2000);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
__1.logger.debug('model response: ' + responseMessage.content);
|
|
43
|
+
const response = parseResponse(responseMessage.content);
|
|
44
|
+
temp.completionMessages.push(humanMessage, responseMessage);
|
|
45
|
+
if (temp.completionMessages.length > 30) {
|
|
46
|
+
while (temp.completionMessages.length <= 30) {
|
|
47
|
+
temp.completionMessages.shift();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (response.length < 1) {
|
|
51
|
+
__1.service.mute(session, config.muteTime);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const random = new koishi_1.Random();
|
|
55
|
+
for (const elements of response) {
|
|
56
|
+
const text = elements
|
|
57
|
+
.map((element) => element.attrs.content ?? '')
|
|
58
|
+
.join('');
|
|
59
|
+
const maxTime = text.length * config.typingTime + 100;
|
|
60
|
+
await (0, koishi_1.sleep)(random.int(maxTime / 2, maxTime));
|
|
61
|
+
session.send(elements);
|
|
62
|
+
}
|
|
63
|
+
const sticker = await __1.stickerService.randomStick();
|
|
64
|
+
if (sticker) {
|
|
65
|
+
session.send(sticker);
|
|
66
|
+
}
|
|
67
|
+
__1.service.mute(session, config.coolDownTime * 1000);
|
|
68
|
+
__1.service.broadcastOnBot(session, response.flat());
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
exports.apply = apply;
|
|
72
|
+
function parseResponse(response) {
|
|
73
|
+
let message;
|
|
74
|
+
try {
|
|
75
|
+
const match = response.matchAll(/\[.*?\]/g);
|
|
76
|
+
message = [...match].pop()?.[0] ?? '';
|
|
77
|
+
__1.logger.debug('message: ' + message);
|
|
78
|
+
message = message.match(/\[.*(:|:).*(:|:)(.*)\]/)?.[3] ?? '';
|
|
79
|
+
message = message.match(/"(.*)"/)?.[1] ?? message;
|
|
80
|
+
__1.logger.debug('message: ' + message);
|
|
81
|
+
if (typeof message !== 'string') {
|
|
82
|
+
__1.logger.error('Failed to parse response: ' + response);
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
__1.logger.error(e);
|
|
88
|
+
__1.logger.error('Failed to parse response: ' + response);
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
const resultElements = [];
|
|
92
|
+
const currentElements = [];
|
|
93
|
+
// match [at:name:id] -> id
|
|
94
|
+
const atMatch = matchAt(message);
|
|
95
|
+
__1.logger.debug('atMatch: ' + JSON.stringify(atMatch));
|
|
96
|
+
if (atMatch.length > 0) {
|
|
97
|
+
let lastAtIndex = 0;
|
|
98
|
+
for (const at of atMatch) {
|
|
99
|
+
const before = message.substring(lastAtIndex, at.start);
|
|
100
|
+
if (before.length > 0) {
|
|
101
|
+
currentElements.push(koishi_1.h.text(before));
|
|
102
|
+
}
|
|
103
|
+
currentElements.push(koishi_1.h.at(at.at));
|
|
104
|
+
lastAtIndex = at.end;
|
|
105
|
+
}
|
|
106
|
+
const after = message.substring(lastAtIndex);
|
|
107
|
+
if (after.length > 0) {
|
|
108
|
+
currentElements.push(koishi_1.h.text(after));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
currentElements.push(koishi_1.h.text(message));
|
|
113
|
+
}
|
|
114
|
+
for (let currentElement of currentElements) {
|
|
115
|
+
if (currentElement.type === 'text') {
|
|
116
|
+
const text = currentElement.attrs.content;
|
|
117
|
+
const matchArray = splitSentence(text).filter((x) => x.length > 0);
|
|
118
|
+
for (const match of matchArray) {
|
|
119
|
+
currentElement = koishi_1.h.text(match);
|
|
120
|
+
resultElements.push([currentElement]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
resultElements.push([currentElement]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (resultElements[0]?.[0]?.type === 'at') {
|
|
128
|
+
resultElements[1].unshift(koishi_1.h.text(' '));
|
|
129
|
+
resultElements[1].unshift(resultElements[0][0]);
|
|
130
|
+
resultElements.shift();
|
|
131
|
+
}
|
|
132
|
+
return resultElements;
|
|
133
|
+
}
|
|
134
|
+
function splitSentence(text) {
|
|
135
|
+
const result = [];
|
|
136
|
+
const lines = text
|
|
137
|
+
.split('\n')
|
|
138
|
+
.filter((line) => line.trim().length > 0)
|
|
139
|
+
.join(' ');
|
|
140
|
+
const state = {
|
|
141
|
+
bracket: 0,
|
|
142
|
+
text: 0
|
|
143
|
+
};
|
|
144
|
+
let current = '';
|
|
145
|
+
const punctuations = [
|
|
146
|
+
',',
|
|
147
|
+
'。',
|
|
148
|
+
'?',
|
|
149
|
+
'!',
|
|
150
|
+
';',
|
|
151
|
+
':',
|
|
152
|
+
',',
|
|
153
|
+
'?',
|
|
154
|
+
'!',
|
|
155
|
+
';',
|
|
156
|
+
':',
|
|
157
|
+
'、',
|
|
158
|
+
'~',
|
|
159
|
+
'—',
|
|
160
|
+
'\r'
|
|
161
|
+
];
|
|
162
|
+
const retainPunctuations = ['?', '!', '?', '!', '~'];
|
|
163
|
+
const mustPunctuations = ['。', '?', '!', '?', '!', ':', ':', '~'];
|
|
164
|
+
const brackets = ['【', '】', '《', '》', '(', ')', '(', ')'];
|
|
165
|
+
for (let index = 0; index < lines.length; index++) {
|
|
166
|
+
const char = lines[index];
|
|
167
|
+
const nextChar = lines?.[index + 1];
|
|
168
|
+
const indexOfBrackets = brackets.indexOf(char);
|
|
169
|
+
if (indexOfBrackets > -1) {
|
|
170
|
+
state.bracket += indexOfBrackets % 2 === 0 ? 1 : -1;
|
|
171
|
+
}
|
|
172
|
+
if (indexOfBrackets > -1 && state.bracket === 0 && state.text > 0) {
|
|
173
|
+
current += char;
|
|
174
|
+
result.push(current);
|
|
175
|
+
state.text = 0;
|
|
176
|
+
current = '';
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
else if (indexOfBrackets % 2 === 0 && state.bracket === 1) {
|
|
180
|
+
result.push(current);
|
|
181
|
+
state.text = 0;
|
|
182
|
+
current = char;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
else if (state.bracket > 0) {
|
|
186
|
+
current += char;
|
|
187
|
+
state.text++;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (!punctuations.includes(char)) {
|
|
191
|
+
current += char;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (retainPunctuations.includes(char)) {
|
|
195
|
+
current += char;
|
|
196
|
+
}
|
|
197
|
+
if (retainPunctuations.indexOf(nextChar) % 2 === 0 &&
|
|
198
|
+
retainPunctuations.indexOf(char) % 2 === 1) {
|
|
199
|
+
index += 1;
|
|
200
|
+
}
|
|
201
|
+
if (current.length < 1) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (current.length > 2 || mustPunctuations.includes(char)) {
|
|
205
|
+
result.push(current.trimStart().trimEnd());
|
|
206
|
+
current = '';
|
|
207
|
+
}
|
|
208
|
+
else if (!retainPunctuations.includes(char)) {
|
|
209
|
+
current += char;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (current.length > 0) {
|
|
213
|
+
result.push(current.trimStart().trimEnd());
|
|
214
|
+
}
|
|
215
|
+
return result.filter((item) => punctuations.indexOf(item) === -1);
|
|
216
|
+
}
|
|
217
|
+
function matchAt(str) {
|
|
218
|
+
// (旧梦旧念:3510003509:<at>)
|
|
219
|
+
// /\(.*\-(\d+)\-<at>\)/g
|
|
220
|
+
const atRegex = /(\(|().*\-(\d+)\-<at>(\)|))/g;
|
|
221
|
+
return [...str.matchAll(atRegex)].map((item) => {
|
|
222
|
+
return {
|
|
223
|
+
at: item[2],
|
|
224
|
+
start: item.index,
|
|
225
|
+
end: item.index + item[0].length
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
async function formatCompletionMessages(messages, humanMessage, config, model) {
|
|
230
|
+
const maxTokens = config.maxTokens - 600;
|
|
231
|
+
const systemMessage = messages.shift();
|
|
232
|
+
let currentTokens = 0;
|
|
233
|
+
currentTokens += await model.getNumTokens(systemMessage.content);
|
|
234
|
+
currentTokens += await model.getNumTokens(humanMessage.content);
|
|
235
|
+
const result = [];
|
|
236
|
+
result.unshift(humanMessage);
|
|
237
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
238
|
+
const message = messages[index];
|
|
239
|
+
const messageTokens = await model.getNumTokens(message.content);
|
|
240
|
+
if (currentTokens + messageTokens > maxTokens) {
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
currentTokens += messageTokens;
|
|
244
|
+
result.unshift(message);
|
|
245
|
+
}
|
|
246
|
+
__1.logger.debug(`maxTokens: ${maxTokens}, currentTokens: ${currentTokens}`);
|
|
247
|
+
result.unshift(systemMessage);
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
async function formatMessage(messages, config, model, systemPrompt, historyPrompt) {
|
|
251
|
+
const maxTokens = config.maxTokens - 300;
|
|
252
|
+
let currentTokens = 0;
|
|
253
|
+
currentTokens += await model.getNumTokens(systemPrompt);
|
|
254
|
+
currentTokens += await model.getNumTokens(historyPrompt);
|
|
255
|
+
const calculatedMessages = [];
|
|
256
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
257
|
+
const message = messages[i];
|
|
258
|
+
const jsonMessage = `[${message.name}:${message.id}:${JSON.stringify(message.content)}]"`;
|
|
259
|
+
const jsonMessageToken = await model.getNumTokens(jsonMessage);
|
|
260
|
+
if (currentTokens + jsonMessageToken > maxTokens - 4) {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
currentTokens += jsonMessageToken;
|
|
264
|
+
calculatedMessages.unshift(jsonMessage);
|
|
265
|
+
}
|
|
266
|
+
const lastMessage = calculatedMessages.pop();
|
|
267
|
+
return [calculatedMessages, lastMessage];
|
|
268
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.apply = void 0;
|
|
4
|
+
const __1 = require("..");
|
|
5
|
+
const filter_1 = require("./filter");
|
|
6
|
+
function apply(ctx, config) {
|
|
7
|
+
ctx.command('chathub.character', '角色扮演相关命令');
|
|
8
|
+
ctx.command('chathub.character.clear [group]', '清除群组的聊天记录').action(async ({ session }, group) => {
|
|
9
|
+
if (!session.isDirect) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const groupId = group ?? session.guildId;
|
|
13
|
+
if (!groupId) {
|
|
14
|
+
return '请检查你是否提供了群组id';
|
|
15
|
+
}
|
|
16
|
+
const groupInfo = filter_1.groupInfos[groupId];
|
|
17
|
+
if (!groupInfo) {
|
|
18
|
+
return '没有找到群组信息';
|
|
19
|
+
}
|
|
20
|
+
groupInfo.messageCount = 0;
|
|
21
|
+
groupInfo.messageSendProbability = 0;
|
|
22
|
+
__1.service.clear(groupId);
|
|
23
|
+
return `已清除群组 ${groupId} 的聊天记录`;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
exports.apply = apply;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.apply = void 0;
|
|
4
|
+
const koishi_1 = require("koishi");
|
|
5
|
+
const types_1 = require("koishi-plugin-chatluna/lib/llm-core/platform/types");
|
|
6
|
+
function apply(ctx, config) {
|
|
7
|
+
ctx.on('chatluna/model-added', async (service) => {
|
|
8
|
+
ctx.schema.set('model', koishi_1.Schema.union(await getModelNames(service)));
|
|
9
|
+
});
|
|
10
|
+
ctx.on('chatluna/model-removed', async (service) => {
|
|
11
|
+
ctx.schema.set('model', koishi_1.Schema.union(await getModelNames(service)));
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
exports.apply = apply;
|
|
15
|
+
async function getModelNames(service) {
|
|
16
|
+
return service.getAllModels(types_1.ModelType.llm).map((m) => koishi_1.Schema.const(m));
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { Config } from '..';
|
|
3
|
+
export declare const groupInfos: Record<string, GroupInfo>;
|
|
4
|
+
export declare function apply(ctx: Context, config: Config): Promise<void>;
|
|
5
|
+
export interface GroupInfo {
|
|
6
|
+
messageCount: number;
|
|
7
|
+
messageSendProbability: number;
|
|
8
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.apply = exports.groupInfos = void 0;
|
|
4
|
+
const __1 = require("..");
|
|
5
|
+
exports.groupInfos = {};
|
|
6
|
+
async function apply(ctx, config) {
|
|
7
|
+
const maxMessages = config.messageInterval;
|
|
8
|
+
const selectedPreset = await __1.preset.getPreset(config.defaultPreset);
|
|
9
|
+
__1.service.addFilter((session, message) => {
|
|
10
|
+
const info = exports.groupInfos[session.guildId] || {
|
|
11
|
+
messageCount: 0,
|
|
12
|
+
messageSendProbability: 1
|
|
13
|
+
};
|
|
14
|
+
let { messageCount, messageSendProbability } = info;
|
|
15
|
+
// 在计算之前先检查是否需要禁言。
|
|
16
|
+
if (config.isForceMute && selectedPreset.mute_keyword?.length > 0) {
|
|
17
|
+
const needMute = selectedPreset.mute_keyword.some((value) => message.content.includes(value));
|
|
18
|
+
if (needMute) {
|
|
19
|
+
__1.logger.debug(`mute content: ${message.content}`);
|
|
20
|
+
__1.service.mute(session, config.muteTime);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// 保底必出
|
|
24
|
+
if ((messageCount > maxMessages ||
|
|
25
|
+
messageSendProbability > 1 ||
|
|
26
|
+
session.stripped.appel ||
|
|
27
|
+
(config.isNickname &&
|
|
28
|
+
selectedPreset.nick_name.some((value) => message.content.startsWith(value)))) &&
|
|
29
|
+
!__1.service.isMute(session)) {
|
|
30
|
+
info.messageCount = 0;
|
|
31
|
+
info.messageSendProbability = 1;
|
|
32
|
+
exports.groupInfos[session.guildId] = info;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
// 按照概率出
|
|
36
|
+
if (Math.random() > messageSendProbability &&
|
|
37
|
+
!__1.service.isMute(session)) {
|
|
38
|
+
info.messageCount = 0;
|
|
39
|
+
info.messageSendProbability = 1;
|
|
40
|
+
exports.groupInfos[session.guildId] = info;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
__1.logger.debug(`messageCount: ${messageCount}, messageSendProbability: ${messageSendProbability}. content: ${JSON.stringify(message)}`);
|
|
44
|
+
messageCount++;
|
|
45
|
+
messageSendProbability -= (1 / maxMessages) * 0.15;
|
|
46
|
+
info.messageCount = messageCount;
|
|
47
|
+
info.messageSendProbability = messageSendProbability;
|
|
48
|
+
exports.groupInfos[session.guildId] = info;
|
|
49
|
+
return false;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
exports.apply = apply;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.apply = void 0;
|
|
4
|
+
function apply(ctx, config) {
|
|
5
|
+
ctx.on('chatluna/before-check-sender', async (session) => {
|
|
6
|
+
return (((session.stripped.appel && !session.isDirect) ||
|
|
7
|
+
!session.isDirect) &&
|
|
8
|
+
config.applyGroup.some((group) => group === session.guildId) &&
|
|
9
|
+
config.disableChatHub);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
exports.apply = apply;
|
package/lib/preset.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { PresetTemplate } from './types';
|
|
3
|
+
export declare class Preset {
|
|
4
|
+
private readonly ctx;
|
|
5
|
+
private readonly _presets;
|
|
6
|
+
constructor(ctx: Context);
|
|
7
|
+
loadAllPreset(): Promise<void>;
|
|
8
|
+
getPreset(triggerKeyword: string, loadForDisk?: boolean, throwError?: boolean): Promise<PresetTemplate>;
|
|
9
|
+
getDefaultPreset(): Promise<PresetTemplate>;
|
|
10
|
+
getAllPreset(): Promise<string[]>;
|
|
11
|
+
resetDefaultPreset(): Promise<void>;
|
|
12
|
+
resolvePresetDir(): string;
|
|
13
|
+
private _checkPresetDir;
|
|
14
|
+
private _copyDefaultPresets;
|
|
15
|
+
}
|
|
16
|
+
export declare function loadPreset(text: string): PresetTemplate;
|