llm-message-react 0.1.2 → 0.3.0
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 +60 -10
- package/dist/highlighters/shiki.d.cts +1 -1
- package/dist/highlighters/shiki.d.ts +1 -1
- package/dist/highlighters/shikiWeb.d.cts +1 -1
- package/dist/highlighters/shikiWeb.d.ts +1 -1
- package/dist/index.cjs +887 -330
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -8
- package/dist/index.d.ts +35 -8
- package/dist/index.js +893 -336
- package/dist/index.js.map +1 -1
- package/dist/styles.css +43 -0
- package/dist/{types-Dz7GupgB.d.cts → types-DETvxTAd.d.cts} +1 -0
- package/dist/{types-Dz7GupgB.d.ts → types-DETvxTAd.d.ts} +1 -0
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var clsx = require('clsx');
|
|
4
3
|
var react = require('react');
|
|
5
4
|
var ReactMarkdown = require('react-markdown');
|
|
6
5
|
var rehypeKatex = require('rehype-katex');
|
|
7
6
|
var remarkGfm = require('remark-gfm');
|
|
8
7
|
var remarkMath = require('remark-math');
|
|
9
|
-
var
|
|
8
|
+
var clsx = require('clsx');
|
|
10
9
|
var jsxRuntime = require('react/jsx-runtime');
|
|
10
|
+
var katex = require('katex');
|
|
11
|
+
var marked = require('marked');
|
|
11
12
|
|
|
12
13
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
14
|
|
|
@@ -18,6 +19,361 @@ var remarkMath__default = /*#__PURE__*/_interopDefault(remarkMath);
|
|
|
18
19
|
var katex__default = /*#__PURE__*/_interopDefault(katex);
|
|
19
20
|
|
|
20
21
|
// src/LLMMessage.tsx
|
|
22
|
+
function CopyIcon() {
|
|
23
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
24
|
+
"svg",
|
|
25
|
+
{
|
|
26
|
+
viewBox: "0 0 24 24",
|
|
27
|
+
width: "1em",
|
|
28
|
+
height: "1em",
|
|
29
|
+
fill: "none",
|
|
30
|
+
stroke: "currentColor",
|
|
31
|
+
strokeWidth: "2",
|
|
32
|
+
strokeLinecap: "round",
|
|
33
|
+
strokeLinejoin: "round",
|
|
34
|
+
"aria-hidden": "true",
|
|
35
|
+
children: [
|
|
36
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
|
|
37
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function CheckIcon() {
|
|
43
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
44
|
+
"svg",
|
|
45
|
+
{
|
|
46
|
+
viewBox: "0 0 24 24",
|
|
47
|
+
width: "1em",
|
|
48
|
+
height: "1em",
|
|
49
|
+
fill: "none",
|
|
50
|
+
stroke: "currentColor",
|
|
51
|
+
strokeWidth: "2",
|
|
52
|
+
strokeLinecap: "round",
|
|
53
|
+
strokeLinejoin: "round",
|
|
54
|
+
"aria-hidden": "true",
|
|
55
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 6 9 17l-5-5" })
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
function CopyButton({ text, className }) {
|
|
60
|
+
const [copied, setCopied] = react.useState(false);
|
|
61
|
+
const timeoutRef = react.useRef(null);
|
|
62
|
+
react.useEffect(() => {
|
|
63
|
+
return () => {
|
|
64
|
+
if (timeoutRef.current != null) clearTimeout(timeoutRef.current);
|
|
65
|
+
};
|
|
66
|
+
}, []);
|
|
67
|
+
const copy = () => {
|
|
68
|
+
const clipboard = navigator.clipboard;
|
|
69
|
+
if (!clipboard) return;
|
|
70
|
+
void clipboard.writeText(text).then(() => {
|
|
71
|
+
setCopied(true);
|
|
72
|
+
if (timeoutRef.current != null) clearTimeout(timeoutRef.current);
|
|
73
|
+
timeoutRef.current = setTimeout(() => setCopied(false), 2e3);
|
|
74
|
+
}).catch(() => {
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
78
|
+
"button",
|
|
79
|
+
{
|
|
80
|
+
type: "button",
|
|
81
|
+
onClick: copy,
|
|
82
|
+
className,
|
|
83
|
+
"aria-label": copied ? "Copied" : "Copy code",
|
|
84
|
+
"data-copied": copied ? "" : void 0,
|
|
85
|
+
children: copied ? /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(CopyIcon, {})
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
function cx(...inputs) {
|
|
90
|
+
const result = clsx.clsx(inputs);
|
|
91
|
+
return result === "" ? void 0 : result;
|
|
92
|
+
}
|
|
93
|
+
function buildMarkdownComponents(classNames, overrides, highlighter) {
|
|
94
|
+
const cn = classNames ?? {};
|
|
95
|
+
const o = overrides ?? {};
|
|
96
|
+
const Highlighter = highlighter;
|
|
97
|
+
const c = {
|
|
98
|
+
// Code — inline and fenced blocks.
|
|
99
|
+
code: cx("llm-code", cn.code),
|
|
100
|
+
codeBlock: cx("llm-code-block", cn.codeBlock),
|
|
101
|
+
codeHeader: cx("llm-code-header", cn.codeHeader),
|
|
102
|
+
codeLanguage: cx("llm-code-language", cn.codeLanguage),
|
|
103
|
+
copyButton: cx("llm-copy-button", cn.copyButton),
|
|
104
|
+
// Tables — wrapper, table shell, and cells.
|
|
105
|
+
tableWrapper: cx("llm-table-wrapper", cn.tableWrapper),
|
|
106
|
+
table: cx("llm-table", cn.table),
|
|
107
|
+
th: cx("llm-th", cn.th),
|
|
108
|
+
td: cx("llm-td", cn.td),
|
|
109
|
+
// Blockquote.
|
|
110
|
+
blockquote: cx("llm-blockquote", cn.blockquote),
|
|
111
|
+
// Lists — ordered, unordered, items, and GFM task checkboxes.
|
|
112
|
+
ul: cx("llm-ul", cn.ul),
|
|
113
|
+
ol: cx("llm-ol", cn.ol),
|
|
114
|
+
li: cx("llm-li", cn.li),
|
|
115
|
+
checkbox: cx("llm-checkbox", cn.checkbox),
|
|
116
|
+
// Paragraph.
|
|
117
|
+
p: cx("llm-p", cn.p),
|
|
118
|
+
// Headings — h1 through h6.
|
|
119
|
+
h1: cx("llm-h1", cn.h1),
|
|
120
|
+
h2: cx("llm-h2", cn.h2),
|
|
121
|
+
h3: cx("llm-h3", cn.h3),
|
|
122
|
+
h4: cx("llm-h4", cn.h4),
|
|
123
|
+
h5: cx("llm-h5", cn.h5),
|
|
124
|
+
h6: cx("llm-h6", cn.h6),
|
|
125
|
+
// Links and media.
|
|
126
|
+
a: cx("llm-a", cn.a),
|
|
127
|
+
img: cx("llm-img", cn.img),
|
|
128
|
+
// Horizontal rule.
|
|
129
|
+
hr: cx("llm-hr", cn.hr),
|
|
130
|
+
// Inline emphasis — bold, italic, strikethrough.
|
|
131
|
+
strong: cx("llm-strong", cn.strong),
|
|
132
|
+
em: cx("llm-em", cn.em),
|
|
133
|
+
del: cx("llm-del", cn.del)
|
|
134
|
+
};
|
|
135
|
+
const merge = (base, extra) => extra ? cx(base, extra) : base;
|
|
136
|
+
return {
|
|
137
|
+
// --- Code: inline `code` and fenced ``` blocks (pre is a passthrough). ---
|
|
138
|
+
code({ node: _node, className, children, ...props }) {
|
|
139
|
+
const match = /language-(\w+)/.exec(className || "");
|
|
140
|
+
const codeText = String(children).replace(/\n$/, "");
|
|
141
|
+
const isBlock = match != null || String(children).includes("\n");
|
|
142
|
+
if (isBlock) {
|
|
143
|
+
const language = match?.[1] ?? "";
|
|
144
|
+
if (o.codeBlock) {
|
|
145
|
+
const CodeBlock = o.codeBlock;
|
|
146
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
147
|
+
CodeBlock,
|
|
148
|
+
{
|
|
149
|
+
code: codeText,
|
|
150
|
+
language,
|
|
151
|
+
className: cn.codeBlock
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: c.codeBlock, children: [
|
|
156
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: c.codeHeader, children: [
|
|
157
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: c.codeLanguage, children: language }),
|
|
158
|
+
o.copyButton ? /* @__PURE__ */ jsxRuntime.jsx(o.copyButton, { text: codeText, className: cn.copyButton }) : /* @__PURE__ */ jsxRuntime.jsx(CopyButton, { text: codeText, className: c.copyButton })
|
|
159
|
+
] }),
|
|
160
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "llm-code-body", children: Highlighter ? /* @__PURE__ */ jsxRuntime.jsx(Highlighter, { code: codeText, language }) : /* @__PURE__ */ jsxRuntime.jsx("code", { className: "llm-code-plain", children: codeText }) })
|
|
161
|
+
] });
|
|
162
|
+
}
|
|
163
|
+
if (o.code) {
|
|
164
|
+
const InlineCode = o.code;
|
|
165
|
+
return /* @__PURE__ */ jsxRuntime.jsx(InlineCode, { className: merge(c.code, className), children });
|
|
166
|
+
}
|
|
167
|
+
return /* @__PURE__ */ jsxRuntime.jsx("code", { className: merge(c.code, className), ...props, children });
|
|
168
|
+
},
|
|
169
|
+
pre({ children }) {
|
|
170
|
+
if (o.pre) {
|
|
171
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.pre, { children });
|
|
172
|
+
}
|
|
173
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
174
|
+
},
|
|
175
|
+
// --- Tables: scrollable wrapper, shell, header and body cells. ---
|
|
176
|
+
table({ children }) {
|
|
177
|
+
if (o.table) {
|
|
178
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.table, { className: cn.table, children });
|
|
179
|
+
}
|
|
180
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: c.tableWrapper, children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: c.table, children }) });
|
|
181
|
+
},
|
|
182
|
+
th({ children, style }) {
|
|
183
|
+
if (o.th) {
|
|
184
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.th, { className: cn.th, children });
|
|
185
|
+
}
|
|
186
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", { className: c.th, style, children });
|
|
187
|
+
},
|
|
188
|
+
td({ children, style }) {
|
|
189
|
+
if (o.td) {
|
|
190
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.td, { className: cn.td, children });
|
|
191
|
+
}
|
|
192
|
+
return /* @__PURE__ */ jsxRuntime.jsx("td", { className: c.td, style, children });
|
|
193
|
+
},
|
|
194
|
+
// --- Blockquote. ---
|
|
195
|
+
blockquote({ children, className, style }) {
|
|
196
|
+
if (o.blockquote) {
|
|
197
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.blockquote, { className: cn.blockquote, children });
|
|
198
|
+
}
|
|
199
|
+
return /* @__PURE__ */ jsxRuntime.jsx("blockquote", { className: merge(c.blockquote, className), style, children });
|
|
200
|
+
},
|
|
201
|
+
// --- Lists: ordered, unordered, items, and GFM task checkboxes. ---
|
|
202
|
+
ul({ children }) {
|
|
203
|
+
if (o.ul) {
|
|
204
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.ul, { className: cn.ul, children });
|
|
205
|
+
}
|
|
206
|
+
return /* @__PURE__ */ jsxRuntime.jsx("ul", { className: c.ul, children });
|
|
207
|
+
},
|
|
208
|
+
ol({ children }) {
|
|
209
|
+
if (o.ol) {
|
|
210
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.ol, { className: cn.ol, children });
|
|
211
|
+
}
|
|
212
|
+
return /* @__PURE__ */ jsxRuntime.jsx("ol", { className: c.ol, children });
|
|
213
|
+
},
|
|
214
|
+
li({ children, className, style }) {
|
|
215
|
+
if (o.li) {
|
|
216
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.li, { className: cn.li, children });
|
|
217
|
+
}
|
|
218
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { className: merge(c.li, className), style, children });
|
|
219
|
+
},
|
|
220
|
+
input({ node: _node, type, checked, disabled, ...props }) {
|
|
221
|
+
if (type === "checkbox") {
|
|
222
|
+
if (o.checkbox) {
|
|
223
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.checkbox, { checked: Boolean(checked), className: cn.checkbox });
|
|
224
|
+
}
|
|
225
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
226
|
+
"input",
|
|
227
|
+
{
|
|
228
|
+
type: "checkbox",
|
|
229
|
+
checked: Boolean(checked),
|
|
230
|
+
disabled: true,
|
|
231
|
+
"aria-label": checked ? "Completed task" : "Incomplete task",
|
|
232
|
+
className: c.checkbox,
|
|
233
|
+
readOnly: true
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
238
|
+
"input",
|
|
239
|
+
{
|
|
240
|
+
type,
|
|
241
|
+
checked,
|
|
242
|
+
disabled,
|
|
243
|
+
readOnly: true,
|
|
244
|
+
...props
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
},
|
|
248
|
+
// --- Paragraph. ---
|
|
249
|
+
p({ children }) {
|
|
250
|
+
if (o.p) {
|
|
251
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.p, { className: cn.p, children });
|
|
252
|
+
}
|
|
253
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: c.p, children });
|
|
254
|
+
},
|
|
255
|
+
// --- Headings: h1 through h6. ---
|
|
256
|
+
h1({ children }) {
|
|
257
|
+
if (o.h1) {
|
|
258
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.h1, { className: cn.h1, children });
|
|
259
|
+
}
|
|
260
|
+
return /* @__PURE__ */ jsxRuntime.jsx("h1", { className: c.h1, children });
|
|
261
|
+
},
|
|
262
|
+
h2({ children }) {
|
|
263
|
+
if (o.h2) {
|
|
264
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.h2, { className: cn.h2, children });
|
|
265
|
+
}
|
|
266
|
+
return /* @__PURE__ */ jsxRuntime.jsx("h2", { className: c.h2, children });
|
|
267
|
+
},
|
|
268
|
+
h3({ children }) {
|
|
269
|
+
if (o.h3) {
|
|
270
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.h3, { className: cn.h3, children });
|
|
271
|
+
}
|
|
272
|
+
return /* @__PURE__ */ jsxRuntime.jsx("h3", { className: c.h3, children });
|
|
273
|
+
},
|
|
274
|
+
h4({ children }) {
|
|
275
|
+
if (o.h4) {
|
|
276
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.h4, { className: cn.h4, children });
|
|
277
|
+
}
|
|
278
|
+
return /* @__PURE__ */ jsxRuntime.jsx("h4", { className: c.h4, children });
|
|
279
|
+
},
|
|
280
|
+
h5({ children }) {
|
|
281
|
+
if (o.h5) {
|
|
282
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.h5, { className: cn.h5, children });
|
|
283
|
+
}
|
|
284
|
+
return /* @__PURE__ */ jsxRuntime.jsx("h5", { className: c.h5, children });
|
|
285
|
+
},
|
|
286
|
+
h6({ children }) {
|
|
287
|
+
if (o.h6) {
|
|
288
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.h6, { className: cn.h6, children });
|
|
289
|
+
}
|
|
290
|
+
return /* @__PURE__ */ jsxRuntime.jsx("h6", { className: c.h6, children });
|
|
291
|
+
},
|
|
292
|
+
// --- Links and media. ---
|
|
293
|
+
a({ href, title, children }) {
|
|
294
|
+
if (o.a) {
|
|
295
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.a, { href, title, className: cn.a, children });
|
|
296
|
+
}
|
|
297
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
298
|
+
"a",
|
|
299
|
+
{
|
|
300
|
+
href,
|
|
301
|
+
title,
|
|
302
|
+
target: "_blank",
|
|
303
|
+
rel: "noopener noreferrer",
|
|
304
|
+
className: c.a,
|
|
305
|
+
children
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
},
|
|
309
|
+
img({ node: _node, src, alt, title, className: _className, ...props }) {
|
|
310
|
+
if (o.img) {
|
|
311
|
+
const Image = o.img;
|
|
312
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
313
|
+
Image,
|
|
314
|
+
{
|
|
315
|
+
src: typeof src === "string" ? src : void 0,
|
|
316
|
+
alt,
|
|
317
|
+
title,
|
|
318
|
+
className: cn.img
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
323
|
+
"img",
|
|
324
|
+
{
|
|
325
|
+
src: typeof src === "string" ? src : void 0,
|
|
326
|
+
alt,
|
|
327
|
+
title,
|
|
328
|
+
className: c.img,
|
|
329
|
+
...props
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
},
|
|
333
|
+
// --- Horizontal rule. ---
|
|
334
|
+
hr() {
|
|
335
|
+
if (o.hr) {
|
|
336
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.hr, { className: cn.hr });
|
|
337
|
+
}
|
|
338
|
+
return /* @__PURE__ */ jsxRuntime.jsx("hr", { className: c.hr });
|
|
339
|
+
},
|
|
340
|
+
// --- Inline emphasis: bold, italic, strikethrough. ---
|
|
341
|
+
strong({ children }) {
|
|
342
|
+
if (o.strong) {
|
|
343
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.strong, { className: cn.strong, children });
|
|
344
|
+
}
|
|
345
|
+
return /* @__PURE__ */ jsxRuntime.jsx("strong", { className: c.strong, children });
|
|
346
|
+
},
|
|
347
|
+
em({ children }) {
|
|
348
|
+
if (o.em) {
|
|
349
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.em, { className: cn.em, children });
|
|
350
|
+
}
|
|
351
|
+
return /* @__PURE__ */ jsxRuntime.jsx("em", { className: c.em, children });
|
|
352
|
+
},
|
|
353
|
+
del({ children }) {
|
|
354
|
+
if (o.del) {
|
|
355
|
+
return /* @__PURE__ */ jsxRuntime.jsx(o.del, { className: cn.del, children });
|
|
356
|
+
}
|
|
357
|
+
return /* @__PURE__ */ jsxRuntime.jsx("del", { className: c.del, children });
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
var MarkdownBlock = react.memo(function MarkdownBlock2({
|
|
362
|
+
content,
|
|
363
|
+
components,
|
|
364
|
+
remarkPlugins: remark,
|
|
365
|
+
rehypePlugins
|
|
366
|
+
}) {
|
|
367
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
368
|
+
ReactMarkdown__default.default,
|
|
369
|
+
{
|
|
370
|
+
remarkPlugins: remark,
|
|
371
|
+
rehypePlugins,
|
|
372
|
+
components,
|
|
373
|
+
children: content
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
});
|
|
21
377
|
function completePartialTokens(text, options) {
|
|
22
378
|
if (!text) return text;
|
|
23
379
|
const showUnfinishedLatexBlocks = options?.showUnfinishedLatexBlocks ?? true;
|
|
@@ -27,24 +383,71 @@ function completePartialTokens(text, options) {
|
|
|
27
383
|
const protectedSpans = [];
|
|
28
384
|
const protect = (value) => `\0llmph${protectedSpans.push(value) - 1}\0`;
|
|
29
385
|
let working = text.replace(/```[\s\S]*?```/g, (match) => protect(match));
|
|
30
|
-
working = working
|
|
31
|
-
working = working.replace(/`[^`\n]+`/g, (match) => protect(match));
|
|
32
|
-
const lastBacktick = working.lastIndexOf("`");
|
|
33
|
-
if (lastBacktick !== -1) {
|
|
34
|
-
const newline = working.indexOf("\n", lastBacktick);
|
|
35
|
-
const end = newline === -1 ? working.length : newline;
|
|
36
|
-
const span = working.slice(lastBacktick, end);
|
|
37
|
-
working = working.slice(0, lastBacktick) + protect(`${span}\``) + working.slice(end);
|
|
38
|
-
}
|
|
386
|
+
working = protectInlineCode(working, protect);
|
|
39
387
|
working = repairIncompleteMath(working, showUnfinishedLatexBlocks);
|
|
40
388
|
working = hideIncompleteLink(working);
|
|
41
389
|
working = completePartialTable(working);
|
|
42
390
|
working = hideDanglingListMarker(working);
|
|
43
391
|
working = closeUnbalancedEmphasis(working);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
392
|
+
while (working.includes("\0llmph")) {
|
|
393
|
+
const restored = working.replace(
|
|
394
|
+
/\u0000llmph(\d+)\u0000/g,
|
|
395
|
+
(_match, index) => protectedSpans[Number(index)] ?? ""
|
|
396
|
+
);
|
|
397
|
+
if (restored === working) break;
|
|
398
|
+
working = restored;
|
|
399
|
+
}
|
|
400
|
+
return working;
|
|
401
|
+
}
|
|
402
|
+
function protectInlineCode(text, protect) {
|
|
403
|
+
let result = "";
|
|
404
|
+
let i = 0;
|
|
405
|
+
const n = text.length;
|
|
406
|
+
while (i < n) {
|
|
407
|
+
if (text[i] !== "`") {
|
|
408
|
+
result += text[i];
|
|
409
|
+
i++;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
let openEnd = i;
|
|
413
|
+
while (openEnd < n && text[openEnd] === "`") openEnd++;
|
|
414
|
+
const width = openEnd - i;
|
|
415
|
+
const lineEnd = text.indexOf("\n", openEnd);
|
|
416
|
+
const limit = lineEnd === -1 ? n : lineEnd;
|
|
417
|
+
let closeStart = -1;
|
|
418
|
+
let scan = openEnd;
|
|
419
|
+
while (scan < limit) {
|
|
420
|
+
if (text[scan] !== "`") {
|
|
421
|
+
scan++;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
let runEnd = scan;
|
|
425
|
+
while (runEnd < limit && text[runEnd] === "`") runEnd++;
|
|
426
|
+
if (runEnd - scan === width) {
|
|
427
|
+
closeStart = scan;
|
|
428
|
+
scan = runEnd;
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
scan = runEnd;
|
|
432
|
+
}
|
|
433
|
+
if (closeStart !== -1) {
|
|
434
|
+
result += protect(text.slice(i, scan));
|
|
435
|
+
i = scan;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const lineTail = text.slice(i, limit);
|
|
439
|
+
const hasContent = lineTail.length > width;
|
|
440
|
+
if (!hasContent) {
|
|
441
|
+
i = limit;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
let trailing = lineTail.length;
|
|
445
|
+
while (trailing > 0 && lineTail[trailing - 1] === "`") trailing--;
|
|
446
|
+
const need = Math.max(0, width - (lineTail.length - trailing));
|
|
447
|
+
result += protect(lineTail + "`".repeat(need));
|
|
448
|
+
i = limit;
|
|
449
|
+
}
|
|
450
|
+
return result;
|
|
48
451
|
}
|
|
49
452
|
function completePartialTable(text) {
|
|
50
453
|
const lines = text.split("\n");
|
|
@@ -85,12 +488,23 @@ function buildDelimiterRow(fragment, columns) {
|
|
|
85
488
|
return `| ${cells.join(" | ")} |`;
|
|
86
489
|
}
|
|
87
490
|
function hideDanglingListMarker(text) {
|
|
88
|
-
const match = text.match(/\n[ \t]
|
|
491
|
+
const match = text.match(/\n([ \t]*)-+[ \t]*$/);
|
|
89
492
|
if (match?.index == null) return text;
|
|
90
493
|
const before = text.slice(0, match.index);
|
|
91
494
|
const prevLine = before.slice(before.lastIndexOf("\n") + 1);
|
|
92
495
|
if (prevLine.trim() === "") return text;
|
|
93
|
-
|
|
496
|
+
const dashIndent = indentWidth(match[1]);
|
|
497
|
+
const listItem = prevLine.match(/^([ \t]*(?:[-*+]|\d+[.)])[ \t]+)/);
|
|
498
|
+
const baseIndent = listItem ? indentWidth(listItem[1]) : 0;
|
|
499
|
+
if (dashIndent <= baseIndent + 3) return before;
|
|
500
|
+
return text;
|
|
501
|
+
}
|
|
502
|
+
function indentWidth(indent) {
|
|
503
|
+
let width = 0;
|
|
504
|
+
for (const char of indent) {
|
|
505
|
+
width += char === " " ? 4 - width % 4 : 1;
|
|
506
|
+
}
|
|
507
|
+
return width;
|
|
94
508
|
}
|
|
95
509
|
function hasUnclosedCodeFence(text) {
|
|
96
510
|
const backticks = (text.match(/^[ \t]{0,3}```/gm) ?? []).length;
|
|
@@ -132,7 +546,7 @@ function repairIncompleteMath(text, showUnfinishedLatexBlocks) {
|
|
|
132
546
|
opens.push({
|
|
133
547
|
index: text.lastIndexOf("\\("),
|
|
134
548
|
open: "\\(",
|
|
135
|
-
close: "",
|
|
549
|
+
close: "\\)",
|
|
136
550
|
block: false,
|
|
137
551
|
display: false
|
|
138
552
|
});
|
|
@@ -155,16 +569,12 @@ function repairIncompleteMath(text, showUnfinishedLatexBlocks) {
|
|
|
155
569
|
/(?<!\\)\$(?!\$)\d[\d\s.,+\-*/=]*\$/g,
|
|
156
570
|
(m) => " ".repeat(m.length)
|
|
157
571
|
);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
lastDollar = match.index;
|
|
161
|
-
}
|
|
162
|
-
const singles = masked.match(/(?<!\\)\$(?!\d)/g) ?? [];
|
|
163
|
-
if (singles.length % 2 === 1 && lastDollar !== -1) {
|
|
572
|
+
const dollars = inlineMathDollarIndices(masked);
|
|
573
|
+
if (dollars.length % 2 === 1) {
|
|
164
574
|
opens.push({
|
|
165
|
-
index:
|
|
575
|
+
index: dollars[dollars.length - 1],
|
|
166
576
|
open: "$",
|
|
167
|
-
close: "",
|
|
577
|
+
close: "$",
|
|
168
578
|
block: false,
|
|
169
579
|
display: false
|
|
170
580
|
});
|
|
@@ -174,14 +584,11 @@ function repairIncompleteMath(text, showUnfinishedLatexBlocks) {
|
|
|
174
584
|
if (valid.length === 0) return text;
|
|
175
585
|
const open = valid.reduce((a, b) => b.index < a.index ? b : a);
|
|
176
586
|
const before = text.slice(0, open.index);
|
|
177
|
-
if (!open.block) {
|
|
178
|
-
return before;
|
|
179
|
-
}
|
|
180
587
|
if (!showUnfinishedLatexBlocks) {
|
|
181
588
|
return before;
|
|
182
589
|
}
|
|
183
590
|
const inner = text.slice(open.index + open.open.length);
|
|
184
|
-
const body = bestRenderableMathBody(inner, open.display);
|
|
591
|
+
const body = bestRenderableMathBody(inner, open.display, open.block);
|
|
185
592
|
if (body == null) return before;
|
|
186
593
|
const blockLayout = /^[ \t]*\r?\n/.test(inner);
|
|
187
594
|
if (!blockLayout) {
|
|
@@ -192,14 +599,51 @@ ${open.open}`;
|
|
|
192
599
|
return `${before}${opener}${body.replace(/\s+$/, "")}
|
|
193
600
|
${open.close}`;
|
|
194
601
|
}
|
|
195
|
-
function
|
|
602
|
+
function inlineMathDollarIndices(masked) {
|
|
603
|
+
const indices = [];
|
|
604
|
+
const pattern = /(?<!\\)\$/g;
|
|
605
|
+
let match;
|
|
606
|
+
while ((match = pattern.exec(masked)) != null) {
|
|
607
|
+
const index = match.index;
|
|
608
|
+
if (/\d/.test(masked[index + 1] ?? "")) {
|
|
609
|
+
const rest = masked.slice(index + 1);
|
|
610
|
+
const end = rest.search(/(?<!\\)\$|\n/);
|
|
611
|
+
const span = end === -1 ? rest : rest.slice(0, end);
|
|
612
|
+
if (!/\\[a-zA-Z]/.test(span)) continue;
|
|
613
|
+
}
|
|
614
|
+
indices.push(index);
|
|
615
|
+
}
|
|
616
|
+
return indices;
|
|
617
|
+
}
|
|
618
|
+
function bestRenderableMathBody(inner, display, block) {
|
|
196
619
|
const candidates = unclosedEnvironments(inner).length > 0 ? [closeOpenMathConstructs(truncateToLastRow(inner)), closeOpenMathConstructs(trimIncompleteMathTail(inner))] : [closeOpenMathConstructs(trimIncompleteMathTail(inner))];
|
|
620
|
+
if (!block) {
|
|
621
|
+
candidates.unshift(
|
|
622
|
+
closeOpenMathConstructs(
|
|
623
|
+
trimIncompleteMathTail(dropIncompleteBraceGroup(inner))
|
|
624
|
+
)
|
|
625
|
+
);
|
|
626
|
+
}
|
|
197
627
|
for (const candidate of candidates) {
|
|
198
628
|
if (!hasRenderableMathContent(candidate)) continue;
|
|
199
629
|
if (isRenderableMath(candidate, display)) return candidate;
|
|
200
630
|
}
|
|
201
631
|
return null;
|
|
202
632
|
}
|
|
633
|
+
function dropIncompleteBraceGroup(text) {
|
|
634
|
+
const stack = [];
|
|
635
|
+
for (let i = 0; i < text.length; i++) {
|
|
636
|
+
const char = text[i];
|
|
637
|
+
if (char === "\\") {
|
|
638
|
+
i++;
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
if (char === "{") stack.push(i);
|
|
642
|
+
else if (char === "}" && stack.length > 0) stack.pop();
|
|
643
|
+
}
|
|
644
|
+
if (stack.length === 0) return text;
|
|
645
|
+
return text.slice(0, stack[0]);
|
|
646
|
+
}
|
|
203
647
|
function hasRenderableMathContent(body) {
|
|
204
648
|
const stripped = body.replace(/\\(?:begin|end)\s*\{[^}]*\}/g, "").replace(/[\s{}]/g, "");
|
|
205
649
|
return stripped.length > 0;
|
|
@@ -309,9 +753,13 @@ function closeDoubleUnderscore(text) {
|
|
|
309
753
|
if (!opensAtWordBoundary(text, lastIndex)) return text;
|
|
310
754
|
return insertBeforeTrailingWhitespace(text, "__");
|
|
311
755
|
}
|
|
756
|
+
function maskBlockAsterisks(text) {
|
|
757
|
+
return text.replace(/^[ \t]*\*(?:[ \t]*\*){2,}[ \t]*$/gm, (m) => " ".repeat(m.length)).replace(/^[ \t]*\*(?=[ \t])/gm, (m) => " ".repeat(m.length));
|
|
758
|
+
}
|
|
312
759
|
function closeRunMarker(text, marker) {
|
|
313
760
|
const escaped = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
314
|
-
const
|
|
761
|
+
const countSource = marker.includes("*") ? maskBlockAsterisks(text) : text;
|
|
762
|
+
const count = (countSource.match(new RegExp(escaped, "g")) ?? []).length;
|
|
315
763
|
if (count % 2 === 0) return text;
|
|
316
764
|
const lastIndex = text.lastIndexOf(marker);
|
|
317
765
|
const after = text.slice(lastIndex + marker.length);
|
|
@@ -319,7 +767,7 @@ function closeRunMarker(text, marker) {
|
|
|
319
767
|
return insertBeforeTrailingWhitespace(text, marker);
|
|
320
768
|
}
|
|
321
769
|
function closeSingleAsterisk(text) {
|
|
322
|
-
const masked = text.replace(/\*\*/g, "");
|
|
770
|
+
const masked = maskBlockAsterisks(text).replace(/\*\*/g, "");
|
|
323
771
|
const count = (masked.match(/\*/g) ?? []).length;
|
|
324
772
|
if (count % 2 === 0) return text;
|
|
325
773
|
const lastIndex = text.lastIndexOf("*");
|
|
@@ -330,78 +778,11 @@ function closeSingleAsterisk(text) {
|
|
|
330
778
|
function insertBeforeTrailingWhitespace(text, marker) {
|
|
331
779
|
const trailing = text.match(/\s+$/)?.[0] ?? "";
|
|
332
780
|
const core = text.slice(0, text.length - trailing.length);
|
|
333
|
-
return core + marker + trailing;
|
|
334
|
-
}
|
|
335
|
-
function CopyIcon() {
|
|
336
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
337
|
-
"svg",
|
|
338
|
-
{
|
|
339
|
-
viewBox: "0 0 24 24",
|
|
340
|
-
width: "1em",
|
|
341
|
-
height: "1em",
|
|
342
|
-
fill: "none",
|
|
343
|
-
stroke: "currentColor",
|
|
344
|
-
strokeWidth: "2",
|
|
345
|
-
strokeLinecap: "round",
|
|
346
|
-
strokeLinejoin: "round",
|
|
347
|
-
"aria-hidden": "true",
|
|
348
|
-
children: [
|
|
349
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
|
|
350
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
|
|
351
|
-
]
|
|
352
|
-
}
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
function CheckIcon() {
|
|
356
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
357
|
-
"svg",
|
|
358
|
-
{
|
|
359
|
-
viewBox: "0 0 24 24",
|
|
360
|
-
width: "1em",
|
|
361
|
-
height: "1em",
|
|
362
|
-
fill: "none",
|
|
363
|
-
stroke: "currentColor",
|
|
364
|
-
strokeWidth: "2",
|
|
365
|
-
strokeLinecap: "round",
|
|
366
|
-
strokeLinejoin: "round",
|
|
367
|
-
"aria-hidden": "true",
|
|
368
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 6 9 17l-5-5" })
|
|
369
|
-
}
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
function CopyButton({ text, className }) {
|
|
373
|
-
const [copied, setCopied] = react.useState(false);
|
|
374
|
-
const timeoutRef = react.useRef(null);
|
|
375
|
-
react.useEffect(() => {
|
|
376
|
-
return () => {
|
|
377
|
-
if (timeoutRef.current != null) clearTimeout(timeoutRef.current);
|
|
378
|
-
};
|
|
379
|
-
}, []);
|
|
380
|
-
const copy = () => {
|
|
381
|
-
const clipboard = navigator.clipboard;
|
|
382
|
-
if (!clipboard) return;
|
|
383
|
-
void clipboard.writeText(text).then(() => {
|
|
384
|
-
setCopied(true);
|
|
385
|
-
if (timeoutRef.current != null) clearTimeout(timeoutRef.current);
|
|
386
|
-
timeoutRef.current = setTimeout(() => setCopied(false), 2e3);
|
|
387
|
-
}).catch(() => {
|
|
388
|
-
});
|
|
389
|
-
};
|
|
390
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
391
|
-
"button",
|
|
392
|
-
{
|
|
393
|
-
type: "button",
|
|
394
|
-
onClick: copy,
|
|
395
|
-
className,
|
|
396
|
-
"aria-label": copied ? "Copied" : "Copy code",
|
|
397
|
-
"data-copied": copied ? "" : void 0,
|
|
398
|
-
children: copied ? /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(CopyIcon, {})
|
|
399
|
-
}
|
|
400
|
-
);
|
|
781
|
+
return core + marker + trailing;
|
|
401
782
|
}
|
|
402
783
|
|
|
403
784
|
// src/preprocess.ts
|
|
404
|
-
var CODE_SPAN = /```[\s\S]
|
|
785
|
+
var CODE_SPAN = /```[\s\S]*?```|``[^\n]*?``|`[^`\n]+`/;
|
|
405
786
|
function preprocessLaTeX(content) {
|
|
406
787
|
const codeBlocks = [];
|
|
407
788
|
content = content.replace(
|
|
@@ -454,237 +835,374 @@ function escapeBrackets(text) {
|
|
|
454
835
|
function escapeMhchem(text) {
|
|
455
836
|
return text.replaceAll("$\\ce{", "$\\\\ce{").replaceAll("$\\pu{", "$\\\\pu{");
|
|
456
837
|
}
|
|
457
|
-
var
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
838
|
+
var FOOTNOTE = /\[\^[\w-]{1,200}\]/;
|
|
839
|
+
function countDollarRuns(text) {
|
|
840
|
+
let runs = 0;
|
|
841
|
+
for (let i = 0; i < text.length - 1; i++) {
|
|
842
|
+
if (text[i] === "$" && text[i + 1] === "$") {
|
|
843
|
+
runs++;
|
|
844
|
+
i++;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return runs;
|
|
462
848
|
}
|
|
463
|
-
function
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
return /* @__PURE__ */ jsxRuntime.jsx("h1", { className: cx("llm-h1", cn.h1), children });
|
|
564
|
-
},
|
|
565
|
-
h2({ children }) {
|
|
566
|
-
if (o.h2) {
|
|
567
|
-
return /* @__PURE__ */ jsxRuntime.jsx(o.h2, { className: cn.h2, children });
|
|
568
|
-
}
|
|
569
|
-
return /* @__PURE__ */ jsxRuntime.jsx("h2", { className: cx("llm-h2", cn.h2), children });
|
|
570
|
-
},
|
|
571
|
-
h3({ children }) {
|
|
572
|
-
if (o.h3) {
|
|
573
|
-
return /* @__PURE__ */ jsxRuntime.jsx(o.h3, { className: cn.h3, children });
|
|
574
|
-
}
|
|
575
|
-
return /* @__PURE__ */ jsxRuntime.jsx("h3", { className: cx("llm-h3", cn.h3), children });
|
|
576
|
-
},
|
|
577
|
-
h4({ children }) {
|
|
578
|
-
if (o.h4) {
|
|
579
|
-
return /* @__PURE__ */ jsxRuntime.jsx(o.h4, { className: cn.h4, children });
|
|
580
|
-
}
|
|
581
|
-
return /* @__PURE__ */ jsxRuntime.jsx("h4", { className: cx("llm-h4", cn.h4), children });
|
|
582
|
-
},
|
|
583
|
-
h5({ children }) {
|
|
584
|
-
if (o.h5) {
|
|
585
|
-
return /* @__PURE__ */ jsxRuntime.jsx(o.h5, { className: cn.h5, children });
|
|
849
|
+
function countEscaped(text, second) {
|
|
850
|
+
let count = 0;
|
|
851
|
+
for (let i = 0; i < text.length - 1; i++) {
|
|
852
|
+
if (text[i] === "\\" && text[i + 1] === second) {
|
|
853
|
+
count++;
|
|
854
|
+
i++;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return count;
|
|
858
|
+
}
|
|
859
|
+
function hasOpenBlockMath(text) {
|
|
860
|
+
if (countDollarRuns(text) % 2 === 1) {
|
|
861
|
+
return true;
|
|
862
|
+
}
|
|
863
|
+
return countEscaped(text, "[") > countEscaped(text, "]");
|
|
864
|
+
}
|
|
865
|
+
function splitMarkdownBlocks(source) {
|
|
866
|
+
if (source === "" || /^\s*$/.test(source)) {
|
|
867
|
+
return [];
|
|
868
|
+
}
|
|
869
|
+
if (!/\n[ \t]*\n/.test(source)) {
|
|
870
|
+
return [source];
|
|
871
|
+
}
|
|
872
|
+
if (FOOTNOTE.test(source)) {
|
|
873
|
+
return [source];
|
|
874
|
+
}
|
|
875
|
+
let tokens;
|
|
876
|
+
try {
|
|
877
|
+
tokens = marked.Lexer.lex(source, { gfm: true });
|
|
878
|
+
} catch {
|
|
879
|
+
return [source];
|
|
880
|
+
}
|
|
881
|
+
const blocks = [];
|
|
882
|
+
let cursor = 0;
|
|
883
|
+
for (const token of tokens) {
|
|
884
|
+
if (token.type === "def") {
|
|
885
|
+
return [source];
|
|
886
|
+
}
|
|
887
|
+
const raw = token.raw;
|
|
888
|
+
if (raw === "") {
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
const end = Math.min(cursor + raw.length, source.length);
|
|
892
|
+
const text = source.slice(cursor, end);
|
|
893
|
+
cursor = end;
|
|
894
|
+
const last = blocks.length - 1;
|
|
895
|
+
const previous = last >= 0 ? blocks[last] : void 0;
|
|
896
|
+
const mergeIntoPrevious = previous != null && (token.type === "space" || hasOpenBlockMath(previous));
|
|
897
|
+
if (mergeIntoPrevious) {
|
|
898
|
+
blocks[last] += text;
|
|
899
|
+
} else {
|
|
900
|
+
blocks.push(text);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (blocks.length === 0) {
|
|
904
|
+
return [source];
|
|
905
|
+
}
|
|
906
|
+
if (cursor < source.length) {
|
|
907
|
+
blocks[blocks.length - 1] += source.slice(cursor);
|
|
908
|
+
}
|
|
909
|
+
if (blocks.join("") !== source) {
|
|
910
|
+
return [source];
|
|
911
|
+
}
|
|
912
|
+
return blocks;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// src/useMarkdownBlocks.ts
|
|
916
|
+
function useMarkdownBlocks({
|
|
917
|
+
source,
|
|
918
|
+
blockMemoization,
|
|
919
|
+
repairPartialTokens,
|
|
920
|
+
showUnfinishedLatexBlocks,
|
|
921
|
+
smoothReveal
|
|
922
|
+
}) {
|
|
923
|
+
const prevSourceRef = react.useRef("");
|
|
924
|
+
const processor = react.useMemo(() => {
|
|
925
|
+
const cache = /* @__PURE__ */ new Map();
|
|
926
|
+
const process = (block) => {
|
|
927
|
+
const repaired = repairPartialTokens ? completePartialTokens(block, { showUnfinishedLatexBlocks }) : block;
|
|
928
|
+
return preprocessLaTeX(repaired);
|
|
929
|
+
};
|
|
930
|
+
return { cache, process };
|
|
931
|
+
}, [repairPartialTokens, showUnfinishedLatexBlocks]);
|
|
932
|
+
const blocks = react.useMemo(() => {
|
|
933
|
+
if (source === "") {
|
|
934
|
+
prevSourceRef.current = source;
|
|
935
|
+
return [];
|
|
936
|
+
}
|
|
937
|
+
if (!source.startsWith(prevSourceRef.current)) {
|
|
938
|
+
processor.cache.clear();
|
|
939
|
+
}
|
|
940
|
+
prevSourceRef.current = source;
|
|
941
|
+
if (!blockMemoization) {
|
|
942
|
+
return [processor.process(source)];
|
|
943
|
+
}
|
|
944
|
+
const rawBlocks = splitMarkdownBlocks(source);
|
|
945
|
+
const lastIndex = rawBlocks.length - 1;
|
|
946
|
+
return rawBlocks.map((raw, index) => {
|
|
947
|
+
if (index === lastIndex) {
|
|
948
|
+
return processor.process(raw);
|
|
586
949
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
return /* @__PURE__ */ jsxRuntime.jsx(o.h6, { className: cn.h6, children });
|
|
950
|
+
let done = processor.cache.get(raw);
|
|
951
|
+
if (done === void 0) {
|
|
952
|
+
done = processor.process(raw);
|
|
953
|
+
processor.cache.set(raw, done);
|
|
592
954
|
}
|
|
593
|
-
return
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
955
|
+
return done;
|
|
956
|
+
});
|
|
957
|
+
}, [source, blockMemoization, processor]);
|
|
958
|
+
const deferredBlocks = react.useDeferredValue(blocks);
|
|
959
|
+
const renderedBlocks = smoothReveal ? blocks : deferredBlocks;
|
|
960
|
+
const activeIndex = renderedBlocks.length - 1;
|
|
961
|
+
const activeSource = renderedBlocks[activeIndex] ?? "";
|
|
962
|
+
return { renderedBlocks, activeIndex, activeSource };
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// src/smoothReveal.ts
|
|
966
|
+
var CHAR_CLASS = "llm-char";
|
|
967
|
+
var FADE_CLASS = "llm-fade";
|
|
968
|
+
var BLOCK_CLASS = "llm-fade-block";
|
|
969
|
+
var BLOCK_INLINE_CLASS = "llm-fade-block-inline";
|
|
970
|
+
var BLOCK_SNAP_CLASS = "llm-fade-block-snap";
|
|
971
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set(["pre", "table", "hr"]);
|
|
972
|
+
var DECORATED_TAGS = /* @__PURE__ */ new Set(["blockquote", "li", "code"]);
|
|
973
|
+
function hasClass(node, name) {
|
|
974
|
+
const className = node.properties?.className;
|
|
975
|
+
if (Array.isArray(className)) {
|
|
976
|
+
return className.includes(name);
|
|
977
|
+
}
|
|
978
|
+
return typeof className === "string" && className.split(/\s+/).includes(name);
|
|
979
|
+
}
|
|
980
|
+
function isComplexElement(node) {
|
|
981
|
+
return BLOCK_TAGS.has(node.tagName) || node.tagName === "img" || // KaTeX output (rehype-katex). `katex-display` is the block wrapper; a bare
|
|
982
|
+
// `katex` is inline math. Either way we fade the whole formula as one unit.
|
|
983
|
+
hasClass(node, "katex-display") || hasClass(node, "katex");
|
|
984
|
+
}
|
|
985
|
+
function createFadeRehypePlugin(options) {
|
|
986
|
+
const { state, onTotal } = options;
|
|
987
|
+
return function fadeRehypePlugin() {
|
|
988
|
+
return (tree) => {
|
|
989
|
+
const counter = { unit: 0 };
|
|
990
|
+
const makeFadeBlock = (node, inline, index, snap = false) => {
|
|
991
|
+
const className = inline ? [BLOCK_CLASS, BLOCK_INLINE_CLASS] : snap ? [BLOCK_CLASS, BLOCK_SNAP_CLASS] : [BLOCK_CLASS];
|
|
992
|
+
return {
|
|
993
|
+
type: "element",
|
|
994
|
+
tagName: inline ? "span" : "div",
|
|
995
|
+
properties: {
|
|
996
|
+
className,
|
|
997
|
+
style: `--i:${index}`
|
|
998
|
+
},
|
|
999
|
+
children: [node]
|
|
1000
|
+
};
|
|
1001
|
+
};
|
|
1002
|
+
const processText = (node) => {
|
|
1003
|
+
if (/^\s*$/.test(node.value)) {
|
|
1004
|
+
return [node];
|
|
599
1005
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
className: cx("llm-checkbox", cn.checkbox),
|
|
608
|
-
readOnly: true
|
|
1006
|
+
const chars = Array.from(node.value);
|
|
1007
|
+
const out = [];
|
|
1008
|
+
let plain = "";
|
|
1009
|
+
const flushPlain = () => {
|
|
1010
|
+
if (plain) {
|
|
1011
|
+
out.push({ type: "text", value: plain });
|
|
1012
|
+
plain = "";
|
|
609
1013
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
1014
|
+
};
|
|
1015
|
+
for (const ch of chars) {
|
|
1016
|
+
const index = counter.unit++;
|
|
1017
|
+
if (index < state.committedUnits) {
|
|
1018
|
+
plain += ch;
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
flushPlain();
|
|
1022
|
+
out.push({
|
|
1023
|
+
type: "element",
|
|
1024
|
+
tagName: "span",
|
|
1025
|
+
properties: { className: [CHAR_CLASS], style: `--i:${index}` },
|
|
1026
|
+
children: [{ type: "text", value: ch }]
|
|
1027
|
+
});
|
|
620
1028
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
"a",
|
|
629
|
-
{
|
|
630
|
-
href,
|
|
631
|
-
target: "_blank",
|
|
632
|
-
rel: "noopener noreferrer",
|
|
633
|
-
className: cx("llm-a", cn.a),
|
|
634
|
-
children
|
|
1029
|
+
flushPlain();
|
|
1030
|
+
return out;
|
|
1031
|
+
};
|
|
1032
|
+
const processComplex = (node) => {
|
|
1033
|
+
const index = counter.unit++;
|
|
1034
|
+
if (index < state.committedUnits) {
|
|
1035
|
+
return node;
|
|
635
1036
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
1037
|
+
const inline = node.tagName === "img" || hasClass(node, "katex");
|
|
1038
|
+
const snap = hasClass(node, "katex-display");
|
|
1039
|
+
return makeFadeBlock(node, inline, index, snap);
|
|
1040
|
+
};
|
|
1041
|
+
const stampDecoration = (node, index) => {
|
|
1042
|
+
const className = node.properties?.className;
|
|
1043
|
+
const classes = Array.isArray(className) ? className.map(String) : typeof className === "string" ? className.split(/\s+/).filter(Boolean) : [];
|
|
1044
|
+
classes.push(FADE_CLASS);
|
|
1045
|
+
node.properties = {
|
|
1046
|
+
...node.properties,
|
|
1047
|
+
className: classes,
|
|
1048
|
+
style: `--i:${index}`
|
|
1049
|
+
};
|
|
1050
|
+
};
|
|
1051
|
+
const processChildren = (parent) => {
|
|
1052
|
+
const next = [];
|
|
1053
|
+
for (const child of parent.children) {
|
|
1054
|
+
if (child.type === "text") {
|
|
1055
|
+
next.push(...processText(child));
|
|
1056
|
+
} else if (child.type === "element") {
|
|
1057
|
+
if (isComplexElement(child)) {
|
|
1058
|
+
next.push(processComplex(child));
|
|
1059
|
+
} else {
|
|
1060
|
+
if (DECORATED_TAGS.has(child.tagName)) {
|
|
1061
|
+
stampDecoration(child, counter.unit);
|
|
1062
|
+
}
|
|
1063
|
+
processChildren(child);
|
|
1064
|
+
next.push(child);
|
|
1065
|
+
}
|
|
1066
|
+
} else {
|
|
1067
|
+
next.push(child);
|
|
648
1068
|
}
|
|
649
|
-
);
|
|
650
|
-
}
|
|
651
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
652
|
-
"img",
|
|
653
|
-
{
|
|
654
|
-
src: typeof src === "string" ? src : void 0,
|
|
655
|
-
alt,
|
|
656
|
-
title,
|
|
657
|
-
className: cx("llm-img", cn.img),
|
|
658
|
-
...props
|
|
659
1069
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1070
|
+
parent.children = next;
|
|
1071
|
+
};
|
|
1072
|
+
processChildren(tree);
|
|
1073
|
+
onTotal(counter.unit);
|
|
1074
|
+
};
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// src/useSmoothReveal.ts
|
|
1079
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
|
|
1080
|
+
var SMOOTH_RAMP = 6;
|
|
1081
|
+
function useSmoothReveal({
|
|
1082
|
+
activeSource,
|
|
1083
|
+
activeKey,
|
|
1084
|
+
enabled,
|
|
1085
|
+
duration
|
|
1086
|
+
}) {
|
|
1087
|
+
const rootRef = react.useRef(null);
|
|
1088
|
+
const revealStateRef = react.useRef({ committedUnits: 0 });
|
|
1089
|
+
const positionRef = react.useRef(0);
|
|
1090
|
+
const targetRef = react.useRef(0);
|
|
1091
|
+
const velocityRef = react.useRef(0);
|
|
1092
|
+
const totalRef = react.useRef(0);
|
|
1093
|
+
const rafRef = react.useRef(null);
|
|
1094
|
+
const lastTsRef = react.useRef(0);
|
|
1095
|
+
const prevSourceRef = react.useRef("");
|
|
1096
|
+
const activeKeyRef = react.useRef(activeKey);
|
|
1097
|
+
const snapRef = react.useRef(false);
|
|
1098
|
+
const [, forceCommit] = react.useState(0);
|
|
1099
|
+
const setRevealVar = (value) => {
|
|
1100
|
+
rootRef.current?.style.setProperty("--llm-reveal", String(value));
|
|
1101
|
+
};
|
|
1102
|
+
if (enabled && activeKey !== activeKeyRef.current) {
|
|
1103
|
+
activeKeyRef.current = activeKey;
|
|
1104
|
+
positionRef.current = 0;
|
|
1105
|
+
targetRef.current = 0;
|
|
1106
|
+
velocityRef.current = 0;
|
|
1107
|
+
revealStateRef.current.committedUnits = 0;
|
|
1108
|
+
prevSourceRef.current = "";
|
|
1109
|
+
snapRef.current = false;
|
|
1110
|
+
}
|
|
1111
|
+
if (enabled) {
|
|
1112
|
+
const isAppend = activeSource.startsWith(prevSourceRef.current);
|
|
1113
|
+
if (isAppend) {
|
|
1114
|
+
const safe = Math.floor(positionRef.current) - SMOOTH_RAMP;
|
|
1115
|
+
if (safe > revealStateRef.current.committedUnits) {
|
|
1116
|
+
revealStateRef.current.committedUnits = safe;
|
|
665
1117
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
1118
|
+
} else {
|
|
1119
|
+
snapRef.current = true;
|
|
1120
|
+
revealStateRef.current.committedUnits = Number.MAX_SAFE_INTEGER;
|
|
1121
|
+
}
|
|
1122
|
+
prevSourceRef.current = activeSource;
|
|
1123
|
+
}
|
|
1124
|
+
const fadePlugin = react.useMemo(() => {
|
|
1125
|
+
if (!enabled) {
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
1128
|
+
return createFadeRehypePlugin({
|
|
1129
|
+
state: revealStateRef.current,
|
|
1130
|
+
onTotal: (total) => {
|
|
1131
|
+
totalRef.current = total;
|
|
671
1132
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
1133
|
+
});
|
|
1134
|
+
}, [enabled]);
|
|
1135
|
+
useIsomorphicLayoutEffect(() => {
|
|
1136
|
+
if (!enabled) {
|
|
1137
|
+
if (rafRef.current != null) {
|
|
1138
|
+
cancelAnimationFrame(rafRef.current);
|
|
1139
|
+
rafRef.current = null;
|
|
677
1140
|
}
|
|
678
|
-
return
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
const total = totalRef.current;
|
|
1144
|
+
if (snapRef.current) {
|
|
1145
|
+
snapRef.current = false;
|
|
1146
|
+
const end = total + SMOOTH_RAMP;
|
|
1147
|
+
positionRef.current = end;
|
|
1148
|
+
targetRef.current = end;
|
|
1149
|
+
velocityRef.current = 0;
|
|
1150
|
+
revealStateRef.current.committedUnits = total;
|
|
1151
|
+
setRevealVar(end);
|
|
1152
|
+
if (rafRef.current != null) {
|
|
1153
|
+
cancelAnimationFrame(rafRef.current);
|
|
1154
|
+
rafRef.current = null;
|
|
683
1155
|
}
|
|
684
|
-
return
|
|
1156
|
+
return;
|
|
685
1157
|
}
|
|
686
|
-
|
|
1158
|
+
setRevealVar(positionRef.current);
|
|
1159
|
+
targetRef.current = total + SMOOTH_RAMP;
|
|
1160
|
+
const reduceMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1161
|
+
const finishImmediately = reduceMotion || duration <= 0 || typeof requestAnimationFrame !== "function";
|
|
1162
|
+
if (finishImmediately) {
|
|
1163
|
+
positionRef.current = targetRef.current;
|
|
1164
|
+
setRevealVar(positionRef.current);
|
|
1165
|
+
if (revealStateRef.current.committedUnits !== total) {
|
|
1166
|
+
revealStateRef.current.committedUnits = total;
|
|
1167
|
+
forceCommit((n) => n + 1);
|
|
1168
|
+
}
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
velocityRef.current = (targetRef.current - positionRef.current) / duration;
|
|
1172
|
+
lastTsRef.current = performance.now();
|
|
1173
|
+
const tick = (ts) => {
|
|
1174
|
+
const dt = Math.max(0, ts - lastTsRef.current);
|
|
1175
|
+
lastTsRef.current = ts;
|
|
1176
|
+
let next = positionRef.current + velocityRef.current * dt;
|
|
1177
|
+
if (next >= targetRef.current) {
|
|
1178
|
+
next = targetRef.current;
|
|
1179
|
+
positionRef.current = next;
|
|
1180
|
+
setRevealVar(next);
|
|
1181
|
+
rafRef.current = null;
|
|
1182
|
+
if (revealStateRef.current.committedUnits !== totalRef.current) {
|
|
1183
|
+
revealStateRef.current.committedUnits = totalRef.current;
|
|
1184
|
+
forceCommit((n) => n + 1);
|
|
1185
|
+
}
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
positionRef.current = next;
|
|
1189
|
+
setRevealVar(next);
|
|
1190
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
1191
|
+
};
|
|
1192
|
+
if (rafRef.current == null) {
|
|
1193
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
1194
|
+
}
|
|
1195
|
+
return () => {
|
|
1196
|
+
if (rafRef.current != null) {
|
|
1197
|
+
cancelAnimationFrame(rafRef.current);
|
|
1198
|
+
rafRef.current = null;
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
}, [activeSource, activeKey, enabled, duration]);
|
|
1202
|
+
const revealStyle = enabled ? { "--llm-ramp": String(SMOOTH_RAMP) } : void 0;
|
|
1203
|
+
return { rootRef, fadePlugin, revealStyle };
|
|
687
1204
|
}
|
|
1205
|
+
var remarkPlugins = [remarkGfm__default.default, remarkMath__default.default];
|
|
688
1206
|
function LLMMessage({
|
|
689
1207
|
children,
|
|
690
1208
|
content,
|
|
@@ -693,27 +1211,66 @@ function LLMMessage({
|
|
|
693
1211
|
components,
|
|
694
1212
|
completePartialTokens: repairPartialTokens = true,
|
|
695
1213
|
showUnfinishedLatexBlocks = true,
|
|
1214
|
+
smoothReveal = false,
|
|
1215
|
+
smoothRevealDuration = 300,
|
|
1216
|
+
blockMemoization = true,
|
|
696
1217
|
highlighter,
|
|
1218
|
+
style,
|
|
697
1219
|
...rest
|
|
698
1220
|
}) {
|
|
699
1221
|
const source = content ?? children ?? "";
|
|
700
1222
|
const markdownComponents = react.useMemo(
|
|
701
|
-
() =>
|
|
1223
|
+
() => buildMarkdownComponents(classNames, components, highlighter),
|
|
702
1224
|
[classNames, components, highlighter]
|
|
703
1225
|
);
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
1226
|
+
const { renderedBlocks, activeIndex, activeSource } = useMarkdownBlocks({
|
|
1227
|
+
source,
|
|
1228
|
+
blockMemoization,
|
|
1229
|
+
repairPartialTokens,
|
|
1230
|
+
showUnfinishedLatexBlocks,
|
|
1231
|
+
smoothReveal
|
|
1232
|
+
});
|
|
1233
|
+
const { rootRef, fadePlugin, revealStyle } = useSmoothReveal({
|
|
1234
|
+
activeSource,
|
|
1235
|
+
activeKey: activeIndex,
|
|
1236
|
+
enabled: smoothReveal,
|
|
1237
|
+
duration: smoothRevealDuration
|
|
1238
|
+
});
|
|
1239
|
+
const pluginSets = react.useMemo(() => {
|
|
1240
|
+
const math = [rehypeKatex__default.default];
|
|
1241
|
+
const none = [];
|
|
1242
|
+
return {
|
|
1243
|
+
math,
|
|
1244
|
+
none,
|
|
1245
|
+
activeMath: fadePlugin ? [rehypeKatex__default.default, fadePlugin] : math,
|
|
1246
|
+
activeNone: fadePlugin ? [fadePlugin] : none
|
|
1247
|
+
};
|
|
1248
|
+
}, [fadePlugin]);
|
|
1249
|
+
const rootStyle = revealStyle ? { ...style, ...revealStyle } : style;
|
|
1250
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1251
|
+
"div",
|
|
710
1252
|
{
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1253
|
+
ref: rootRef,
|
|
1254
|
+
className: cx("llm-message", classNames?.root, className),
|
|
1255
|
+
style: rootStyle,
|
|
1256
|
+
...rest,
|
|
1257
|
+
children: renderedBlocks.map((block, index) => {
|
|
1258
|
+
const isActive = index === activeIndex;
|
|
1259
|
+
const hasMath = block.includes("$");
|
|
1260
|
+
const rehypePlugins = isActive ? hasMath ? pluginSets.activeMath : pluginSets.activeNone : hasMath ? pluginSets.math : pluginSets.none;
|
|
1261
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1262
|
+
MarkdownBlock,
|
|
1263
|
+
{
|
|
1264
|
+
content: block,
|
|
1265
|
+
components: markdownComponents,
|
|
1266
|
+
remarkPlugins,
|
|
1267
|
+
rehypePlugins
|
|
1268
|
+
},
|
|
1269
|
+
index
|
|
1270
|
+
);
|
|
1271
|
+
})
|
|
715
1272
|
}
|
|
716
|
-
)
|
|
1273
|
+
);
|
|
717
1274
|
}
|
|
718
1275
|
var DEFAULT_THEMES = { light: "github-light", dark: "github-dark" };
|
|
719
1276
|
function createShikiHighlighter(codeToHtml, options) {
|