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