llm-message-react 0.1.3 → 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/dist/index.js CHANGED
@@ -1,13 +1,369 @@
1
1
  export { createShikiHighlighter } from './chunk-S3LNINQ5.js';
2
- import { clsx } from 'clsx';
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 katex from 'katex';
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.replace(/``[\s\S]*?``/g, (match) => protect(match));
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
- return working.replace(
35
- /\u0000llmph(\d+)\u0000/g,
36
- (_match, index) => protectedSpans[Number(index)] ?? ""
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]{0,3}-+[ \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
- return before;
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;
@@ -349,83 +763,16 @@ function closeSingleAsterisk(text) {
349
763
  const lastIndex = text.lastIndexOf("*");
350
764
  const after = text.slice(lastIndex + 1);
351
765
  if (after.length === 0 || /^[\s*]/.test(after)) return text;
352
- return insertBeforeTrailingWhitespace(text, "*");
353
- }
354
- function insertBeforeTrailingWhitespace(text, marker) {
355
- const trailing = text.match(/\s+$/)?.[0] ?? "";
356
- const core = text.slice(0, text.length - trailing.length);
357
- return core + marker + trailing;
358
- }
359
- function CopyIcon() {
360
- return /* @__PURE__ */ jsxs(
361
- "svg",
362
- {
363
- viewBox: "0 0 24 24",
364
- width: "1em",
365
- height: "1em",
366
- fill: "none",
367
- stroke: "currentColor",
368
- strokeWidth: "2",
369
- strokeLinecap: "round",
370
- strokeLinejoin: "round",
371
- "aria-hidden": "true",
372
- children: [
373
- /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
374
- /* @__PURE__ */ jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
375
- ]
376
- }
377
- );
378
- }
379
- function CheckIcon() {
380
- return /* @__PURE__ */ jsx(
381
- "svg",
382
- {
383
- viewBox: "0 0 24 24",
384
- width: "1em",
385
- height: "1em",
386
- fill: "none",
387
- stroke: "currentColor",
388
- strokeWidth: "2",
389
- strokeLinecap: "round",
390
- strokeLinejoin: "round",
391
- "aria-hidden": "true",
392
- children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" })
393
- }
394
- );
395
- }
396
- function CopyButton({ text, className }) {
397
- const [copied, setCopied] = useState(false);
398
- const timeoutRef = useRef(null);
399
- useEffect(() => {
400
- return () => {
401
- if (timeoutRef.current != null) clearTimeout(timeoutRef.current);
402
- };
403
- }, []);
404
- const copy = () => {
405
- const clipboard = navigator.clipboard;
406
- if (!clipboard) return;
407
- void clipboard.writeText(text).then(() => {
408
- setCopied(true);
409
- if (timeoutRef.current != null) clearTimeout(timeoutRef.current);
410
- timeoutRef.current = setTimeout(() => setCopied(false), 2e3);
411
- }).catch(() => {
412
- });
413
- };
414
- return /* @__PURE__ */ jsx(
415
- "button",
416
- {
417
- type: "button",
418
- onClick: copy,
419
- className,
420
- "aria-label": copied ? "Copied" : "Copy code",
421
- "data-copied": copied ? "" : void 0,
422
- children: copied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {})
423
- }
424
- );
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;
425
772
  }
426
773
 
427
774
  // src/preprocess.ts
428
- var CODE_SPAN = /```[\s\S]*?```|`[^`\n]+`/;
775
+ var CODE_SPAN = /```[\s\S]*?```|``[^\n]*?``|`[^`\n]+`/;
429
776
  function preprocessLaTeX(content) {
430
777
  const codeBlocks = [];
431
778
  content = content.replace(
@@ -478,237 +825,374 @@ function escapeBrackets(text) {
478
825
  function escapeMhchem(text) {
479
826
  return text.replaceAll("$\\ce{", "$\\\\ce{").replaceAll("$\\pu{", "$\\\\pu{");
480
827
  }
481
- var remarkPlugins = [remarkGfm, remarkMath];
482
- var rehypePlugins = [rehypeKatex];
483
- function cx(...inputs) {
484
- const result = clsx(inputs);
485
- return result === "" ? void 0 : result;
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;
486
838
  }
487
- function buildComponents(classNames, overrides, highlighter) {
488
- const cn = classNames ?? {};
489
- const o = overrides ?? {};
490
- const Highlighter = highlighter;
491
- return {
492
- code({ node: _node, className, children, ...props }) {
493
- const match = /language-(\w+)/.exec(className || "");
494
- const codeText = String(children).replace(/\n$/, "");
495
- const isBlock = match != null || String(children).includes("\n");
496
- if (isBlock) {
497
- const language = match?.[1] ?? "";
498
- if (o.codeBlock) {
499
- const CodeBlock = o.codeBlock;
500
- return /* @__PURE__ */ jsx(
501
- CodeBlock,
502
- {
503
- code: codeText,
504
- language,
505
- className: cn.codeBlock
506
- }
507
- );
508
- }
509
- return /* @__PURE__ */ jsxs("div", { className: cx("llm-code-block", cn.codeBlock), children: [
510
- /* @__PURE__ */ jsxs("div", { className: cx("llm-code-header", cn.codeHeader), children: [
511
- /* @__PURE__ */ jsx("span", { className: cx("llm-code-language", cn.codeLanguage), children: language }),
512
- o.copyButton ? /* @__PURE__ */ jsx(o.copyButton, { text: codeText, className: cn.copyButton }) : /* @__PURE__ */ jsx(
513
- CopyButton,
514
- {
515
- text: codeText,
516
- className: cx("llm-copy-button", cn.copyButton)
517
- }
518
- )
519
- ] }),
520
- /* @__PURE__ */ jsx("div", { className: "llm-code-body", children: Highlighter ? /* @__PURE__ */ jsx(Highlighter, { code: codeText, language }) : /* @__PURE__ */ jsx("code", { className: "llm-code-plain", children: codeText }) })
521
- ] });
522
- }
523
- if (o.code) {
524
- const InlineCode = o.code;
525
- return /* @__PURE__ */ jsx(InlineCode, { className: cx("llm-code", cn.code, className), children });
526
- }
527
- return /* @__PURE__ */ jsx("code", { className: cx("llm-code", cn.code, className), ...props, children });
528
- },
529
- pre({ children }) {
530
- if (o.pre) {
531
- return /* @__PURE__ */ jsx(o.pre, { children });
532
- }
533
- return /* @__PURE__ */ jsx(Fragment, { children });
534
- },
535
- table({ children }) {
536
- if (o.table) {
537
- return /* @__PURE__ */ jsx(o.table, { className: cn.table, children });
538
- }
539
- return /* @__PURE__ */ jsx("div", { className: cx("llm-table-wrapper", cn.tableWrapper), children: /* @__PURE__ */ jsx("table", { className: cx("llm-table", cn.table), children }) });
540
- },
541
- th({ children }) {
542
- if (o.th) {
543
- return /* @__PURE__ */ jsx(o.th, { className: cn.th, children });
544
- }
545
- return /* @__PURE__ */ jsx("th", { className: cx("llm-th", cn.th), children });
546
- },
547
- td({ children }) {
548
- if (o.td) {
549
- return /* @__PURE__ */ jsx(o.td, { className: cn.td, children });
550
- }
551
- return /* @__PURE__ */ jsx("td", { className: cx("llm-td", cn.td), children });
552
- },
553
- blockquote({ children }) {
554
- if (o.blockquote) {
555
- return /* @__PURE__ */ jsx(o.blockquote, { className: cn.blockquote, children });
556
- }
557
- return /* @__PURE__ */ jsx("blockquote", { className: cx("llm-blockquote", cn.blockquote), children });
558
- },
559
- ul({ children }) {
560
- if (o.ul) {
561
- return /* @__PURE__ */ jsx(o.ul, { className: cn.ul, children });
562
- }
563
- return /* @__PURE__ */ jsx("ul", { className: cx("llm-ul", cn.ul), children });
564
- },
565
- ol({ children }) {
566
- if (o.ol) {
567
- return /* @__PURE__ */ jsx(o.ol, { className: cn.ol, children });
568
- }
569
- return /* @__PURE__ */ jsx("ol", { className: cx("llm-ol", cn.ol), children });
570
- },
571
- li({ children }) {
572
- if (o.li) {
573
- return /* @__PURE__ */ jsx(o.li, { className: cn.li, children });
574
- }
575
- return /* @__PURE__ */ jsx("li", { className: cx("llm-li", cn.li), children });
576
- },
577
- p({ children }) {
578
- if (o.p) {
579
- return /* @__PURE__ */ jsx(o.p, { className: cn.p, children });
580
- }
581
- return /* @__PURE__ */ jsx("p", { className: cx("llm-p", cn.p), children });
582
- },
583
- h1({ children }) {
584
- if (o.h1) {
585
- return /* @__PURE__ */ jsx(o.h1, { className: cn.h1, children });
586
- }
587
- return /* @__PURE__ */ jsx("h1", { className: cx("llm-h1", cn.h1), children });
588
- },
589
- h2({ children }) {
590
- if (o.h2) {
591
- return /* @__PURE__ */ jsx(o.h2, { className: cn.h2, children });
592
- }
593
- return /* @__PURE__ */ jsx("h2", { className: cx("llm-h2", cn.h2), children });
594
- },
595
- h3({ children }) {
596
- if (o.h3) {
597
- return /* @__PURE__ */ jsx(o.h3, { className: cn.h3, children });
598
- }
599
- return /* @__PURE__ */ jsx("h3", { className: cx("llm-h3", cn.h3), children });
600
- },
601
- h4({ children }) {
602
- if (o.h4) {
603
- return /* @__PURE__ */ jsx(o.h4, { className: cn.h4, children });
604
- }
605
- return /* @__PURE__ */ jsx("h4", { className: cx("llm-h4", cn.h4), children });
606
- },
607
- h5({ children }) {
608
- if (o.h5) {
609
- 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);
610
939
  }
611
- return /* @__PURE__ */ jsx("h5", { className: cx("llm-h5", cn.h5), children });
612
- },
613
- h6({ children }) {
614
- if (o.h6) {
615
- 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);
616
944
  }
617
- return /* @__PURE__ */ jsx("h6", { className: cx("llm-h6", cn.h6), children });
618
- },
619
- input({ node: _node, type, checked, disabled, ...props }) {
620
- if (type === "checkbox") {
621
- if (o.checkbox) {
622
- return /* @__PURE__ */ jsx(o.checkbox, { checked: Boolean(checked), className: cn.checkbox });
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];
623
995
  }
624
- return /* @__PURE__ */ jsx(
625
- "input",
626
- {
627
- type: "checkbox",
628
- checked: Boolean(checked),
629
- disabled: true,
630
- "aria-label": checked ? "Completed task" : "Incomplete task",
631
- className: cx("llm-checkbox", cn.checkbox),
632
- 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 = "";
633
1003
  }
634
- );
635
- }
636
- return /* @__PURE__ */ jsx(
637
- "input",
638
- {
639
- type,
640
- checked,
641
- disabled,
642
- readOnly: true,
643
- ...props
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
+ });
644
1018
  }
645
- );
646
- },
647
- a({ href, children }) {
648
- if (o.a) {
649
- return /* @__PURE__ */ jsx(o.a, { href, className: cn.a, children });
650
- }
651
- return /* @__PURE__ */ jsx(
652
- "a",
653
- {
654
- href,
655
- target: "_blank",
656
- rel: "noopener noreferrer",
657
- className: cx("llm-a", cn.a),
658
- children
1019
+ flushPlain();
1020
+ return out;
1021
+ };
1022
+ const processComplex = (node) => {
1023
+ const index = counter.unit++;
1024
+ if (index < state.committedUnits) {
1025
+ return node;
659
1026
  }
660
- );
661
- },
662
- img({ node: _node, src, alt, title, className: _className, ...props }) {
663
- if (o.img) {
664
- const Image = o.img;
665
- return /* @__PURE__ */ jsx(
666
- Image,
667
- {
668
- src: typeof src === "string" ? src : void 0,
669
- alt,
670
- title,
671
- className: cn.img
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);
672
1058
  }
673
- );
674
- }
675
- return /* @__PURE__ */ jsx(
676
- "img",
677
- {
678
- src: typeof src === "string" ? src : void 0,
679
- alt,
680
- title,
681
- className: cx("llm-img", cn.img),
682
- ...props
683
1059
  }
684
- );
685
- },
686
- hr() {
687
- if (o.hr) {
688
- return /* @__PURE__ */ jsx(o.hr, { className: cn.hr });
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;
689
1107
  }
690
- return /* @__PURE__ */ jsx("hr", { className: cx("llm-hr", cn.hr) });
691
- },
692
- strong({ children }) {
693
- if (o.strong) {
694
- return /* @__PURE__ */ jsx(o.strong, { className: cn.strong, children });
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;
695
1122
  }
696
- return /* @__PURE__ */ jsx("strong", { className: cx("llm-strong", cn.strong), children });
697
- },
698
- em({ children }) {
699
- if (o.em) {
700
- return /* @__PURE__ */ jsx(o.em, { className: cn.em, children });
1123
+ });
1124
+ }, [enabled]);
1125
+ useIsomorphicLayoutEffect(() => {
1126
+ if (!enabled) {
1127
+ if (rafRef.current != null) {
1128
+ cancelAnimationFrame(rafRef.current);
1129
+ rafRef.current = null;
701
1130
  }
702
- return /* @__PURE__ */ jsx("em", { className: cx("llm-em", cn.em), children });
703
- },
704
- del({ children }) {
705
- if (o.del) {
706
- return /* @__PURE__ */ jsx(o.del, { className: cn.del, children });
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;
707
1145
  }
708
- return /* @__PURE__ */ jsx("del", { className: cx("llm-del", cn.del), children });
1146
+ return;
709
1147
  }
710
- };
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 };
711
1194
  }
1195
+ var remarkPlugins = [remarkGfm, remarkMath];
712
1196
  function LLMMessage({
713
1197
  children,
714
1198
  content,
@@ -717,27 +1201,66 @@ function LLMMessage({
717
1201
  components,
718
1202
  completePartialTokens: repairPartialTokens = true,
719
1203
  showUnfinishedLatexBlocks = true,
1204
+ smoothReveal = false,
1205
+ smoothRevealDuration = 300,
1206
+ blockMemoization = true,
720
1207
  highlighter,
1208
+ style,
721
1209
  ...rest
722
1210
  }) {
723
1211
  const source = content ?? children ?? "";
724
1212
  const markdownComponents = useMemo(
725
- () => buildComponents(classNames, components, highlighter),
1213
+ () => buildMarkdownComponents(classNames, components, highlighter),
726
1214
  [classNames, components, highlighter]
727
1215
  );
728
- const processed = useMemo(() => {
729
- const repaired = repairPartialTokens ? completePartialTokens(source, { showUnfinishedLatexBlocks }) : source;
730
- return preprocessLaTeX(repaired);
731
- }, [source, repairPartialTokens, showUnfinishedLatexBlocks]);
732
- return /* @__PURE__ */ jsx("div", { className: cx("llm-message", classNames?.root, className), ...rest, children: /* @__PURE__ */ jsx(
733
- ReactMarkdown,
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",
734
1242
  {
735
- remarkPlugins,
736
- rehypePlugins,
737
- components: markdownComponents,
738
- children: processed
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
+ })
739
1262
  }
740
- ) });
1263
+ );
741
1264
  }
742
1265
 
743
1266
  export { LLMMessage, completePartialTokens, escapeBrackets, escapeMhchem, preprocessLaTeX };