@lobehub/ui 5.15.16 → 5.16.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.
Files changed (37) hide show
  1. package/es/Markdown/Markdown.mjs +2 -1
  2. package/es/Markdown/Markdown.mjs.map +1 -1
  3. package/es/Markdown/SyntaxMarkdown/CachedMarkdown.mjs +82 -0
  4. package/es/Markdown/SyntaxMarkdown/CachedMarkdown.mjs.map +1 -0
  5. package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs +127 -97
  6. package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs.map +1 -1
  7. package/es/Markdown/SyntaxMarkdown/style.mjs +1 -2
  8. package/es/Markdown/SyntaxMarkdown/style.mjs.map +1 -1
  9. package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs +32 -14
  10. package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs.map +1 -1
  11. package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs +2 -5
  12. package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs.map +1 -1
  13. package/es/Markdown/components/MarkdownProvider.mjs +2 -1
  14. package/es/Markdown/components/MarkdownProvider.mjs.map +1 -1
  15. package/es/Markdown/index.d.mts +2 -2
  16. package/es/Markdown/plugins/rehypeStreamAnimated.d.mts +21 -0
  17. package/es/Markdown/plugins/rehypeStreamAnimated.mjs +61 -25
  18. package/es/Markdown/plugins/rehypeStreamAnimated.mjs.map +1 -1
  19. package/es/Markdown/type.d.mts +3 -1
  20. package/es/Tabs/Tabs.mjs +6 -14
  21. package/es/Tabs/Tabs.mjs.map +1 -1
  22. package/es/base-ui/Tabs/Tabs.d.mts +8 -0
  23. package/es/base-ui/Tabs/Tabs.mjs +54 -0
  24. package/es/base-ui/Tabs/Tabs.mjs.map +1 -0
  25. package/es/base-ui/Tabs/atoms.d.mts +22 -0
  26. package/es/base-ui/Tabs/atoms.mjs +68 -0
  27. package/es/base-ui/Tabs/atoms.mjs.map +1 -0
  28. package/es/base-ui/Tabs/index.d.mts +4 -0
  29. package/es/base-ui/Tabs/style.d.mts +20 -0
  30. package/es/base-ui/Tabs/style.mjs +203 -0
  31. package/es/base-ui/Tabs/style.mjs.map +1 -0
  32. package/es/base-ui/Tabs/type.d.mts +64 -0
  33. package/es/base-ui/index.d.mts +5 -1
  34. package/es/base-ui/index.mjs +4 -1
  35. package/es/utils/getNow.mjs +8 -0
  36. package/es/utils/getNow.mjs.map +1 -0
  37. package/package.json +6 -1
@@ -12,7 +12,7 @@ import { jsx } from "react/jsx-runtime";
12
12
  import { cx } from "antd-style";
13
13
  //#region src/Markdown/Markdown.tsx
14
14
  const Markdown = memo((props) => {
15
- const { ref, children = "", className, style, fullFeaturedCodeBlock, onDoubleClick, animated, enableHtmlPreview = false, enableLatex = true, enableMermaid = true, enableImageGallery, enableCustomFootnotes, enableGithubAlert, enableStream = true, componentProps, rehypePluginsAhead, allowHtml, borderRadius, fontSize = props.variant === "chat" ? 14 : void 0, headerMultiple = props.variant === "chat" ? .25 : void 0, marginMultiple = props.variant === "chat" ? 1 : void 0, variant = "default", reactMarkdownProps, lineHeight = props.variant === "chat" ? 1.6 : void 0, rehypePlugins, remarkPlugins, remarkPluginsAhead, components = {}, customRender, showFootnotes = true, streamSmoothingPreset, citations, ...rest } = props;
15
+ const { ref, children = "", className, style, fullFeaturedCodeBlock, onDoubleClick, animated, enableHtmlPreview = false, enableLatex = true, enableMermaid = true, enableImageGallery, enableCustomFootnotes, enableGithubAlert, enableStream = true, componentProps, rehypePluginsAhead, allowHtml, borderRadius, fontSize = props.variant === "chat" ? 14 : void 0, headerMultiple = props.variant === "chat" ? .25 : void 0, marginMultiple = props.variant === "chat" ? 1 : void 0, variant = "default", reactMarkdownProps, lineHeight = props.variant === "chat" ? 1.6 : void 0, rehypePlugins, remarkPlugins, remarkPluginsAhead, components = {}, customRender, showFootnotes = true, streamAnimationGranularity, streamSmoothingPreset, citations, ...rest } = props;
16
16
  const delayedAnimated = useDelayedAnimated(animated);
17
17
  const stableComponentProps = useStableValue(componentProps);
18
18
  const stableComponents = useStableValue(components);
@@ -57,6 +57,7 @@ const Markdown = memo((props) => {
57
57
  remarkPlugins,
58
58
  remarkPluginsAhead,
59
59
  showFootnotes,
60
+ streamAnimationGranularity,
60
61
  streamSmoothingPreset,
61
62
  variant,
62
63
  children: /* @__PURE__ */ jsx(Render, {
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.mjs","names":["MarkdownRender"],"sources":["../../src/Markdown/Markdown.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'antd-style';\nimport { memo, useCallback } from 'react';\n\nimport { useStableValue } from '@/hooks/useStableValue';\nimport { PreviewGroup } from '@/Image';\n\nimport { MarkdownProvider } from './components/MarkdownProvider';\nimport { useDelayedAnimated } from './components/useDelayedAnimated';\nimport { variants } from './style';\nimport { MarkdownRender, StreamdownRender } from './SyntaxMarkdown';\nimport { type MarkdownProps } from './type';\nimport Typography from './Typography';\n\nconst Markdown = memo<MarkdownProps>((props) => {\n const {\n ref,\n children = '',\n className,\n style,\n fullFeaturedCodeBlock,\n onDoubleClick,\n animated,\n enableHtmlPreview = false,\n enableLatex = true,\n enableMermaid = true,\n enableImageGallery,\n enableCustomFootnotes,\n enableGithubAlert,\n enableStream = true,\n componentProps,\n rehypePluginsAhead,\n allowHtml,\n borderRadius,\n fontSize = props.variant === 'chat' ? 14 : undefined,\n headerMultiple = props.variant === 'chat' ? 0.25 : undefined,\n marginMultiple = props.variant === 'chat' ? 1 : undefined,\n variant = 'default',\n reactMarkdownProps,\n lineHeight = props.variant === 'chat' ? 1.6 : undefined,\n rehypePlugins,\n remarkPlugins,\n remarkPluginsAhead,\n components = {},\n customRender,\n showFootnotes = true,\n streamSmoothingPreset,\n citations,\n ...rest\n } = props;\n\n const delayedAnimated = useDelayedAnimated(animated);\n\n // Stabilise structural props so an inline `componentProps={{ html:\n // {...} }}` from the caller doesn't cascade through MarkdownProvider →\n // useMarkdownComponents → react-markdown's `components` and remount the\n // entire code-block subtree (including the HtmlPreview iframe) on\n // every parent render. The deep-equal compare treats inline literals\n // as the same object as long as their structure matches. Inline\n // callbacks (`actionsRender`, custom `components`) still need\n // `useCallback` at the call site — function bodies aren't compared.\n const stableComponentProps = useStableValue(componentProps);\n const stableComponents = useStableValue(components);\n\n const Render = useCallback(\n ({\n enableStream,\n children,\n reactMarkdownProps,\n }: Pick<MarkdownProps, 'children' | 'enableStream' | 'reactMarkdownProps'>) => {\n const DefaultRender = enableStream ? StreamdownRender : MarkdownRender;\n const defaultDOM = <DefaultRender {...reactMarkdownProps}>{children}</DefaultRender>;\n return customRender ? customRender(defaultDOM, { text: children }) : defaultDOM;\n },\n [customRender],\n );\n\n return (\n <PreviewGroup enable={enableImageGallery}>\n <Typography\n borderRadius={borderRadius}\n className={cx(variants({ enableLatex, variant }), className)}\n data-code-type=\"markdown\"\n fontSize={fontSize}\n headerMultiple={headerMultiple}\n lineHeight={lineHeight}\n marginMultiple={marginMultiple}\n ref={ref}\n style={style}\n onDoubleClick={onDoubleClick}\n {...rest}\n >\n <MarkdownProvider\n allowHtml={allowHtml}\n animated={delayedAnimated}\n citations={citations}\n componentProps={stableComponentProps}\n components={stableComponents}\n enableCustomFootnotes={enableCustomFootnotes}\n enableGithubAlert={enableGithubAlert}\n enableHtmlPreview={enableHtmlPreview}\n enableLatex={enableLatex}\n enableMermaid={enableMermaid}\n fullFeaturedCodeBlock={fullFeaturedCodeBlock}\n rehypePlugins={rehypePlugins}\n rehypePluginsAhead={rehypePluginsAhead}\n remarkPlugins={remarkPlugins}\n remarkPluginsAhead={remarkPluginsAhead}\n showFootnotes={showFootnotes}\n streamSmoothingPreset={streamSmoothingPreset}\n variant={variant}\n >\n <Render\n enableStream={enableStream && delayedAnimated}\n reactMarkdownProps={reactMarkdownProps}\n >\n {children}\n </Render>\n </MarkdownProvider>\n </Typography>\n </PreviewGroup>\n );\n});\n\nMarkdown.displayName = 'Markdown';\n\nexport default Markdown;\n"],"mappings":";;;;;;;;;;;;;AAeA,MAAM,WAAW,MAAqB,UAAU;CAC9C,MAAM,EACJ,KACA,WAAW,IACX,WACA,OACA,uBACA,eACA,UACA,oBAAoB,OACpB,cAAc,MACd,gBAAgB,MAChB,oBACA,uBACA,mBACA,eAAe,MACf,gBACA,oBACA,WACA,cACA,WAAW,MAAM,YAAY,SAAS,KAAK,KAAA,GAC3C,iBAAiB,MAAM,YAAY,SAAS,MAAO,KAAA,GACnD,iBAAiB,MAAM,YAAY,SAAS,IAAI,KAAA,GAChD,UAAU,WACV,oBACA,aAAa,MAAM,YAAY,SAAS,MAAM,KAAA,GAC9C,eACA,eACA,oBACA,aAAa,EAAE,EACf,cACA,gBAAgB,MAChB,uBACA,WACA,GAAG,SACD;CAEJ,MAAM,kBAAkB,mBAAmB,SAAS;CAUpD,MAAM,uBAAuB,eAAe,eAAe;CAC3D,MAAM,mBAAmB,eAAe,WAAW;CAEnD,MAAM,SAAS,aACZ,EACC,cACA,UACA,yBAC6E;EAE7E,MAAM,aAAa,oBADG,eAAe,mBAAmBA,kBACrC;GAAe,GAAI;GAAqB;GAAyB,CAAA;AACpF,SAAO,eAAe,aAAa,YAAY,EAAE,MAAM,UAAU,CAAC,GAAG;IAEvE,CAAC,aAAa,CACf;AAED,QACE,oBAAC,cAAD;EAAc,QAAQ;YACpB,oBAAC,YAAD;GACgB;GACd,WAAW,GAAG,SAAS;IAAE;IAAa;IAAS,CAAC,EAAE,UAAU;GAC5D,kBAAe;GACL;GACM;GACJ;GACI;GACX;GACE;GACQ;GACf,GAAI;aAEJ,oBAAC,kBAAD;IACa;IACX,UAAU;IACC;IACX,gBAAgB;IAChB,YAAY;IACW;IACJ;IACA;IACN;IACE;IACQ;IACR;IACK;IACL;IACK;IACL;IACQ;IACd;cAET,oBAAC,QAAD;KACE,cAAc,gBAAgB;KACV;KAEnB;KACM,CAAA;IACQ,CAAA;GACR,CAAA;EACA,CAAA;EAEjB;AAEF,SAAS,cAAc"}
1
+ {"version":3,"file":"Markdown.mjs","names":["MarkdownRender"],"sources":["../../src/Markdown/Markdown.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'antd-style';\nimport { memo, useCallback } from 'react';\n\nimport { useStableValue } from '@/hooks/useStableValue';\nimport { PreviewGroup } from '@/Image';\n\nimport { MarkdownProvider } from './components/MarkdownProvider';\nimport { useDelayedAnimated } from './components/useDelayedAnimated';\nimport { variants } from './style';\nimport { MarkdownRender, StreamdownRender } from './SyntaxMarkdown';\nimport { type MarkdownProps } from './type';\nimport Typography from './Typography';\n\nconst Markdown = memo<MarkdownProps>((props) => {\n const {\n ref,\n children = '',\n className,\n style,\n fullFeaturedCodeBlock,\n onDoubleClick,\n animated,\n enableHtmlPreview = false,\n enableLatex = true,\n enableMermaid = true,\n enableImageGallery,\n enableCustomFootnotes,\n enableGithubAlert,\n enableStream = true,\n componentProps,\n rehypePluginsAhead,\n allowHtml,\n borderRadius,\n fontSize = props.variant === 'chat' ? 14 : undefined,\n headerMultiple = props.variant === 'chat' ? 0.25 : undefined,\n marginMultiple = props.variant === 'chat' ? 1 : undefined,\n variant = 'default',\n reactMarkdownProps,\n lineHeight = props.variant === 'chat' ? 1.6 : undefined,\n rehypePlugins,\n remarkPlugins,\n remarkPluginsAhead,\n components = {},\n customRender,\n showFootnotes = true,\n streamAnimationGranularity,\n streamSmoothingPreset,\n citations,\n ...rest\n } = props;\n\n const delayedAnimated = useDelayedAnimated(animated);\n\n // Stabilise structural props so an inline `componentProps={{ html:\n // {...} }}` from the caller doesn't cascade through MarkdownProvider →\n // useMarkdownComponents → react-markdown's `components` and remount the\n // entire code-block subtree (including the HtmlPreview iframe) on\n // every parent render. The deep-equal compare treats inline literals\n // as the same object as long as their structure matches. Inline\n // callbacks (`actionsRender`, custom `components`) still need\n // `useCallback` at the call site — function bodies aren't compared.\n const stableComponentProps = useStableValue(componentProps);\n const stableComponents = useStableValue(components);\n\n const Render = useCallback(\n ({\n enableStream,\n children,\n reactMarkdownProps,\n }: Pick<MarkdownProps, 'children' | 'enableStream' | 'reactMarkdownProps'>) => {\n const DefaultRender = enableStream ? StreamdownRender : MarkdownRender;\n const defaultDOM = <DefaultRender {...reactMarkdownProps}>{children}</DefaultRender>;\n return customRender ? customRender(defaultDOM, { text: children }) : defaultDOM;\n },\n [customRender],\n );\n\n return (\n <PreviewGroup enable={enableImageGallery}>\n <Typography\n borderRadius={borderRadius}\n className={cx(variants({ enableLatex, variant }), className)}\n data-code-type=\"markdown\"\n fontSize={fontSize}\n headerMultiple={headerMultiple}\n lineHeight={lineHeight}\n marginMultiple={marginMultiple}\n ref={ref}\n style={style}\n onDoubleClick={onDoubleClick}\n {...rest}\n >\n <MarkdownProvider\n allowHtml={allowHtml}\n animated={delayedAnimated}\n citations={citations}\n componentProps={stableComponentProps}\n components={stableComponents}\n enableCustomFootnotes={enableCustomFootnotes}\n enableGithubAlert={enableGithubAlert}\n enableHtmlPreview={enableHtmlPreview}\n enableLatex={enableLatex}\n enableMermaid={enableMermaid}\n fullFeaturedCodeBlock={fullFeaturedCodeBlock}\n rehypePlugins={rehypePlugins}\n rehypePluginsAhead={rehypePluginsAhead}\n remarkPlugins={remarkPlugins}\n remarkPluginsAhead={remarkPluginsAhead}\n showFootnotes={showFootnotes}\n streamAnimationGranularity={streamAnimationGranularity}\n streamSmoothingPreset={streamSmoothingPreset}\n variant={variant}\n >\n <Render\n enableStream={enableStream && delayedAnimated}\n reactMarkdownProps={reactMarkdownProps}\n >\n {children}\n </Render>\n </MarkdownProvider>\n </Typography>\n </PreviewGroup>\n );\n});\n\nMarkdown.displayName = 'Markdown';\n\nexport default Markdown;\n"],"mappings":";;;;;;;;;;;;;AAeA,MAAM,WAAW,MAAqB,UAAU;CAC9C,MAAM,EACJ,KACA,WAAW,IACX,WACA,OACA,uBACA,eACA,UACA,oBAAoB,OACpB,cAAc,MACd,gBAAgB,MAChB,oBACA,uBACA,mBACA,eAAe,MACf,gBACA,oBACA,WACA,cACA,WAAW,MAAM,YAAY,SAAS,KAAK,KAAA,GAC3C,iBAAiB,MAAM,YAAY,SAAS,MAAO,KAAA,GACnD,iBAAiB,MAAM,YAAY,SAAS,IAAI,KAAA,GAChD,UAAU,WACV,oBACA,aAAa,MAAM,YAAY,SAAS,MAAM,KAAA,GAC9C,eACA,eACA,oBACA,aAAa,EAAE,EACf,cACA,gBAAgB,MAChB,4BACA,uBACA,WACA,GAAG,SACD;CAEJ,MAAM,kBAAkB,mBAAmB,SAAS;CAUpD,MAAM,uBAAuB,eAAe,eAAe;CAC3D,MAAM,mBAAmB,eAAe,WAAW;CAEnD,MAAM,SAAS,aACZ,EACC,cACA,UACA,yBAC6E;EAE7E,MAAM,aAAa,oBADG,eAAe,mBAAmBA,kBACrC;GAAe,GAAI;GAAqB;GAAyB,CAAA;AACpF,SAAO,eAAe,aAAa,YAAY,EAAE,MAAM,UAAU,CAAC,GAAG;IAEvE,CAAC,aAAa,CACf;AAED,QACE,oBAAC,cAAD;EAAc,QAAQ;YACpB,oBAAC,YAAD;GACgB;GACd,WAAW,GAAG,SAAS;IAAE;IAAa;IAAS,CAAC,EAAE,UAAU;GAC5D,kBAAe;GACL;GACM;GACJ;GACI;GACX;GACE;GACQ;GACf,GAAI;aAEJ,oBAAC,kBAAD;IACa;IACX,UAAU;IACC;IACX,gBAAgB;IAChB,YAAY;IACW;IACJ;IACA;IACN;IACE;IACQ;IACR;IACK;IACL;IACK;IACL;IACa;IACL;IACd;cAET,oBAAC,QAAD;KACE,cAAc,gBAAgB;KACV;KAEnB;KACM,CAAA;IACQ,CAAA;GACR,CAAA;EACA,CAAA;EAEjB;AAEF,SAAS,cAAc"}
@@ -0,0 +1,82 @@
1
+ "use client";
2
+ import { visit } from "../../node_modules/unist-util-visit/lib/index.mjs";
3
+ import { useMemo } from "react";
4
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
+ import { defaultUrlTransform } from "react-markdown";
6
+ import { toJsxRuntime } from "hast-util-to-jsx-runtime";
7
+ import { urlAttributes } from "html-url-attributes";
8
+ import remarkParse from "remark-parse";
9
+ import remarkRehype from "remark-rehype";
10
+ import { unified } from "unified";
11
+ import { VFile } from "vfile";
12
+ //#region src/Markdown/SyntaxMarkdown/CachedMarkdown.tsx
13
+ const EMPTY_PLUGINS = [];
14
+ const DEFAULT_REMARK_REHYPE_OPTIONS = { allowDangerousHtml: true };
15
+ /**
16
+ * Render-equivalent of react-markdown's synchronous `<Markdown>` that keeps
17
+ * the unified processor across renders. react-markdown rebuilds the whole
18
+ * plugin chain on every render; the streaming tail block re-renders on every
19
+ * reveal commit (~20/s) with identical plugin identities, so caching the
20
+ * processor removes the per-commit chain construction. `post`/`transform`
21
+ * mirror react-markdown@10 — keep them in sync when upgrading it.
22
+ */
23
+ const CachedMarkdown = (options) => {
24
+ const { children, rehypePlugins, remarkPlugins, remarkRehypeOptions } = options;
25
+ const processor = useMemo(() => {
26
+ return unified().use(remarkParse).use(remarkPlugins || EMPTY_PLUGINS).use(remarkRehype, remarkRehypeOptions ? {
27
+ ...remarkRehypeOptions,
28
+ ...DEFAULT_REMARK_REHYPE_OPTIONS
29
+ } : DEFAULT_REMARK_REHYPE_OPTIONS).use(rehypePlugins || EMPTY_PLUGINS);
30
+ }, [
31
+ rehypePlugins,
32
+ remarkPlugins,
33
+ remarkRehypeOptions
34
+ ]);
35
+ const file = new VFile();
36
+ file.value = typeof children === "string" ? children : "";
37
+ return post(processor.runSync(processor.parse(file), file), options);
38
+ };
39
+ function post(tree, options) {
40
+ const { allowedElements, allowElement, components, disallowedElements, skipHtml, unwrapDisallowed } = options;
41
+ const urlTransform = options.urlTransform || defaultUrlTransform;
42
+ if (allowedElements && disallowedElements) throw new Error("Unexpected combined `allowedElements` and `disallowedElements`, expected one or the other");
43
+ const transform = (node, index, parent) => {
44
+ if (node.type === "raw" && parent && typeof index === "number") {
45
+ if (skipHtml) parent.children.splice(index, 1);
46
+ else parent.children[index] = {
47
+ type: "text",
48
+ value: node.value
49
+ };
50
+ return index;
51
+ }
52
+ if (node.type === "element") {
53
+ let key;
54
+ for (key in urlAttributes) if (Object.hasOwn(urlAttributes, key) && Object.hasOwn(node.properties, key)) {
55
+ const value = node.properties[key];
56
+ const test = urlAttributes[key];
57
+ if (test === null || test.includes(node.tagName)) node.properties[key] = urlTransform(String(value || ""), key, node);
58
+ }
59
+ let remove = allowedElements ? !allowedElements.includes(node.tagName) : disallowedElements ? disallowedElements.includes(node.tagName) : false;
60
+ if (!remove && allowElement && typeof index === "number") remove = !allowElement(node, index, parent);
61
+ if (remove && parent && typeof index === "number") {
62
+ if (unwrapDisallowed && node.children) parent.children.splice(index, 1, ...node.children);
63
+ else parent.children.splice(index, 1);
64
+ return index;
65
+ }
66
+ }
67
+ };
68
+ visit(tree, transform);
69
+ return toJsxRuntime(tree, {
70
+ Fragment: Fragment$1,
71
+ components,
72
+ ignoreInvalidStyle: true,
73
+ jsx,
74
+ jsxs,
75
+ passKeys: true,
76
+ passNode: true
77
+ });
78
+ }
79
+ //#endregion
80
+ export { CachedMarkdown };
81
+
82
+ //# sourceMappingURL=CachedMarkdown.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CachedMarkdown.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/CachedMarkdown.tsx"],"sourcesContent":["'use client';\n\nimport { type Nodes, type Root } from 'hast';\nimport { toJsxRuntime } from 'hast-util-to-jsx-runtime';\nimport { urlAttributes } from 'html-url-attributes';\nimport { useMemo } from 'react';\nimport { Fragment, jsx, jsxs } from 'react/jsx-runtime';\nimport { defaultUrlTransform, type Options } from 'react-markdown';\nimport remarkParse from 'remark-parse';\nimport remarkRehype from 'remark-rehype';\nimport { type PluggableList, unified } from 'unified';\nimport { type BuildVisitor, visit } from 'unist-util-visit';\nimport { VFile } from 'vfile';\n\nconst EMPTY_PLUGINS: PluggableList = [];\nconst DEFAULT_REMARK_REHYPE_OPTIONS = { allowDangerousHtml: true };\n\n/**\n * Render-equivalent of react-markdown's synchronous `<Markdown>` that keeps\n * the unified processor across renders. react-markdown rebuilds the whole\n * plugin chain on every render; the streaming tail block re-renders on every\n * reveal commit (~20/s) with identical plugin identities, so caching the\n * processor removes the per-commit chain construction. `post`/`transform`\n * mirror react-markdown@10 — keep them in sync when upgrading it.\n */\nexport const CachedMarkdown = (options: Options) => {\n const { children, rehypePlugins, remarkPlugins, remarkRehypeOptions } = options;\n\n const processor = useMemo(() => {\n return unified()\n .use(remarkParse)\n .use(remarkPlugins || EMPTY_PLUGINS)\n .use(\n remarkRehype,\n remarkRehypeOptions\n ? { ...remarkRehypeOptions, ...DEFAULT_REMARK_REHYPE_OPTIONS }\n : DEFAULT_REMARK_REHYPE_OPTIONS,\n )\n .use(rehypePlugins || EMPTY_PLUGINS);\n }, [rehypePlugins, remarkPlugins, remarkRehypeOptions]);\n\n const file = new VFile();\n file.value = typeof children === 'string' ? children : '';\n\n return post(processor.runSync(processor.parse(file), file) as Nodes, options);\n};\n\nfunction post(tree: Nodes, options: Options) {\n const {\n allowedElements,\n allowElement,\n components,\n disallowedElements,\n skipHtml,\n unwrapDisallowed,\n } = options;\n const urlTransform = options.urlTransform || defaultUrlTransform;\n\n if (allowedElements && disallowedElements) {\n throw new Error(\n 'Unexpected combined `allowedElements` and `disallowedElements`, expected one or the other',\n );\n }\n\n const transform: BuildVisitor<Root> = (node, index, parent) => {\n if (node.type === 'raw' && parent && typeof index === 'number') {\n if (skipHtml) {\n parent.children.splice(index, 1);\n } else {\n parent.children[index] = { type: 'text', value: node.value };\n }\n\n return index;\n }\n\n if (node.type === 'element') {\n let key: string;\n\n for (key in urlAttributes) {\n if (Object.hasOwn(urlAttributes, key) && Object.hasOwn(node.properties, key)) {\n const value = node.properties[key];\n const test = urlAttributes[key];\n if (test === null || test.includes(node.tagName)) {\n node.properties[key] = urlTransform(String(value || ''), key, node);\n }\n }\n }\n\n let remove = allowedElements\n ? !allowedElements.includes(node.tagName)\n : disallowedElements\n ? disallowedElements.includes(node.tagName)\n : false;\n\n if (!remove && allowElement && typeof index === 'number') {\n remove = !allowElement(node, index, parent);\n }\n\n if (remove && parent && typeof index === 'number') {\n if (unwrapDisallowed && node.children) {\n parent.children.splice(index, 1, ...node.children);\n } else {\n parent.children.splice(index, 1);\n }\n\n return index;\n }\n }\n };\n\n visit(tree as Root, transform);\n\n return toJsxRuntime(tree, {\n Fragment,\n components,\n ignoreInvalidStyle: true,\n jsx,\n jsxs,\n passKeys: true,\n passNode: true,\n });\n}\n"],"mappings":";;;;;;;;;;;;AAcA,MAAM,gBAA+B,EAAE;AACvC,MAAM,gCAAgC,EAAE,oBAAoB,MAAM;;;;;;;;;AAUlE,MAAa,kBAAkB,YAAqB;CAClD,MAAM,EAAE,UAAU,eAAe,eAAe,wBAAwB;CAExE,MAAM,YAAY,cAAc;AAC9B,SAAO,SAAS,CACb,IAAI,YAAY,CAChB,IAAI,iBAAiB,cAAc,CACnC,IACC,cACA,sBACI;GAAE,GAAG;GAAqB,GAAG;GAA+B,GAC5D,8BACL,CACA,IAAI,iBAAiB,cAAc;IACrC;EAAC;EAAe;EAAe;EAAoB,CAAC;CAEvD,MAAM,OAAO,IAAI,OAAO;AACxB,MAAK,QAAQ,OAAO,aAAa,WAAW,WAAW;AAEvD,QAAO,KAAK,UAAU,QAAQ,UAAU,MAAM,KAAK,EAAE,KAAK,EAAW,QAAQ;;AAG/E,SAAS,KAAK,MAAa,SAAkB;CAC3C,MAAM,EACJ,iBACA,cACA,YACA,oBACA,UACA,qBACE;CACJ,MAAM,eAAe,QAAQ,gBAAgB;AAE7C,KAAI,mBAAmB,mBACrB,OAAM,IAAI,MACR,4FACD;CAGH,MAAM,aAAiC,MAAM,OAAO,WAAW;AAC7D,MAAI,KAAK,SAAS,SAAS,UAAU,OAAO,UAAU,UAAU;AAC9D,OAAI,SACF,QAAO,SAAS,OAAO,OAAO,EAAE;OAEhC,QAAO,SAAS,SAAS;IAAE,MAAM;IAAQ,OAAO,KAAK;IAAO;AAG9D,UAAO;;AAGT,MAAI,KAAK,SAAS,WAAW;GAC3B,IAAI;AAEJ,QAAK,OAAO,cACV,KAAI,OAAO,OAAO,eAAe,IAAI,IAAI,OAAO,OAAO,KAAK,YAAY,IAAI,EAAE;IAC5E,MAAM,QAAQ,KAAK,WAAW;IAC9B,MAAM,OAAO,cAAc;AAC3B,QAAI,SAAS,QAAQ,KAAK,SAAS,KAAK,QAAQ,CAC9C,MAAK,WAAW,OAAO,aAAa,OAAO,SAAS,GAAG,EAAE,KAAK,KAAK;;GAKzE,IAAI,SAAS,kBACT,CAAC,gBAAgB,SAAS,KAAK,QAAQ,GACvC,qBACE,mBAAmB,SAAS,KAAK,QAAQ,GACzC;AAEN,OAAI,CAAC,UAAU,gBAAgB,OAAO,UAAU,SAC9C,UAAS,CAAC,aAAa,MAAM,OAAO,OAAO;AAG7C,OAAI,UAAU,UAAU,OAAO,UAAU,UAAU;AACjD,QAAI,oBAAoB,KAAK,SAC3B,QAAO,SAAS,OAAO,OAAO,GAAG,GAAG,KAAK,SAAS;QAElD,QAAO,SAAS,OAAO,OAAO,EAAE;AAGlC,WAAO;;;;AAKb,OAAM,MAAc,UAAU;AAE9B,QAAO,aAAa,MAAM;EACxB,UAAA;EACA;EACA,oBAAoB;EACpB;EACA;EACA,UAAU;EACV,UAAU;EACX,CAAC"}
@@ -1,30 +1,24 @@
1
1
  "use client";
2
2
  import { isDeepEqual } from "../../utils/isDeepEqual.mjs";
3
+ import { useStableValue } from "../../hooks/useStableValue.mjs";
3
4
  import { useMarkdownContext } from "../components/MarkdownProvider.mjs";
4
5
  import { useMarkdownComponents } from "../../hooks/useMarkdown/useMarkdownComponents.mjs";
5
6
  import { useMarkdownContent } from "../../hooks/useMarkdown/useMarkdownContent.mjs";
6
7
  import { useMarkdownRehypePlugins } from "../../hooks/useMarkdown/useMarkdownRehypePlugins.mjs";
7
8
  import { useMarkdownRemarkPlugins } from "../../hooks/useMarkdown/useMarkdownRemarkPlugins.mjs";
9
+ import { getNow } from "../../utils/getNow.mjs";
8
10
  import { rehypeStreamAnimated } from "../plugins/rehypeStreamAnimated.mjs";
9
11
  import { useStreamdownProfiler } from "../streamProfiler/StreamdownProfilerProvider.mjs";
12
+ import { CachedMarkdown } from "./CachedMarkdown.mjs";
10
13
  import { resolveBlockAnimationMeta } from "./streamAnimationMeta.mjs";
11
14
  import { styles } from "./style.mjs";
12
- import { useSmoothStreamContent } from "./useSmoothStreamContent.mjs";
15
+ import { countChars, useSmoothStreamContent } from "./useSmoothStreamContent.mjs";
13
16
  import { useStreamQueue } from "./useStreamQueue.mjs";
14
17
  import { Profiler, createElement, memo, useCallback, useEffect, useId, useMemo, useRef } from "react";
15
18
  import { jsx } from "react/jsx-runtime";
16
- import Markdown from "react-markdown";
17
19
  import { marked } from "marked";
18
20
  import remend from "remend";
19
21
  //#region src/Markdown/SyntaxMarkdown/StreamdownRender.tsx
20
- const STREAM_FADE_DURATION = 280;
21
- const REVEALED_STREAM_PLUGIN = [rehypeStreamAnimated, { revealed: true }];
22
- function countChars(text) {
23
- return [...text].length;
24
- }
25
- function getNow() {
26
- return typeof performance === "undefined" ? Date.now() : performance.now();
27
- }
28
22
  const isSamePlugin = (prevPlugin, nextPlugin) => {
29
23
  const prevTuple = Array.isArray(prevPlugin) ? prevPlugin : [prevPlugin];
30
24
  const nextTuple = Array.isArray(nextPlugin) ? nextPlugin : [nextPlugin];
@@ -45,21 +39,90 @@ const useStablePlugins = (plugins) => {
45
39
  return stableRef.current;
46
40
  };
47
41
  const StreamdownBlock = memo(({ children, ...rest }) => {
48
- return /* @__PURE__ */ jsx(Markdown, {
42
+ return /* @__PURE__ */ jsx(CachedMarkdown, {
49
43
  ...rest,
50
44
  children
51
45
  });
52
46
  }, (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.components === nextProps.components && isSamePlugins(prevProps.rehypePlugins, nextProps.rehypePlugins) && isSamePlugins(prevProps.remarkPlugins, nextProps.remarkPlugins));
53
47
  StreamdownBlock.displayName = "StreamdownBlock";
54
- const StreamdownRender = memo(({ children, ...rest }) => {
55
- const { streamSmoothingPreset = "balanced" } = useMarkdownContext();
48
+ const MIN_STREAM_CHAR_PACE_MS = 2;
49
+ const MAX_REVEAL_GAP_MS = 160;
50
+ const updateBlockAnimation = ({ blocks, charDelay, getBlockState, pluginsCache, renderNow, revealClock, runtimes }) => {
51
+ const blockAnimationMeta = /* @__PURE__ */ new Map();
52
+ const alive = /* @__PURE__ */ new Set();
53
+ let revealedNewChars = false;
54
+ for (const [index, block] of blocks.entries()) {
55
+ alive.add(block.startOffset);
56
+ const state = getBlockState(index);
57
+ if (state === "queued") continue;
58
+ let runtime = runtimes.get(block.startOffset);
59
+ if (!runtime) {
60
+ runtime = {
61
+ births: [],
62
+ charCount: 0,
63
+ rawLength: -1,
64
+ settled: false,
65
+ styles: []
66
+ };
67
+ runtimes.set(block.startOffset, runtime);
68
+ }
69
+ if (runtime.rawLength !== block.content.length) {
70
+ runtime.charCount = countChars(block.content);
71
+ runtime.rawLength = block.content.length;
72
+ }
73
+ const blockCharCount = runtime.charCount;
74
+ const births = runtime.births;
75
+ if (births.length > blockCharCount) {
76
+ births.length = blockCharCount;
77
+ runtime.styles.length = blockCharCount;
78
+ }
79
+ if (births.length < blockCharCount) {
80
+ const newChars = blockCharCount - births.length;
81
+ let pace = charDelay;
82
+ let cap = renderNow + 180;
83
+ if (state === "streaming") {
84
+ revealedNewChars = true;
85
+ const gapMs = Math.min(Math.max(renderNow - revealClock.lastTs, 16), MAX_REVEAL_GAP_MS);
86
+ pace = Math.min(charDelay, Math.max(gapMs / newChars, MIN_STREAM_CHAR_PACE_MS));
87
+ cap = renderNow + gapMs + 180;
88
+ }
89
+ for (let i = births.length; i < blockCharCount; i++) {
90
+ const chained = (i > 0 ? births[i - 1] : renderNow - pace) + pace;
91
+ births.push(Math.min(cap, Math.max(chained, renderNow)));
92
+ }
93
+ }
94
+ let meta;
95
+ if (runtime.settled) meta = {
96
+ charDelay: runtime.charDelay ?? charDelay,
97
+ settled: true
98
+ };
99
+ else {
100
+ meta = resolveBlockAnimationMeta({
101
+ currentCharDelay: charDelay,
102
+ fadeDuration: 180,
103
+ lastElapsedMs: renderNow - (births.length > 0 ? births.at(-1) ?? renderNow : renderNow),
104
+ previousCharDelay: runtime.charDelay,
105
+ state
106
+ });
107
+ runtime.settled = meta.settled;
108
+ }
109
+ runtime.charDelay = meta.charDelay;
110
+ blockAnimationMeta.set(block.startOffset, meta);
111
+ }
112
+ if (revealedNewChars) revealClock.lastTs = renderNow;
113
+ for (const key of runtimes.keys()) if (!alive.has(key)) {
114
+ runtimes.delete(key);
115
+ pluginsCache.delete(key);
116
+ }
117
+ return blockAnimationMeta;
118
+ };
119
+ const StreamdownBlocks = memo(({ content: smoothedContent, markdownOptions: rest }) => {
120
+ const { streamAnimationGranularity = "char" } = useMarkdownContext();
56
121
  const profiler = useStreamdownProfiler();
57
- const escapedContent = useMarkdownContent(children || "");
58
122
  const components = useMarkdownComponents();
59
123
  const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());
60
124
  const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());
61
125
  const generatedId = useId();
62
- const smoothedContent = useSmoothStreamContent(typeof escapedContent === "string" ? escapedContent : "", { preset: streamSmoothingPreset });
63
126
  const processedContentResult = useMemo(() => {
64
127
  const start = profiler ? getNow() : 0;
65
128
  const value = remend(smoothedContent);
@@ -88,43 +151,21 @@ const StreamdownRender = memo(({ children, ...rest }) => {
88
151
  }, [processedContent, profiler]);
89
152
  const blocks = blocksResult.value;
90
153
  const { getBlockState, charDelay } = useStreamQueue(blocks);
91
- const blockCharDelayRef = useRef(/* @__PURE__ */ new Map());
92
- const blockBirthsRef = useRef(/* @__PURE__ */ new Map());
154
+ const blockRuntimesRef = useRef(/* @__PURE__ */ new Map());
155
+ const blockPluginsRef = useRef(/* @__PURE__ */ new Map());
156
+ const revealClockRef = useRef({ lastTs: 0 });
93
157
  const renderNow = getNow();
94
- const birthsResult = useMemo(() => {
95
- const start = profiler ? getNow() : 0;
96
- const nextBirths = /* @__PURE__ */ new Map();
97
- const prevBirths = blockBirthsRef.current;
98
- for (const [index, block] of blocks.entries()) {
99
- if (getBlockState(index) === "queued") continue;
100
- const blockCharCount = countChars(block.content);
101
- const prev = prevBirths.get(block.startOffset);
102
- let arr;
103
- if (prev && prev.length === blockCharCount) arr = prev;
104
- else if (prev && prev.length > blockCharCount) arr = prev.slice(0, blockCharCount);
105
- else {
106
- arr = prev ? prev.slice() : [];
107
- const startIdx = arr.length;
108
- const cap = renderNow + STREAM_FADE_DURATION;
109
- for (let i = startIdx; i < blockCharCount; i++) {
110
- const chained = (i > 0 ? arr[i - 1] : renderNow - charDelay) + charDelay;
111
- arr.push(Math.min(cap, Math.max(chained, renderNow)));
112
- }
113
- }
114
- nextBirths.set(block.startOffset, arr);
115
- }
116
- return {
117
- durationMs: profiler ? getNow() - start : 0,
118
- value: nextBirths
119
- };
120
- }, [
158
+ const animationStart = profiler ? getNow() : 0;
159
+ const blockAnimationMeta = updateBlockAnimation({
121
160
  blocks,
122
161
  charDelay,
123
162
  getBlockState,
124
- profiler,
125
- renderNow
126
- ]);
127
- const birthsForRender = birthsResult.value;
163
+ pluginsCache: blockPluginsRef.current,
164
+ renderNow,
165
+ revealClock: revealClockRef.current,
166
+ runtimes: blockRuntimesRef.current
167
+ });
168
+ const blockAnimationDurationMs = profiler ? getNow() - animationStart : 0;
128
169
  useEffect(() => {
129
170
  if (!profiler) return;
130
171
  profiler.recordCalculation({
@@ -154,48 +195,35 @@ const StreamdownRender = memo(({ children, ...rest }) => {
154
195
  useEffect(() => {
155
196
  if (!profiler) return;
156
197
  profiler.recordCalculation({
157
- durationMs: birthsResult.durationMs,
198
+ durationMs: blockAnimationDurationMs,
158
199
  itemCount: blocks.length,
159
200
  name: "block-births",
160
201
  textLength: processedContent.length
161
202
  });
162
203
  }, [
163
- birthsResult.durationMs,
204
+ blockAnimationDurationMs,
164
205
  blocks.length,
165
206
  processedContent.length,
166
207
  profiler
167
208
  ]);
168
- const blockAnimationMetaResult = useMemo(() => {
169
- const nextBlockCharDelay = /* @__PURE__ */ new Map();
170
- const blockAnimationMeta = /* @__PURE__ */ new Map();
171
- for (const [index, block] of blocks.entries()) {
172
- const state = getBlockState(index);
173
- const births = birthsForRender.get(block.startOffset);
174
- const animationMeta = resolveBlockAnimationMeta({
175
- currentCharDelay: charDelay,
176
- fadeDuration: STREAM_FADE_DURATION,
177
- lastElapsedMs: renderNow - (births && births.length > 0 ? births.at(-1) ?? renderNow : renderNow),
178
- previousCharDelay: blockCharDelayRef.current.get(block.startOffset),
179
- state
180
- });
181
- nextBlockCharDelay.set(block.startOffset, animationMeta.charDelay);
182
- blockAnimationMeta.set(block.startOffset, animationMeta);
183
- }
184
- return {
185
- blockAnimationMeta,
186
- blockCharDelay: nextBlockCharDelay
187
- };
188
- }, [
189
- birthsForRender,
190
- blocks,
191
- charDelay,
192
- getBlockState,
193
- renderNow
194
- ]);
195
- useEffect(() => {
196
- blockCharDelayRef.current = blockAnimationMetaResult.blockCharDelay;
197
- blockBirthsRef.current = birthsForRender;
198
- }, [birthsForRender, blockAnimationMetaResult.blockCharDelay]);
209
+ const resolveBlockPlugins = (startOffset, settled) => {
210
+ if (settled) return baseRehypePlugins;
211
+ const cache = blockPluginsRef.current;
212
+ const entry = cache.get(startOffset);
213
+ if (entry && entry.base === baseRehypePlugins && entry.granularity === streamAnimationGranularity) return entry.value;
214
+ const runtime = blockRuntimesRef.current.get(startOffset);
215
+ const value = [...baseRehypePlugins, [rehypeStreamAnimated, {
216
+ fadeDuration: 180,
217
+ granularity: streamAnimationGranularity,
218
+ runtime
219
+ }]];
220
+ cache.set(startOffset, {
221
+ base: baseRehypePlugins,
222
+ granularity: streamAnimationGranularity,
223
+ value
224
+ });
225
+ return value;
226
+ };
199
227
  const handleRootRender = useCallback((_, phase, actualDuration, baseDuration) => {
200
228
  profiler?.recordRootCommit({
201
229
  actualDuration,
@@ -233,23 +261,10 @@ const StreamdownRender = memo(({ children, ...rest }) => {
233
261
  const content = /* @__PURE__ */ jsx("div", {
234
262
  className: styles.animated,
235
263
  children: blocks.map((block, index) => {
236
- if (getBlockState(index) === "queued") return null;
237
- const animationMeta = blockAnimationMetaResult.blockAnimationMeta.get(block.startOffset);
264
+ const animationMeta = blockAnimationMeta.get(block.startOffset);
238
265
  if (!animationMeta) return null;
239
- const births = birthsForRender.get(block.startOffset);
240
- const plugins = animationMeta.settled ? [...baseRehypePlugins, REVEALED_STREAM_PLUGIN] : [...baseRehypePlugins, [rehypeStreamAnimated, {
241
- births,
242
- fadeDuration: STREAM_FADE_DURATION,
243
- nowMs: renderNow
244
- }]];
266
+ const plugins = resolveBlockPlugins(block.startOffset, animationMeta.settled);
245
267
  const key = `${generatedId}-${block.startOffset}`;
246
- const blockNode = /* @__PURE__ */ jsx(StreamdownBlock, {
247
- ...rest,
248
- components,
249
- rehypePlugins: plugins,
250
- remarkPlugins,
251
- children: block.content
252
- });
253
268
  if (!profiler) return /* @__PURE__ */ createElement(StreamdownBlock, {
254
269
  ...rest,
255
270
  components,
@@ -260,7 +275,13 @@ const StreamdownRender = memo(({ children, ...rest }) => {
260
275
  return /* @__PURE__ */ jsx(Profiler, {
261
276
  id: `streamdown-block:${index}:${block.startOffset}`,
262
277
  onRender: handleBlockRender,
263
- children: blockNode
278
+ children: /* @__PURE__ */ jsx(StreamdownBlock, {
279
+ ...rest,
280
+ components,
281
+ rehypePlugins: plugins,
282
+ remarkPlugins,
283
+ children: block.content
284
+ })
264
285
  }, key);
265
286
  })
266
287
  });
@@ -271,6 +292,15 @@ const StreamdownRender = memo(({ children, ...rest }) => {
271
292
  children: content
272
293
  });
273
294
  });
295
+ StreamdownBlocks.displayName = "StreamdownBlocks";
296
+ const StreamdownRender = memo(({ children, ...rest }) => {
297
+ const { streamSmoothingPreset = "balanced" } = useMarkdownContext();
298
+ const escapedContent = useMarkdownContent(children || "");
299
+ return /* @__PURE__ */ jsx(StreamdownBlocks, {
300
+ content: useSmoothStreamContent(typeof escapedContent === "string" ? escapedContent : "", { preset: streamSmoothingPreset }),
301
+ markdownOptions: useStableValue(rest)
302
+ });
303
+ });
274
304
  StreamdownRender.displayName = "StreamdownRender";
275
305
  //#endregion
276
306
  export { StreamdownRender as default };
@@ -1 +1 @@
1
- {"version":3,"file":"StreamdownRender.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/StreamdownRender.tsx"],"sourcesContent":["'use client';\n\nimport { marked } from 'marked';\nimport {\n memo,\n Profiler,\n type ProfilerOnRenderCallback,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n} from 'react';\nimport Markdown, { type Options } from 'react-markdown';\nimport remend from 'remend';\nimport type { Pluggable, PluggableList } from 'unified';\n\nimport {\n useMarkdownComponents,\n useMarkdownContent,\n useMarkdownRehypePlugins,\n useMarkdownRemarkPlugins,\n} from '@/hooks/useMarkdown';\nimport { useMarkdownContext } from '@/Markdown/components/MarkdownProvider';\nimport { rehypeStreamAnimated } from '@/Markdown/plugins/rehypeStreamAnimated';\nimport { useStreamdownProfiler } from '@/Markdown/streamProfiler';\nimport { isDeepEqual } from '@/utils/isDeepEqual';\n\nimport { resolveBlockAnimationMeta } from './streamAnimationMeta';\nimport { styles } from './style';\nimport { useSmoothStreamContent } from './useSmoothStreamContent';\nimport { type BlockInfo, useStreamQueue } from './useStreamQueue';\n\nconst STREAM_FADE_DURATION = 280;\nconst REVEALED_STREAM_PLUGIN: Pluggable = [rehypeStreamAnimated, { revealed: true }];\n\nfunction countChars(text: string): number {\n return [...text].length;\n}\n\nfunction getNow(): number {\n return typeof performance === 'undefined' ? Date.now() : performance.now();\n}\n\nconst isSamePlugin = (prevPlugin: Pluggable, nextPlugin: Pluggable): boolean => {\n const prevTuple = Array.isArray(prevPlugin) ? prevPlugin : [prevPlugin];\n const nextTuple = Array.isArray(nextPlugin) ? nextPlugin : [nextPlugin];\n\n if (prevTuple.length !== nextTuple.length) return false;\n if (prevTuple[0] !== nextTuple[0]) return false;\n\n return isDeepEqual(prevTuple.slice(1), nextTuple.slice(1));\n};\n\nconst isSamePlugins = (\n prevPlugins?: PluggableList | null,\n nextPlugins?: PluggableList | null,\n): boolean => {\n if (prevPlugins === nextPlugins) return true;\n if (!prevPlugins || !nextPlugins) return !prevPlugins && !nextPlugins;\n if (prevPlugins.length !== nextPlugins.length) return false;\n\n for (let i = 0; i < prevPlugins.length; i++) {\n if (!isSamePlugin(prevPlugins[i], nextPlugins[i])) return false;\n }\n\n return true;\n};\n\nconst useStablePlugins = (plugins: PluggableList): PluggableList => {\n const stableRef = useRef<PluggableList>(plugins);\n\n if (!isSamePlugins(stableRef.current, plugins)) {\n stableRef.current = plugins;\n }\n\n return stableRef.current;\n};\n\nconst StreamdownBlock = memo<Options>(\n ({ children, ...rest }) => {\n return <Markdown {...rest}>{children}</Markdown>;\n },\n (prevProps, nextProps) =>\n prevProps.children === nextProps.children &&\n prevProps.components === nextProps.components &&\n isSamePlugins(prevProps.rehypePlugins, nextProps.rehypePlugins) &&\n isSamePlugins(prevProps.remarkPlugins, nextProps.remarkPlugins),\n);\n\nStreamdownBlock.displayName = 'StreamdownBlock';\n\nexport const StreamdownRender = memo<Options>(({ children, ...rest }) => {\n const { streamSmoothingPreset = 'balanced' } = useMarkdownContext();\n const profiler = useStreamdownProfiler();\n const escapedContent = useMarkdownContent(children || '');\n const components = useMarkdownComponents();\n const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());\n const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());\n const generatedId = useId();\n const smoothedContent = useSmoothStreamContent(\n typeof escapedContent === 'string' ? escapedContent : '',\n { preset: streamSmoothingPreset },\n );\n\n const processedContentResult = useMemo(() => {\n const start = profiler ? getNow() : 0;\n const value = remend(smoothedContent);\n\n return {\n durationMs: profiler ? getNow() - start : 0,\n value,\n };\n }, [profiler, smoothedContent]);\n const processedContent = processedContentResult.value;\n\n const blocksResult = useMemo(() => {\n const start = profiler ? getNow() : 0;\n const tokens = marked.lexer(processedContent);\n let offset = 0;\n\n const value = tokens.map((token) => {\n const block = { content: token.raw, startOffset: offset };\n offset += token.raw.length;\n return block;\n });\n\n return {\n durationMs: profiler ? getNow() - start : 0,\n value,\n };\n }, [processedContent, profiler]);\n const blocks: BlockInfo[] = blocksResult.value;\n\n const { getBlockState, charDelay } = useStreamQueue(blocks);\n const blockCharDelayRef = useRef<Map<number, number>>(new Map());\n const blockBirthsRef = useRef<Map<number, number[]>>(new Map());\n\n const renderNow = getNow();\n\n const birthsResult = useMemo(() => {\n const start = profiler ? getNow() : 0;\n const nextBirths = new Map<number, number[]>();\n const prevBirths = blockBirthsRef.current;\n\n for (const [index, block] of blocks.entries()) {\n const state = getBlockState(index);\n // Queued blocks are not rendered. Defer birth assignment so that\n // when the block later transitions to animating/streaming, its\n // chars start fading from that moment instead of having already\n // \"aged out\" of the fade window.\n if (state === 'queued') continue;\n\n const blockCharCount = countChars(block.content);\n const prev = prevBirths.get(block.startOffset);\n let arr: number[];\n\n if (prev && prev.length === blockCharCount) {\n arr = prev;\n } else if (prev && prev.length > blockCharCount) {\n // Block content shrunk (stream restart or upstream rewrite).\n arr = prev.slice(0, blockCharCount);\n } else {\n arr = prev ? prev.slice() : [];\n const startIdx = arr.length;\n // Chain each new char monotonically after the previous one so fades\n // never race out of order. Cap how far the fade queue can run ahead\n // of renderNow to prevent stream-faster-than-fade producing seconds\n // of invisible backlog at the tail.\n const cap = renderNow + STREAM_FADE_DURATION;\n for (let i = startIdx; i < blockCharCount; i++) {\n const prevBirth = i > 0 ? (arr[i - 1] as number) : renderNow - charDelay;\n const chained = prevBirth + charDelay;\n arr.push(Math.min(cap, Math.max(chained, renderNow)));\n }\n }\n\n nextBirths.set(block.startOffset, arr);\n }\n\n return {\n durationMs: profiler ? getNow() - start : 0,\n value: nextBirths,\n };\n }, [blocks, charDelay, getBlockState, profiler, renderNow]);\n const birthsForRender = birthsResult.value;\n\n useEffect(() => {\n if (!profiler) return;\n\n profiler.recordCalculation({\n durationMs: processedContentResult.durationMs,\n name: 'content-normalize',\n textLength: processedContent.length,\n });\n }, [processedContent.length, processedContentResult.durationMs, profiler]);\n\n useEffect(() => {\n if (!profiler) return;\n\n profiler.recordCalculation({\n durationMs: blocksResult.durationMs,\n itemCount: blocks.length,\n name: 'block-lex',\n textLength: processedContent.length,\n });\n }, [blocks.length, blocksResult.durationMs, processedContent.length, profiler]);\n\n useEffect(() => {\n if (!profiler) return;\n\n profiler.recordCalculation({\n durationMs: birthsResult.durationMs,\n itemCount: blocks.length,\n name: 'block-births',\n textLength: processedContent.length,\n });\n }, [birthsResult.durationMs, blocks.length, processedContent.length, profiler]);\n\n const blockAnimationMetaResult = useMemo(() => {\n const nextBlockCharDelay = new Map<number, number>();\n const blockAnimationMeta = new Map<number, ReturnType<typeof resolveBlockAnimationMeta>>();\n\n for (const [index, block] of blocks.entries()) {\n const state = getBlockState(index);\n const births = birthsForRender.get(block.startOffset);\n const lastBirthTs = births && births.length > 0 ? (births.at(-1) ?? renderNow) : renderNow;\n const lastElapsedMs = renderNow - lastBirthTs;\n const animationMeta = resolveBlockAnimationMeta({\n currentCharDelay: charDelay,\n fadeDuration: STREAM_FADE_DURATION,\n lastElapsedMs,\n previousCharDelay: blockCharDelayRef.current.get(block.startOffset),\n state,\n });\n\n nextBlockCharDelay.set(block.startOffset, animationMeta.charDelay);\n blockAnimationMeta.set(block.startOffset, animationMeta);\n }\n\n return {\n blockAnimationMeta,\n blockCharDelay: nextBlockCharDelay,\n };\n }, [birthsForRender, blocks, charDelay, getBlockState, renderNow]);\n\n useEffect(() => {\n blockCharDelayRef.current = blockAnimationMetaResult.blockCharDelay;\n blockBirthsRef.current = birthsForRender;\n }, [birthsForRender, blockAnimationMetaResult.blockCharDelay]);\n\n const handleRootRender = useCallback<ProfilerOnRenderCallback>(\n (_, phase, actualDuration, baseDuration) => {\n profiler?.recordRootCommit({\n actualDuration,\n baseDuration,\n blockCount: blocks.length,\n phase,\n textLength: processedContent.length,\n });\n },\n [blocks.length, processedContent.length, profiler],\n );\n\n const handleBlockRender = useCallback<ProfilerOnRenderCallback>(\n (id, phase, actualDuration, baseDuration) => {\n if (!profiler) return;\n\n const [, indexText, offsetText] = id.split(':');\n const blockIndex = Number(indexText);\n\n if (!Number.isFinite(blockIndex)) return;\n\n const block = blocks[blockIndex];\n if (!block) return;\n\n profiler.recordBlockCommit({\n actualDuration,\n baseDuration,\n blockChars: countChars(block.content),\n blockIndex,\n blockKey: offsetText ?? String(block.startOffset),\n phase,\n state: getBlockState(blockIndex),\n });\n },\n [blocks, getBlockState, profiler],\n );\n\n const content = (\n <div className={styles.animated}>\n {blocks.map((block, index) => {\n const state = getBlockState(index);\n if (state === 'queued') return null;\n const animationMeta = blockAnimationMetaResult.blockAnimationMeta.get(block.startOffset);\n if (!animationMeta) return null;\n\n const births = birthsForRender.get(block.startOffset);\n const plugins: Pluggable[] = animationMeta.settled\n ? [...baseRehypePlugins, REVEALED_STREAM_PLUGIN]\n : [\n ...baseRehypePlugins,\n [\n rehypeStreamAnimated,\n {\n births,\n fadeDuration: STREAM_FADE_DURATION,\n nowMs: renderNow,\n },\n ],\n ];\n\n const key = `${generatedId}-${block.startOffset}`;\n const blockNode = (\n <StreamdownBlock\n {...rest}\n components={components}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n );\n\n if (!profiler) {\n return (\n <StreamdownBlock\n {...rest}\n components={components}\n key={key}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n );\n }\n\n return (\n <Profiler\n id={`streamdown-block:${index}:${block.startOffset}`}\n key={key}\n onRender={handleBlockRender}\n >\n {blockNode}\n </Profiler>\n );\n })}\n </div>\n );\n\n if (!profiler) return content;\n\n return (\n <Profiler id={'streamdown-root'} onRender={handleRootRender}>\n {content}\n </Profiler>\n );\n});\n\nStreamdownRender.displayName = 'StreamdownRender';\n\nexport default StreamdownRender;\n"],"mappings":"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"}
1
+ {"version":3,"file":"StreamdownRender.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/StreamdownRender.tsx"],"sourcesContent":["'use client';\n\nimport { marked } from 'marked';\nimport {\n memo,\n Profiler,\n type ProfilerOnRenderCallback,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n} from 'react';\nimport { type Options } from 'react-markdown';\nimport remend from 'remend';\nimport type { Pluggable, PluggableList } from 'unified';\n\nimport {\n useMarkdownComponents,\n useMarkdownContent,\n useMarkdownRehypePlugins,\n useMarkdownRemarkPlugins,\n} from '@/hooks/useMarkdown';\nimport { useStableValue } from '@/hooks/useStableValue';\nimport { useMarkdownContext } from '@/Markdown/components/MarkdownProvider';\nimport {\n rehypeStreamAnimated,\n type StreamAnimatedRuntime,\n} from '@/Markdown/plugins/rehypeStreamAnimated';\nimport { useStreamdownProfiler } from '@/Markdown/streamProfiler';\nimport { type StreamAnimationGranularity } from '@/Markdown/type';\nimport { getNow } from '@/utils/getNow';\nimport { isDeepEqual } from '@/utils/isDeepEqual';\n\nimport { CachedMarkdown } from './CachedMarkdown';\nimport { type BlockAnimationMeta, resolveBlockAnimationMeta } from './streamAnimationMeta';\nimport { STREAM_FADE_DURATION, styles } from './style';\nimport { countChars, useSmoothStreamContent } from './useSmoothStreamContent';\nimport { type BlockInfo, type BlockState, useStreamQueue } from './useStreamQueue';\n\nconst isSamePlugin = (prevPlugin: Pluggable, nextPlugin: Pluggable): boolean => {\n const prevTuple = Array.isArray(prevPlugin) ? prevPlugin : [prevPlugin];\n const nextTuple = Array.isArray(nextPlugin) ? nextPlugin : [nextPlugin];\n\n if (prevTuple.length !== nextTuple.length) return false;\n if (prevTuple[0] !== nextTuple[0]) return false;\n\n return isDeepEqual(prevTuple.slice(1), nextTuple.slice(1));\n};\n\nconst isSamePlugins = (\n prevPlugins?: PluggableList | null,\n nextPlugins?: PluggableList | null,\n): boolean => {\n if (prevPlugins === nextPlugins) return true;\n if (!prevPlugins || !nextPlugins) return !prevPlugins && !nextPlugins;\n if (prevPlugins.length !== nextPlugins.length) return false;\n\n for (let i = 0; i < prevPlugins.length; i++) {\n if (!isSamePlugin(prevPlugins[i], nextPlugins[i])) return false;\n }\n\n return true;\n};\n\nconst useStablePlugins = (plugins: PluggableList): PluggableList => {\n const stableRef = useRef<PluggableList>(plugins);\n\n if (!isSamePlugins(stableRef.current, plugins)) {\n stableRef.current = plugins;\n }\n\n return stableRef.current;\n};\n\nconst StreamdownBlock = memo<Options>(\n ({ children, ...rest }) => {\n return <CachedMarkdown {...rest}>{children}</CachedMarkdown>;\n },\n (prevProps, nextProps) =>\n prevProps.children === nextProps.children &&\n prevProps.components === nextProps.components &&\n isSamePlugins(prevProps.rehypePlugins, nextProps.rehypePlugins) &&\n isSamePlugins(prevProps.remarkPlugins, nextProps.remarkPlugins),\n);\n\nStreamdownBlock.displayName = 'StreamdownBlock';\n\ninterface BlockRuntime extends StreamAnimatedRuntime {\n charCount: number;\n charDelay?: number;\n rawLength: number;\n settled: boolean;\n}\n\ninterface BlockPluginsCacheEntry {\n base: PluggableList;\n granularity: StreamAnimationGranularity;\n value: PluggableList;\n}\n\ninterface UpdateBlockAnimationArgs {\n blocks: BlockInfo[];\n charDelay: number;\n getBlockState: (index: number) => BlockState;\n pluginsCache: Map<number, BlockPluginsCacheEntry>;\n renderNow: number;\n revealClock: { lastTs: number };\n runtimes: Map<number, BlockRuntime>;\n}\n\nconst MIN_STREAM_CHAR_PACE_MS = 2;\nconst MAX_REVEAL_GAP_MS = 160;\n\n// Runs in the render phase: extends each visible block's birth timeline in\n// place and resolves its animation meta in one pass. Mutations are\n// idempotent for a given block content/length, so discarded or StrictMode\n// double renders re-derive the same state.\nconst updateBlockAnimation = ({\n blocks,\n charDelay,\n getBlockState,\n pluginsCache,\n renderNow,\n revealClock,\n runtimes,\n}: UpdateBlockAnimationArgs): Map<number, BlockAnimationMeta> => {\n const blockAnimationMeta = new Map<number, BlockAnimationMeta>();\n const alive = new Set<number>();\n let revealedNewChars = false;\n\n for (const [index, block] of blocks.entries()) {\n alive.add(block.startOffset);\n\n // Queued blocks are not rendered. Defer birth assignment so that\n // when the block later transitions to animating/streaming, its\n // chars start fading from that moment instead of having already\n // \"aged out\" of the fade window.\n const state = getBlockState(index);\n if (state === 'queued') continue;\n\n let runtime = runtimes.get(block.startOffset);\n if (!runtime) {\n runtime = { births: [], charCount: 0, rawLength: -1, settled: false, styles: [] };\n runtimes.set(block.startOffset, runtime);\n }\n\n if (runtime.rawLength !== block.content.length) {\n runtime.charCount = countChars(block.content);\n runtime.rawLength = block.content.length;\n }\n\n const blockCharCount = runtime.charCount;\n const births = runtime.births;\n\n if (births.length > blockCharCount) {\n // Block content shrunk (stream restart or upstream rewrite).\n births.length = blockCharCount;\n runtime.styles.length = blockCharCount;\n }\n\n if (births.length < blockCharCount) {\n // Chain each new char monotonically after the previous one so fades\n // never race out of order. Cap how far the fade queue can run ahead\n // of renderNow to prevent stream-faster-than-fade producing seconds\n // of invisible backlog at the tail.\n //\n // The streaming tail paces its stagger from the observed reveal-commit\n // gap instead of the queue's fixed charDelay: a commit's chars are\n // spread to land exactly until the next commit arrives, so the flow\n // stays per-char continuous no matter how far apart the throttled\n // commits are.\n const newChars = blockCharCount - births.length;\n let pace = charDelay;\n let cap = renderNow + STREAM_FADE_DURATION;\n if (state === 'streaming') {\n revealedNewChars = true;\n const gapMs = Math.min(Math.max(renderNow - revealClock.lastTs, 16), MAX_REVEAL_GAP_MS);\n pace = Math.min(charDelay, Math.max(gapMs / newChars, MIN_STREAM_CHAR_PACE_MS));\n cap = renderNow + gapMs + STREAM_FADE_DURATION;\n }\n for (let i = births.length; i < blockCharCount; i++) {\n const prevBirth = i > 0 ? births[i - 1] : renderNow - pace;\n const chained = prevBirth + pace;\n births.push(Math.min(cap, Math.max(chained, renderNow)));\n }\n }\n\n let meta: BlockAnimationMeta;\n if (runtime.settled) {\n // Settled is monotone: a revealed block's births are frozen, so once\n // its last fade completed it stays settled until the runtime is\n // pruned by a stream restart.\n meta = { charDelay: runtime.charDelay ?? charDelay, settled: true };\n } else {\n const lastBirthTs = births.length > 0 ? (births.at(-1) ?? renderNow) : renderNow;\n meta = resolveBlockAnimationMeta({\n currentCharDelay: charDelay,\n fadeDuration: STREAM_FADE_DURATION,\n lastElapsedMs: renderNow - lastBirthTs,\n previousCharDelay: runtime.charDelay,\n state,\n });\n runtime.settled = meta.settled;\n }\n runtime.charDelay = meta.charDelay;\n\n blockAnimationMeta.set(block.startOffset, meta);\n }\n\n if (revealedNewChars) {\n revealClock.lastTs = renderNow;\n }\n\n for (const key of runtimes.keys()) {\n if (!alive.has(key)) {\n runtimes.delete(key);\n pluginsCache.delete(key);\n }\n }\n\n return blockAnimationMeta;\n};\n\ninterface StreamdownBlocksProps {\n content: string;\n markdownOptions: Omit<Options, 'children'>;\n}\n\nconst StreamdownBlocks = memo<StreamdownBlocksProps>(\n ({ content: smoothedContent, markdownOptions: rest }) => {\n const { streamAnimationGranularity = 'char' } = useMarkdownContext();\n const profiler = useStreamdownProfiler();\n const components = useMarkdownComponents();\n const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());\n const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());\n const generatedId = useId();\n\n const processedContentResult = useMemo(() => {\n const start = profiler ? getNow() : 0;\n const value = remend(smoothedContent);\n\n return {\n durationMs: profiler ? getNow() - start : 0,\n value,\n };\n }, [profiler, smoothedContent]);\n const processedContent = processedContentResult.value;\n\n const blocksResult = useMemo(() => {\n const start = profiler ? getNow() : 0;\n const tokens = marked.lexer(processedContent);\n let offset = 0;\n\n const value = tokens.map((token) => {\n const block = { content: token.raw, startOffset: offset };\n offset += token.raw.length;\n return block;\n });\n\n return {\n durationMs: profiler ? getNow() - start : 0,\n value,\n };\n }, [processedContent, profiler]);\n const blocks: BlockInfo[] = blocksResult.value;\n\n const { getBlockState, charDelay } = useStreamQueue(blocks);\n const blockRuntimesRef = useRef<Map<number, BlockRuntime>>(new Map());\n const blockPluginsRef = useRef<Map<number, BlockPluginsCacheEntry>>(new Map());\n const revealClockRef = useRef<{ lastTs: number }>({ lastTs: 0 });\n\n const renderNow = getNow();\n\n const animationStart = profiler ? getNow() : 0;\n const blockAnimationMeta = updateBlockAnimation({\n blocks,\n charDelay,\n getBlockState,\n pluginsCache: blockPluginsRef.current,\n renderNow,\n revealClock: revealClockRef.current,\n runtimes: blockRuntimesRef.current,\n });\n const blockAnimationDurationMs = profiler ? getNow() - animationStart : 0;\n\n useEffect(() => {\n if (!profiler) return;\n\n profiler.recordCalculation({\n durationMs: processedContentResult.durationMs,\n name: 'content-normalize',\n textLength: processedContent.length,\n });\n }, [processedContent.length, processedContentResult.durationMs, profiler]);\n\n useEffect(() => {\n if (!profiler) return;\n\n profiler.recordCalculation({\n durationMs: blocksResult.durationMs,\n itemCount: blocks.length,\n name: 'block-lex',\n textLength: processedContent.length,\n });\n }, [blocks.length, blocksResult.durationMs, processedContent.length, profiler]);\n\n useEffect(() => {\n if (!profiler) return;\n\n profiler.recordCalculation({\n durationMs: blockAnimationDurationMs,\n itemCount: blocks.length,\n name: 'block-births',\n textLength: processedContent.length,\n });\n }, [blockAnimationDurationMs, blocks.length, processedContent.length, profiler]);\n\n const resolveBlockPlugins = (startOffset: number, settled: boolean): PluggableList => {\n if (settled) return baseRehypePlugins;\n\n const cache = blockPluginsRef.current;\n const entry = cache.get(startOffset);\n if (\n entry &&\n entry.base === baseRehypePlugins &&\n entry.granularity === streamAnimationGranularity\n ) {\n return entry.value;\n }\n\n const runtime = blockRuntimesRef.current.get(startOffset);\n const value: PluggableList = [\n ...baseRehypePlugins,\n [\n rehypeStreamAnimated,\n {\n fadeDuration: STREAM_FADE_DURATION,\n granularity: streamAnimationGranularity,\n runtime,\n },\n ],\n ];\n cache.set(startOffset, {\n base: baseRehypePlugins,\n granularity: streamAnimationGranularity,\n value,\n });\n return value;\n };\n\n const handleRootRender = useCallback<ProfilerOnRenderCallback>(\n (_, phase, actualDuration, baseDuration) => {\n profiler?.recordRootCommit({\n actualDuration,\n baseDuration,\n blockCount: blocks.length,\n phase,\n textLength: processedContent.length,\n });\n },\n [blocks.length, processedContent.length, profiler],\n );\n\n const handleBlockRender = useCallback<ProfilerOnRenderCallback>(\n (id, phase, actualDuration, baseDuration) => {\n if (!profiler) return;\n\n const [, indexText, offsetText] = id.split(':');\n const blockIndex = Number(indexText);\n\n if (!Number.isFinite(blockIndex)) return;\n\n const block = blocks[blockIndex];\n if (!block) return;\n\n profiler.recordBlockCommit({\n actualDuration,\n baseDuration,\n blockChars: countChars(block.content),\n blockIndex,\n blockKey: offsetText ?? String(block.startOffset),\n phase,\n state: getBlockState(blockIndex),\n });\n },\n [blocks, getBlockState, profiler],\n );\n\n const content = (\n <div className={styles.animated}>\n {blocks.map((block, index) => {\n const animationMeta = blockAnimationMeta.get(block.startOffset);\n if (!animationMeta) return null;\n\n const plugins = resolveBlockPlugins(block.startOffset, animationMeta.settled);\n const key = `${generatedId}-${block.startOffset}`;\n\n if (!profiler) {\n return (\n <StreamdownBlock\n {...rest}\n components={components}\n key={key}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n );\n }\n\n return (\n <Profiler\n id={`streamdown-block:${index}:${block.startOffset}`}\n key={key}\n onRender={handleBlockRender}\n >\n <StreamdownBlock\n {...rest}\n components={components}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n </Profiler>\n );\n })}\n </div>\n );\n\n if (!profiler) return content;\n\n return (\n <Profiler id={'streamdown-root'} onRender={handleRootRender}>\n {content}\n </Profiler>\n );\n },\n);\n\nStreamdownBlocks.displayName = 'StreamdownBlocks';\n\n// The outer component absorbs the upstream per-chunk prop churn: every\n// streamed chunk re-renders it, but it only feeds the smoother and renders\n// a memoized child keyed on the smoother's output — so the expensive block\n// pipeline runs per reveal commit, not per chunk AND per commit.\nexport const StreamdownRender = memo<Options>(({ children, ...rest }) => {\n const { streamSmoothingPreset = 'balanced' } = useMarkdownContext();\n const escapedContent = useMarkdownContent(children || '');\n const smoothedContent = useSmoothStreamContent(\n typeof escapedContent === 'string' ? escapedContent : '',\n { preset: streamSmoothingPreset },\n );\n const markdownOptions = useStableValue(rest);\n\n return <StreamdownBlocks content={smoothedContent} markdownOptions={markdownOptions} />;\n});\n\nStreamdownRender.displayName = 'StreamdownRender';\n\nexport default StreamdownRender;\n"],"mappings":"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"}
@@ -1,13 +1,12 @@
1
1
  import { fadeIn } from "../../styles/animations.mjs";
2
2
  import { createStaticStyles } from "antd-style";
3
- //#region src/Markdown/SyntaxMarkdown/style.ts
4
3
  const styles = createStaticStyles(({ css }) => {
5
4
  return { animated: css`
6
5
  .stream-char {
7
6
  opacity: 0;
8
7
 
9
8
  animation-name: ${fadeIn};
10
- animation-duration: 280ms;
9
+ animation-duration: ${180}ms;
11
10
  animation-timing-function: cubic-bezier(0.33, 0, 0.67, 1);
12
11
  animation-fill-mode: forwards;
13
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"style.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/style.ts"],"sourcesContent":["import { createStaticStyles } from 'antd-style';\n\nimport { fadeIn } from '@/styles/animations';\n\nexport const styles = createStaticStyles(({ css }) => {\n return {\n animated: css`\n .stream-char {\n opacity: 0;\n\n animation-name: ${fadeIn};\n animation-duration: 280ms;\n animation-timing-function: cubic-bezier(0.33, 0, 0.67, 1);\n animation-fill-mode: forwards;\n }\n\n .stream-char-revealed {\n opacity: 1;\n animation: none;\n }\n\n .katex-display .katex-html span {\n mask: none !important;\n animation: none !important;\n }\n `,\n };\n});\n"],"mappings":";;;AAIA,MAAa,SAAS,oBAAoB,EAAE,UAAU;AACpD,QAAO,EACL,UAAU,GAAG;;;;0BAIS,OAAO;;;;;;;;;;;;;;;OAgB9B;EACD"}
1
+ {"version":3,"file":"style.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/style.ts"],"sourcesContent":["import { createStaticStyles } from 'antd-style';\n\nimport { fadeIn } from '@/styles/animations';\n\nexport const STREAM_FADE_DURATION = 180;\n\nexport const styles = createStaticStyles(({ css }) => {\n return {\n animated: css`\n .stream-char {\n opacity: 0;\n\n animation-name: ${fadeIn};\n animation-duration: ${STREAM_FADE_DURATION}ms;\n animation-timing-function: cubic-bezier(0.33, 0, 0.67, 1);\n animation-fill-mode: forwards;\n }\n\n .stream-char-revealed {\n opacity: 1;\n animation: none;\n }\n\n .katex-display .katex-html span {\n mask: none !important;\n animation: none !important;\n }\n `,\n };\n});\n"],"mappings":";;AAMA,MAAa,SAAS,oBAAoB,EAAE,UAAU;AACpD,QAAO,EACL,UAAU,GAAG;;;;0BAIS,OAAO;kCACkB;;;;;;;;;;;;;;OAehD;EACD"}