koishi-plugin-latex-render 1.0.7 → 1.0.8

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/index.d.ts +15 -0
  2. package/index.js +306 -0
  3. package/package.json +7 -6
package/index.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { Context, Schema } from 'koishi';
2
+ export declare const name = "latex-render";
3
+ export declare const inject: string[];
4
+ export interface Config {
5
+ /** 图片宽度 */
6
+ width?: number;
7
+ /** 背景色 */
8
+ backgroundColor?: string;
9
+ /** 文字颜色 */
10
+ textColor?: string;
11
+ /** 调试模式 */
12
+ debug?: boolean;
13
+ }
14
+ export declare const Config: Schema<Config>;
15
+ export declare function apply(ctx: Context, config: Config): void;
package/index.js ADDED
@@ -0,0 +1,306 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __export = (target, all) => {
9
+ for (var name2 in all)
10
+ __defProp(target, name2, { get: all[name2], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ Config: () => Config,
34
+ apply: () => apply,
35
+ inject: () => inject,
36
+ name: () => name
37
+ });
38
+ module.exports = __toCommonJS(src_exports);
39
+ var import_koishi = require("koishi");
40
+
41
+ // src/renderer.ts
42
+ var import_katex = __toESM(require("katex"));
43
+ function containsLatex(content) {
44
+ return /\$\$/.test(content) || /\\begin\{/.test(content) || /\\\[[\s\S]*?\\\]/.test(content) || /\\\([\s\S]*?\\\)/.test(content) || /\$[^\$\n]/.test(content);
45
+ }
46
+ __name(containsLatex, "containsLatex");
47
+ function decodeHtmlEntities(text) {
48
+ return text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ");
49
+ }
50
+ __name(decodeHtmlEntities, "decodeHtmlEntities");
51
+ function parseMessage(content) {
52
+ content = decodeHtmlEntities(content);
53
+ const result = [];
54
+ const regex = /(\$\$[\s\S]+?\$\$|\\\[[\s\S]*?\\\]|\\\([\s\S]*?\\\)|\$(?:[^\$\n]|\$(?!\$)|[\s\S])*?\$|\\begin\{[a-zA-Z*]+\}[\s\S]*?\\end\{[a-zA-Z*]+\})/g;
55
+ let lastIndex = 0;
56
+ let match;
57
+ while ((match = regex.exec(content)) !== null) {
58
+ if (match.index > lastIndex) {
59
+ const text = content.slice(lastIndex, match.index).trim();
60
+ if (text) {
61
+ result.push({ type: "text", content: text });
62
+ }
63
+ }
64
+ const latex = match[1];
65
+ let isDisplay = false;
66
+ let formula = latex;
67
+ if (latex.startsWith("$$") && latex.endsWith("$$")) {
68
+ isDisplay = true;
69
+ formula = latex.slice(2, -2).trim();
70
+ } else if (latex.startsWith("\\[") && latex.endsWith("\\]")) {
71
+ isDisplay = true;
72
+ formula = latex.slice(2, -2).trim();
73
+ } else if (latex.startsWith("\\(") && latex.endsWith("\\)")) {
74
+ formula = latex.slice(2, -2).trim();
75
+ } else if (latex.startsWith("$") && latex.endsWith("$")) {
76
+ formula = latex.slice(1, -1).trim();
77
+ } else if (latex.startsWith("\\begin")) {
78
+ isDisplay = true;
79
+ }
80
+ result.push({
81
+ type: "latex",
82
+ content: formula,
83
+ display: isDisplay
84
+ });
85
+ lastIndex = match.index + match[0].length;
86
+ }
87
+ if (lastIndex < content.length) {
88
+ const text = content.slice(lastIndex).trim();
89
+ if (text) {
90
+ result.push({ type: "text", content: text });
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+ __name(parseMessage, "parseMessage");
96
+ function generateHtml(parsed, config) {
97
+ const textColor = config.textColor || "#333333";
98
+ const bgColor = config.backgroundColor || "#ffffff";
99
+ const width = config.width || 800;
100
+ let html = `<!DOCTYPE html>
101
+ <html>
102
+ <head>
103
+ <meta charset="UTF-8">
104
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
105
+ <style>
106
+ * { margin: 0; padding: 0; box-sizing: border-box; }
107
+ body {
108
+ font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
109
+ font-size: 16px;
110
+ line-height: 1.8;
111
+ color: ${textColor};
112
+ background-color: ${bgColor};
113
+ padding: 24px;
114
+ width: ${width}px;
115
+ min-height: 100px;
116
+ }
117
+ .content {
118
+ word-wrap: break-word;
119
+ white-space: pre-wrap;
120
+ }
121
+ .latex-display {
122
+ margin: 12px 0;
123
+ text-align: center;
124
+ overflow-x: auto;
125
+ }
126
+ .latex-inline {
127
+ margin: 0 2px;
128
+ }
129
+ .text-line {
130
+ margin: 4px 0;
131
+ }
132
+ </style>
133
+ </head>
134
+ <body>
135
+ <div class="content">`;
136
+ for (const item of parsed) {
137
+ if (item.type === "text") {
138
+ const escaped = item.content.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
139
+ html += `<span class="text-line">${escaped}</span>`;
140
+ } else {
141
+ try {
142
+ const htmlContent = import_katex.default.renderToString(item.content, {
143
+ throwOnError: false,
144
+ displayMode: item.display || false,
145
+ trust: true,
146
+ strict: false
147
+ });
148
+ const className = item.display ? "latex-display" : "latex-inline";
149
+ html += `<span class="${className}">${htmlContent}</span>`;
150
+ } catch (e) {
151
+ html += `<span class="latex-display"><code>${item.content}</code></span>`;
152
+ }
153
+ }
154
+ }
155
+ html += `</div></body></html>`;
156
+ return html;
157
+ }
158
+ __name(generateHtml, "generateHtml");
159
+ function estimateHeight(parsed) {
160
+ let charCount = 0;
161
+ for (const item of parsed) {
162
+ if (item.type === "text") {
163
+ charCount += item.content.length;
164
+ } else {
165
+ charCount += item.content.length * 1.5;
166
+ }
167
+ }
168
+ const lines = Math.ceil(charCount / 35);
169
+ return Math.max(100, lines * 24 + 48);
170
+ }
171
+ __name(estimateHeight, "estimateHeight");
172
+ async function renderLatex(ctx, content, config) {
173
+ console.log("[latex-render] 开始渲染...");
174
+ let parsed;
175
+ try {
176
+ parsed = parseMessage(content);
177
+ console.log("[latex-render] 解析完成,共", parsed.length, "个片段");
178
+ } catch (error) {
179
+ console.error("[latex-render] 解析消息失败:", error);
180
+ throw new Error(`消息解析失败: ${error}`);
181
+ }
182
+ if (parsed.length === 0) {
183
+ throw new Error("No content to render");
184
+ }
185
+ const width = config.width || 800;
186
+ const height = estimateHeight(parsed);
187
+ let html;
188
+ try {
189
+ html = generateHtml(parsed, config);
190
+ console.log("[latex-render] HTML 生成完成,长度:", html.length);
191
+ } catch (error) {
192
+ console.error("[latex-render] HTML 生成失败:", error);
193
+ throw new Error(`HTML 生成失败: ${error}`);
194
+ }
195
+ let page = null;
196
+ try {
197
+ page = await ctx.puppeteer.page();
198
+ console.log("[latex-render] Puppeteer 页面获取成功");
199
+ await page.setContent(html, {
200
+ waitUntil: "networkidle2",
201
+ timeout: 3e4
202
+ // 30秒超时等待 CSS
203
+ });
204
+ console.log("[latex-render] HTML 内容设置完成");
205
+ await new Promise((resolve) => setTimeout(resolve, 500));
206
+ const actualHeight = await page.evaluate(() => {
207
+ const body = document.body;
208
+ return body ? body.scrollHeight : 0;
209
+ }).catch((e) => {
210
+ console.warn("[latex-render] 获取高度失败,使用预估高度:", e);
211
+ return height;
212
+ });
213
+ const finalHeight = Math.max(actualHeight + 20, height);
214
+ console.log("[latex-render] 实际高度:", finalHeight);
215
+ const buffer = await page.screenshot({
216
+ clip: {
217
+ x: 0,
218
+ y: 0,
219
+ width,
220
+ height: finalHeight
221
+ },
222
+ type: "png"
223
+ });
224
+ console.log("[latex-render] 截图完成,buffer 长度:", buffer.length);
225
+ await page.close().catch(() => {
226
+ });
227
+ page = null;
228
+ const base64 = Buffer.from(buffer).toString("base64");
229
+ const dataUrl = `data:image/png;base64,${base64}`;
230
+ const url = await ctx.assets.upload(dataUrl, "latex-render.png");
231
+ console.log("[latex-render] 图片上传完成,URL:", url);
232
+ return url;
233
+ } catch (error) {
234
+ console.error("[latex-render] Puppeteer 渲染失败:", error?.message || error);
235
+ console.error("[latex-render] 错误详情:", error?.stack || "无堆栈信息");
236
+ if (page) {
237
+ try {
238
+ await page.close();
239
+ } catch (e) {
240
+ }
241
+ }
242
+ throw new Error(`LaTeX 渲染失败: ${error?.message || error}`);
243
+ }
244
+ }
245
+ __name(renderLatex, "renderLatex");
246
+
247
+ // src/index.ts
248
+ var name = "latex-render";
249
+ var inject = ["assets", "puppeteer"];
250
+ var Config = import_koishi.Schema.object({
251
+ width: import_koishi.Schema.number().default(800).description("图片宽度"),
252
+ backgroundColor: import_koishi.Schema.string().default("#ffffff").description("背景色"),
253
+ textColor: import_koishi.Schema.string().default("#333333").description("文字颜色"),
254
+ debug: import_koishi.Schema.boolean().default(false).description("调试模式")
255
+ });
256
+ function apply(ctx, config) {
257
+ const debug = config.debug || false;
258
+ const handler = /* @__PURE__ */ __name(async (conversationId, sourceMessage, displayResponse, promptVariables, chatInterface, session) => {
259
+ try {
260
+ let content;
261
+ if (Array.isArray(displayResponse)) {
262
+ content = displayResponse[0]?.content;
263
+ } else if (displayResponse?.content) {
264
+ content = displayResponse.content;
265
+ } else if (typeof displayResponse === "string") {
266
+ content = displayResponse;
267
+ }
268
+ if (!content) return;
269
+ const contentStr = typeof content === "string" ? content : JSON.stringify(content);
270
+ if (containsLatex(contentStr)) {
271
+ if (debug) {
272
+ console.log("[latex-render] 检测到 LaTeX 公式,开始渲染...");
273
+ }
274
+ try {
275
+ const imageUrl = await renderLatex(ctx, contentStr, config);
276
+ if (session) {
277
+ await session.send(import_koishi.h.image(imageUrl));
278
+ }
279
+ const imageContent = import_koishi.h.image(imageUrl).toString();
280
+ if (Array.isArray(displayResponse)) {
281
+ displayResponse[0].content = imageContent;
282
+ } else {
283
+ displayResponse.content = imageContent;
284
+ }
285
+ } catch (error) {
286
+ if (debug) {
287
+ console.error("[latex-render] 渲染失败:", error);
288
+ }
289
+ }
290
+ }
291
+ } catch (error) {
292
+ if (debug) {
293
+ console.error("[latex-render] 处理失败:", error);
294
+ }
295
+ }
296
+ }, "handler");
297
+ ctx.on("chatluna/after-chat", handler);
298
+ }
299
+ __name(apply, "apply");
300
+ // Annotate the CommonJS export names for ESM import in node:
301
+ 0 && (module.exports = {
302
+ Config,
303
+ apply,
304
+ inject,
305
+ name
306
+ });
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "koishi-plugin-latex-render",
3
3
  "description": "ChatLuna 专用 - 将 AI 返回的 LaTeX 公式渲染为图片",
4
- "version": "1.0.7",
5
- "main": "lib/index.js",
6
- "typings": "lib/index.d.ts",
4
+ "version": "1.0.8",
5
+ "main": "index.js",
6
+ "typings": "index.d.ts",
7
7
  "exports": {
8
- ".": "./lib/index.js"
8
+ ".": "./index.js"
9
9
  },
10
10
  "files": [
11
- "lib",
12
- "package.json"
11
+ "index.js",
12
+ "index.d.ts",
13
+ "lib"
13
14
  ],
14
15
  "license": "MIT",
15
16
  "homepage": "https://github.com/gt4404gb/koishi-plugin-latex-render",