koishi-plugin-chatluna-think-viewer 1.0.17 → 1.0.18
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/README.md +43 -38
- package/index.js +145 -38
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -1,39 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
##
|
|
11
|
-
```bash
|
|
12
|
-
# Koishi
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
# koishi-plugin-chatluna-think-viewer
|
|
2
|
+
|
|
3
|
+
????/????? `chatluna-character` ??????? `<think>` ???????????????????????
|
|
4
|
+
|
|
5
|
+
## ??
|
|
6
|
+
- ?? `chatluna-character` ??????????????????????
|
|
7
|
+
- ??????????????????
|
|
8
|
+
- **????????**?? bot ???????????????? `<think>` ????? JSON ???????????????????
|
|
9
|
+
|
|
10
|
+
## ??
|
|
11
|
+
```bash
|
|
12
|
+
# Koishi ??????? chatluna-think-viewer ??
|
|
13
|
+
npm install koishi-plugin-chatluna-think-viewer
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## ???? (koishi.yml)
|
|
17
|
+
```yaml
|
|
18
|
+
plugins:
|
|
19
|
+
chatluna-character: {}
|
|
20
|
+
chatluna-think-viewer:
|
|
21
|
+
command: think
|
|
22
|
+
keywords:
|
|
23
|
+
- ????
|
|
24
|
+
- ????
|
|
25
|
+
allowPrivate: false
|
|
26
|
+
emptyMessage: ????????????
|
|
27
|
+
# ??????
|
|
28
|
+
guardEnabled: true
|
|
29
|
+
guardMode: recall # recall | block
|
|
30
|
+
guardForbiddenPatterns:
|
|
31
|
+
- '<think>[\s\S]*?<\/think>'
|
|
32
|
+
- '"role"\s*:\s*"assistant"'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## ??
|
|
36
|
+
- ????? `think` ????????????? `<think>` ???`think 2` ????? 2 ??
|
|
37
|
+
- ???????????? bot ???????????????????????????????
|
|
38
|
+
|
|
39
|
+
## ??
|
|
40
|
+
- koishi >= 4.18.0
|
|
41
|
+
- koishi-plugin-chatluna-character >= 0.0.180
|
|
42
|
+
|
|
43
|
+
## ??
|
|
39
44
|
MIT
|
package/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
const { Schema, h } = require('koishi');
|
|
2
|
+
const { Bot } = require('@satorijs/core');
|
|
2
3
|
|
|
3
4
|
const name = 'chatluna-think-viewer';
|
|
4
5
|
|
|
@@ -7,37 +8,59 @@ const inject = {
|
|
|
7
8
|
chatluna: { required: false },
|
|
8
9
|
};
|
|
9
10
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
const defaultForbidden = [
|
|
12
|
+
'<think>[\\s\\S]*?<\\/think>',
|
|
13
|
+
'```\\s*think[\\s\\S]*?```',
|
|
14
|
+
'"role"\\s*:\\s*"assistant"',
|
|
15
|
+
'"analysis"\\s*:',
|
|
16
|
+
'"thought"\\s*:',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const Config = Schema.intersect([
|
|
20
|
+
Schema.object({
|
|
21
|
+
command: Schema.string().default('think').description('�鿴˼�����ݵ�ָ����'),
|
|
22
|
+
keywords: Schema.array(Schema.string()).default(['�鿴˼��', '�ϴ�˼��']).description('����ǰ��ָ��Ĺؼ���'),
|
|
23
|
+
allowPrivate: Schema.boolean().default(false).description('�Ƿ�������˽��ʹ��'),
|
|
24
|
+
emptyMessage: Schema.string().default('��ʱû�п��õ�˼����¼��').description('û�м�¼ʱ����ʾ�ı�'),
|
|
25
|
+
renderImage: Schema.boolean().default(false).description('�Ƿ�ͨ�� ChatLuna �� image renderer ��˼����ȾΪͼƬ��ʧ��ʱ�����ı�'),
|
|
26
|
+
}).description('˼���鿴����'),
|
|
27
|
+
Schema.object({
|
|
28
|
+
guardEnabled: Schema.boolean().default(true).description('����쳣��ʽ���Զ�����/����'),
|
|
29
|
+
guardMode: Schema.union(['recall', 'block']).default('recall').description('recall=�ȷ��أ�block=ֱ����ֹ����'),
|
|
30
|
+
guardDelay: Schema.number().default(1).min(0).max(60).description('�����ӳ٣��룩'),
|
|
31
|
+
guardAllowPrivate: Schema.boolean().default(true).description('�Ƿ���˽���������쳣���'),
|
|
32
|
+
guardGroups: Schema.array(Schema.string()).default([]).description('ֻ����ЩȺ���ã���ձ�ʾȫ��'),
|
|
33
|
+
guardForbiddenPatterns: Schema.array(Schema.string())
|
|
34
|
+
.default(defaultForbidden)
|
|
35
|
+
.description('��������������Ϊ�쳣������˼����й©������ JSON ��'),
|
|
36
|
+
guardAllowedPatterns: Schema.array(Schema.string())
|
|
37
|
+
.default(['[\\s\\S]+'])
|
|
38
|
+
.description('��ѡ�İ�����������������һ������Ϊ����'),
|
|
39
|
+
guardLog: Schema.boolean().default(true).description('�Ƿ�����־�м�¼�쳣���ݺ�ԭ��'),
|
|
40
|
+
guardContentPreview: Schema.number().default(80).min(10).max(500).description('��־�ﱣ�������Ԥ���ַ���'),
|
|
41
|
+
}).description('�쳣��ʽ�Զ�����'),
|
|
42
|
+
]);
|
|
17
43
|
|
|
18
44
|
function extractText(content) {
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (typeof content.text === 'string') return content.text;
|
|
33
|
-
if (typeof content.content === 'string') return content.content;
|
|
34
|
-
if (typeof content.value === 'string') return content.value;
|
|
45
|
+
if (content == null) return '';
|
|
46
|
+
const normalized = h.normalize(content);
|
|
47
|
+
const parts = [];
|
|
48
|
+
for (const el of normalized) {
|
|
49
|
+
if (typeof el === 'string') {
|
|
50
|
+
parts.push(el);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(el.children) && el.children.length) {
|
|
54
|
+
parts.push(extractText(el.children));
|
|
55
|
+
}
|
|
56
|
+
const textLike = el.attrs?.content ?? el.attrs?.text ?? el.children?.join?.('') ?? '';
|
|
57
|
+
if (textLike) parts.push(textLike);
|
|
35
58
|
}
|
|
36
|
-
return '';
|
|
59
|
+
return parts.join('');
|
|
37
60
|
}
|
|
38
61
|
|
|
39
62
|
function extractThink(text) {
|
|
40
|
-
//
|
|
63
|
+
// ijЩģ��/�м������ͬһ����Ϣ���γ��� <think>��ȡ���һ�γ��ֵ�Ƭ��
|
|
41
64
|
let last = '';
|
|
42
65
|
const regex = /<think>([\s\S]*?)<\/think>/gi;
|
|
43
66
|
let m;
|
|
@@ -49,12 +72,11 @@ function extractThink(text) {
|
|
|
49
72
|
|
|
50
73
|
function formatThink(text) {
|
|
51
74
|
if (!text) return text;
|
|
52
|
-
//
|
|
75
|
+
// ���Ը�ʽ�� JSON��ʧ�����ȥ��β/�ϲ�����
|
|
53
76
|
try {
|
|
54
77
|
const parsed = JSON.parse(text);
|
|
55
78
|
return JSON.stringify(parsed, null, 2);
|
|
56
79
|
} catch {
|
|
57
|
-
// 保留原文,去掉多余空行并统一缩进
|
|
58
80
|
const lines = text.split('\n').map((l) => l.trimEnd());
|
|
59
81
|
const filtered = lines.filter((l, idx, arr) => !(l === '' && arr[idx - 1] === ''));
|
|
60
82
|
const nonEmpty = filtered.filter((l) => l.trim().length > 0);
|
|
@@ -122,10 +144,93 @@ function getLatestRawThink(temp) {
|
|
|
122
144
|
return '';
|
|
123
145
|
}
|
|
124
146
|
|
|
147
|
+
function compileRegex(list) {
|
|
148
|
+
return (list || []).map((p) => {
|
|
149
|
+
try {
|
|
150
|
+
return new RegExp(p, 'i');
|
|
151
|
+
} catch (err) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}).filter(Boolean);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function detectAbnormal(text, forbidden, allowed) {
|
|
158
|
+
if (!text) return null;
|
|
159
|
+
for (const re of forbidden) {
|
|
160
|
+
if (re.test(text)) return `���н�ֹ����: /${re.source}/`;
|
|
161
|
+
}
|
|
162
|
+
if (allowed.length && !allowed.some((re) => re.test(text))) {
|
|
163
|
+
return 'δ�����κΰ���������';
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function shorten(text, limit = 80) {
|
|
169
|
+
if (!text) return '';
|
|
170
|
+
if (text.length <= limit) return text;
|
|
171
|
+
return `${text.slice(0, limit)}...(${text.length} chars)`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function shouldGuard(config, options) {
|
|
175
|
+
const session = options?.session;
|
|
176
|
+
if (!session) return false;
|
|
177
|
+
const guildId = session.guildId || session.event?.guild?.id;
|
|
178
|
+
const isGroup = !!guildId;
|
|
179
|
+
if (!config.guardAllowPrivate && !isGroup) return false;
|
|
180
|
+
if (config.guardGroups?.length && isGroup && !config.guardGroups.includes(guildId)) return false;
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function applyGuard(ctx, config) {
|
|
185
|
+
if (!config.guardEnabled) return;
|
|
186
|
+
const logger = ctx.logger(`${name}:guard`);
|
|
187
|
+
const forbidden = compileRegex(config.guardForbiddenPatterns);
|
|
188
|
+
const allowed = compileRegex(config.guardAllowedPatterns);
|
|
189
|
+
const original = Bot.prototype.sendMessage;
|
|
190
|
+
|
|
191
|
+
Bot.prototype.sendMessage = async function patched(channelId, content, referrer, options = {}) {
|
|
192
|
+
if (!shouldGuard(config, options)) {
|
|
193
|
+
return original.call(this, channelId, content, referrer, options);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const text = extractText(content);
|
|
197
|
+
const reason = detectAbnormal(text, forbidden, allowed);
|
|
198
|
+
|
|
199
|
+
if (!reason) {
|
|
200
|
+
return original.call(this, channelId, content, referrer, options);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const preview = shorten(text, config.guardContentPreview);
|
|
204
|
+
if (config.guardMode === 'block') {
|
|
205
|
+
if (config.guardLog) logger.warn(`[block] ${reason} | content: ${preview}`);
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const ids = await original.call(this, channelId, content, referrer, options);
|
|
210
|
+
if (config.guardLog) logger.warn(`[recall] ${reason} | content: ${preview}`);
|
|
211
|
+
const delay = Math.max(0, config.guardDelay) * 1000;
|
|
212
|
+
if (Array.isArray(ids) && ids.length && typeof this.deleteMessage === 'function') {
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
for (const id of ids) {
|
|
215
|
+
this.deleteMessage(channelId, id).catch((err) => {
|
|
216
|
+
logger.warn(`[recall-failed] id=${id} reason=${err?.message || err}`);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}, delay);
|
|
220
|
+
}
|
|
221
|
+
return ids;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
ctx.on('dispose', () => {
|
|
225
|
+
Bot.prototype.sendMessage = original;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
125
229
|
function apply(ctx, config) {
|
|
230
|
+
// ˼���鿴����
|
|
126
231
|
const cmd = ctx
|
|
127
|
-
.command(`${config.command} [index:string]`, '
|
|
128
|
-
.usage('
|
|
232
|
+
.command(`${config.command} [index:string]`, '��ȡ���һ�ΰ��� <think> �����ݣ���ָ���� N ��')
|
|
233
|
+
.usage('���� think 2 �ɲ鿴������ 2 �� AI �ظ���˼������');
|
|
129
234
|
|
|
130
235
|
for (const keyword of config.keywords || []) {
|
|
131
236
|
cmd.shortcut(keyword, { prefix: false });
|
|
@@ -133,23 +238,23 @@ function apply(ctx, config) {
|
|
|
133
238
|
|
|
134
239
|
cmd.action(async ({ session, args }, rawIndex) => {
|
|
135
240
|
if (!config.allowPrivate && !session.guildId) {
|
|
136
|
-
return '
|
|
241
|
+
return '��֧����˽�����ѯ��';
|
|
137
242
|
}
|
|
138
243
|
|
|
139
244
|
const service = ctx.chatluna_character;
|
|
140
|
-
if (!service) return 'chatluna-character
|
|
245
|
+
if (!service) return 'chatluna-character δ���ء�';
|
|
141
246
|
|
|
142
247
|
const temp = await service.getTemp(session);
|
|
143
248
|
const targetIndex = parseIndex(rawIndex ?? args?.[0]);
|
|
144
249
|
|
|
145
|
-
// 1)
|
|
250
|
+
// 1) �ȶ����һ��ԭʼ��Ӧ��ͨ��ֻ�Ե� 1 ����Ч��
|
|
146
251
|
const thinkFromRaw = targetIndex === 1 ? getLatestRawThink(temp) : '';
|
|
147
252
|
|
|
148
|
-
// 2)
|
|
253
|
+
// 2) ����ʷ completionMessages �в��Ұ��� <think> �� AI ��Ϣ
|
|
149
254
|
const messages = temp?.completionMessages || [];
|
|
150
255
|
const thinkFromHistory = thinkFromRaw ? '' : getNthThink(messages, targetIndex);
|
|
151
256
|
|
|
152
|
-
// 3)
|
|
257
|
+
// 3) ���ף�ȡ�� N �� AI ��Ϣ�ٴ�����ȡ <think>
|
|
153
258
|
const fallbackMsg = thinkFromRaw || thinkFromHistory ? null : getNthAiMessage(messages, targetIndex);
|
|
154
259
|
const think = thinkFromRaw || thinkFromHistory || extractThink(extractText(fallbackMsg?.content));
|
|
155
260
|
const formatted = formatThink(think);
|
|
@@ -157,11 +262,10 @@ function apply(ctx, config) {
|
|
|
157
262
|
|
|
158
263
|
if (config.renderImage && ctx.chatluna?.renderer) {
|
|
159
264
|
try {
|
|
160
|
-
const title = `###
|
|
265
|
+
const title = `### ���˼�����ݣ������� ${targetIndex} ����`;
|
|
161
266
|
const markdown = `<div align="center">\n${title}\n</div>\n\n<div align="left">\n${formatted}\n</div>`;
|
|
162
267
|
const rendered = await ctx.chatluna.renderer.render(
|
|
163
268
|
{
|
|
164
|
-
// 居中标题、左对齐正文,保持 renderer 兼容
|
|
165
269
|
content: [{ type: 'text', text: markdown }],
|
|
166
270
|
},
|
|
167
271
|
{ type: 'image', session },
|
|
@@ -172,8 +276,11 @@ function apply(ctx, config) {
|
|
|
172
276
|
}
|
|
173
277
|
}
|
|
174
278
|
|
|
175
|
-
return
|
|
279
|
+
return `���˼�����ݣ������� ${targetIndex} ����\n${formatted}`;
|
|
176
280
|
});
|
|
281
|
+
|
|
282
|
+
// �쳣��ʽ�Զ�����
|
|
283
|
+
applyGuard(ctx, config);
|
|
177
284
|
}
|
|
178
285
|
|
|
179
286
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
{
|
|
2
2
|
"name": "koishi-plugin-chatluna-think-viewer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"main": "index.js",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "View chatluna <think> blocks and auto recall abnormal formatted replies.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"koishi",
|
|
9
9
|
"chatluna",
|
|
10
10
|
"character",
|
|
11
|
-
"think"
|
|
11
|
+
"think",
|
|
12
|
+
"recall",
|
|
13
|
+
"guard",
|
|
14
|
+
"moderation"
|
|
12
15
|
],
|
|
13
16
|
"homepage": "https://github.com/sCR0WN-s/koishi-plugin-chatluna-think-viewer",
|
|
14
17
|
"repository": {
|
|
@@ -24,8 +27,8 @@
|
|
|
24
27
|
},
|
|
25
28
|
"koishi": {
|
|
26
29
|
"description": {
|
|
27
|
-
"zh": "
|
|
28
|
-
"en": "
|
|
30
|
+
"zh": "?? chatluna-character ? <think> ??????????????????/???",
|
|
31
|
+
"en": "View <think> blocks from chatluna-character and auto recall malformed replies."
|
|
29
32
|
},
|
|
30
33
|
"service": {
|
|
31
34
|
"required": [
|