koishi-plugin-chatluna-think-viewer 1.0.16 → 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.
Files changed (3) hide show
  1. package/README.md +44 -40
  2. package/index.js +290 -183
  3. package/package.json +42 -39
package/README.md CHANGED
@@ -1,40 +1,44 @@
1
- # koishi-plugin-chatluna-think-viewer
2
-
3
- 通过命令或快捷关键词查看 `chatluna-character` 最近一次回复中的 `<think>` 思考内容,便于调试和复盘。
4
-
5
- ## 特性
6
- - 复用 `chatluna-character` 内存,不额外占用数据库。
7
- - 支持命令与无前缀关键词触发。
8
- - 群聊可用,默认禁止私聊(可配置)。
9
-
10
- ## 安装
11
- ```bash
12
- # 使用 Koishi 控制台市场搜索「chatluna-think-viewer」安装
13
- # 或者 npm/yarn 安装:
14
- npm install koishi-plugin-chatluna-think-viewer
15
- # 或
16
- yarn add koishi-plugin-chatluna-think-viewer
17
- ```
18
-
19
- ## 配置示例 (koishi.yml)
20
- ```yaml
21
- plugins:
22
- chatluna-character: {}
23
- chatluna-think-viewer:
24
- command: think
25
- keywords:
26
- - 查看思考
27
- - 上次思考
28
- allowPrivate: false
29
- emptyMessage: 暂时没有可用的思考记录。
30
- ```
31
-
32
- ## 使用
33
- - 群聊中发送 `think`(按你的命令前缀)或关键词“查看思考”/“上次思考”,返回上一条回复的 `<think>` 内容。
34
-
35
- ## 依赖
36
- - koishi >= 4.18.0
37
- - koishi-plugin-chatluna-character >= 0.0.180
38
-
39
- ## 协议
40
- MIT
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
+ ## ??
44
+ MIT
package/index.js CHANGED
@@ -1,184 +1,291 @@
1
- const { Schema } = require('koishi');
2
-
3
- const name = 'chatluna-think-viewer';
4
-
5
- const inject = {
6
- chatluna_character: { required: true },
7
- chatluna: { required: false },
8
- };
9
-
10
- const Config = Schema.object({
11
- command: Schema.string().default('think').description('��������'),
12
- keywords: Schema.array(Schema.string()).default(['�鿴˼��', '�ϴ�˼��']).description('����ǰ׺���ɴ����Ĺؼ���'),
13
- allowPrivate: Schema.boolean().default(false).description('�Ƿ�������˽����ʹ��'),
14
- emptyMessage: Schema.string().default('��ʱû�п��õ�˼����¼��').description('û�м�¼ʱ����ʾ�İ�'),
15
- renderImage: Schema.boolean().default(false).description('����ʹ�� ChatLuna �� image renderer ��˼��������ȾΪͼƬ���ͣ�ʧ��������ı�'),
16
- });
17
-
18
- function extractText(content) {
19
- if (typeof content === 'string') return content;
20
- if (Array.isArray(content)) {
21
- return content
22
- .map((part) => {
23
- if (typeof part === 'string') return part;
24
- if (part && typeof part === 'object') {
25
- return part.text || part.content || part.value || '';
26
- }
27
- return '';
28
- })
29
- .join('');
30
- }
31
- if (content && typeof content === 'object') {
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;
35
- }
36
- return '';
37
- }
38
-
39
- function extractThink(text) {
40
- // ��Щģ��/�м������ͬһ����Ϣ������� <think>��ȡ���һ�α������þ�Ƭ��
41
- let last = '';
42
- const regex = /<think>([\s\S]*?)<\/think>/gi;
43
- let m;
44
- while ((m = regex.exec(text)) !== null) {
45
- last = m[1];
46
- }
47
- return last.trim();
48
- }
49
-
50
- function formatThink(text) {
51
- if (!text) return text;
52
- // ���� JSON ����
53
- try {
54
- const parsed = JSON.parse(text);
55
- return JSON.stringify(parsed, null, 2);
56
- } catch {
57
- // ����ԭ�ģ�ȥ�����������ͳһ�������
58
- const lines = text.split('\n').map((l) => l.trimEnd());
59
- const filtered = lines.filter((l, idx, arr) => !(l === '' && arr[idx - 1] === ''));
60
- const nonEmpty = filtered.filter((l) => l.trim().length > 0);
61
- const minIndent = nonEmpty.length
62
- ? Math.min(
63
- ...nonEmpty.map((l) => {
64
- const m = l.match(/^(\s*)/);
65
- return m ? m[1].length : 0;
66
- }),
67
- )
68
- : 0;
69
- return filtered.map((l) => l.slice(minIndent)).join('\n');
70
- }
71
- }
72
-
73
- function parseIndex(rawIndex) {
74
- if (!rawIndex) return 1;
75
- if (typeof rawIndex === 'number' && Number.isFinite(rawIndex) && rawIndex > 0) return Math.floor(rawIndex);
76
- const match = String(rawIndex).match(/\d+/);
77
- if (!match) return 1;
78
- const num = parseInt(match[0], 10);
79
- return Number.isFinite(num) && num > 0 ? num : 1;
80
- }
81
-
82
- function getNthAiMessage(messages, n = 1) {
83
- if (!Array.isArray(messages) || n < 1) return null;
84
- let count = 0;
85
- for (let i = messages.length - 1; i >= 0; i--) {
86
- const msg = messages[i];
87
- const type = typeof msg?._getType === 'function' ? msg._getType() : msg?.type || msg?.role;
88
- if (type === 'ai' || type === 'assistant') {
89
- count += 1;
90
- if (count === n) return msg;
91
- }
92
- }
93
- return null;
94
- }
95
-
96
- function getNthThink(messages, n = 1) {
97
- if (!Array.isArray(messages) || n < 1) return null;
98
- let count = 0;
99
- for (let i = messages.length - 1; i >= 0; i--) {
100
- const msg = messages[i];
101
- const type = typeof msg?._getType === 'function' ? msg._getType() : msg?.type || msg?.role;
102
- if (type !== 'ai' && type !== 'assistant') continue;
103
- const think = extractThink(extractText(msg.content));
104
- if (!think) continue;
105
- count += 1;
106
- if (count === n) return think;
107
- }
108
- return null;
109
- }
110
-
111
- function getLatestRawThink(temp) {
112
- if (!temp) return '';
113
- const candidates = [
114
- temp?.lastCompletion?.raw?.choices?.[0]?.message?.content,
115
- temp?.lastCompletion?.raw?.content,
116
- temp?.lastCompletion?.content,
117
- ];
118
- for (const c of candidates) {
119
- const think = extractThink(extractText(c));
120
- if (think) return think;
121
- }
122
- return '';
123
- }
124
-
125
- function apply(ctx, config) {
126
- const cmd = ctx
127
- .command(`${config.command} [index:string]`, '��ȡ��һ���ظ��е� <think> ���ݣ���ָ�������� N ����')
128
- .usage('��������Ĭ�϶�ȡ���һ�������� think 2 ��ȡ�����ڶ��� AI �ظ���˼��');
129
-
130
- for (const keyword of config.keywords || []) {
131
- cmd.shortcut(keyword, { prefix: false });
132
- }
133
-
134
- cmd.action(async ({ session, args }, rawIndex) => {
135
- if (!config.allowPrivate && !session.guildId) {
136
- return '��֧����Ⱥ���в�ѯ��';
137
- }
138
-
139
- const service = ctx.chatluna_character;
140
- if (!service) return 'chatluna-character δ���á�';
141
-
142
- const temp = await service.getTemp(session);
143
- const targetIndex = parseIndex(rawIndex ?? args?.[0]);
144
-
145
- // 1) ���ȶ�ȡ����һ��ԭʼ��Ӧ��ͨ���Ժ� <think>����ֻ�Ե� 1 ����Ч
146
- const thinkFromRaw = targetIndex === 1 ? getLatestRawThink(temp) : '';
147
-
148
- // 2) ��ʷ completionMessages �������� <think> �� AI ��Ϣ
149
- const messages = temp?.completionMessages || [];
150
- const thinkFromHistory = thinkFromRaw ? '' : getNthThink(messages, targetIndex);
151
-
152
- // 3) ���˵��� N �� AI ��Ϣ�ٳ��Գ�ȡ
153
- const fallbackMsg = thinkFromRaw || thinkFromHistory ? null : getNthAiMessage(messages, targetIndex);
154
- const think = thinkFromRaw || thinkFromHistory || extractThink(extractText(fallbackMsg?.content));
155
- const formatted = formatThink(think);
156
- if (!formatted) return config.emptyMessage;
157
-
158
- if (config.renderImage && ctx.chatluna?.renderer) {
159
- try {
160
- const title = `### ��һ��˼���������� ${targetIndex} ����`;
161
- const markdown = `<div align="center">\n${title}\n</div>\n\n<div align="left">\n${formatted}\n</div>`;
162
- const rendered = await ctx.chatluna.renderer.render(
163
- {
164
- // �м������С���������룬�������������
165
- content: [{ type: 'text', text: markdown }],
166
- },
167
- { type: 'image', session },
168
- );
169
- if (rendered?.length) return rendered.map((r) => r.element);
170
- } catch (err) {
171
- ctx.logger?.warn?.('[think-viewer] image render failed, fallback text', err);
172
- }
173
- }
174
-
175
- return `��һ��˼����\n${formatted}`;
176
- });
177
- }
178
-
179
- module.exports = {
180
- name,
181
- apply,
182
- Config,
183
- inject,
1
+ const { Schema, h } = require('koishi');
2
+ const { Bot } = require('@satorijs/core');
3
+
4
+ const name = 'chatluna-think-viewer';
5
+
6
+ const inject = {
7
+ chatluna_character: { required: true },
8
+ chatluna: { required: false },
9
+ };
10
+
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
+ ]);
43
+
44
+ function extractText(content) {
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);
58
+ }
59
+ return parts.join('');
60
+ }
61
+
62
+ function extractThink(text) {
63
+ // ijЩģ��/�м������ͬһ����Ϣ���γ��� <think>��ȡ���һ�γ��ֵ�Ƭ��
64
+ let last = '';
65
+ const regex = /<think>([\s\S]*?)<\/think>/gi;
66
+ let m;
67
+ while ((m = regex.exec(text)) !== null) {
68
+ last = m[1];
69
+ }
70
+ return last.trim();
71
+ }
72
+
73
+ function formatThink(text) {
74
+ if (!text) return text;
75
+ // ���Ը�ʽ�� JSON��ʧ�����ȥ��β/�ϲ�����
76
+ try {
77
+ const parsed = JSON.parse(text);
78
+ return JSON.stringify(parsed, null, 2);
79
+ } catch {
80
+ const lines = text.split('\n').map((l) => l.trimEnd());
81
+ const filtered = lines.filter((l, idx, arr) => !(l === '' && arr[idx - 1] === ''));
82
+ const nonEmpty = filtered.filter((l) => l.trim().length > 0);
83
+ const minIndent = nonEmpty.length
84
+ ? Math.min(
85
+ ...nonEmpty.map((l) => {
86
+ const m = l.match(/^(\s*)/);
87
+ return m ? m[1].length : 0;
88
+ }),
89
+ )
90
+ : 0;
91
+ return filtered.map((l) => l.slice(minIndent)).join('\n');
92
+ }
93
+ }
94
+
95
+ function parseIndex(rawIndex) {
96
+ if (!rawIndex) return 1;
97
+ if (typeof rawIndex === 'number' && Number.isFinite(rawIndex) && rawIndex > 0) return Math.floor(rawIndex);
98
+ const match = String(rawIndex).match(/\d+/);
99
+ if (!match) return 1;
100
+ const num = parseInt(match[0], 10);
101
+ return Number.isFinite(num) && num > 0 ? num : 1;
102
+ }
103
+
104
+ function getNthAiMessage(messages, n = 1) {
105
+ if (!Array.isArray(messages) || n < 1) return null;
106
+ let count = 0;
107
+ for (let i = messages.length - 1; i >= 0; i--) {
108
+ const msg = messages[i];
109
+ const type = typeof msg?._getType === 'function' ? msg._getType() : msg?.type || msg?.role;
110
+ if (type === 'ai' || type === 'assistant') {
111
+ count += 1;
112
+ if (count === n) return msg;
113
+ }
114
+ }
115
+ return null;
116
+ }
117
+
118
+ function getNthThink(messages, n = 1) {
119
+ if (!Array.isArray(messages) || n < 1) return null;
120
+ let count = 0;
121
+ for (let i = messages.length - 1; i >= 0; i--) {
122
+ const msg = messages[i];
123
+ const type = typeof msg?._getType === 'function' ? msg._getType() : msg?.type || msg?.role;
124
+ if (type !== 'ai' && type !== 'assistant') continue;
125
+ const think = extractThink(extractText(msg.content));
126
+ if (!think) continue;
127
+ count += 1;
128
+ if (count === n) return think;
129
+ }
130
+ return null;
131
+ }
132
+
133
+ function getLatestRawThink(temp) {
134
+ if (!temp) return '';
135
+ const candidates = [
136
+ temp?.lastCompletion?.raw?.choices?.[0]?.message?.content,
137
+ temp?.lastCompletion?.raw?.content,
138
+ temp?.lastCompletion?.content,
139
+ ];
140
+ for (const c of candidates) {
141
+ const think = extractThink(extractText(c));
142
+ if (think) return think;
143
+ }
144
+ return '';
145
+ }
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
+
229
+ function apply(ctx, config) {
230
+ // ˼���鿴����
231
+ const cmd = ctx
232
+ .command(`${config.command} [index:string]`, '��ȡ���һ�ΰ��� <think> �����ݣ���ָ���� N ��')
233
+ .usage('���� think 2 �ɲ鿴������ 2 �� AI �ظ���˼������');
234
+
235
+ for (const keyword of config.keywords || []) {
236
+ cmd.shortcut(keyword, { prefix: false });
237
+ }
238
+
239
+ cmd.action(async ({ session, args }, rawIndex) => {
240
+ if (!config.allowPrivate && !session.guildId) {
241
+ return '��֧����˽�����ѯ��';
242
+ }
243
+
244
+ const service = ctx.chatluna_character;
245
+ if (!service) return 'chatluna-character δ���ء�';
246
+
247
+ const temp = await service.getTemp(session);
248
+ const targetIndex = parseIndex(rawIndex ?? args?.[0]);
249
+
250
+ // 1) �ȶ����һ��ԭʼ��Ӧ��ͨ��ֻ�Ե� 1 ����Ч��
251
+ const thinkFromRaw = targetIndex === 1 ? getLatestRawThink(temp) : '';
252
+
253
+ // 2) ����ʷ completionMessages �в��Ұ��� <think> �� AI ��Ϣ
254
+ const messages = temp?.completionMessages || [];
255
+ const thinkFromHistory = thinkFromRaw ? '' : getNthThink(messages, targetIndex);
256
+
257
+ // 3) ���ף�ȡ�� N �� AI ��Ϣ�ٴ�����ȡ <think>
258
+ const fallbackMsg = thinkFromRaw || thinkFromHistory ? null : getNthAiMessage(messages, targetIndex);
259
+ const think = thinkFromRaw || thinkFromHistory || extractThink(extractText(fallbackMsg?.content));
260
+ const formatted = formatThink(think);
261
+ if (!formatted) return config.emptyMessage;
262
+
263
+ if (config.renderImage && ctx.chatluna?.renderer) {
264
+ try {
265
+ const title = `### ���˼�����ݣ������� ${targetIndex} ����`;
266
+ const markdown = `<div align="center">\n${title}\n</div>\n\n<div align="left">\n${formatted}\n</div>`;
267
+ const rendered = await ctx.chatluna.renderer.render(
268
+ {
269
+ content: [{ type: 'text', text: markdown }],
270
+ },
271
+ { type: 'image', session },
272
+ );
273
+ if (rendered?.length) return rendered.map((r) => r.element);
274
+ } catch (err) {
275
+ ctx.logger?.warn?.('[think-viewer] image render failed, fallback text', err);
276
+ }
277
+ }
278
+
279
+ return `���˼�����ݣ������� ${targetIndex} ����\n${formatted}`;
280
+ });
281
+
282
+ // �쳣��ʽ�Զ�����
283
+ applyGuard(ctx, config);
284
+ }
285
+
286
+ module.exports = {
287
+ name,
288
+ apply,
289
+ Config,
290
+ inject,
184
291
  };
package/package.json CHANGED
@@ -1,40 +1,43 @@
1
- {
2
- "name": "koishi-plugin-chatluna-think-viewer",
3
- "version": "1.0.16",
4
- "main": "index.js",
5
- "description": "Expose a command/shortcut to read the latest <think> block from chatluna-character.",
6
- "license": "MIT",
7
- "keywords": [
8
- "koishi",
9
- "chatluna",
10
- "character",
11
- "think"
12
- ],
13
- "homepage": "https://github.com/sCR0WN-s/koishi-plugin-chatluna-think-viewer",
14
- "repository": {
15
- "type": "git",
16
- "url": "git+https://github.com/sCR0WN-s/koishi-plugin-chatluna-think-viewer.git"
17
- },
18
- "contributors": [
19
- "sCR0WN-s <2892511968@qq.com>"
20
- ],
21
- "peerDependencies": {
22
- "koishi": "^4.18.0",
23
- "koishi-plugin-chatluna-character": "^0.0.180"
24
- },
25
- "koishi": {
26
- "description": {
27
- "zh": "通过命令/关键词查看 chatluna-character 最近一次回复的 <think> 思考内容。",
28
- "en": "Expose a command/shortcut to read the last <think> block from chatluna-character."
29
- },
30
- "service": {
31
- "required": [
32
- "chatluna_character"
33
- ]
34
- }
35
- },
36
- "files": [
37
- "index.js",
38
- "README.md"
39
- ]
1
+ {
2
+ "name": "koishi-plugin-chatluna-think-viewer",
3
+ "version": "1.0.18",
4
+ "main": "index.js",
5
+ "description": "View chatluna <think> blocks and auto recall abnormal formatted replies.",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "koishi",
9
+ "chatluna",
10
+ "character",
11
+ "think",
12
+ "recall",
13
+ "guard",
14
+ "moderation"
15
+ ],
16
+ "homepage": "https://github.com/sCR0WN-s/koishi-plugin-chatluna-think-viewer",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/sCR0WN-s/koishi-plugin-chatluna-think-viewer.git"
20
+ },
21
+ "contributors": [
22
+ "sCR0WN-s <2892511968@qq.com>"
23
+ ],
24
+ "peerDependencies": {
25
+ "koishi": "^4.18.0",
26
+ "koishi-plugin-chatluna-character": "^0.0.180"
27
+ },
28
+ "koishi": {
29
+ "description": {
30
+ "zh": "?? chatluna-character ? <think> ??????????????????/???",
31
+ "en": "View <think> blocks from chatluna-character and auto recall malformed replies."
32
+ },
33
+ "service": {
34
+ "required": [
35
+ "chatluna_character"
36
+ ]
37
+ }
38
+ },
39
+ "files": [
40
+ "index.js",
41
+ "README.md"
42
+ ]
40
43
  }