@ai-react-markdown/mantine 2.11.0 → 2.12.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
@@ -25,7 +25,7 @@ var DefaultExtraStyles_default = MantineAIMDefaultExtraStyles;
25
25
 
26
26
  // src/components/customized/PreCode.tsx
27
27
  import { memo as memo3, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
28
- import { CodeHighlight, CodeHighlightTabs as CodeHighlightTabs2 } from "@mantine/code-highlight";
28
+ import { CodeHighlight, CodeHighlightTabs as CodeHighlightTabs2, CodeHighlightControl as CodeHighlightControl2 } from "@mantine/code-highlight";
29
29
  import { useAIMarkdownState as useAIMarkdownState2, useAIMarkdownTheme as useAIMarkdownTheme2 } from "@ai-react-markdown/core";
30
30
 
31
31
  // src/hooks/useMantineCodeBlockOptions.ts
@@ -35,7 +35,9 @@ import { useAIMarkdownBehaviors } from "@ai-react-markdown/core";
35
35
  // src/defs.tsx
36
36
  var defaultMantineCodeBlockOptions = Object.freeze({
37
37
  defaultExpanded: true,
38
- autoDetectUnknownLanguage: false
38
+ autoDetectUnknownLanguage: false,
39
+ formatJson: true,
40
+ expandNestedJson: true
39
41
  });
40
42
 
41
43
  // src/hooks/useMantineCodeBlockOptions.ts
@@ -52,6 +54,43 @@ function useMantineCodeBlockOptions() {
52
54
  }, [group]);
53
55
  }
54
56
 
57
+ // src/components/customized/MermaidCode/renderQueue.ts
58
+ function createRenderQueue() {
59
+ const pending = /* @__PURE__ */ new Map();
60
+ let running = false;
61
+ async function drain() {
62
+ if (running) return;
63
+ running = true;
64
+ try {
65
+ while (pending.size) {
66
+ const [owner, job] = pending.entries().next().value;
67
+ pending.delete(owner);
68
+ try {
69
+ await job.run();
70
+ job.resolve();
71
+ } catch (error) {
72
+ job.reject(error);
73
+ }
74
+ }
75
+ } finally {
76
+ running = false;
77
+ }
78
+ }
79
+ return {
80
+ enqueue(owner, run) {
81
+ pending.get(owner)?.resolve();
82
+ const promise = new Promise((resolve, reject) => pending.set(owner, { run, resolve, reject }));
83
+ void drain();
84
+ return promise;
85
+ },
86
+ cancel(owner) {
87
+ pending.get(owner)?.resolve();
88
+ pending.delete(owner);
89
+ }
90
+ };
91
+ }
92
+ var mermaidRenderQueue = createRenderQueue();
93
+
55
94
  // src/components/customized/MermaidCode/index.tsx
56
95
  import { memo as memo2, useEffect, useRef, useState, useCallback } from "react";
57
96
  import { CodeHighlightControl, CodeHighlightTabs } from "@mantine/code-highlight";
@@ -173,14 +212,7 @@ var MantineAIMMermaidCode = memo2((props) => {
173
212
  if (!ref.current || cancelled || renderVersion !== renderVersionRef.current) {
174
213
  return;
175
214
  }
176
- let rendered = await mermaid.render(generateMermaidUUID(), props.code);
177
- if (initializedTheme !== (isDark ? "dark" : "light")) {
178
- if (!ref.current || cancelled || renderVersion !== renderVersionRef.current) {
179
- return;
180
- }
181
- ensureMermaidInitialized(mermaid, isDark);
182
- rendered = await mermaid.render(generateMermaidUUID(), props.code);
183
- }
215
+ const rendered = await mermaid.render(generateMermaidUUID(), props.code);
184
216
  const { svg, bindFunctions, diagramType } = rendered;
185
217
  if (!ref.current || cancelled || renderVersion !== renderVersionRef.current) {
186
218
  return;
@@ -208,9 +240,11 @@ var MantineAIMMermaidCode = memo2((props) => {
208
240
  applyView({ kind: "error" });
209
241
  }
210
242
  };
211
- void renderMermaid();
243
+ const owner = renderVersionRef;
244
+ void mermaidRenderQueue.enqueue(owner, renderMermaid);
212
245
  return () => {
213
246
  cancelled = true;
247
+ mermaidRenderQueue.cancel(owner);
214
248
  if (retryTimer !== void 0) clearTimeout(retryTimer);
215
249
  };
216
250
  }, [props.code, isDark, showOriginalCode, streaming, loadAttempt]);
@@ -361,7 +395,96 @@ MantineAIMMermaidCode.displayName = "MantineAIMMermaidCode";
361
395
  var MermaidCode_default = MantineAIMMermaidCode;
362
396
 
363
397
  // src/components/customized/PreCode.tsx
364
- import { jsx as jsx4 } from "react/jsx-runtime";
398
+ import { CopyButton as CopyButton2 } from "@mantine/core";
399
+
400
+ // src/components/customized/formatJson.ts
401
+ function prettyPrintJson(text, expandNested = true) {
402
+ try {
403
+ return format(text, expandNested, 0);
404
+ } catch {
405
+ return text;
406
+ }
407
+ }
408
+ function format(text, expandNested, baseIndent) {
409
+ JSON.parse(text);
410
+ const tokens = text.match(/"(?:\\[\s\S]|[^"\\])*"|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null|[{}[\],:]/g) ?? [];
411
+ const out = [];
412
+ let indent = baseIndent;
413
+ const newline = () => out.push("\n", " ".repeat(indent));
414
+ for (let i = 0; i < tokens.length; i++) {
415
+ const token = tokens[i];
416
+ if (token === "{" || token === "[") {
417
+ out.push(token);
418
+ if (tokens[i + 1] !== (token === "{" ? "}" : "]")) {
419
+ indent++;
420
+ newline();
421
+ }
422
+ } else if (token === "}" || token === "]") {
423
+ if (tokens[i - 1] !== (token === "}" ? "{" : "[")) {
424
+ indent--;
425
+ newline();
426
+ }
427
+ out.push(token);
428
+ } else if (token === ",") {
429
+ out.push(",");
430
+ newline();
431
+ } else if (token === ":") {
432
+ out.push(": ");
433
+ } else if (token.startsWith('"')) {
434
+ const value = JSON.parse(token);
435
+ const trimmed = value.trim();
436
+ if (expandNested && tokens[i + 1] !== ":" && /^[{[]/.test(trimmed) && indent < 100) {
437
+ try {
438
+ out.push(format(trimmed, true, indent));
439
+ continue;
440
+ } catch {
441
+ }
442
+ }
443
+ out.push(JSON.stringify(value));
444
+ } else {
445
+ out.push(token);
446
+ }
447
+ }
448
+ return out.join("");
449
+ }
450
+
451
+ // src/components/customized/jsonCompleteness.ts
452
+ function createJsonCompletenessScanner() {
453
+ let previous = "";
454
+ let depth = 0;
455
+ let quoted = false;
456
+ let escaped = false;
457
+ let invalid = false;
458
+ let last = "";
459
+ return (text) => {
460
+ let from = previous.length;
461
+ if (!text.startsWith(previous)) {
462
+ from = 0;
463
+ depth = 0;
464
+ quoted = escaped = invalid = false;
465
+ last = "";
466
+ }
467
+ previous = text;
468
+ for (let i = from; i < text.length; i++) {
469
+ const c = text[i];
470
+ if (!/\s/.test(c)) last = c;
471
+ if (quoted) {
472
+ if (escaped) escaped = false;
473
+ else if (c === "\\") escaped = true;
474
+ else if (c === '"') quoted = false;
475
+ } else if (c === '"') quoted = true;
476
+ else if (c === "{" || c === "[") depth++;
477
+ else if (c === "}" || c === "]") {
478
+ depth--;
479
+ if (depth < 0) invalid = true;
480
+ }
481
+ }
482
+ return !invalid && !quoted && depth === 0 && (last === "}" || last === "]");
483
+ };
484
+ }
485
+
486
+ // src/components/customized/PreCode.tsx
487
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
365
488
  var hljsAutoPromise = null;
366
489
  var loadHljsForAutoDetect = () => {
367
490
  hljsAutoPromise ??= import("highlight.js").then(
@@ -382,9 +505,15 @@ function useAutoDetectedLanguage(codeText, enabled, streaming) {
382
505
  );
383
506
  const [loadAttempt, setLoadAttempt] = useState2(0);
384
507
  const loadFailuresRef = useRef2(0);
508
+ const previousTextRef = useRef2(codeText);
385
509
  useEffect2(() => {
386
- if (!enabled) return;
387
- const prior = detected !== null && codeText.length < detected.atLength ? null : detected;
510
+ const appended = codeText.startsWith(previousTextRef.current);
511
+ previousTextRef.current = codeText;
512
+ if (!enabled) {
513
+ if (detected) setDetected(null);
514
+ return;
515
+ }
516
+ const prior = !appended || detected !== null && codeText.length < detected.atLength ? null : detected;
388
517
  if (prior !== detected) setDetected(null);
389
518
  if (codeText.length < AUTODETECT_MIN_CHARS) return;
390
519
  const due = prior === null || // Not streaming: the verdict must be for THIS text (end-of-stream, or a
@@ -423,54 +552,31 @@ function preloadMantineCodeAssets() {
423
552
  () => void 0
424
553
  );
425
554
  }
426
- function prettyPrintJson(text) {
427
- let parsed;
428
- try {
429
- parsed = JSON.parse(text);
430
- } catch {
431
- return text;
432
- }
433
- return JSON.stringify(expandNestedJson(parsed), null, 2);
434
- }
435
- function expandNestedJson(value) {
436
- if (typeof value === "string") {
437
- const t = value.trim();
438
- if (t.length > 1 && (t[0] === "{" || t[0] === "[")) {
439
- try {
440
- const inner = JSON.parse(t);
441
- if (inner !== null && typeof inner === "object") return expandNestedJson(inner);
442
- } catch {
443
- }
444
- }
445
- return value;
446
- }
447
- if (Array.isArray(value)) return value.map(expandNestedJson);
448
- if (value !== null && typeof value === "object") {
449
- const out = /* @__PURE__ */ Object.create(null);
450
- for (const [k, v] of Object.entries(value)) out[k] = expandNestedJson(v);
451
- return out;
452
- }
453
- return value;
454
- }
455
- function jsonLooksComplete(text) {
456
- if (!/[}\]]\s*$/.test(text)) return false;
457
- let depth = 0;
458
- let inString = false;
459
- for (let i = 0; i < text.length; i++) {
460
- const c = text.charCodeAt(i);
461
- if (inString) {
462
- if (c === 92) i += 1;
463
- else if (c === 34) inString = false;
464
- } else if (c === 34) {
465
- inString = true;
466
- } else if (c === 123 || c === 91) {
467
- depth += 1;
468
- } else if (c === 125 || c === 93) {
469
- depth -= 1;
470
- if (depth < 0) return false;
555
+ function RawCodeCopy({ code }) {
556
+ return /* @__PURE__ */ jsx4(CopyButton2, { value: code, children: ({ copied, copy }) => /* @__PURE__ */ jsx4(
557
+ CodeHighlightControl2,
558
+ {
559
+ tooltipLabel: copied ? "Copied" : "Copy",
560
+ "aria-label": copied ? "Copied" : "Copy code",
561
+ onClick: copy,
562
+ children: copied ? "\u2713" : /* @__PURE__ */ jsxs2(
563
+ "svg",
564
+ {
565
+ width: "16",
566
+ height: "16",
567
+ viewBox: "0 0 24 24",
568
+ fill: "none",
569
+ stroke: "currentColor",
570
+ strokeWidth: "1.5",
571
+ "aria-hidden": "true",
572
+ children: [
573
+ /* @__PURE__ */ jsx4("rect", { x: "8", y: "8", width: "12", height: "12", rx: "2" }),
574
+ /* @__PURE__ */ jsx4("path", { d: "M16 8V4H4v12h4" })
575
+ ]
576
+ }
577
+ )
471
578
  }
472
- }
473
- return depth === 0 && !inString;
579
+ ) });
474
580
  }
475
581
  var SpecialCodeLanguage = /* @__PURE__ */ ((SpecialCodeLanguage2) => {
476
582
  SpecialCodeLanguage2["Mermaid"] = "mermaid";
@@ -481,7 +587,7 @@ var MantineAIMPreCode = memo3(
481
587
  (props) => {
482
588
  const { fontSize } = useAIMarkdownTheme2();
483
589
  const { streaming } = useAIMarkdownState2();
484
- const { autoDetectUnknownLanguage, defaultExpanded } = useMantineCodeBlockOptions();
590
+ const { autoDetectUnknownLanguage, defaultExpanded, formatJson, expandNestedJson } = useMantineCodeBlockOptions();
485
591
  const detectedLanguage = useAutoDetectedLanguage(
486
592
  props.codeText,
487
593
  autoDetectUnknownLanguage && !props.existLanguage,
@@ -493,11 +599,16 @@ var MantineAIMPreCode = memo3(
493
599
  [codeLanguage]
494
600
  );
495
601
  const isSpecialCodeBlock = SPECIAL_LANGUAGES.has(codeLanguage);
602
+ const [scanJson] = useState2(createJsonCompletenessScanner);
603
+ const jsonComplete = useMemo2(
604
+ () => usedCodeLanguage === "json" && formatJson && streaming ? scanJson(props.codeText) : false,
605
+ [usedCodeLanguage, formatJson, streaming, scanJson, props.codeText]
606
+ );
496
607
  const normalCodeBlockContent = useMemo2(() => {
497
608
  if (isSpecialCodeBlock) return null;
498
609
  let usedCodeStr = props.codeText;
499
- if (usedCodeStr && usedCodeLanguage === "json" && (!streaming || jsonLooksComplete(usedCodeStr))) {
500
- usedCodeStr = prettyPrintJson(usedCodeStr);
610
+ if (formatJson && usedCodeStr && usedCodeLanguage === "json" && (!streaming || jsonComplete)) {
611
+ usedCodeStr = prettyPrintJson(usedCodeStr, expandNestedJson);
501
612
  }
502
613
  return usedFileName === "unknown" ? /* @__PURE__ */ jsx4(
503
614
  CodeHighlight,
@@ -509,7 +620,9 @@ var MantineAIMPreCode = memo3(
509
620
  withBorder: true,
510
621
  withExpandButton: true,
511
622
  defaultExpanded,
512
- maxCollapsedHeight: "320px"
623
+ maxCollapsedHeight: "320px",
624
+ withCopyButton: false,
625
+ controls: [/* @__PURE__ */ jsx4(RawCodeCopy, { code: props.codeText }, "copy")]
513
626
  }
514
627
  ) : /* @__PURE__ */ jsx4(
515
628
  CodeHighlightTabs2,
@@ -527,10 +640,23 @@ var MantineAIMPreCode = memo3(
527
640
  withBorder: true,
528
641
  withExpandButton: true,
529
642
  defaultExpanded,
530
- maxCollapsedHeight: "320px"
643
+ maxCollapsedHeight: "320px",
644
+ withCopyButton: false,
645
+ controls: [/* @__PURE__ */ jsx4(RawCodeCopy, { code: props.codeText }, "copy")]
531
646
  }
532
647
  );
533
- }, [isSpecialCodeBlock, props.codeText, usedCodeLanguage, usedFileName, fontSize, defaultExpanded, streaming]);
648
+ }, [
649
+ isSpecialCodeBlock,
650
+ props.codeText,
651
+ usedCodeLanguage,
652
+ usedFileName,
653
+ fontSize,
654
+ defaultExpanded,
655
+ streaming,
656
+ formatJson,
657
+ expandNestedJson,
658
+ jsonComplete
659
+ ]);
534
660
  const specialCodeBlockContent = useMemo2(() => {
535
661
  switch (codeLanguage) {
536
662
  case "mermaid" /* Mermaid */:
@@ -555,13 +681,14 @@ var MANTINE_STABILITY_TABLE = {
555
681
  var DefaultCustomComponents = {
556
682
  pre: ({ node, ...usefulProps }) => {
557
683
  const code = node?.children[0];
558
- if (!code || code.type !== "element" || code.tagName !== "code" || !code.position) {
684
+ if (!code || code.type !== "element" || code.tagName !== "code" || !code.position || node?.children.length !== 1 || code.children.some((child) => !("value" in child) || typeof child.value !== "string") || Object.keys(code.properties ?? {}).some((key2) => key2 !== "className") || Object.keys(node.properties ?? {}).length > 0) {
559
685
  return /* @__PURE__ */ jsx5("pre", { ...usefulProps });
560
686
  }
561
687
  const key = `pre-code-${node?.position?.start?.offset || 0}`;
562
688
  const classNames = code.properties?.className;
563
689
  const classList = Array.isArray(classNames) ? classNames.filter((c) => typeof c === "string") : typeof classNames === "string" ? classNames.split(/\s+/) : [];
564
690
  const detectedLanguage = classList.find((className) => className.startsWith("language-"))?.substring("language-".length);
691
+ if (classList.some((className) => !className.startsWith("language-"))) return /* @__PURE__ */ jsx5("pre", { ...usefulProps });
565
692
  const codeText = code.children.map((child) => child.value ?? "").join("");
566
693
  return /* @__PURE__ */ jsx5(PreCode_default, { codeText, existLanguage: detectedLanguage }, key);
567
694
  }