streaming-markdown-react 0.1.4 → 0.2.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
@@ -30,15 +30,51 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ Block: () => Block,
33
34
  CodeBlock: () => CodeBlock,
35
+ MemoBlockquote: () => MemoBlockquote,
36
+ MemoCode: () => MemoCode,
37
+ MemoDelete: () => MemoDelete,
38
+ MemoEmphasis: () => MemoEmphasis,
39
+ MemoH1: () => MemoH1,
40
+ MemoH2: () => MemoH2,
41
+ MemoH3: () => MemoH3,
42
+ MemoH4: () => MemoH4,
43
+ MemoH5: () => MemoH5,
44
+ MemoH6: () => MemoH6,
45
+ MemoHr: () => MemoHr,
46
+ MemoImage: () => MemoImage,
47
+ MemoLink: () => MemoLink,
48
+ MemoList: () => MemoList,
49
+ MemoListItem: () => MemoListItem,
50
+ MemoParagraph: () => MemoParagraph,
51
+ MemoPreformatted: () => MemoPreformatted,
52
+ MemoStrong: () => MemoStrong,
53
+ MemoTable: () => MemoTable,
54
+ MemoTableBody: () => MemoTableBody,
55
+ MemoTableCell: () => MemoTableCell,
56
+ MemoTableHead: () => MemoTableHead,
57
+ MemoTableRow: () => MemoTableRow,
34
58
  MessageBlockRenderer: () => MessageBlockRenderer,
35
59
  MessageBlockStatus: () => MessageBlockStatus,
36
60
  MessageBlockStore: () => MessageBlockStore,
37
61
  MessageBlockType: () => MessageBlockType,
38
62
  MessageItem: () => MessageItem,
39
63
  MessageStatus: () => MessageStatus,
64
+ ShikiHighlighterManager: () => ShikiHighlighterManager,
40
65
  StreamingMarkdown: () => StreamingMarkdown,
66
+ getLanguageImport: () => getLanguageImport,
67
+ getSupportedLanguages: () => getSupportedLanguages,
68
+ getSupportedThemes: () => getSupportedThemes,
69
+ getThemeImport: () => getThemeImport,
70
+ highlighterManager: () => highlighterManager,
71
+ isLanguageSupported: () => isLanguageSupported,
72
+ isThemeSupported: () => isThemeSupported,
41
73
  messageBlockStore: () => messageBlockStore,
74
+ parseMarkdownIntoBlocks: () => parseMarkdownIntoBlocks,
75
+ sameClassAndNode: () => sameClassAndNode,
76
+ sameNodePosition: () => sameNodePosition,
77
+ splitMarkdownIntoBlocks: () => splitMarkdownIntoBlocks,
42
78
  useShikiHighlight: () => useShikiHighlight,
43
79
  useSmoothStream: () => useSmoothStream
44
80
  });
@@ -110,8 +146,7 @@ var MessageBlockStore = class {
110
146
  var messageBlockStore = new MessageBlockStore();
111
147
 
112
148
  // src/components/Markdown/StreamingMarkdown.tsx
113
- var import_react3 = require("react");
114
- var import_react_markdown = __toESM(require("react-markdown"));
149
+ var import_react5 = require("react");
115
150
  var import_remark_gfm = __toESM(require("remark-gfm"));
116
151
 
117
152
  // src/hooks/useSmoothStream.ts
@@ -225,8 +260,126 @@ function useSmoothStream({
225
260
  return { addChunk, reset };
226
261
  }
227
262
 
228
- // src/hooks/useShikiHighlight.ts
263
+ // src/utils/markdown/parseMarkdownIntoBlocks.ts
264
+ var import_marked = require("marked");
265
+ var blockIdCounter = 0;
266
+ function generateBlockId() {
267
+ blockIdCounter += 1;
268
+ return `block-${Date.now()}-${blockIdCounter}`;
269
+ }
270
+ function parseMarkdownIntoBlocks(markdown) {
271
+ if (!markdown.trim()) {
272
+ return [];
273
+ }
274
+ const hasFootnoteReference = /\[\^[^\]\s]{1,200}\](?!:)/.test(markdown);
275
+ const hasFootnoteDefinition = /\[\^[^\]\s]{1,200}\]:/.test(markdown);
276
+ if (hasFootnoteReference || hasFootnoteDefinition) {
277
+ return [markdown];
278
+ }
279
+ const tokens = import_marked.Lexer.lex(markdown, { gfm: true });
280
+ const mergedBlocks = [];
281
+ const htmlStack = [];
282
+ for (let i = 0; i < tokens.length; i++) {
283
+ const token = tokens[i];
284
+ const currentBlock = token.raw;
285
+ if (htmlStack.length > 0) {
286
+ mergedBlocks[mergedBlocks.length - 1] += currentBlock;
287
+ if (token.type === "html") {
288
+ const closingTagMatch = currentBlock.match(/<\/(\w+)>/);
289
+ if (closingTagMatch) {
290
+ const closingTag = closingTagMatch[1];
291
+ if (htmlStack[htmlStack.length - 1] === closingTag) {
292
+ htmlStack.pop();
293
+ }
294
+ }
295
+ }
296
+ continue;
297
+ }
298
+ if (token.type === "html" && token.block) {
299
+ const openingTagMatch = currentBlock.match(/<(\w+)[\s>]/);
300
+ if (openingTagMatch) {
301
+ const tagName = openingTagMatch[1];
302
+ const hasClosingTag = currentBlock.includes(`</${tagName}>`);
303
+ if (!hasClosingTag) {
304
+ htmlStack.push(tagName);
305
+ }
306
+ }
307
+ }
308
+ if (currentBlock.trim() === "$" && mergedBlocks.length > 0) {
309
+ const previousBlock = mergedBlocks.at(-1);
310
+ if (!previousBlock) {
311
+ mergedBlocks.push(currentBlock);
312
+ continue;
313
+ }
314
+ const prevStartsWith$ = previousBlock.trimStart().startsWith("$");
315
+ const prevDollarCount = (previousBlock.match(/\$\$/g) || []).length;
316
+ if (prevStartsWith$ && prevDollarCount % 2 === 1) {
317
+ mergedBlocks[mergedBlocks.length - 1] = previousBlock + currentBlock;
318
+ continue;
319
+ }
320
+ }
321
+ if (mergedBlocks.length > 0 && currentBlock.trimEnd().endsWith("$")) {
322
+ const previousBlock = mergedBlocks.at(-1);
323
+ if (!previousBlock) {
324
+ mergedBlocks.push(currentBlock);
325
+ continue;
326
+ }
327
+ const prevStartsWith$ = previousBlock.trimStart().startsWith("$");
328
+ const prevDollarCount = (previousBlock.match(/\$\$/g) || []).length;
329
+ const currDollarCount = (currentBlock.match(/\$\$/g) || []).length;
330
+ if (prevStartsWith$ && prevDollarCount % 2 === 1 && !currentBlock.trimStart().startsWith("$") && currDollarCount === 1) {
331
+ mergedBlocks[mergedBlocks.length - 1] = previousBlock + currentBlock;
332
+ continue;
333
+ }
334
+ }
335
+ mergedBlocks.push(currentBlock);
336
+ }
337
+ return mergedBlocks;
338
+ }
339
+ function splitMarkdownIntoBlocks({
340
+ messageId,
341
+ markdown,
342
+ status = "idle" /* IDLE */
343
+ }) {
344
+ const blocks = parseMarkdownIntoBlocks(markdown);
345
+ return blocks.map((content) => {
346
+ const block = {
347
+ id: generateBlockId(),
348
+ messageId,
349
+ type: "main_text" /* MAIN_TEXT */,
350
+ status,
351
+ content,
352
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
353
+ };
354
+ return block;
355
+ });
356
+ }
357
+
358
+ // src/components/Markdown/Block.tsx
229
359
  var import_react2 = require("react");
360
+ var import_react_markdown = __toESM(require("react-markdown"));
361
+ var import_jsx_runtime = require("react/jsx-runtime");
362
+ var Block = (0, import_react2.memo)(
363
+ ({ content, components, remarkPlugins, rehypePlugins, className }) => {
364
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
365
+ import_react_markdown.default,
366
+ {
367
+ className,
368
+ components,
369
+ remarkPlugins,
370
+ rehypePlugins,
371
+ children: content
372
+ }
373
+ );
374
+ },
375
+ (prevProps, nextProps) => {
376
+ return prevProps.content === nextProps.content && prevProps.className === nextProps.className;
377
+ }
378
+ );
379
+ Block.displayName = "Block";
380
+
381
+ // src/hooks/useShikiHighlight.ts
382
+ var import_react3 = require("react");
230
383
  var import_core = require("shiki/core");
231
384
  var import_javascript = require("shiki/engine/javascript");
232
385
  var highlighterPromise;
@@ -274,10 +427,10 @@ function useShikiHighlight({
274
427
  language = "text",
275
428
  theme = "light"
276
429
  }) {
277
- const [html, setHtml] = (0, import_react2.useState)("");
278
- const [isLoading, setIsLoading] = (0, import_react2.useState)(true);
279
- const [error, setError] = (0, import_react2.useState)(null);
280
- (0, import_react2.useEffect)(() => {
430
+ const [html, setHtml] = (0, import_react3.useState)("");
431
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(true);
432
+ const [error, setError] = (0, import_react3.useState)(null);
433
+ (0, import_react3.useEffect)(() => {
281
434
  let cancelled = false;
282
435
  async function highlight() {
283
436
  try {
@@ -316,7 +469,7 @@ function escapeHtml(unsafe) {
316
469
  }
317
470
 
318
471
  // src/components/Markdown/CodeBlock.tsx
319
- var import_jsx_runtime = require("react/jsx-runtime");
472
+ var import_jsx_runtime2 = require("react/jsx-runtime");
320
473
  function CodeBlock({
321
474
  code,
322
475
  language = "text",
@@ -325,9 +478,9 @@ function CodeBlock({
325
478
  }) {
326
479
  const { html, isLoading } = useShikiHighlight({ code, language, theme });
327
480
  if (isLoading) {
328
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { "data-language": language, children: code }) });
481
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("pre", { className, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { "data-language": language, children: code }) });
329
482
  }
330
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
483
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
331
484
  "div",
332
485
  {
333
486
  dangerouslySetInnerHTML: { __html: html },
@@ -340,89 +493,286 @@ function CodeBlock({
340
493
  );
341
494
  }
342
495
 
343
- // src/components/Markdown/StreamingMarkdown.tsx
344
- var import_jsx_runtime2 = require("react/jsx-runtime");
345
- function StreamingMarkdown({
346
- children,
347
- className,
348
- components: customComponents,
349
- status = "idle",
350
- onComplete,
351
- minDelay = 10
352
- }) {
353
- const markdown = typeof children === "string" ? children : String(children || "");
354
- const [displayedText, setDisplayedText] = (0, import_react3.useState)(status !== "streaming" ? markdown : "");
355
- const previousChildrenRef = (0, import_react3.useRef)(status !== "streaming" ? markdown : "");
356
- console.log("StreamingMarkdown children:", markdown);
357
- const { addChunk, reset } = useSmoothStream({
358
- onUpdate: setDisplayedText,
359
- streamDone: status !== "streaming",
360
- minDelay,
361
- initialText: status !== "streaming" ? markdown : "",
362
- onComplete
363
- });
364
- (0, import_react3.useEffect)(() => {
365
- const currentContent = markdown;
366
- const previousContent = previousChildrenRef.current;
367
- if (currentContent !== previousContent) {
368
- if (currentContent.startsWith(previousContent)) {
369
- const delta = currentContent.slice(previousContent.length);
370
- addChunk(delta);
371
- } else {
372
- reset(currentContent);
373
- }
374
- previousChildrenRef.current = currentContent;
496
+ // src/components/Markdown/MemoizedComponents.tsx
497
+ var import_react4 = require("react");
498
+
499
+ // src/utils/markdown/sameNodePosition.ts
500
+ function sameNodePosition(prev, next) {
501
+ if (!(prev?.position || next?.position)) {
502
+ return true;
503
+ }
504
+ if (!(prev?.position && next?.position)) {
505
+ return false;
506
+ }
507
+ const prevStart = prev.position.start;
508
+ const nextStart = next.position.start;
509
+ const prevEnd = prev.position.end;
510
+ const nextEnd = next.position.end;
511
+ return prevStart?.line === nextStart?.line && prevStart?.column === nextStart?.column && prevEnd?.line === nextEnd?.line && prevEnd?.column === nextEnd?.column;
512
+ }
513
+ function sameClassAndNode(prev, next) {
514
+ return prev.className === next.className && sameNodePosition(prev.node, next.node);
515
+ }
516
+
517
+ // src/components/Markdown/MemoizedComponents.tsx
518
+ var import_jsx_runtime3 = require("react/jsx-runtime");
519
+ var MemoH1 = (0, import_react4.memo)(
520
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { className, ...props, children }),
521
+ (p, n) => sameClassAndNode(p, n)
522
+ );
523
+ MemoH1.displayName = "MemoH1";
524
+ var MemoH2 = (0, import_react4.memo)(
525
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className, ...props, children }),
526
+ (p, n) => sameClassAndNode(p, n)
527
+ );
528
+ MemoH2.displayName = "MemoH2";
529
+ var MemoH3 = (0, import_react4.memo)(
530
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className, ...props, children }),
531
+ (p, n) => sameClassAndNode(p, n)
532
+ );
533
+ MemoH3.displayName = "MemoH3";
534
+ var MemoH4 = (0, import_react4.memo)(
535
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h4", { className, ...props, children }),
536
+ (p, n) => sameClassAndNode(p, n)
537
+ );
538
+ MemoH4.displayName = "MemoH4";
539
+ var MemoH5 = (0, import_react4.memo)(
540
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h5", { className, ...props, children }),
541
+ (p, n) => sameClassAndNode(p, n)
542
+ );
543
+ MemoH5.displayName = "MemoH5";
544
+ var MemoH6 = (0, import_react4.memo)(
545
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h6", { className, ...props, children }),
546
+ (p, n) => sameClassAndNode(p, n)
547
+ );
548
+ MemoH6.displayName = "MemoH6";
549
+ var MemoParagraph = (0, import_react4.memo)(
550
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className, ...props, children }),
551
+ (p, n) => sameClassAndNode(p, n)
552
+ );
553
+ MemoParagraph.displayName = "MemoParagraph";
554
+ var MemoLink = (0, import_react4.memo)(
555
+ ({ children, className, href, title, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
556
+ "a",
557
+ {
558
+ className,
559
+ href,
560
+ title,
561
+ target: "_blank",
562
+ rel: "noopener noreferrer",
563
+ ...props,
564
+ children
375
565
  }
376
- }, [markdown, addChunk, reset]);
377
- const components = (0, import_react3.useMemo)(() => {
378
- const baseComponents = {
379
- code: (props) => {
380
- const { node, inline, className: className2, children: children2, ...rest } = props;
381
- if (inline) {
382
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { className: className2, ...rest, children: children2 });
383
- }
384
- const match = /language-(\w+)/.exec(className2 || "");
385
- const language = match ? match[1] : void 0;
386
- const code = String(children2).replace(/\n$/, "");
387
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CodeBlock, { code, language, className: className2 });
388
- },
389
- pre: (props) => {
390
- const { children: children2, ...rest } = props;
391
- if (children2?.type === CodeBlock) {
392
- return children2;
393
- }
394
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("pre", { style: { overflow: "visible" }, ...rest, children: children2 });
395
- },
396
- p: (props) => {
397
- const hasCodeBlock = props?.node?.children?.some(
398
- (child) => child.tagName === "pre" || child.tagName === "code"
399
- );
400
- if (hasCodeBlock) {
401
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: props.children });
402
- }
403
- const hasImage = props?.node?.children?.some((child) => child.tagName === "img");
404
- if (hasImage) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...props });
405
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { ...props });
406
- }
407
- };
408
- if (/<style\b[^>]*>/i.test(markdown)) {
409
- baseComponents.style = (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...props });
566
+ ),
567
+ (p, n) => sameClassAndNode(p, n) && p.href === n.href && p.title === n.title
568
+ );
569
+ MemoLink.displayName = "MemoLink";
570
+ var MemoBlockquote = (0, import_react4.memo)(
571
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("blockquote", { className, ...props, children }),
572
+ (p, n) => sameClassAndNode(p, n)
573
+ );
574
+ MemoBlockquote.displayName = "MemoBlockquote";
575
+ var MemoList = (0, import_react4.memo)(
576
+ ({ children, className, ordered, start, ...props }) => {
577
+ if (ordered) {
578
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ol", { className, start, ...props, children });
410
579
  }
411
- return { ...baseComponents, ...customComponents };
412
- }, [markdown, customComponents]);
413
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
414
- import_react_markdown.default,
580
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className, ...props, children });
581
+ },
582
+ (p, n) => sameClassAndNode(p, n) && p.ordered === n.ordered && p.start === n.start
583
+ );
584
+ MemoList.displayName = "MemoList";
585
+ var MemoListItem = (0, import_react4.memo)(
586
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { className, ...props, children }),
587
+ (p, n) => sameClassAndNode(p, n)
588
+ );
589
+ MemoListItem.displayName = "MemoListItem";
590
+ var MemoTable = (0, import_react4.memo)(
591
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("table", { className, ...props, children }),
592
+ (p, n) => sameClassAndNode(p, n)
593
+ );
594
+ MemoTable.displayName = "MemoTable";
595
+ var MemoTableHead = (0, import_react4.memo)(
596
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { className, ...props, children }),
597
+ (p, n) => sameClassAndNode(p, n)
598
+ );
599
+ MemoTableHead.displayName = "MemoTableHead";
600
+ var MemoTableBody = (0, import_react4.memo)(
601
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { className, ...props, children }),
602
+ (p, n) => sameClassAndNode(p, n)
603
+ );
604
+ MemoTableBody.displayName = "MemoTableBody";
605
+ var MemoTableRow = (0, import_react4.memo)(
606
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tr", { className, ...props, children }),
607
+ (p, n) => sameClassAndNode(p, n)
608
+ );
609
+ MemoTableRow.displayName = "MemoTableRow";
610
+ var MemoTableCell = (0, import_react4.memo)(
611
+ ({ children, className, isHeader, align, ...props }) => {
612
+ const style = align ? { textAlign: align } : void 0;
613
+ if (isHeader) {
614
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { className, style, ...props, children });
615
+ }
616
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { className, style, ...props, children });
617
+ },
618
+ (p, n) => sameClassAndNode(p, n) && p.isHeader === n.isHeader && p.align === n.align
619
+ );
620
+ MemoTableCell.displayName = "MemoTableCell";
621
+ var MemoCode = (0, import_react4.memo)(
622
+ ({ children, className, inline, ...props }) => {
623
+ if (inline) {
624
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { className, ...props, children });
625
+ }
626
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { className, ...props, children });
627
+ },
628
+ (p, n) => sameClassAndNode(p, n) && p.inline === n.inline
629
+ );
630
+ MemoCode.displayName = "MemoCode";
631
+ var MemoPreformatted = (0, import_react4.memo)(
632
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("pre", { className, ...props, children }),
633
+ (p, n) => sameClassAndNode(p, n)
634
+ );
635
+ MemoPreformatted.displayName = "MemoPreformatted";
636
+ var MemoStrong = (0, import_react4.memo)(
637
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { className, ...props, children }),
638
+ (p, n) => sameClassAndNode(p, n)
639
+ );
640
+ MemoStrong.displayName = "MemoStrong";
641
+ var MemoEmphasis = (0, import_react4.memo)(
642
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("em", { className, ...props, children }),
643
+ (p, n) => sameClassAndNode(p, n)
644
+ );
645
+ MemoEmphasis.displayName = "MemoEmphasis";
646
+ var MemoHr = (0, import_react4.memo)(
647
+ ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("hr", { className, ...props }),
648
+ (p, n) => sameClassAndNode(p, n)
649
+ );
650
+ MemoHr.displayName = "MemoHr";
651
+ var MemoImage = (0, import_react4.memo)(
652
+ ({ className, href, title, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
653
+ "img",
415
654
  {
416
655
  className,
417
- remarkPlugins: [import_remark_gfm.default],
418
- components,
419
- children: displayedText
656
+ src: href,
657
+ alt: title ?? "",
658
+ title,
659
+ ...props
420
660
  }
421
- );
422
- }
661
+ ),
662
+ (p, n) => sameClassAndNode(p, n) && p.href === n.href && p.title === n.title
663
+ );
664
+ MemoImage.displayName = "MemoImage";
665
+ var MemoDelete = (0, import_react4.memo)(
666
+ ({ children, className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("del", { className, ...props, children }),
667
+ (p, n) => sameClassAndNode(p, n)
668
+ );
669
+ MemoDelete.displayName = "MemoDelete";
670
+
671
+ // src/components/Markdown/StreamingMarkdown.tsx
672
+ var import_jsx_runtime4 = require("react/jsx-runtime");
673
+ var StreamingMarkdown = (0, import_react5.memo)(
674
+ function StreamingMarkdown2({
675
+ children,
676
+ className,
677
+ components: customComponents,
678
+ status = "idle",
679
+ onComplete,
680
+ minDelay = 10
681
+ }) {
682
+ const markdown = typeof children === "string" ? children : String(children || "");
683
+ const [displayedText, setDisplayedText] = (0, import_react5.useState)(status !== "streaming" ? markdown : "");
684
+ const previousChildrenRef = (0, import_react5.useRef)(status !== "streaming" ? markdown : "");
685
+ const generatedId = (0, import_react5.useId)();
686
+ const { addChunk, reset } = useSmoothStream({
687
+ onUpdate: setDisplayedText,
688
+ streamDone: status !== "streaming",
689
+ minDelay,
690
+ initialText: status !== "streaming" ? markdown : "",
691
+ onComplete
692
+ });
693
+ (0, import_react5.useEffect)(() => {
694
+ const currentContent = markdown;
695
+ const previousContent = previousChildrenRef.current;
696
+ if (currentContent !== previousContent) {
697
+ if (currentContent.startsWith(previousContent)) {
698
+ const delta = currentContent.slice(previousContent.length);
699
+ addChunk(delta);
700
+ } else {
701
+ reset(currentContent);
702
+ }
703
+ previousChildrenRef.current = currentContent;
704
+ }
705
+ }, [markdown, addChunk, reset]);
706
+ const blocks = (0, import_react5.useMemo)(
707
+ () => parseMarkdownIntoBlocks(displayedText),
708
+ [displayedText]
709
+ );
710
+ const components = (0, import_react5.useMemo)(() => {
711
+ const baseComponents = {
712
+ h1: MemoH1,
713
+ h2: MemoH2,
714
+ h3: MemoH3,
715
+ h4: MemoH4,
716
+ h5: MemoH5,
717
+ h6: MemoH6,
718
+ p: MemoParagraph,
719
+ a: MemoLink,
720
+ blockquote: MemoBlockquote,
721
+ ul: (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MemoList, { ordered: false, ...props }),
722
+ ol: (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MemoList, { ordered: true, ...props }),
723
+ li: MemoListItem,
724
+ table: MemoTable,
725
+ thead: MemoTableHead,
726
+ tbody: MemoTableBody,
727
+ tr: MemoTableRow,
728
+ th: (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MemoTableCell, { isHeader: true, ...props }),
729
+ td: MemoTableCell,
730
+ strong: MemoStrong,
731
+ em: MemoEmphasis,
732
+ hr: MemoHr,
733
+ img: (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MemoImage, { href: props.src, title: props.alt, ...props }),
734
+ del: MemoDelete,
735
+ code: (props) => {
736
+ const { node, inline, className: className2, children: children2, ...rest } = props;
737
+ if (inline) {
738
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MemoCode, { inline: true, className: className2, node, ...rest, children: children2 });
739
+ }
740
+ const match = /language-(\w+)/.exec(className2 || "");
741
+ const language = match ? match[1] : void 0;
742
+ const code = String(children2).replace(/\n$/, "");
743
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CodeBlock, { code, language, className: className2 });
744
+ },
745
+ pre: (props) => {
746
+ const { children: children2, ...rest } = props;
747
+ if (children2?.type === CodeBlock) {
748
+ return children2;
749
+ }
750
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MemoPreformatted, { ...rest, children: children2 });
751
+ }
752
+ };
753
+ if (/<style\b[^>]*>/i.test(markdown)) {
754
+ baseComponents.style = (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ...props });
755
+ }
756
+ return { ...baseComponents, ...customComponents };
757
+ }, [markdown, customComponents]);
758
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className, children: blocks.map((block, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
759
+ Block,
760
+ {
761
+ content: block,
762
+ components,
763
+ remarkPlugins: [import_remark_gfm.default]
764
+ },
765
+ `${generatedId}-block-${index}`
766
+ )) });
767
+ },
768
+ (prevProps, nextProps) => {
769
+ return prevProps.children === nextProps.children && prevProps.status === nextProps.status && prevProps.className === nextProps.className;
770
+ }
771
+ );
772
+ StreamingMarkdown.displayName = "StreamingMarkdown";
423
773
 
424
774
  // src/components/Message/MessageBlockRenderer.tsx
425
- var import_jsx_runtime3 = require("react/jsx-runtime");
775
+ var import_jsx_runtime5 = require("react/jsx-runtime");
426
776
  function MessageBlockRenderer({ block, className }) {
427
777
  switch (block.type) {
428
778
  case "main_text" /* MAIN_TEXT */:
@@ -431,7 +781,7 @@ function MessageBlockRenderer({ block, className }) {
431
781
  case "error" /* ERROR */:
432
782
  case "unknown" /* UNKNOWN */: {
433
783
  const textBlock = block;
434
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
784
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
435
785
  StreamingMarkdown,
436
786
  {
437
787
  className,
@@ -442,18 +792,18 @@ function MessageBlockRenderer({ block, className }) {
442
792
  }
443
793
  case "code" /* CODE */: {
444
794
  const codeBlock = block;
445
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { children: codeBlock.content }) }) });
795
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { children: codeBlock.content }) }) });
446
796
  }
447
797
  case "image" /* IMAGE */:
448
798
  case "video" /* VIDEO */:
449
799
  case "file" /* FILE */: {
450
800
  const mediaBlock = block;
451
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { href: mediaBlock.url, target: "_blank", rel: "noopener noreferrer", children: mediaBlock.name ?? "Media File" }) });
801
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { href: mediaBlock.url, target: "_blank", rel: "noopener noreferrer", children: mediaBlock.name ?? "Media File" }) });
452
802
  }
453
803
  case "tool" /* TOOL */:
454
804
  case "citation" /* CITATION */: {
455
805
  const toolBlock = block;
456
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("pre", { children: JSON.stringify(toolBlock.payload ?? {}, null, 2) }) });
806
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { children: JSON.stringify(toolBlock.payload ?? {}, null, 2) }) });
457
807
  }
458
808
  default:
459
809
  return null;
@@ -461,17 +811,17 @@ function MessageBlockRenderer({ block, className }) {
461
811
  }
462
812
 
463
813
  // src/components/Message/MessageItem.tsx
464
- var import_react4 = require("react");
814
+ var import_react6 = require("react");
465
815
  var import_react_markdown2 = __toESM(require("react-markdown"));
466
816
  var import_remark_gfm2 = __toESM(require("remark-gfm"));
467
817
 
468
818
  // src/utils/markdown/splitMarkdownIntoBlocks.ts
469
- var blockIdCounter = 0;
470
- function generateBlockId() {
471
- blockIdCounter += 1;
472
- return `block-${Date.now()}-${blockIdCounter}`;
819
+ var blockIdCounter2 = 0;
820
+ function generateBlockId2() {
821
+ blockIdCounter2 += 1;
822
+ return `block-${Date.now()}-${blockIdCounter2}`;
473
823
  }
474
- function splitMarkdownIntoBlocks({
824
+ function splitMarkdownIntoBlocks2({
475
825
  messageId,
476
826
  markdown,
477
827
  status = "idle" /* IDLE */
@@ -480,7 +830,7 @@ function splitMarkdownIntoBlocks({
480
830
  return [];
481
831
  }
482
832
  const block = {
483
- id: generateBlockId(),
833
+ id: generateBlockId2(),
484
834
  messageId,
485
835
  type: "main_text" /* MAIN_TEXT */,
486
836
  status,
@@ -491,7 +841,7 @@ function splitMarkdownIntoBlocks({
491
841
  }
492
842
 
493
843
  // src/components/Message/MessageItem.tsx
494
- var import_jsx_runtime4 = require("react/jsx-runtime");
844
+ var import_jsx_runtime6 = require("react/jsx-runtime");
495
845
  function MessageItem({
496
846
  children,
497
847
  role = "assistant",
@@ -501,17 +851,17 @@ function MessageItem({
501
851
  }) {
502
852
  const markdown = typeof children === "string" ? children : String(children ?? "");
503
853
  if (role === "user") {
504
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className, "data-role": "user", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_markdown2.default, { remarkPlugins: [import_remark_gfm2.default], children: markdown }) });
854
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className, "data-role": "user", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_markdown2.default, { remarkPlugins: [import_remark_gfm2.default], children: markdown }) });
505
855
  }
506
- const generatedId = (0, import_react4.useId)();
856
+ const generatedId = (0, import_react6.useId)();
507
857
  const messageIdRef = messageId ?? generatedId;
508
- const blocks = (0, import_react4.useMemo)(() => {
858
+ const blocks = (0, import_react6.useMemo)(() => {
509
859
  const allBlocks = messageBlockStore.selectAll();
510
860
  const oldBlockIds = allBlocks.filter((b) => b.messageId === messageIdRef).map((b) => b.id);
511
861
  if (oldBlockIds.length > 0) {
512
862
  messageBlockStore.remove(oldBlockIds);
513
863
  }
514
- const newBlocks = splitMarkdownIntoBlocks({
864
+ const newBlocks = splitMarkdownIntoBlocks2({
515
865
  messageId: messageIdRef,
516
866
  markdown,
517
867
  status: "idle" /* IDLE */
@@ -519,19 +869,237 @@ function MessageItem({
519
869
  messageBlockStore.upsert(newBlocks);
520
870
  return newBlocks;
521
871
  }, [markdown, messageIdRef]);
522
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className, "data-message-id": messageIdRef, "data-role": role, children: blocks.map((block) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MessageBlockRenderer, { block, className: blockClassName }, block.id)) });
872
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className, "data-message-id": messageIdRef, "data-role": role, children: blocks.map((block) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageBlockRenderer, { block, className: blockClassName }, block.id)) });
873
+ }
874
+
875
+ // src/utils/shiki/ShikiHighlighterManager.ts
876
+ var import_core2 = require("shiki/core");
877
+ var import_javascript2 = require("shiki/engine/javascript");
878
+
879
+ // src/utils/shiki/languageRegistry.ts
880
+ var LANGUAGE_REGISTRY = {
881
+ javascript: () => import("shiki/langs/javascript.mjs"),
882
+ js: () => import("shiki/langs/javascript.mjs"),
883
+ typescript: () => import("shiki/langs/typescript.mjs"),
884
+ ts: () => import("shiki/langs/typescript.mjs"),
885
+ tsx: () => import("shiki/langs/tsx.mjs"),
886
+ jsx: () => import("shiki/langs/jsx.mjs"),
887
+ python: () => import("shiki/langs/python.mjs"),
888
+ py: () => import("shiki/langs/python.mjs"),
889
+ java: () => import("shiki/langs/java.mjs"),
890
+ json: () => import("shiki/langs/json.mjs"),
891
+ html: () => import("shiki/langs/html.mjs"),
892
+ css: () => import("shiki/langs/css.mjs"),
893
+ bash: () => import("shiki/langs/bash.mjs"),
894
+ sh: () => import("shiki/langs/bash.mjs"),
895
+ shell: () => import("shiki/langs/shell.mjs"),
896
+ sql: () => import("shiki/langs/sql.mjs"),
897
+ markdown: () => import("shiki/langs/markdown.mjs"),
898
+ md: () => import("shiki/langs/markdown.mjs"),
899
+ go: () => import("shiki/langs/go.mjs"),
900
+ rust: () => import("shiki/langs/rust.mjs"),
901
+ rs: () => import("shiki/langs/rust.mjs"),
902
+ c: () => import("shiki/langs/c.mjs"),
903
+ cpp: () => import("shiki/langs/cpp.mjs"),
904
+ "c++": () => import("shiki/langs/cpp.mjs"),
905
+ csharp: () => import("shiki/langs/csharp.mjs"),
906
+ "c#": () => import("shiki/langs/csharp.mjs"),
907
+ cs: () => import("shiki/langs/csharp.mjs"),
908
+ php: () => import("shiki/langs/php.mjs"),
909
+ ruby: () => import("shiki/langs/ruby.mjs"),
910
+ rb: () => import("shiki/langs/ruby.mjs"),
911
+ swift: () => import("shiki/langs/swift.mjs"),
912
+ kotlin: () => import("shiki/langs/kotlin.mjs"),
913
+ kt: () => import("shiki/langs/kotlin.mjs"),
914
+ yaml: () => import("shiki/langs/yaml.mjs"),
915
+ yml: () => import("shiki/langs/yaml.mjs"),
916
+ xml: () => import("shiki/langs/xml.mjs"),
917
+ dockerfile: () => import("shiki/langs/dockerfile.mjs"),
918
+ graphql: () => import("shiki/langs/graphql.mjs"),
919
+ gql: () => import("shiki/langs/graphql.mjs"),
920
+ terraform: () => import("shiki/langs/terraform.mjs"),
921
+ tf: () => import("shiki/langs/terraform.mjs"),
922
+ lua: () => import("shiki/langs/lua.mjs"),
923
+ r: () => import("shiki/langs/r.mjs"),
924
+ scala: () => import("shiki/langs/scala.mjs"),
925
+ elixir: () => import("shiki/langs/elixir.mjs"),
926
+ ex: () => import("shiki/langs/elixir.mjs"),
927
+ dart: () => import("shiki/langs/dart.mjs"),
928
+ vue: () => import("shiki/langs/vue.mjs"),
929
+ svelte: () => import("shiki/langs/svelte.mjs"),
930
+ astro: () => import("shiki/langs/astro.mjs")
931
+ };
932
+ var THEME_REGISTRY = {
933
+ "github-light": () => import("shiki/themes/github-light.mjs"),
934
+ "github-dark": () => import("shiki/themes/github-dark.mjs"),
935
+ "github-dark-dimmed": () => import("shiki/themes/github-dark-dimmed.mjs"),
936
+ "dracula": () => import("shiki/themes/dracula.mjs"),
937
+ "monokai": () => import("shiki/themes/monokai.mjs"),
938
+ "nord": () => import("shiki/themes/nord.mjs"),
939
+ "one-dark-pro": () => import("shiki/themes/one-dark-pro.mjs"),
940
+ "solarized-light": () => import("shiki/themes/solarized-light.mjs"),
941
+ "solarized-dark": () => import("shiki/themes/solarized-dark.mjs"),
942
+ "vitesse-light": () => import("shiki/themes/vitesse-light.mjs"),
943
+ "vitesse-dark": () => import("shiki/themes/vitesse-dark.mjs")
944
+ };
945
+ function getLanguageImport(lang) {
946
+ const normalizedLang = lang.toLowerCase();
947
+ const importFn = LANGUAGE_REGISTRY[normalizedLang];
948
+ if (!importFn) {
949
+ console.warn(`[Shiki] Language "${lang}" not found in registry, falling back to plain text`);
950
+ return import("shiki/langs/javascript.mjs");
951
+ }
952
+ return importFn();
953
+ }
954
+ function getThemeImport(theme) {
955
+ const normalizedTheme = theme.toLowerCase();
956
+ const importFn = THEME_REGISTRY[normalizedTheme];
957
+ if (!importFn) {
958
+ console.warn(`[Shiki] Theme "${theme}" not found in registry, falling back to github-light`);
959
+ return import("shiki/themes/github-light.mjs");
960
+ }
961
+ return importFn();
962
+ }
963
+ function isLanguageSupported(lang) {
964
+ return lang.toLowerCase() in LANGUAGE_REGISTRY;
965
+ }
966
+ function isThemeSupported(theme) {
967
+ return theme.toLowerCase() in THEME_REGISTRY;
523
968
  }
969
+ function getSupportedLanguages() {
970
+ return Object.keys(LANGUAGE_REGISTRY);
971
+ }
972
+ function getSupportedThemes() {
973
+ return Object.keys(THEME_REGISTRY);
974
+ }
975
+
976
+ // src/utils/shiki/ShikiHighlighterManager.ts
977
+ var ShikiHighlighterManager = class {
978
+ constructor() {
979
+ this.lightHighlighter = null;
980
+ this.darkHighlighter = null;
981
+ this.lightTheme = null;
982
+ this.darkTheme = null;
983
+ this.loadedLanguages = /* @__PURE__ */ new Set();
984
+ }
985
+ /**
986
+ * 高亮代码(返回 light 和 dark 两个主题的 HTML)
987
+ *
988
+ * @param code - 代码字符串
989
+ * @param language - 语言标识符
990
+ * @param themes - [light theme, dark theme] 元组
991
+ * @returns [lightHtml, darkHtml] - 两个主题的 HTML 元组
992
+ */
993
+ async highlightCode(code, language, themes) {
994
+ const [lightTheme, darkTheme] = themes;
995
+ const needsLightRecreation = !this.lightHighlighter || this.lightTheme !== lightTheme;
996
+ const needsDarkRecreation = !this.darkHighlighter || this.darkTheme !== darkTheme;
997
+ if (needsLightRecreation || needsDarkRecreation) {
998
+ this.loadedLanguages.clear();
999
+ }
1000
+ const languageSupported = isLanguageSupported(language);
1001
+ const needsLanguageLoad = !this.loadedLanguages.has(language) && languageSupported;
1002
+ if (needsLightRecreation) {
1003
+ this.lightHighlighter = await (0, import_core2.createHighlighterCore)({
1004
+ themes: [getThemeImport(lightTheme)],
1005
+ langs: languageSupported ? [getLanguageImport(language)] : [],
1006
+ engine: (0, import_javascript2.createJavaScriptRegexEngine)({ forgiving: true })
1007
+ });
1008
+ this.lightTheme = lightTheme;
1009
+ if (languageSupported) {
1010
+ this.loadedLanguages.add(language);
1011
+ }
1012
+ } else if (needsLanguageLoad && this.lightHighlighter) {
1013
+ await this.lightHighlighter.loadLanguage(getLanguageImport(language));
1014
+ this.loadedLanguages.add(language);
1015
+ }
1016
+ if (needsDarkRecreation) {
1017
+ this.darkHighlighter = await (0, import_core2.createHighlighterCore)({
1018
+ themes: [getThemeImport(darkTheme)],
1019
+ langs: languageSupported ? [getLanguageImport(language)] : [],
1020
+ engine: (0, import_javascript2.createJavaScriptRegexEngine)({ forgiving: true })
1021
+ });
1022
+ this.darkTheme = darkTheme;
1023
+ } else if (needsLanguageLoad && this.darkHighlighter) {
1024
+ await this.darkHighlighter.loadLanguage(getLanguageImport(language));
1025
+ }
1026
+ const lang = languageSupported ? language : "text";
1027
+ const light = this.lightHighlighter?.codeToHtml(code, {
1028
+ lang,
1029
+ theme: lightTheme
1030
+ }) ?? "";
1031
+ const dark = this.darkHighlighter?.codeToHtml(code, {
1032
+ lang,
1033
+ theme: darkTheme
1034
+ }) ?? "";
1035
+ return [light, dark];
1036
+ }
1037
+ /**
1038
+ * 高亮代码(单一主题版本)
1039
+ */
1040
+ async highlightCodeSingle(code, language, theme) {
1041
+ const [light] = await this.highlightCode(code, language, [theme, theme]);
1042
+ return light;
1043
+ }
1044
+ /**
1045
+ * 清除缓存(用于测试或内存清理)
1046
+ */
1047
+ clear() {
1048
+ this.lightHighlighter = null;
1049
+ this.darkHighlighter = null;
1050
+ this.lightTheme = null;
1051
+ this.darkTheme = null;
1052
+ this.loadedLanguages.clear();
1053
+ }
1054
+ };
1055
+ var highlighterManager = new ShikiHighlighterManager();
524
1056
  // Annotate the CommonJS export names for ESM import in node:
525
1057
  0 && (module.exports = {
1058
+ Block,
526
1059
  CodeBlock,
1060
+ MemoBlockquote,
1061
+ MemoCode,
1062
+ MemoDelete,
1063
+ MemoEmphasis,
1064
+ MemoH1,
1065
+ MemoH2,
1066
+ MemoH3,
1067
+ MemoH4,
1068
+ MemoH5,
1069
+ MemoH6,
1070
+ MemoHr,
1071
+ MemoImage,
1072
+ MemoLink,
1073
+ MemoList,
1074
+ MemoListItem,
1075
+ MemoParagraph,
1076
+ MemoPreformatted,
1077
+ MemoStrong,
1078
+ MemoTable,
1079
+ MemoTableBody,
1080
+ MemoTableCell,
1081
+ MemoTableHead,
1082
+ MemoTableRow,
527
1083
  MessageBlockRenderer,
528
1084
  MessageBlockStatus,
529
1085
  MessageBlockStore,
530
1086
  MessageBlockType,
531
1087
  MessageItem,
532
1088
  MessageStatus,
1089
+ ShikiHighlighterManager,
533
1090
  StreamingMarkdown,
1091
+ getLanguageImport,
1092
+ getSupportedLanguages,
1093
+ getSupportedThemes,
1094
+ getThemeImport,
1095
+ highlighterManager,
1096
+ isLanguageSupported,
1097
+ isThemeSupported,
534
1098
  messageBlockStore,
1099
+ parseMarkdownIntoBlocks,
1100
+ sameClassAndNode,
1101
+ sameNodePosition,
1102
+ splitMarkdownIntoBlocks,
535
1103
  useShikiHighlight,
536
1104
  useSmoothStream
537
1105
  });