@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.
- package/es/Markdown/Markdown.mjs +2 -1
- package/es/Markdown/Markdown.mjs.map +1 -1
- package/es/Markdown/SyntaxMarkdown/CachedMarkdown.mjs +82 -0
- package/es/Markdown/SyntaxMarkdown/CachedMarkdown.mjs.map +1 -0
- package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs +127 -97
- package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs.map +1 -1
- package/es/Markdown/SyntaxMarkdown/style.mjs +1 -2
- package/es/Markdown/SyntaxMarkdown/style.mjs.map +1 -1
- package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs +32 -14
- package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs.map +1 -1
- package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs +2 -5
- package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs.map +1 -1
- package/es/Markdown/components/MarkdownProvider.mjs +2 -1
- package/es/Markdown/components/MarkdownProvider.mjs.map +1 -1
- package/es/Markdown/index.d.mts +2 -2
- package/es/Markdown/plugins/rehypeStreamAnimated.d.mts +21 -0
- package/es/Markdown/plugins/rehypeStreamAnimated.mjs +61 -25
- package/es/Markdown/plugins/rehypeStreamAnimated.mjs.map +1 -1
- package/es/Markdown/type.d.mts +3 -1
- package/es/Tabs/Tabs.mjs +6 -14
- package/es/Tabs/Tabs.mjs.map +1 -1
- package/es/base-ui/Tabs/Tabs.d.mts +8 -0
- package/es/base-ui/Tabs/Tabs.mjs +54 -0
- package/es/base-ui/Tabs/Tabs.mjs.map +1 -0
- package/es/base-ui/Tabs/atoms.d.mts +22 -0
- package/es/base-ui/Tabs/atoms.mjs +68 -0
- package/es/base-ui/Tabs/atoms.mjs.map +1 -0
- package/es/base-ui/Tabs/index.d.mts +4 -0
- package/es/base-ui/Tabs/style.d.mts +20 -0
- package/es/base-ui/Tabs/style.mjs +203 -0
- package/es/base-ui/Tabs/style.mjs.map +1 -0
- package/es/base-ui/Tabs/type.d.mts +64 -0
- package/es/base-ui/index.d.mts +5 -1
- package/es/base-ui/index.mjs +4 -1
- package/es/utils/getNow.mjs +8 -0
- package/es/utils/getNow.mjs.map +1 -0
- package/package.json +6 -1
package/es/Markdown/Markdown.mjs
CHANGED
|
@@ -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;
|
|
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(
|
|
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
|
|
55
|
-
|
|
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
|
|
92
|
-
const
|
|
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
|
|
95
|
-
|
|
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
|
-
|
|
125
|
-
renderNow
|
|
126
|
-
|
|
127
|
-
|
|
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:
|
|
198
|
+
durationMs: blockAnimationDurationMs,
|
|
158
199
|
itemCount: blocks.length,
|
|
159
200
|
name: "block-births",
|
|
160
201
|
textLength: processedContent.length
|
|
161
202
|
});
|
|
162
203
|
}, [
|
|
163
|
-
|
|
204
|
+
blockAnimationDurationMs,
|
|
164
205
|
blocks.length,
|
|
165
206
|
processedContent.length,
|
|
166
207
|
profiler
|
|
167
208
|
]);
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
return
|
|
185
|
-
|
|
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
|
-
|
|
237
|
-
const animationMeta = blockAnimationMetaResult.blockAnimationMeta.get(block.startOffset);
|
|
264
|
+
const animationMeta = blockAnimationMeta.get(block.startOffset);
|
|
238
265
|
if (!animationMeta) return null;
|
|
239
|
-
const
|
|
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:
|
|
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":";;;;;;;;;;;;;;;;;;;AAiCA,MAAM,uBAAuB;AAC7B,MAAM,yBAAoC,CAAC,sBAAsB,EAAE,UAAU,MAAM,CAAC;AAEpF,SAAS,WAAW,MAAsB;AACxC,QAAO,CAAC,GAAG,KAAK,CAAC;;AAGnB,SAAS,SAAiB;AACxB,QAAO,OAAO,gBAAgB,cAAc,KAAK,KAAK,GAAG,YAAY,KAAK;;AAG5E,MAAM,gBAAgB,YAAuB,eAAmC;CAC9E,MAAM,YAAY,MAAM,QAAQ,WAAW,GAAG,aAAa,CAAC,WAAW;CACvE,MAAM,YAAY,MAAM,QAAQ,WAAW,GAAG,aAAa,CAAC,WAAW;AAEvE,KAAI,UAAU,WAAW,UAAU,OAAQ,QAAO;AAClD,KAAI,UAAU,OAAO,UAAU,GAAI,QAAO;AAE1C,QAAO,YAAY,UAAU,MAAM,EAAE,EAAE,UAAU,MAAM,EAAE,CAAC;;AAG5D,MAAM,iBACJ,aACA,gBACY;AACZ,KAAI,gBAAgB,YAAa,QAAO;AACxC,KAAI,CAAC,eAAe,CAAC,YAAa,QAAO,CAAC,eAAe,CAAC;AAC1D,KAAI,YAAY,WAAW,YAAY,OAAQ,QAAO;AAEtD,MAAK,IAAI,IAAI,GAAG,IAAI,YAAY,QAAQ,IACtC,KAAI,CAAC,aAAa,YAAY,IAAI,YAAY,GAAG,CAAE,QAAO;AAG5D,QAAO;;AAGT,MAAM,oBAAoB,YAA0C;CAClE,MAAM,YAAY,OAAsB,QAAQ;AAEhD,KAAI,CAAC,cAAc,UAAU,SAAS,QAAQ,CAC5C,WAAU,UAAU;AAGtB,QAAO,UAAU;;AAGnB,MAAM,kBAAkB,MACrB,EAAE,UAAU,GAAG,WAAW;AACzB,QAAO,oBAAC,UAAD;EAAU,GAAI;EAAO;EAAoB,CAAA;IAEjD,WAAW,cACV,UAAU,aAAa,UAAU,YACjC,UAAU,eAAe,UAAU,cACnC,cAAc,UAAU,eAAe,UAAU,cAAc,IAC/D,cAAc,UAAU,eAAe,UAAU,cAAc,CAClE;AAED,gBAAgB,cAAc;AAE9B,MAAa,mBAAmB,MAAe,EAAE,UAAU,GAAG,WAAW;CACvE,MAAM,EAAE,wBAAwB,eAAe,oBAAoB;CACnE,MAAM,WAAW,uBAAuB;CACxC,MAAM,iBAAiB,mBAAmB,YAAY,GAAG;CACzD,MAAM,aAAa,uBAAuB;CAC1C,MAAM,oBAAoB,iBAAiB,0BAA0B,CAAC;CACtE,MAAM,gBAAgB,iBAAiB,0BAA0B,CAAC;CAClE,MAAM,cAAc,OAAO;CAC3B,MAAM,kBAAkB,uBACtB,OAAO,mBAAmB,WAAW,iBAAiB,IACtD,EAAE,QAAQ,uBAAuB,CAClC;CAED,MAAM,yBAAyB,cAAc;EAC3C,MAAM,QAAQ,WAAW,QAAQ,GAAG;EACpC,MAAM,QAAQ,OAAO,gBAAgB;AAErC,SAAO;GACL,YAAY,WAAW,QAAQ,GAAG,QAAQ;GAC1C;GACD;IACA,CAAC,UAAU,gBAAgB,CAAC;CAC/B,MAAM,mBAAmB,uBAAuB;CAEhD,MAAM,eAAe,cAAc;EACjC,MAAM,QAAQ,WAAW,QAAQ,GAAG;EACpC,MAAM,SAAS,OAAO,MAAM,iBAAiB;EAC7C,IAAI,SAAS;EAEb,MAAM,QAAQ,OAAO,KAAK,UAAU;GAClC,MAAM,QAAQ;IAAE,SAAS,MAAM;IAAK,aAAa;IAAQ;AACzD,aAAU,MAAM,IAAI;AACpB,UAAO;IACP;AAEF,SAAO;GACL,YAAY,WAAW,QAAQ,GAAG,QAAQ;GAC1C;GACD;IACA,CAAC,kBAAkB,SAAS,CAAC;CAChC,MAAM,SAAsB,aAAa;CAEzC,MAAM,EAAE,eAAe,cAAc,eAAe,OAAO;CAC3D,MAAM,oBAAoB,uBAA4B,IAAI,KAAK,CAAC;CAChE,MAAM,iBAAiB,uBAA8B,IAAI,KAAK,CAAC;CAE/D,MAAM,YAAY,QAAQ;CAE1B,MAAM,eAAe,cAAc;EACjC,MAAM,QAAQ,WAAW,QAAQ,GAAG;EACpC,MAAM,6BAAa,IAAI,KAAuB;EAC9C,MAAM,aAAa,eAAe;AAElC,OAAK,MAAM,CAAC,OAAO,UAAU,OAAO,SAAS,EAAE;AAM7C,OALc,cAAc,MAKnB,KAAK,SAAU;GAExB,MAAM,iBAAiB,WAAW,MAAM,QAAQ;GAChD,MAAM,OAAO,WAAW,IAAI,MAAM,YAAY;GAC9C,IAAI;AAEJ,OAAI,QAAQ,KAAK,WAAW,eAC1B,OAAM;YACG,QAAQ,KAAK,SAAS,eAE/B,OAAM,KAAK,MAAM,GAAG,eAAe;QAC9B;AACL,UAAM,OAAO,KAAK,OAAO,GAAG,EAAE;IAC9B,MAAM,WAAW,IAAI;IAKrB,MAAM,MAAM,YAAY;AACxB,SAAK,IAAI,IAAI,UAAU,IAAI,gBAAgB,KAAK;KAE9C,MAAM,WADY,IAAI,IAAK,IAAI,IAAI,KAAgB,YAAY,aACnC;AAC5B,SAAI,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,SAAS,UAAU,CAAC,CAAC;;;AAIzD,cAAW,IAAI,MAAM,aAAa,IAAI;;AAGxC,SAAO;GACL,YAAY,WAAW,QAAQ,GAAG,QAAQ;GAC1C,OAAO;GACR;IACA;EAAC;EAAQ;EAAW;EAAe;EAAU;EAAU,CAAC;CAC3D,MAAM,kBAAkB,aAAa;AAErC,iBAAgB;AACd,MAAI,CAAC,SAAU;AAEf,WAAS,kBAAkB;GACzB,YAAY,uBAAuB;GACnC,MAAM;GACN,YAAY,iBAAiB;GAC9B,CAAC;IACD;EAAC,iBAAiB;EAAQ,uBAAuB;EAAY;EAAS,CAAC;AAE1E,iBAAgB;AACd,MAAI,CAAC,SAAU;AAEf,WAAS,kBAAkB;GACzB,YAAY,aAAa;GACzB,WAAW,OAAO;GAClB,MAAM;GACN,YAAY,iBAAiB;GAC9B,CAAC;IACD;EAAC,OAAO;EAAQ,aAAa;EAAY,iBAAiB;EAAQ;EAAS,CAAC;AAE/E,iBAAgB;AACd,MAAI,CAAC,SAAU;AAEf,WAAS,kBAAkB;GACzB,YAAY,aAAa;GACzB,WAAW,OAAO;GAClB,MAAM;GACN,YAAY,iBAAiB;GAC9B,CAAC;IACD;EAAC,aAAa;EAAY,OAAO;EAAQ,iBAAiB;EAAQ;EAAS,CAAC;CAE/E,MAAM,2BAA2B,cAAc;EAC7C,MAAM,qCAAqB,IAAI,KAAqB;EACpD,MAAM,qCAAqB,IAAI,KAA2D;AAE1F,OAAK,MAAM,CAAC,OAAO,UAAU,OAAO,SAAS,EAAE;GAC7C,MAAM,QAAQ,cAAc,MAAM;GAClC,MAAM,SAAS,gBAAgB,IAAI,MAAM,YAAY;GAGrD,MAAM,gBAAgB,0BAA0B;IAC9C,kBAAkB;IAClB,cAAc;IACd,eAJoB,aADF,UAAU,OAAO,SAAS,IAAK,OAAO,GAAG,GAAG,IAAI,YAAa;IAM/E,mBAAmB,kBAAkB,QAAQ,IAAI,MAAM,YAAY;IACnE;IACD,CAAC;AAEF,sBAAmB,IAAI,MAAM,aAAa,cAAc,UAAU;AAClE,sBAAmB,IAAI,MAAM,aAAa,cAAc;;AAG1D,SAAO;GACL;GACA,gBAAgB;GACjB;IACA;EAAC;EAAiB;EAAQ;EAAW;EAAe;EAAU,CAAC;AAElE,iBAAgB;AACd,oBAAkB,UAAU,yBAAyB;AACrD,iBAAe,UAAU;IACxB,CAAC,iBAAiB,yBAAyB,eAAe,CAAC;CAE9D,MAAM,mBAAmB,aACtB,GAAG,OAAO,gBAAgB,iBAAiB;AAC1C,YAAU,iBAAiB;GACzB;GACA;GACA,YAAY,OAAO;GACnB;GACA,YAAY,iBAAiB;GAC9B,CAAC;IAEJ;EAAC,OAAO;EAAQ,iBAAiB;EAAQ;EAAS,CACnD;CAED,MAAM,oBAAoB,aACvB,IAAI,OAAO,gBAAgB,iBAAiB;AAC3C,MAAI,CAAC,SAAU;EAEf,MAAM,GAAG,WAAW,cAAc,GAAG,MAAM,IAAI;EAC/C,MAAM,aAAa,OAAO,UAAU;AAEpC,MAAI,CAAC,OAAO,SAAS,WAAW,CAAE;EAElC,MAAM,QAAQ,OAAO;AACrB,MAAI,CAAC,MAAO;AAEZ,WAAS,kBAAkB;GACzB;GACA;GACA,YAAY,WAAW,MAAM,QAAQ;GACrC;GACA,UAAU,cAAc,OAAO,MAAM,YAAY;GACjD;GACA,OAAO,cAAc,WAAW;GACjC,CAAC;IAEJ;EAAC;EAAQ;EAAe;EAAS,CAClC;CAED,MAAM,UACJ,oBAAC,OAAD;EAAK,WAAW,OAAO;YACpB,OAAO,KAAK,OAAO,UAAU;AAE5B,OADc,cAAc,MACnB,KAAK,SAAU,QAAO;GAC/B,MAAM,gBAAgB,yBAAyB,mBAAmB,IAAI,MAAM,YAAY;AACxF,OAAI,CAAC,cAAe,QAAO;GAE3B,MAAM,SAAS,gBAAgB,IAAI,MAAM,YAAY;GACrD,MAAM,UAAuB,cAAc,UACvC,CAAC,GAAG,mBAAmB,uBAAuB,GAC9C,CACE,GAAG,mBACH,CACE,sBACA;IACE;IACA,cAAc;IACd,OAAO;IACR,CACF,CACF;GAEL,MAAM,MAAM,GAAG,YAAY,GAAG,MAAM;GACpC,MAAM,YACJ,oBAAC,iBAAD;IACE,GAAI;IACQ;IACZ,eAAe;IACA;cAEd,MAAM;IACS,CAAA;AAGpB,OAAI,CAAC,SACH,QACE,8BAAC,iBAAD;IACE,GAAI;IACQ;IACP;IACL,eAAe;IACA;IAGC,EADf,MAAM,QACS;AAItB,UACE,oBAAC,UAAD;IACE,IAAI,oBAAoB,MAAM,GAAG,MAAM;IAEvC,UAAU;cAET;IACQ,EAJJ,IAII;IAEb;EACE,CAAA;AAGR,KAAI,CAAC,SAAU,QAAO;AAEtB,QACE,oBAAC,UAAD;EAAU,IAAI;EAAmB,UAAU;YACxC;EACQ,CAAA;EAEb;AAEF,iBAAiB,cAAc"}
|
|
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:
|
|
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:
|
|
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"}
|