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