@lobehub/ui 5.32.4 → 5.32.6
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/SyntaxMarkdown/StreamdownRender.mjs +15 -294
- package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs.map +1 -1
- package/es/Markdown/type.d.mts +2 -3
- package/es/base-ui/ContextMenu/renderItems.mjs +24 -1
- package/es/base-ui/ContextMenu/renderItems.mjs.map +1 -1
- package/es/base-ui/Input/style.d.mts +2 -2
- package/es/base-ui/Segmented/Segmented.mjs +4 -0
- package/es/base-ui/Segmented/Segmented.mjs.map +1 -1
- package/es/base-ui/Segmented/style.mjs +4 -0
- package/es/base-ui/Segmented/style.mjs.map +1 -1
- package/es/base-ui/Tabs/style.mjs +12 -0
- package/es/base-ui/Tabs/style.mjs.map +1 -1
- package/es/base-ui/Tooltip/style.mjs +104 -20
- package/es/base-ui/Tooltip/style.mjs.map +1 -1
- package/es/hooks/useMarkdown/useMarkdownContent.mjs +4 -16
- package/es/hooks/useMarkdown/useMarkdownContent.mjs.map +1 -1
- package/es/hooks/useMarkdown/utils.mjs +1 -1
- package/es/hooks/useMarkdown/utils.mjs.map +1 -1
- package/es/index.d.mts +1 -1
- package/es/index.mjs +1 -1
- package/package.json +2 -88
- package/es/Markdown/SyntaxMarkdown/CachedMarkdown.mjs +0 -83
- package/es/Markdown/SyntaxMarkdown/CachedMarkdown.mjs.map +0 -1
- package/es/Markdown/SyntaxMarkdown/fenceState.mjs +0 -42
- package/es/Markdown/SyntaxMarkdown/fenceState.mjs.map +0 -1
- package/es/Markdown/SyntaxMarkdown/streamAnimationMeta.mjs +0 -14
- package/es/Markdown/SyntaxMarkdown/streamAnimationMeta.mjs.map +0 -1
- package/es/Markdown/SyntaxMarkdown/style.mjs +0 -28
- package/es/Markdown/SyntaxMarkdown/style.mjs.map +0 -1
- package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs +0 -314
- package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs.map +0 -1
- package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs +0 -88
- package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs.map +0 -1
- package/es/Markdown/plugins/rehypeStreamAnimated.d.mts +0 -33
- package/es/Markdown/plugins/rehypeStreamAnimated.mjs +0 -115
- package/es/Markdown/plugins/rehypeStreamAnimated.mjs.map +0 -1
- package/es/Markdown/streamProfiler/StreamdownProfilerProvider.mjs +0 -19
- package/es/Markdown/streamProfiler/StreamdownProfilerProvider.mjs.map +0 -1
- package/es/hooks/useMarkdown/latex.mjs +0 -294
- package/es/hooks/useMarkdown/latex.mjs.map +0 -1
- package/es/node_modules/.pnpm/@types_hast@3.0.5/node_modules/@types/hast/index.d.mts +0 -869
- package/es/utils/getNow.mjs +0 -8
- package/es/utils/getNow.mjs.map +0 -1
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { visit } from "../../node_modules/.pnpm/unist-util-visit@5.1.0/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
|
-
const rawNode = node;
|
|
45
|
-
if (rawNode.type === "raw" && typeof rawNode.value === "string" && parent && typeof index === "number") {
|
|
46
|
-
if (skipHtml) parent.children.splice(index, 1);
|
|
47
|
-
else parent.children[index] = {
|
|
48
|
-
type: "text",
|
|
49
|
-
value: rawNode.value
|
|
50
|
-
};
|
|
51
|
-
return index;
|
|
52
|
-
}
|
|
53
|
-
if (node.type === "element") {
|
|
54
|
-
let key;
|
|
55
|
-
for (key in urlAttributes) if (Object.hasOwn(urlAttributes, key) && Object.hasOwn(node.properties, key)) {
|
|
56
|
-
const value = node.properties[key];
|
|
57
|
-
const test = urlAttributes[key];
|
|
58
|
-
if (test === null || test.includes(node.tagName)) node.properties[key] = urlTransform(String(value || ""), key, node);
|
|
59
|
-
}
|
|
60
|
-
let remove = allowedElements ? !allowedElements.includes(node.tagName) : disallowedElements ? disallowedElements.includes(node.tagName) : false;
|
|
61
|
-
if (!remove && allowElement && typeof index === "number") remove = !allowElement(node, index, parent);
|
|
62
|
-
if (remove && parent && typeof index === "number") {
|
|
63
|
-
if (unwrapDisallowed && node.children) parent.children.splice(index, 1, ...node.children);
|
|
64
|
-
else parent.children.splice(index, 1);
|
|
65
|
-
return index;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
visit(tree, transform);
|
|
70
|
-
return toJsxRuntime(tree, {
|
|
71
|
-
Fragment: Fragment$1,
|
|
72
|
-
components,
|
|
73
|
-
ignoreInvalidStyle: true,
|
|
74
|
-
jsx,
|
|
75
|
-
jsxs,
|
|
76
|
-
passKeys: true,
|
|
77
|
-
passNode: true
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
//#endregion
|
|
81
|
-
export { CachedMarkdown };
|
|
82
|
-
|
|
83
|
-
//# sourceMappingURL=CachedMarkdown.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
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 const rawNode = node as unknown as { type?: string; value?: unknown };\n if (\n rawNode.type === 'raw' &&\n typeof rawNode.value === 'string' &&\n parent &&\n typeof index === 'number'\n ) {\n if (skipHtml) {\n parent.children.splice(index, 1);\n } else {\n parent.children[index] = { type: 'text', value: rawNode.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,CAAC;AACtC,MAAM,gCAAgC,EAAE,oBAAoB,KAAK;;;;;;;;;AAUjE,MAAa,kBAAkB,YAAqB;CAClD,MAAM,EAAE,UAAU,eAAe,eAAe,wBAAwB;CAExE,MAAM,YAAY,cAAc;EAC9B,OAAO,QAAQ,CAAC,CACb,IAAI,WAAW,CAAC,CAChB,IAAI,iBAAiB,aAAa,CAAC,CACnC,IACC,cACA,sBACI;GAAE,GAAG;GAAqB,GAAG;EAA8B,IAC3D,6BACN,CAAC,CACA,IAAI,iBAAiB,aAAa;CACvC,GAAG;EAAC;EAAe;EAAe;CAAmB,CAAC;CAEtD,MAAM,OAAO,IAAI,MAAM;CACvB,KAAK,QAAQ,OAAO,aAAa,WAAW,WAAW;CAEvD,OAAO,KAAK,UAAU,QAAQ,UAAU,MAAM,IAAI,GAAG,IAAI,GAAY,OAAO;AAC9E;AAEA,SAAS,KAAK,MAAa,SAAkB;CAC3C,MAAM,EACJ,iBACA,cACA,YACA,oBACA,UACA,qBACE;CACJ,MAAM,eAAe,QAAQ,gBAAgB;CAE7C,IAAI,mBAAmB,oBACrB,MAAM,IAAI,MACR,2FACF;CAGF,MAAM,aAAiC,MAAM,OAAO,WAAW;EAC7D,MAAM,UAAU;EAChB,IACE,QAAQ,SAAS,SACjB,OAAO,QAAQ,UAAU,YACzB,UACA,OAAO,UAAU,UACjB;GACA,IAAI,UACF,OAAO,SAAS,OAAO,OAAO,CAAC;QAE/B,OAAO,SAAS,SAAS;IAAE,MAAM;IAAQ,OAAO,QAAQ;GAAM;GAGhE,OAAO;EACT;EAEA,IAAI,KAAK,SAAS,WAAW;GAC3B,IAAI;GAEJ,KAAK,OAAO,eACV,IAAI,OAAO,OAAO,eAAe,GAAG,KAAK,OAAO,OAAO,KAAK,YAAY,GAAG,GAAG;IAC5E,MAAM,QAAQ,KAAK,WAAW;IAC9B,MAAM,OAAO,cAAc;IAC3B,IAAI,SAAS,QAAQ,KAAK,SAAS,KAAK,OAAO,GAC7C,KAAK,WAAW,OAAO,aAAa,OAAO,SAAS,EAAE,GAAG,KAAK,IAAI;GAEtE;GAGF,IAAI,SAAS,kBACT,CAAC,gBAAgB,SAAS,KAAK,OAAO,IACtC,qBACE,mBAAmB,SAAS,KAAK,OAAO,IACxC;GAEN,IAAI,CAAC,UAAU,gBAAgB,OAAO,UAAU,UAC9C,SAAS,CAAC,aAAa,MAAM,OAAO,MAAM;GAG5C,IAAI,UAAU,UAAU,OAAO,UAAU,UAAU;IACjD,IAAI,oBAAoB,KAAK,UAC3B,OAAO,SAAS,OAAO,OAAO,GAAG,GAAG,KAAK,QAAQ;SAEjD,OAAO,SAAS,OAAO,OAAO,CAAC;IAGjC,OAAO;GACT;EACF;CACF;CAEA,MAAM,MAAc,SAAS;CAE7B,OAAO,aAAa,MAAM;EACxB,UAAA;EACA;EACA,oBAAoB;EACpB;EACA;EACA,UAAU;EACV,UAAU;CACZ,CAAC;AACH"}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
//#region src/Markdown/SyntaxMarkdown/fenceState.ts
|
|
2
|
-
/**
|
|
3
|
-
* Walk `content` and return the language of the last *unclosed* fenced
|
|
4
|
-
* code block, or `null` if every fence is closed (or there are none).
|
|
5
|
-
*
|
|
6
|
-
* Used by the smoother to decide whether to bypass its buffer for the
|
|
7
|
-
* current input. CommonMark recognises an opening fence as a line whose
|
|
8
|
-
* first non-space chars are 3+ backticks; we deliberately stay simple:
|
|
9
|
-
* 3 literal backticks at line start, language word after. That covers
|
|
10
|
-
* the markdown LLMs actually emit; tildes and indented fences fall back
|
|
11
|
-
* to normal smoothing rather than getting clever.
|
|
12
|
-
*
|
|
13
|
-
* Linear scan over the input, ~10ns per char on V8. Called on every
|
|
14
|
-
* smoothing tick during streaming so the simplicity matters.
|
|
15
|
-
*/
|
|
16
|
-
const findOpenFenceLanguage = (content) => {
|
|
17
|
-
let inFence = false;
|
|
18
|
-
let lang = "";
|
|
19
|
-
let i = 0;
|
|
20
|
-
const len = content.length;
|
|
21
|
-
while (i < len) {
|
|
22
|
-
const nl = content.indexOf("\n", i);
|
|
23
|
-
const lineEnd = nl === -1 ? len : nl;
|
|
24
|
-
const line = content.slice(i, lineEnd);
|
|
25
|
-
if (line.startsWith("```")) {
|
|
26
|
-
if (inFence) {
|
|
27
|
-
inFence = false;
|
|
28
|
-
lang = "";
|
|
29
|
-
} else {
|
|
30
|
-
inFence = true;
|
|
31
|
-
lang = line.slice(3).trim().toLowerCase();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
if (nl === -1) break;
|
|
35
|
-
i = nl + 1;
|
|
36
|
-
}
|
|
37
|
-
return inFence ? lang : null;
|
|
38
|
-
};
|
|
39
|
-
//#endregion
|
|
40
|
-
export { findOpenFenceLanguage };
|
|
41
|
-
|
|
42
|
-
//# sourceMappingURL=fenceState.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fenceState.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/fenceState.ts"],"sourcesContent":["/**\n * Walk `content` and return the language of the last *unclosed* fenced\n * code block, or `null` if every fence is closed (or there are none).\n *\n * Used by the smoother to decide whether to bypass its buffer for the\n * current input. CommonMark recognises an opening fence as a line whose\n * first non-space chars are 3+ backticks; we deliberately stay simple:\n * 3 literal backticks at line start, language word after. That covers\n * the markdown LLMs actually emit; tildes and indented fences fall back\n * to normal smoothing rather than getting clever.\n *\n * Linear scan over the input, ~10ns per char on V8. Called on every\n * smoothing tick during streaming so the simplicity matters.\n */\nexport const findOpenFenceLanguage = (content: string): string | null => {\n let inFence = false;\n let lang = '';\n let i = 0;\n const len = content.length;\n while (i < len) {\n const nl = content.indexOf('\\n', i);\n const lineEnd = nl === -1 ? len : nl;\n const line = content.slice(i, lineEnd);\n if (line.startsWith('```')) {\n if (inFence) {\n inFence = false;\n lang = '';\n } else {\n inFence = true;\n lang = line.slice(3).trim().toLowerCase();\n }\n }\n if (nl === -1) break;\n i = nl + 1;\n }\n return inFence ? lang : null;\n};\n"],"mappings":";;;;;;;;;;;;;;;AAcA,MAAa,yBAAyB,YAAmC;CACvE,IAAI,UAAU;CACd,IAAI,OAAO;CACX,IAAI,IAAI;CACR,MAAM,MAAM,QAAQ;CACpB,OAAO,IAAI,KAAK;EACd,MAAM,KAAK,QAAQ,QAAQ,MAAM,CAAC;EAClC,MAAM,UAAU,OAAO,KAAK,MAAM;EAClC,MAAM,OAAO,QAAQ,MAAM,GAAG,OAAO;EACrC,IAAI,KAAK,WAAW,KAAK,GAAG;GAC1B,IAAI,SAAS;IACX,UAAU;IACV,OAAO;GACT,OAAO;IACL,UAAU;IACV,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY;GAC1C;EACF;EACA,IAAI,OAAO,IAAI;EACf,IAAI,KAAK;CACX;CACA,OAAO,UAAU,OAAO;AAC1B"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
//#region src/Markdown/SyntaxMarkdown/streamAnimationMeta.ts
|
|
2
|
-
const isActiveBlock = (state) => {
|
|
3
|
-
return state === "animating" || state === "streaming";
|
|
4
|
-
};
|
|
5
|
-
const resolveBlockAnimationMeta = ({ currentCharDelay, fadeDuration, lastElapsedMs, previousCharDelay, state }) => {
|
|
6
|
-
return {
|
|
7
|
-
charDelay: isActiveBlock(state) ? currentCharDelay : previousCharDelay ?? currentCharDelay,
|
|
8
|
-
settled: state === "revealed" && lastElapsedMs >= fadeDuration
|
|
9
|
-
};
|
|
10
|
-
};
|
|
11
|
-
//#endregion
|
|
12
|
-
export { resolveBlockAnimationMeta };
|
|
13
|
-
|
|
14
|
-
//# sourceMappingURL=streamAnimationMeta.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"streamAnimationMeta.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/streamAnimationMeta.ts"],"sourcesContent":["import { type BlockState } from './useStreamQueue';\n\nexport interface ResolveBlockAnimationMetaOptions {\n currentCharDelay: number;\n fadeDuration: number;\n lastElapsedMs: number;\n previousCharDelay?: number;\n state: BlockState;\n}\n\nexport interface BlockAnimationMeta {\n charDelay: number;\n settled: boolean;\n}\n\nconst isActiveBlock = (state: BlockState) => {\n return state === 'animating' || state === 'streaming';\n};\n\nexport const resolveBlockAnimationMeta = ({\n currentCharDelay,\n fadeDuration,\n lastElapsedMs,\n previousCharDelay,\n state,\n}: ResolveBlockAnimationMetaOptions): BlockAnimationMeta => {\n const charDelay = isActiveBlock(state)\n ? currentCharDelay\n : (previousCharDelay ?? currentCharDelay);\n const settled = state === 'revealed' && lastElapsedMs >= fadeDuration;\n\n return {\n charDelay,\n settled,\n };\n};\n"],"mappings":";AAeA,MAAM,iBAAiB,UAAsB;CAC3C,OAAO,UAAU,eAAe,UAAU;AAC5C;AAEA,MAAa,6BAA6B,EACxC,kBACA,cACA,eACA,mBACA,YAC0D;CAM1D,OAAO;EACL,WANgB,cAAc,KAAK,IACjC,mBACC,qBAAqB;EAKxB,SAJc,UAAU,cAAc,iBAAiB;CAKzD;AACF"}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { fadeIn } from "../../styles/animations.mjs";
|
|
2
|
-
import { createStaticStyles } from "antd-style";
|
|
3
|
-
const styles = createStaticStyles(({ css }) => {
|
|
4
|
-
return { animated: css`
|
|
5
|
-
.stream-char {
|
|
6
|
-
opacity: 0;
|
|
7
|
-
|
|
8
|
-
animation-name: ${fadeIn};
|
|
9
|
-
animation-duration: ${180}ms;
|
|
10
|
-
animation-timing-function: cubic-bezier(0.33, 0, 0.67, 1);
|
|
11
|
-
animation-fill-mode: forwards;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
.stream-char-revealed {
|
|
15
|
-
opacity: 1;
|
|
16
|
-
animation: none;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.katex-display .katex-html span {
|
|
20
|
-
mask: none !important;
|
|
21
|
-
animation: none !important;
|
|
22
|
-
}
|
|
23
|
-
` };
|
|
24
|
-
});
|
|
25
|
-
//#endregion
|
|
26
|
-
export { styles };
|
|
27
|
-
|
|
28
|
-
//# sourceMappingURL=style.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
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;CACpD,OAAO,EACL,UAAU,GAAG;;;;0BAIS,OAAO;8BACkB,IAAA;;;;;;;;;;;;;;MAejD;AACF,CAAC"}
|
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
import { getNow } from "../../utils/getNow.mjs";
|
|
2
|
-
import { useStreamdownProfiler } from "../streamProfiler/StreamdownProfilerProvider.mjs";
|
|
3
|
-
import { findOpenFenceLanguage } from "./fenceState.mjs";
|
|
4
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
-
//#region src/Markdown/SyntaxMarkdown/useSmoothStreamContent.ts
|
|
6
|
-
const DEFAULT_BYPASS_LANGUAGES = ["html"];
|
|
7
|
-
const PRESET_CONFIG = {
|
|
8
|
-
balanced: {
|
|
9
|
-
activeInputWindowMs: 220,
|
|
10
|
-
bypassFencedLanguages: DEFAULT_BYPASS_LANGUAGES,
|
|
11
|
-
defaultCps: 38,
|
|
12
|
-
emaAlpha: .2,
|
|
13
|
-
flushCps: 120,
|
|
14
|
-
largeAppendChars: 120,
|
|
15
|
-
maxActiveCps: 132,
|
|
16
|
-
maxCps: 72,
|
|
17
|
-
maxFlushCps: 280,
|
|
18
|
-
minCommitIntervalMs: 48,
|
|
19
|
-
minCps: 18,
|
|
20
|
-
settleAfterMs: 360,
|
|
21
|
-
settleDrainMaxMs: 520,
|
|
22
|
-
settleDrainMinMs: 180,
|
|
23
|
-
targetBufferMs: 120
|
|
24
|
-
},
|
|
25
|
-
realtime: {
|
|
26
|
-
activeInputWindowMs: 140,
|
|
27
|
-
bypassFencedLanguages: DEFAULT_BYPASS_LANGUAGES,
|
|
28
|
-
defaultCps: 50,
|
|
29
|
-
emaAlpha: .3,
|
|
30
|
-
flushCps: 170,
|
|
31
|
-
largeAppendChars: 180,
|
|
32
|
-
maxActiveCps: 180,
|
|
33
|
-
maxCps: 96,
|
|
34
|
-
maxFlushCps: 360,
|
|
35
|
-
minCommitIntervalMs: 32,
|
|
36
|
-
minCps: 24,
|
|
37
|
-
settleAfterMs: 260,
|
|
38
|
-
settleDrainMaxMs: 360,
|
|
39
|
-
settleDrainMinMs: 140,
|
|
40
|
-
targetBufferMs: 40
|
|
41
|
-
},
|
|
42
|
-
silky: {
|
|
43
|
-
activeInputWindowMs: 320,
|
|
44
|
-
bypassFencedLanguages: DEFAULT_BYPASS_LANGUAGES,
|
|
45
|
-
defaultCps: 28,
|
|
46
|
-
emaAlpha: .14,
|
|
47
|
-
flushCps: 96,
|
|
48
|
-
largeAppendChars: 100,
|
|
49
|
-
maxActiveCps: 102,
|
|
50
|
-
maxCps: 56,
|
|
51
|
-
maxFlushCps: 220,
|
|
52
|
-
minCommitIntervalMs: 56,
|
|
53
|
-
minCps: 14,
|
|
54
|
-
settleAfterMs: 460,
|
|
55
|
-
settleDrainMaxMs: 680,
|
|
56
|
-
settleDrainMinMs: 240,
|
|
57
|
-
targetBufferMs: 170
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
const clamp = (value, min, max) => {
|
|
61
|
-
return Math.min(max, Math.max(min, value));
|
|
62
|
-
};
|
|
63
|
-
const MAX_COMMIT_INTERVAL_MS = 96;
|
|
64
|
-
const COMMIT_INTERVAL_TAIL_SCALE_UNITS = 256;
|
|
65
|
-
const findTailUnits = (content) => {
|
|
66
|
-
const boundary = content.lastIndexOf("\n\n");
|
|
67
|
-
return boundary === -1 ? content.length : content.length - boundary - 2;
|
|
68
|
-
};
|
|
69
|
-
const countChars = (text) => {
|
|
70
|
-
let count = 0;
|
|
71
|
-
for (const _char of text) count += 1;
|
|
72
|
-
return count;
|
|
73
|
-
};
|
|
74
|
-
const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" } = {}) => {
|
|
75
|
-
const config = PRESET_CONFIG[preset];
|
|
76
|
-
const profiler = useStreamdownProfiler();
|
|
77
|
-
const [displayedContent, setDisplayedContent] = useState(content);
|
|
78
|
-
const displayedContentRef = useRef(content);
|
|
79
|
-
const displayedCountRef = useRef(countChars(content));
|
|
80
|
-
const targetContentRef = useRef(content);
|
|
81
|
-
const targetCharsRef = useRef([...content]);
|
|
82
|
-
const targetCountRef = useRef(targetCharsRef.current.length);
|
|
83
|
-
const emaCpsRef = useRef(config.defaultCps);
|
|
84
|
-
const tailUnitsRef = useRef(findTailUnits(content));
|
|
85
|
-
const lastInputTsRef = useRef(0);
|
|
86
|
-
const lastInputCountRef = useRef(targetCountRef.current);
|
|
87
|
-
const chunkSizeEmaRef = useRef(1);
|
|
88
|
-
const arrivalCpsEmaRef = useRef(config.defaultCps);
|
|
89
|
-
const rafRef = useRef(null);
|
|
90
|
-
const lastFrameTsRef = useRef(null);
|
|
91
|
-
const wakeTimerRef = useRef(null);
|
|
92
|
-
const clearWakeTimer = useCallback(() => {
|
|
93
|
-
if (wakeTimerRef.current !== null) {
|
|
94
|
-
clearTimeout(wakeTimerRef.current);
|
|
95
|
-
wakeTimerRef.current = null;
|
|
96
|
-
}
|
|
97
|
-
}, []);
|
|
98
|
-
const stopFrameLoop = useCallback(() => {
|
|
99
|
-
if (rafRef.current !== null) {
|
|
100
|
-
cancelAnimationFrame(rafRef.current);
|
|
101
|
-
rafRef.current = null;
|
|
102
|
-
}
|
|
103
|
-
lastFrameTsRef.current = null;
|
|
104
|
-
}, []);
|
|
105
|
-
const stopScheduling = useCallback(() => {
|
|
106
|
-
stopFrameLoop();
|
|
107
|
-
clearWakeTimer();
|
|
108
|
-
}, [clearWakeTimer, stopFrameLoop]);
|
|
109
|
-
const startFrameLoopRef = useRef(() => {});
|
|
110
|
-
const scheduleFrameWake = useCallback((delayMs) => {
|
|
111
|
-
clearWakeTimer();
|
|
112
|
-
wakeTimerRef.current = setTimeout(() => {
|
|
113
|
-
wakeTimerRef.current = null;
|
|
114
|
-
startFrameLoopRef.current();
|
|
115
|
-
}, Math.max(1, Math.ceil(delayMs)));
|
|
116
|
-
}, [clearWakeTimer]);
|
|
117
|
-
const syncImmediate = useCallback((nextContent) => {
|
|
118
|
-
stopScheduling();
|
|
119
|
-
const chars = [...nextContent];
|
|
120
|
-
const now = getNow();
|
|
121
|
-
targetContentRef.current = nextContent;
|
|
122
|
-
targetCharsRef.current = chars;
|
|
123
|
-
targetCountRef.current = chars.length;
|
|
124
|
-
displayedContentRef.current = nextContent;
|
|
125
|
-
displayedCountRef.current = chars.length;
|
|
126
|
-
setDisplayedContent(nextContent);
|
|
127
|
-
emaCpsRef.current = config.defaultCps;
|
|
128
|
-
chunkSizeEmaRef.current = 1;
|
|
129
|
-
arrivalCpsEmaRef.current = config.defaultCps;
|
|
130
|
-
tailUnitsRef.current = findTailUnits(nextContent);
|
|
131
|
-
lastInputTsRef.current = now;
|
|
132
|
-
lastInputCountRef.current = chars.length;
|
|
133
|
-
}, [config.defaultCps, stopScheduling]);
|
|
134
|
-
const startFrameLoop = useCallback(() => {
|
|
135
|
-
clearWakeTimer();
|
|
136
|
-
if (rafRef.current !== null) return;
|
|
137
|
-
const tick = (ts) => {
|
|
138
|
-
const targetCount = targetCountRef.current;
|
|
139
|
-
const displayedCount = displayedCountRef.current;
|
|
140
|
-
const backlog = targetCount - displayedCount;
|
|
141
|
-
if (backlog <= 0) {
|
|
142
|
-
stopFrameLoop();
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
if (lastFrameTsRef.current === null) {
|
|
146
|
-
lastFrameTsRef.current = ts;
|
|
147
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
const commitIntervalMs = Math.min(MAX_COMMIT_INTERVAL_MS, config.minCommitIntervalMs * (1 + tailUnitsRef.current / COMMIT_INTERVAL_TAIL_SCALE_UNITS));
|
|
151
|
-
const frameIntervalMs = Math.max(0, ts - lastFrameTsRef.current);
|
|
152
|
-
if (frameIntervalMs < commitIntervalMs) {
|
|
153
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const frameStart = getNow();
|
|
157
|
-
const dtSeconds = Math.max(.001, Math.min(frameIntervalMs / 1e3, .12));
|
|
158
|
-
lastFrameTsRef.current = ts;
|
|
159
|
-
const idleMs = frameStart - lastInputTsRef.current;
|
|
160
|
-
const inputActive = idleMs <= config.activeInputWindowMs;
|
|
161
|
-
const settling = !inputActive && idleMs >= config.settleAfterMs;
|
|
162
|
-
const baseCps = clamp(emaCpsRef.current, config.minCps, config.maxCps);
|
|
163
|
-
const baseLagChars = Math.max(1, Math.round(baseCps * config.targetBufferMs / 1e3));
|
|
164
|
-
const lagUpperBound = Math.max(baseLagChars + 2, baseLagChars * 3);
|
|
165
|
-
const targetLagChars = inputActive ? Math.round(clamp(baseLagChars + chunkSizeEmaRef.current * .35, baseLagChars, lagUpperBound)) : 0;
|
|
166
|
-
const desiredDisplayed = Math.max(0, targetCount - targetLagChars);
|
|
167
|
-
let currentCps;
|
|
168
|
-
if (inputActive) {
|
|
169
|
-
const backlogPressure = targetLagChars > 0 ? backlog / targetLagChars : 1;
|
|
170
|
-
const chunkPressure = targetLagChars > 0 ? chunkSizeEmaRef.current / targetLagChars : 1;
|
|
171
|
-
const arrivalPressure = arrivalCpsEmaRef.current / Math.max(baseCps, 1);
|
|
172
|
-
const combinedPressure = clamp(backlogPressure * .6 + chunkPressure * .25 + arrivalPressure * .15, 1, 4.5);
|
|
173
|
-
const activeCap = clamp(config.maxActiveCps + chunkSizeEmaRef.current * 6, config.maxActiveCps, config.maxFlushCps);
|
|
174
|
-
currentCps = clamp(baseCps * combinedPressure, config.minCps, activeCap);
|
|
175
|
-
} else if (settling) {
|
|
176
|
-
const drainTargetMs = clamp(backlog * 8, config.settleDrainMinMs, config.settleDrainMaxMs);
|
|
177
|
-
const settleCps = backlog * 1e3 / drainTargetMs;
|
|
178
|
-
currentCps = clamp(settleCps, config.flushCps, config.maxFlushCps);
|
|
179
|
-
} else {
|
|
180
|
-
const idleFlushCps = Math.max(config.flushCps, baseCps * 1.8, arrivalCpsEmaRef.current * .8);
|
|
181
|
-
currentCps = clamp(idleFlushCps, config.flushCps, config.maxFlushCps);
|
|
182
|
-
}
|
|
183
|
-
const urgentBacklog = inputActive && targetLagChars > 0 && backlog > targetLagChars * 2.2;
|
|
184
|
-
const burstyInput = inputActive && chunkSizeEmaRef.current >= targetLagChars * .9;
|
|
185
|
-
let revealChars = Math.max(inputActive ? urgentBacklog || burstyInput ? 2 : 1 : 2, Math.round(currentCps * dtSeconds));
|
|
186
|
-
if (inputActive) {
|
|
187
|
-
const shortfall = desiredDisplayed - displayedCount;
|
|
188
|
-
if (shortfall <= 0) {
|
|
189
|
-
stopFrameLoop();
|
|
190
|
-
scheduleFrameWake(config.activeInputWindowMs - idleMs);
|
|
191
|
-
profiler?.recordAnimationFrame({
|
|
192
|
-
backlog,
|
|
193
|
-
durationMs: getNow() - frameStart,
|
|
194
|
-
frameIntervalMs,
|
|
195
|
-
inputActive,
|
|
196
|
-
revealChars: 0,
|
|
197
|
-
settling
|
|
198
|
-
});
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
revealChars = Math.min(revealChars, shortfall, backlog);
|
|
202
|
-
} else revealChars = Math.min(revealChars, backlog);
|
|
203
|
-
const nextCount = displayedCount + revealChars;
|
|
204
|
-
const segment = targetCharsRef.current.slice(displayedCount, nextCount).join("");
|
|
205
|
-
if (segment) {
|
|
206
|
-
const nextDisplayed = displayedContentRef.current + segment;
|
|
207
|
-
displayedContentRef.current = nextDisplayed;
|
|
208
|
-
displayedCountRef.current = nextCount;
|
|
209
|
-
setDisplayedContent(nextDisplayed);
|
|
210
|
-
} else {
|
|
211
|
-
displayedContentRef.current = targetContentRef.current;
|
|
212
|
-
displayedCountRef.current = targetCount;
|
|
213
|
-
setDisplayedContent(targetContentRef.current);
|
|
214
|
-
}
|
|
215
|
-
profiler?.recordAnimationFrame({
|
|
216
|
-
backlog,
|
|
217
|
-
durationMs: getNow() - frameStart,
|
|
218
|
-
frameIntervalMs,
|
|
219
|
-
inputActive,
|
|
220
|
-
revealChars: segment ? revealChars : backlog,
|
|
221
|
-
settling
|
|
222
|
-
});
|
|
223
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
224
|
-
};
|
|
225
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
226
|
-
}, [
|
|
227
|
-
clearWakeTimer,
|
|
228
|
-
config.activeInputWindowMs,
|
|
229
|
-
config.flushCps,
|
|
230
|
-
config.maxActiveCps,
|
|
231
|
-
config.maxCps,
|
|
232
|
-
config.maxFlushCps,
|
|
233
|
-
config.minCommitIntervalMs,
|
|
234
|
-
config.minCps,
|
|
235
|
-
config.settleAfterMs,
|
|
236
|
-
config.settleDrainMaxMs,
|
|
237
|
-
config.settleDrainMinMs,
|
|
238
|
-
config.targetBufferMs,
|
|
239
|
-
scheduleFrameWake,
|
|
240
|
-
stopFrameLoop
|
|
241
|
-
]);
|
|
242
|
-
startFrameLoopRef.current = startFrameLoop;
|
|
243
|
-
useEffect(() => {
|
|
244
|
-
if (!enabled) {
|
|
245
|
-
syncImmediate(content);
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
const prevTargetContent = targetContentRef.current;
|
|
249
|
-
if (content === prevTargetContent) return;
|
|
250
|
-
const now = getNow();
|
|
251
|
-
if (!content.startsWith(prevTargetContent)) {
|
|
252
|
-
syncImmediate(content);
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
if (config.bypassFencedLanguages.length > 0) {
|
|
256
|
-
const openLang = findOpenFenceLanguage(content);
|
|
257
|
-
if (openLang !== null && config.bypassFencedLanguages.includes(openLang)) {
|
|
258
|
-
syncImmediate(content);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
const appendedChars = [...content.slice(prevTargetContent.length)];
|
|
263
|
-
const appendedCount = appendedChars.length;
|
|
264
|
-
profiler?.recordInputAppend({
|
|
265
|
-
appendedChars: appendedCount,
|
|
266
|
-
contentLength: countChars(content)
|
|
267
|
-
});
|
|
268
|
-
if (appendedCount > config.largeAppendChars) {
|
|
269
|
-
syncImmediate(content);
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
targetContentRef.current = content;
|
|
273
|
-
targetCharsRef.current.push(...appendedChars);
|
|
274
|
-
targetCountRef.current += appendedCount;
|
|
275
|
-
tailUnitsRef.current = findTailUnits(content);
|
|
276
|
-
const deltaChars = targetCountRef.current - lastInputCountRef.current;
|
|
277
|
-
const deltaMs = Math.max(1, now - lastInputTsRef.current);
|
|
278
|
-
if (deltaChars > 0) {
|
|
279
|
-
const instantCps = deltaChars * 1e3 / deltaMs;
|
|
280
|
-
const normalizedInstantCps = clamp(instantCps, config.minCps, config.maxFlushCps * 2);
|
|
281
|
-
const chunkEmaAlpha = .35;
|
|
282
|
-
chunkSizeEmaRef.current = chunkSizeEmaRef.current * .65 + appendedCount * chunkEmaAlpha;
|
|
283
|
-
arrivalCpsEmaRef.current = arrivalCpsEmaRef.current * .65 + normalizedInstantCps * chunkEmaAlpha;
|
|
284
|
-
const clampedCps = clamp(instantCps, config.minCps, config.maxActiveCps);
|
|
285
|
-
emaCpsRef.current = emaCpsRef.current * (1 - config.emaAlpha) + clampedCps * config.emaAlpha;
|
|
286
|
-
}
|
|
287
|
-
lastInputTsRef.current = now;
|
|
288
|
-
lastInputCountRef.current = targetCountRef.current;
|
|
289
|
-
startFrameLoop();
|
|
290
|
-
}, [
|
|
291
|
-
config.bypassFencedLanguages,
|
|
292
|
-
config.emaAlpha,
|
|
293
|
-
config.largeAppendChars,
|
|
294
|
-
config.maxActiveCps,
|
|
295
|
-
config.maxCps,
|
|
296
|
-
config.maxFlushCps,
|
|
297
|
-
config.minCps,
|
|
298
|
-
content,
|
|
299
|
-
enabled,
|
|
300
|
-
startFrameLoop,
|
|
301
|
-
syncImmediate,
|
|
302
|
-
profiler
|
|
303
|
-
]);
|
|
304
|
-
useEffect(() => {
|
|
305
|
-
return () => {
|
|
306
|
-
stopScheduling();
|
|
307
|
-
};
|
|
308
|
-
}, [stopScheduling]);
|
|
309
|
-
return displayedContent;
|
|
310
|
-
};
|
|
311
|
-
//#endregion
|
|
312
|
-
export { countChars, useSmoothStreamContent };
|
|
313
|
-
|
|
314
|
-
//# sourceMappingURL=useSmoothStreamContent.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useSmoothStreamContent.mjs","names":["now"],"sources":["../../../src/Markdown/SyntaxMarkdown/useSmoothStreamContent.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { useStreamdownProfiler } from '@/Markdown/streamProfiler';\nimport { type StreamSmoothingPreset } from '@/Markdown/type';\nimport { getNow } from '@/utils/getNow';\n\nimport { findOpenFenceLanguage } from './fenceState';\n\ninterface StreamSmoothingPresetConfig {\n activeInputWindowMs: number;\n /**\n * Code-fence languages whose contents bypass smoothing entirely. While\n * the input ends inside an open fence in this set, every chunk is\n * `syncImmediate`-d to the display so downstream consumers (the\n * HtmlPreview iframe in particular) see partial HTML at the demo's\n * actual production rate instead of waiting on the smoother's\n * ~maxFlushCps budget. As soon as the fence closes — or another\n * non-bypass fence opens — smoothing resumes for the rest of the\n * stream. Default `['html']` covers the artifact case that motivated\n * this; tune via preset if you need to bypass `mermaid`, `svg`, etc.\n */\n bypassFencedLanguages: readonly string[];\n defaultCps: number;\n emaAlpha: number;\n flushCps: number;\n largeAppendChars: number;\n maxActiveCps: number;\n maxCps: number;\n maxFlushCps: number;\n minCommitIntervalMs: number;\n minCps: number;\n settleAfterMs: number;\n settleDrainMaxMs: number;\n settleDrainMinMs: number;\n targetBufferMs: number;\n}\n\nconst DEFAULT_BYPASS_LANGUAGES = ['html'] as const;\n\nconst PRESET_CONFIG: Record<StreamSmoothingPreset, StreamSmoothingPresetConfig> = {\n balanced: {\n activeInputWindowMs: 220,\n bypassFencedLanguages: DEFAULT_BYPASS_LANGUAGES,\n defaultCps: 38,\n emaAlpha: 0.2,\n flushCps: 120,\n largeAppendChars: 120,\n maxActiveCps: 132,\n maxCps: 72,\n maxFlushCps: 280,\n minCommitIntervalMs: 48,\n minCps: 18,\n settleAfterMs: 360,\n settleDrainMaxMs: 520,\n settleDrainMinMs: 180,\n targetBufferMs: 120,\n },\n realtime: {\n activeInputWindowMs: 140,\n bypassFencedLanguages: DEFAULT_BYPASS_LANGUAGES,\n defaultCps: 50,\n emaAlpha: 0.3,\n flushCps: 170,\n largeAppendChars: 180,\n maxActiveCps: 180,\n maxCps: 96,\n maxFlushCps: 360,\n minCommitIntervalMs: 32,\n minCps: 24,\n settleAfterMs: 260,\n settleDrainMaxMs: 360,\n settleDrainMinMs: 140,\n targetBufferMs: 40,\n },\n silky: {\n activeInputWindowMs: 320,\n bypassFencedLanguages: DEFAULT_BYPASS_LANGUAGES,\n defaultCps: 28,\n emaAlpha: 0.14,\n flushCps: 96,\n largeAppendChars: 100,\n maxActiveCps: 102,\n maxCps: 56,\n maxFlushCps: 220,\n minCommitIntervalMs: 56,\n minCps: 14,\n settleAfterMs: 460,\n settleDrainMaxMs: 680,\n settleDrainMinMs: 240,\n targetBufferMs: 170,\n },\n};\n\nconst clamp = (value: number, min: number, max: number): number => {\n return Math.min(max, Math.max(min, value));\n};\n\n// Every reveal commit re-parses and re-wraps the entire trailing block, so\n// the per-commit cost grows linearly with how far the content is from the\n// last block boundary. Widen the commit interval as that distance grows —\n// long paragraphs/code fences keep total work bounded while short blocks\n// stay at the preset's snappy interval. Per-char animation-delay stagger\n// hides the lower commit rate.\nconst MAX_COMMIT_INTERVAL_MS = 96;\nconst COMMIT_INTERVAL_TAIL_SCALE_UNITS = 256;\n\nconst findTailUnits = (content: string): number => {\n const boundary = content.lastIndexOf('\\n\\n');\n return boundary === -1 ? content.length : content.length - boundary - 2;\n};\n\nexport const countChars = (text: string): number => {\n let count = 0;\n for (const _char of text) count += 1;\n return count;\n};\n\ninterface UseSmoothStreamContentOptions {\n enabled?: boolean;\n preset?: StreamSmoothingPreset;\n}\n\nexport const useSmoothStreamContent = (\n content: string,\n { enabled = true, preset = 'balanced' }: UseSmoothStreamContentOptions = {},\n): string => {\n const config = PRESET_CONFIG[preset];\n const profiler = useStreamdownProfiler();\n const [displayedContent, setDisplayedContent] = useState(content);\n\n const displayedContentRef = useRef(content);\n const displayedCountRef = useRef(countChars(content));\n\n const targetContentRef = useRef(content);\n const targetCharsRef = useRef([...content]);\n const targetCountRef = useRef(targetCharsRef.current.length);\n\n const emaCpsRef = useRef(config.defaultCps);\n const tailUnitsRef = useRef(findTailUnits(content));\n const lastInputTsRef = useRef(0);\n const lastInputCountRef = useRef(targetCountRef.current);\n const chunkSizeEmaRef = useRef(1);\n const arrivalCpsEmaRef = useRef(config.defaultCps);\n\n const rafRef = useRef<number | null>(null);\n const lastFrameTsRef = useRef<number | null>(null);\n const wakeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearWakeTimer = useCallback(() => {\n if (wakeTimerRef.current !== null) {\n clearTimeout(wakeTimerRef.current);\n wakeTimerRef.current = null;\n }\n }, []);\n\n const stopFrameLoop = useCallback(() => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n lastFrameTsRef.current = null;\n }, []);\n\n const stopScheduling = useCallback(() => {\n stopFrameLoop();\n clearWakeTimer();\n }, [clearWakeTimer, stopFrameLoop]);\n\n const startFrameLoopRef = useRef<() => void>(() => {});\n\n const scheduleFrameWake = useCallback(\n (delayMs: number) => {\n clearWakeTimer();\n\n wakeTimerRef.current = setTimeout(\n () => {\n wakeTimerRef.current = null;\n startFrameLoopRef.current();\n },\n Math.max(1, Math.ceil(delayMs)),\n );\n },\n [clearWakeTimer],\n );\n\n const syncImmediate = useCallback(\n (nextContent: string) => {\n stopScheduling();\n\n const chars = [...nextContent];\n const now = getNow();\n\n targetContentRef.current = nextContent;\n targetCharsRef.current = chars;\n targetCountRef.current = chars.length;\n\n displayedContentRef.current = nextContent;\n displayedCountRef.current = chars.length;\n setDisplayedContent(nextContent);\n\n emaCpsRef.current = config.defaultCps;\n chunkSizeEmaRef.current = 1;\n arrivalCpsEmaRef.current = config.defaultCps;\n tailUnitsRef.current = findTailUnits(nextContent);\n lastInputTsRef.current = now;\n lastInputCountRef.current = chars.length;\n },\n [config.defaultCps, stopScheduling],\n );\n\n const startFrameLoop = useCallback(() => {\n clearWakeTimer();\n if (rafRef.current !== null) return;\n\n const tick = (ts: number) => {\n const targetCount = targetCountRef.current;\n const displayedCount = displayedCountRef.current;\n const backlog = targetCount - displayedCount;\n\n if (backlog <= 0) {\n stopFrameLoop();\n return;\n }\n\n if (lastFrameTsRef.current === null) {\n lastFrameTsRef.current = ts;\n rafRef.current = requestAnimationFrame(tick);\n return;\n }\n\n // Reveal commits are throttled below the display refresh rate: each\n // commit re-renders the tail block and re-runs remend + lexing, so\n // committing at 60-120fps burns CPU without visible benefit — the\n // per-char stagger inside a commit is carried by animation-delay.\n const commitIntervalMs = Math.min(\n MAX_COMMIT_INTERVAL_MS,\n config.minCommitIntervalMs * (1 + tailUnitsRef.current / COMMIT_INTERVAL_TAIL_SCALE_UNITS),\n );\n const frameIntervalMs = Math.max(0, ts - lastFrameTsRef.current);\n if (frameIntervalMs < commitIntervalMs) {\n rafRef.current = requestAnimationFrame(tick);\n return;\n }\n\n const frameStart = getNow();\n const dtSeconds = Math.max(0.001, Math.min(frameIntervalMs / 1000, 0.12));\n lastFrameTsRef.current = ts;\n\n const now = frameStart;\n const idleMs = now - lastInputTsRef.current;\n const inputActive = idleMs <= config.activeInputWindowMs;\n const settling = !inputActive && idleMs >= config.settleAfterMs;\n\n const baseCps = clamp(emaCpsRef.current, config.minCps, config.maxCps);\n const baseLagChars = Math.max(1, Math.round((baseCps * config.targetBufferMs) / 1000));\n const lagUpperBound = Math.max(baseLagChars + 2, baseLagChars * 3);\n const targetLagChars = inputActive\n ? Math.round(\n clamp(baseLagChars + chunkSizeEmaRef.current * 0.35, baseLagChars, lagUpperBound),\n )\n : 0;\n const desiredDisplayed = Math.max(0, targetCount - targetLagChars);\n\n let currentCps: number;\n if (inputActive) {\n const backlogPressure = targetLagChars > 0 ? backlog / targetLagChars : 1;\n const chunkPressure = targetLagChars > 0 ? chunkSizeEmaRef.current / targetLagChars : 1;\n const arrivalPressure = arrivalCpsEmaRef.current / Math.max(baseCps, 1);\n const combinedPressure = clamp(\n backlogPressure * 0.6 + chunkPressure * 0.25 + arrivalPressure * 0.15,\n 1,\n 4.5,\n );\n const activeCap = clamp(\n config.maxActiveCps + chunkSizeEmaRef.current * 6,\n config.maxActiveCps,\n config.maxFlushCps,\n );\n currentCps = clamp(baseCps * combinedPressure, config.minCps, activeCap);\n } else if (settling) {\n // If upstream likely ended, cap the remaining tail duration so\n // we do not keep replaying old backlog for seconds.\n const drainTargetMs = clamp(backlog * 8, config.settleDrainMinMs, config.settleDrainMaxMs);\n const settleCps = (backlog * 1000) / drainTargetMs;\n currentCps = clamp(settleCps, config.flushCps, config.maxFlushCps);\n } else {\n const idleFlushCps = Math.max(\n config.flushCps,\n baseCps * 1.8,\n arrivalCpsEmaRef.current * 0.8,\n );\n currentCps = clamp(idleFlushCps, config.flushCps, config.maxFlushCps);\n }\n\n const urgentBacklog = inputActive && targetLagChars > 0 && backlog > targetLagChars * 2.2;\n const burstyInput = inputActive && chunkSizeEmaRef.current >= targetLagChars * 0.9;\n const minRevealChars = inputActive ? (urgentBacklog || burstyInput ? 2 : 1) : 2;\n let revealChars = Math.max(minRevealChars, Math.round(currentCps * dtSeconds));\n\n if (inputActive) {\n const shortfall = desiredDisplayed - displayedCount;\n if (shortfall <= 0) {\n stopFrameLoop();\n scheduleFrameWake(config.activeInputWindowMs - idleMs);\n\n profiler?.recordAnimationFrame({\n backlog,\n durationMs: getNow() - frameStart,\n frameIntervalMs,\n inputActive,\n revealChars: 0,\n settling,\n });\n return;\n }\n revealChars = Math.min(revealChars, shortfall, backlog);\n } else {\n revealChars = Math.min(revealChars, backlog);\n }\n\n const nextCount = displayedCount + revealChars;\n const segment = targetCharsRef.current.slice(displayedCount, nextCount).join('');\n\n if (segment) {\n const nextDisplayed = displayedContentRef.current + segment;\n displayedContentRef.current = nextDisplayed;\n displayedCountRef.current = nextCount;\n setDisplayedContent(nextDisplayed);\n } else {\n displayedContentRef.current = targetContentRef.current;\n displayedCountRef.current = targetCount;\n setDisplayedContent(targetContentRef.current);\n }\n\n profiler?.recordAnimationFrame({\n backlog,\n durationMs: getNow() - frameStart,\n frameIntervalMs,\n inputActive,\n revealChars: segment ? revealChars : backlog,\n settling,\n });\n\n rafRef.current = requestAnimationFrame(tick);\n };\n\n rafRef.current = requestAnimationFrame(tick);\n }, [\n clearWakeTimer,\n config.activeInputWindowMs,\n config.flushCps,\n config.maxActiveCps,\n config.maxCps,\n config.maxFlushCps,\n config.minCommitIntervalMs,\n config.minCps,\n config.settleAfterMs,\n config.settleDrainMaxMs,\n config.settleDrainMinMs,\n config.targetBufferMs,\n scheduleFrameWake,\n stopFrameLoop,\n ]);\n startFrameLoopRef.current = startFrameLoop;\n\n useEffect(() => {\n if (!enabled) {\n syncImmediate(content);\n return;\n }\n\n const prevTargetContent = targetContentRef.current;\n if (content === prevTargetContent) return;\n\n const now = getNow();\n const appendOnly = content.startsWith(prevTargetContent);\n\n if (!appendOnly) {\n syncImmediate(content);\n return;\n }\n\n // Bypass smoothing entirely while the input ends inside an open\n // fence whose language is opted out — see the preset config.\n // Without this, a 5 KB inline `<style>` block can keep `</style>`\n // (and therefore the HtmlPreview head-close trigger) trapped in the\n // smoother's buffer for tens of seconds, leaving the iframe pinned\n // on the loading placeholder while the artifact \"looks stuck\".\n if (config.bypassFencedLanguages.length > 0) {\n const openLang = findOpenFenceLanguage(content);\n if (openLang !== null && config.bypassFencedLanguages.includes(openLang)) {\n syncImmediate(content);\n return;\n }\n }\n\n const appended = content.slice(prevTargetContent.length);\n const appendedChars = [...appended];\n const appendedCount = appendedChars.length;\n\n profiler?.recordInputAppend({\n appendedChars: appendedCount,\n contentLength: countChars(content),\n });\n\n if (appendedCount > config.largeAppendChars) {\n syncImmediate(content);\n return;\n }\n\n targetContentRef.current = content;\n targetCharsRef.current.push(...appendedChars);\n targetCountRef.current += appendedCount;\n\n tailUnitsRef.current = findTailUnits(content);\n\n const deltaChars = targetCountRef.current - lastInputCountRef.current;\n const deltaMs = Math.max(1, now - lastInputTsRef.current);\n\n if (deltaChars > 0) {\n const instantCps = (deltaChars * 1000) / deltaMs;\n const normalizedInstantCps = clamp(instantCps, config.minCps, config.maxFlushCps * 2);\n const chunkEmaAlpha = 0.35;\n chunkSizeEmaRef.current =\n chunkSizeEmaRef.current * (1 - chunkEmaAlpha) + appendedCount * chunkEmaAlpha;\n arrivalCpsEmaRef.current =\n arrivalCpsEmaRef.current * (1 - chunkEmaAlpha) + normalizedInstantCps * chunkEmaAlpha;\n\n const clampedCps = clamp(instantCps, config.minCps, config.maxActiveCps);\n emaCpsRef.current = emaCpsRef.current * (1 - config.emaAlpha) + clampedCps * config.emaAlpha;\n }\n\n lastInputTsRef.current = now;\n lastInputCountRef.current = targetCountRef.current;\n\n startFrameLoop();\n }, [\n config.bypassFencedLanguages,\n config.emaAlpha,\n config.largeAppendChars,\n config.maxActiveCps,\n config.maxCps,\n config.maxFlushCps,\n config.minCps,\n content,\n enabled,\n startFrameLoop,\n syncImmediate,\n profiler,\n ]);\n\n useEffect(() => {\n return () => {\n stopScheduling();\n };\n }, [stopScheduling]);\n\n return displayedContent;\n};\n"],"mappings":"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"}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { countChars } from "./useSmoothStreamContent.mjs";
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
-
//#region src/Markdown/SyntaxMarkdown/useStreamQueue.ts
|
|
4
|
-
const BASE_DELAY = 18;
|
|
5
|
-
const ACCELERATION_FACTOR = .3;
|
|
6
|
-
const MAX_BLOCK_DURATION = 3e3;
|
|
7
|
-
function computeCharDelay(queueLength, charCount) {
|
|
8
|
-
const acceleration = 1 + queueLength * ACCELERATION_FACTOR;
|
|
9
|
-
let delay = BASE_DELAY / acceleration;
|
|
10
|
-
delay = Math.min(delay, MAX_BLOCK_DURATION / Math.max(charCount, 1));
|
|
11
|
-
return delay;
|
|
12
|
-
}
|
|
13
|
-
function useStreamQueue(blocks) {
|
|
14
|
-
const [revealedCount, setRevealedCount] = useState(0);
|
|
15
|
-
const timerRef = useRef(null);
|
|
16
|
-
const prevBlocksLenRef = useRef(0);
|
|
17
|
-
const minRevealedRef = useRef(0);
|
|
18
|
-
if (blocks.length === 0 && prevBlocksLenRef.current !== 0) minRevealedRef.current = 0;
|
|
19
|
-
if (blocks.length > prevBlocksLenRef.current && prevBlocksLenRef.current > 0) {
|
|
20
|
-
const prevTail = prevBlocksLenRef.current - 1;
|
|
21
|
-
minRevealedRef.current = Math.max(minRevealedRef.current, prevTail + 1);
|
|
22
|
-
}
|
|
23
|
-
prevBlocksLenRef.current = blocks.length;
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (blocks.length === 0) {
|
|
26
|
-
setRevealedCount(0);
|
|
27
|
-
minRevealedRef.current = 0;
|
|
28
|
-
if (timerRef.current) {
|
|
29
|
-
clearTimeout(timerRef.current);
|
|
30
|
-
timerRef.current = null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}, [blocks.length]);
|
|
34
|
-
const effectiveRevealedCount = Math.max(revealedCount, minRevealedRef.current);
|
|
35
|
-
const tailIndex = blocks.length - 1;
|
|
36
|
-
const getBlockState = useCallback((index) => {
|
|
37
|
-
if (index < effectiveRevealedCount) return "revealed";
|
|
38
|
-
if (index === effectiveRevealedCount && index < tailIndex) return "animating";
|
|
39
|
-
if (index === effectiveRevealedCount && index === tailIndex) return "streaming";
|
|
40
|
-
return "queued";
|
|
41
|
-
}, [effectiveRevealedCount, tailIndex]);
|
|
42
|
-
const queueLength = Math.max(0, tailIndex - effectiveRevealedCount - 1);
|
|
43
|
-
const animatingIndex = effectiveRevealedCount < tailIndex ? effectiveRevealedCount : -1;
|
|
44
|
-
const animatingCharCount = animatingIndex >= 0 ? countChars(blocks[animatingIndex]?.content ?? "") : 0;
|
|
45
|
-
const activeIndex = animatingIndex >= 0 ? animatingIndex : animatingIndex < 0 && tailIndex >= effectiveRevealedCount ? tailIndex : -1;
|
|
46
|
-
const activeCharCount = activeIndex >= 0 ? countChars(blocks[activeIndex]?.content ?? "") : 0;
|
|
47
|
-
const frozenRef = useRef({
|
|
48
|
-
delay: BASE_DELAY,
|
|
49
|
-
index: -1
|
|
50
|
-
});
|
|
51
|
-
if (activeIndex >= 0 && activeIndex !== frozenRef.current.index) frozenRef.current = {
|
|
52
|
-
delay: computeCharDelay(queueLength, activeCharCount),
|
|
53
|
-
index: activeIndex
|
|
54
|
-
};
|
|
55
|
-
const charDelay = activeIndex >= 0 ? frozenRef.current.delay : BASE_DELAY;
|
|
56
|
-
const onAnimationDone = useCallback(() => {
|
|
57
|
-
setRevealedCount(effectiveRevealedCount + 1);
|
|
58
|
-
}, [effectiveRevealedCount]);
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
if (timerRef.current) {
|
|
61
|
-
clearTimeout(timerRef.current);
|
|
62
|
-
timerRef.current = null;
|
|
63
|
-
}
|
|
64
|
-
if (animatingIndex < 0) return;
|
|
65
|
-
const totalTime = Math.max(0, (animatingCharCount - 1) * charDelay) + 180;
|
|
66
|
-
timerRef.current = setTimeout(onAnimationDone, totalTime);
|
|
67
|
-
return () => {
|
|
68
|
-
if (timerRef.current) {
|
|
69
|
-
clearTimeout(timerRef.current);
|
|
70
|
-
timerRef.current = null;
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
}, [
|
|
74
|
-
animatingIndex,
|
|
75
|
-
animatingCharCount,
|
|
76
|
-
charDelay,
|
|
77
|
-
onAnimationDone
|
|
78
|
-
]);
|
|
79
|
-
return {
|
|
80
|
-
charDelay,
|
|
81
|
-
getBlockState,
|
|
82
|
-
queueLength
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
//#endregion
|
|
86
|
-
export { useStreamQueue };
|
|
87
|
-
|
|
88
|
-
//# sourceMappingURL=useStreamQueue.mjs.map
|