@lobehub/ui 5.6.0 → 5.6.2
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/Accordion/Accordion.d.mts +2 -2
- package/es/Accordion/AccordionItem.d.mts +2 -2
- package/es/ActionIcon/ActionIcon.d.mts +2 -2
- package/es/Alert/Alert.d.mts +2 -2
- package/es/AutoComplete/Select.d.mts +2 -2
- package/es/Avatar/AvatarGroup/index.d.mts +2 -2
- package/es/CodeDiff/CodeDiff.d.mts +2 -2
- package/es/CodeDiff/PatchDiff.d.mts +2 -2
- package/es/CodeEditor/CodeEditor.d.mts +2 -2
- package/es/Collapse/Collapse.d.mts +2 -2
- package/es/ConfigProvider/index.d.mts +2 -2
- package/es/CopyButton/CopyButton.d.mts +2 -2
- package/es/DatePicker/DatePicker.d.mts +2 -2
- package/es/DraggablePanel/components/DraggablePanelBody.d.mts +2 -2
- package/es/DraggablePanel/components/DraggablePanelContainer.d.mts +2 -2
- package/es/DraggablePanel/components/DraggablePanelFooter.d.mts +2 -2
- package/es/DraggablePanel/components/DraggablePanelHeader.d.mts +2 -2
- package/es/DraggableSideNav/DraggableSideNav.d.mts +2 -2
- package/es/Drawer/Drawer.d.mts +2 -2
- package/es/Dropdown/Dropdown.d.mts +2 -2
- package/es/EditableText/EditableText.d.mts +2 -2
- package/es/EmojiPicker/EmojiPicker.d.mts +2 -2
- package/es/Flex/FlexBasic.d.mts +2 -2
- package/es/FontLoader/index.d.mts +2 -2
- package/es/Footer/Footer.d.mts +2 -2
- package/es/Form/components/FormGroup.d.mts +2 -2
- package/es/Form/components/FormItem.d.mts +2 -2
- package/es/Form/components/FormSubmitFooter.d.mts +2 -2
- package/es/FormModal/FormModal.d.mts +2 -2
- package/es/Freeze/Freeze.d.mts +2 -2
- package/es/GuideCard/GuideCard.d.mts +2 -2
- package/es/Header/Header.d.mts +2 -2
- package/es/Highlighter/Highlighter.d.mts +2 -2
- package/es/Highlighter/SyntaxHighlighter/index.d.mts +2 -2
- package/es/Hotkey/Hotkey.d.mts +2 -2
- package/es/HotkeyInput/HotkeyInput.d.mts +2 -2
- package/es/Icon/Icon.d.mts +2 -2
- package/es/Icon/components/IconProvider.d.mts +3 -3
- package/es/Image/PreviewGroup.d.mts +2 -2
- package/es/ImageSelect/ImageSelect.d.mts +2 -2
- package/es/Input/Input.d.mts +2 -2
- package/es/Input/InputNumber.d.mts +2 -2
- package/es/Input/InputPassword.d.mts +2 -2
- package/es/Input/TextArea.d.mts +2 -2
- package/es/Layout/components/LayoutFooter.d.mts +2 -2
- package/es/Layout/components/LayoutHeader.d.mts +2 -2
- package/es/Layout/components/LayoutMain.d.mts +2 -2
- package/es/Layout/components/LayoutSidebar.d.mts +2 -2
- package/es/Layout/components/LayoutSidebarInner.d.mts +2 -2
- package/es/Layout/components/LayoutToc.d.mts +2 -2
- package/es/List/ListItem/index.d.mts +2 -2
- package/es/Markdown/Markdown.d.mts +2 -2
- package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs +173 -33
- package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs.map +1 -1
- package/es/Markdown/SyntaxMarkdown/streamAnimationMeta.mjs +18 -0
- package/es/Markdown/SyntaxMarkdown/streamAnimationMeta.mjs.map +1 -0
- package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs +62 -10
- package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs.map +1 -1
- package/es/Markdown/Typography.d.mts +2 -2
- package/es/Markdown/components/SearchResultCards/index.d.mts +2 -2
- package/es/Markdown/streamProfiler/StreamdownProfilerProvider.mjs +21 -0
- package/es/Markdown/streamProfiler/StreamdownProfilerProvider.mjs.map +1 -0
- package/es/MaskShadow/MaskShadow.d.mts +2 -2
- package/es/Menu/Menu.d.mts +2 -2
- package/es/Mermaid/Mermaid.d.mts +2 -2
- package/es/Mermaid/SyntaxMermaid/index.d.mts +2 -2
- package/es/Modal/Modal.d.mts +2 -2
- package/es/Modal/ModalProvider.d.mts +2 -2
- package/es/Modal/imperative.d.mts +2 -2
- package/es/MotionProvider/index.d.mts +2 -2
- package/es/SearchBar/SearchBar.d.mts +2 -2
- package/es/Segmented/Segmented.d.mts +2 -2
- package/es/Select/Select.d.mts +2 -2
- package/es/SideNav/SideNav.d.mts +2 -2
- package/es/SliderWithInput/SliderWithInput.d.mts +2 -2
- package/es/SortableList/components/DragHandle.d.mts +2 -2
- package/es/SortableList/components/SortableItem.d.mts +2 -2
- package/es/ThemeProvider/ThemeProvider.d.mts +2 -2
- package/es/Toc/Toc.d.mts +2 -2
- package/es/Video/index.d.mts +2 -2
- package/es/awesome/AuroraBackground/AuroraBackground.d.mts +2 -2
- package/es/awesome/BottomGradientButton/BottomGradientButton.d.mts +2 -2
- package/es/awesome/Features/Features.d.mts +2 -2
- package/es/awesome/Giscus/Giscus.d.mts +2 -2
- package/es/awesome/GradientButton/GradientButton.d.mts +2 -2
- package/es/awesome/GridBackground/GridBackground.d.mts +2 -2
- package/es/awesome/GridBackground/GridShowcase.d.mts +2 -2
- package/es/awesome/Hero/Hero.d.mts +2 -2
- package/es/awesome/Spline/Spine.d.mts +2 -2
- package/es/awesome/Spotlight/Spotlight.d.mts +2 -2
- package/es/awesome/SpotlightCard/SpotlightCard.d.mts +2 -2
- package/es/awesome/TypewriterEffect/TypewriterEffect.d.mts +2 -2
- package/es/base-ui/ContextMenu/ContextMenuHost.d.mts +2 -2
- package/es/base-ui/DropdownMenu/DropdownMenu.d.mts +2 -2
- package/es/base-ui/DropdownMenu/atoms.d.mts +18 -18
- package/es/base-ui/Modal/atoms.d.mts +12 -12
- package/es/base-ui/Modal/context.d.mts +2 -2
- package/es/base-ui/Modal/imperative.d.mts +2 -2
- package/es/base-ui/Popover/ArrowIcon.d.mts +2 -2
- package/es/base-ui/Popover/atoms.d.mts +9 -9
- package/es/base-ui/Popover/context.d.mts +2 -2
- package/es/base-ui/ScrollArea/atoms.d.mts +7 -7
- package/es/base-ui/Select/Select.d.mts +2 -2
- package/es/base-ui/Select/atoms.d.mts +19 -19
- package/es/base-ui/Switch/Switch.d.mts +2 -2
- package/es/base-ui/Switch/atoms.d.mts +4 -4
- package/es/base-ui/Toast/imperative.d.mts +4 -3
- package/es/base-ui/Toast/imperative.mjs +1 -1
- package/es/base-ui/Toast/imperative.mjs.map +1 -1
- package/es/brand/LobeChat/index.d.mts +2 -2
- package/es/brand/LobeHub/index.d.mts +2 -2
- package/es/brand/LogoThree/LogoSpline.d.mts +2 -2
- package/es/brand/LogoThree/index.d.mts +2 -2
- package/es/chat/BackBottom/BackBottom.d.mts +2 -2
- package/es/chat/ChatInputArea/components/ChatInputAreaInner.d.mts +2 -2
- package/es/chat/ChatItem/ChatItem.d.mts +2 -2
- package/es/chat/ChatList/ChatList.d.mts +2 -2
- package/es/chat/EditableMessage/EditableMessage.d.mts +2 -2
- package/es/chat/EditableMessageList/EditableMessageList.d.mts +2 -2
- package/es/chat/MessageInput/MessageInput.d.mts +2 -2
- package/es/chat/MessageModal/MessageModal.d.mts +2 -2
- package/es/color/ColorScales/index.d.mts +2 -2
- package/es/color/CssVar/index.d.mts +2 -2
- package/es/i18n/context.d.mts +2 -2
- package/es/icons/lucideExtra/AndroidIcon.d.mts +2 -2
- package/es/icons/lucideExtra/AppleIcon.d.mts +2 -2
- package/es/icons/lucideExtra/AppstoreIcon.d.mts +3 -3
- package/es/icons/lucideExtra/BotPromptIcon.d.mts +2 -2
- package/es/icons/lucideExtra/BrainOffIcon.d.mts +2 -2
- package/es/icons/lucideExtra/ChromeIcon.d.mts +2 -2
- package/es/icons/lucideExtra/CodepenIcon.d.mts +3 -3
- package/es/icons/lucideExtra/CodesandboxIcon.d.mts +2 -2
- package/es/icons/lucideExtra/CreateBotIcon.d.mts +2 -2
- package/es/icons/lucideExtra/DiscordIcon.d.mts +2 -2
- package/es/icons/lucideExtra/FacebookIcon.d.mts +3 -3
- package/es/icons/lucideExtra/FigmaIcon.d.mts +3 -3
- package/es/icons/lucideExtra/FramerIcon.d.mts +3 -3
- package/es/icons/lucideExtra/GithubIcon.d.mts +3 -3
- package/es/icons/lucideExtra/GitlabIcon.d.mts +2 -2
- package/es/icons/lucideExtra/GlobeOffIcon.d.mts +3 -3
- package/es/icons/lucideExtra/GooglePlayIcon.d.mts +3 -3
- package/es/icons/lucideExtra/GroupBotIcon.d.mts +3 -3
- package/es/icons/lucideExtra/GroupBotSquareIcon.d.mts +3 -3
- package/es/icons/lucideExtra/InstagramIcon.d.mts +3 -3
- package/es/icons/lucideExtra/LeftClickIcon.d.mts +3 -3
- package/es/icons/lucideExtra/LeftDoubleClickIcon.d.mts +2 -2
- package/es/icons/lucideExtra/LinkedinIcon.d.mts +2 -2
- package/es/icons/lucideExtra/McpIcon.d.mts +2 -2
- package/es/icons/lucideExtra/NotionIcon.d.mts +2 -2
- package/es/icons/lucideExtra/PocketIcon.d.mts +2 -2
- package/es/icons/lucideExtra/ProviderIcon.d.mts +2 -2
- package/es/icons/lucideExtra/RailSymbolIcon.d.mts +2 -2
- package/es/icons/lucideExtra/RedditIcon.d.mts +2 -2
- package/es/icons/lucideExtra/RightClickIcon.d.mts +2 -2
- package/es/icons/lucideExtra/RightDoubleClickIcon.d.mts +2 -2
- package/es/icons/lucideExtra/ShapesUploadIcon.d.mts +2 -2
- package/es/icons/lucideExtra/SkillsIcon.d.mts +2 -2
- package/es/icons/lucideExtra/SlackIcon.d.mts +2 -2
- package/es/icons/lucideExtra/ThinkIcon.d.mts +2 -2
- package/es/icons/lucideExtra/TreeDownRightIcon.d.mts +2 -2
- package/es/icons/lucideExtra/TreeUpDownRightIcon.d.mts +3 -3
- package/es/mdx/Mdx/index.d.mts +2 -2
- package/es/mobile/ChatHeader/ChatHeaderTitle.d.mts +2 -2
- package/es/mobile/ChatInputArea/components/ChatSendButton.d.mts +2 -2
- package/es/mobile/TabBar/TabBar.d.mts +2 -2
- package/es/storybook/StoryBook/index.d.mts +2 -2
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LayoutTocProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react25 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Layout/components/LayoutToc.d.ts
|
|
5
|
-
declare const LayoutToc:
|
|
5
|
+
declare const LayoutToc: react25.NamedExoticComponent<LayoutTocProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { LayoutToc };
|
|
8
8
|
//# sourceMappingURL=LayoutToc.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ListItemProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react10 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/List/ListItem/index.d.ts
|
|
5
|
-
declare const ListItem:
|
|
5
|
+
declare const ListItem: react10.NamedExoticComponent<ListItemProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { ListItem };
|
|
8
8
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MarkdownProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react12 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Markdown/Markdown.d.ts
|
|
5
|
-
declare const Markdown:
|
|
5
|
+
declare const Markdown: react12.NamedExoticComponent<MarkdownProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { Markdown };
|
|
8
8
|
//# sourceMappingURL=Markdown.d.mts.map
|
|
@@ -6,10 +6,12 @@ import { useMarkdownContent } from "../../hooks/useMarkdown/useMarkdownContent.m
|
|
|
6
6
|
import { useMarkdownRehypePlugins } from "../../hooks/useMarkdown/useMarkdownRehypePlugins.mjs";
|
|
7
7
|
import { useMarkdownRemarkPlugins } from "../../hooks/useMarkdown/useMarkdownRemarkPlugins.mjs";
|
|
8
8
|
import { rehypeStreamAnimated } from "../plugins/rehypeStreamAnimated.mjs";
|
|
9
|
+
import { useStreamdownProfiler } from "../streamProfiler/StreamdownProfilerProvider.mjs";
|
|
10
|
+
import { resolveBlockAnimationMeta } from "./streamAnimationMeta.mjs";
|
|
9
11
|
import { styles } from "./style.mjs";
|
|
10
12
|
import { useSmoothStreamContent } from "./useSmoothStreamContent.mjs";
|
|
11
13
|
import { useStreamQueue } from "./useStreamQueue.mjs";
|
|
12
|
-
import { createElement, memo, useEffect, useId, useMemo, useRef } from "react";
|
|
14
|
+
import { Profiler, createElement, memo, useCallback, useEffect, useId, useMemo, useRef } from "react";
|
|
13
15
|
import { jsx } from "react/jsx-runtime";
|
|
14
16
|
import Markdown from "react-markdown";
|
|
15
17
|
import { marked } from "marked";
|
|
@@ -17,9 +19,13 @@ import remend from "remend";
|
|
|
17
19
|
|
|
18
20
|
//#region src/Markdown/SyntaxMarkdown/StreamdownRender.tsx
|
|
19
21
|
const STREAM_FADE_DURATION = 280;
|
|
22
|
+
const REVEALED_STREAM_PLUGIN = [rehypeStreamAnimated, { revealed: true }];
|
|
20
23
|
function countChars(text) {
|
|
21
24
|
return [...text].length;
|
|
22
25
|
}
|
|
26
|
+
function getNow() {
|
|
27
|
+
return typeof performance === "undefined" ? Date.now() : performance.now();
|
|
28
|
+
}
|
|
23
29
|
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
24
30
|
const isDeepEqualValue = (a, b) => {
|
|
25
31
|
if (a === b) return true;
|
|
@@ -64,19 +70,27 @@ const StreamdownBlock = memo(({ children, ...rest }) => {
|
|
|
64
70
|
StreamdownBlock.displayName = "StreamdownBlock";
|
|
65
71
|
const StreamdownRender = memo(({ children, ...rest }) => {
|
|
66
72
|
const { streamSmoothingPreset = "balanced" } = useMarkdownContext();
|
|
73
|
+
const profiler = useStreamdownProfiler();
|
|
67
74
|
const escapedContent = useMarkdownContent(children || "");
|
|
68
75
|
const components = useMarkdownComponents();
|
|
69
76
|
const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());
|
|
70
77
|
const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());
|
|
71
78
|
const generatedId = useId();
|
|
72
79
|
const smoothedContent = useSmoothStreamContent(typeof escapedContent === "string" ? escapedContent : "", { preset: streamSmoothingPreset });
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
const processedContentResult = useMemo(() => {
|
|
81
|
+
const start = profiler ? getNow() : 0;
|
|
82
|
+
const value = remend(smoothedContent);
|
|
83
|
+
return {
|
|
84
|
+
durationMs: profiler ? getNow() - start : 0,
|
|
85
|
+
value
|
|
86
|
+
};
|
|
87
|
+
}, [profiler, smoothedContent]);
|
|
88
|
+
const processedContent = processedContentResult.value;
|
|
89
|
+
const blocksResult = useMemo(() => {
|
|
90
|
+
const start = profiler ? getNow() : 0;
|
|
77
91
|
const tokens = marked.lexer(processedContent);
|
|
78
92
|
let offset = 0;
|
|
79
|
-
|
|
93
|
+
const value = tokens.map((token) => {
|
|
80
94
|
const block = {
|
|
81
95
|
content: token.raw,
|
|
82
96
|
startOffset: offset
|
|
@@ -84,14 +98,21 @@ const StreamdownRender = memo(({ children, ...rest }) => {
|
|
|
84
98
|
offset += token.raw.length;
|
|
85
99
|
return block;
|
|
86
100
|
});
|
|
87
|
-
|
|
101
|
+
return {
|
|
102
|
+
durationMs: profiler ? getNow() - start : 0,
|
|
103
|
+
value
|
|
104
|
+
};
|
|
105
|
+
}, [processedContent, profiler]);
|
|
106
|
+
const blocks = blocksResult.value;
|
|
88
107
|
const { getBlockState, charDelay } = useStreamQueue(blocks);
|
|
89
108
|
const prevBlockCharCountRef = useRef(/* @__PURE__ */ new Map());
|
|
109
|
+
const blockCharDelayRef = useRef(/* @__PURE__ */ new Map());
|
|
90
110
|
const blockTimelineRef = useRef(/* @__PURE__ */ new Map());
|
|
91
111
|
const lastRenderTsRef = useRef(null);
|
|
92
|
-
const renderTs =
|
|
112
|
+
const renderTs = getNow();
|
|
93
113
|
const frameDt = lastRenderTsRef.current === null ? 0 : Math.max(0, Math.min(renderTs - lastRenderTsRef.current, 120));
|
|
94
|
-
const
|
|
114
|
+
const timelineResult = useMemo(() => {
|
|
115
|
+
const start = profiler ? getNow() : 0;
|
|
95
116
|
const next = /* @__PURE__ */ new Map();
|
|
96
117
|
const prevTimeline = blockTimelineRef.current;
|
|
97
118
|
const prevCharCounts = prevBlockCharCountRef.current;
|
|
@@ -108,50 +129,169 @@ const StreamdownRender = memo(({ children, ...rest }) => {
|
|
|
108
129
|
const minElapsed = Math.max(0, latestCharStart - charDelay * 2);
|
|
109
130
|
next.set(block.startOffset, Math.max(elapsedByTime, minElapsed));
|
|
110
131
|
}
|
|
111
|
-
return
|
|
132
|
+
return {
|
|
133
|
+
durationMs: profiler ? getNow() - start : 0,
|
|
134
|
+
value: next
|
|
135
|
+
};
|
|
112
136
|
}, [
|
|
113
137
|
blocks,
|
|
114
138
|
charDelay,
|
|
115
|
-
frameDt
|
|
139
|
+
frameDt,
|
|
140
|
+
profiler
|
|
141
|
+
]);
|
|
142
|
+
const timelineForRender = timelineResult.value;
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
if (!profiler) return;
|
|
145
|
+
profiler.recordCalculation({
|
|
146
|
+
durationMs: processedContentResult.durationMs,
|
|
147
|
+
name: "content-normalize",
|
|
148
|
+
textLength: processedContent.length
|
|
149
|
+
});
|
|
150
|
+
}, [
|
|
151
|
+
processedContent.length,
|
|
152
|
+
processedContentResult.durationMs,
|
|
153
|
+
profiler
|
|
154
|
+
]);
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!profiler) return;
|
|
157
|
+
profiler.recordCalculation({
|
|
158
|
+
durationMs: blocksResult.durationMs,
|
|
159
|
+
itemCount: blocks.length,
|
|
160
|
+
name: "block-lex",
|
|
161
|
+
textLength: processedContent.length
|
|
162
|
+
});
|
|
163
|
+
}, [
|
|
164
|
+
blocks.length,
|
|
165
|
+
blocksResult.durationMs,
|
|
166
|
+
processedContent.length,
|
|
167
|
+
profiler
|
|
168
|
+
]);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (!profiler) return;
|
|
171
|
+
profiler.recordCalculation({
|
|
172
|
+
durationMs: timelineResult.durationMs,
|
|
173
|
+
itemCount: blocks.length,
|
|
174
|
+
name: "block-timeline",
|
|
175
|
+
textLength: processedContent.length
|
|
176
|
+
});
|
|
177
|
+
}, [
|
|
178
|
+
blocks.length,
|
|
179
|
+
processedContent.length,
|
|
180
|
+
profiler,
|
|
181
|
+
timelineResult.durationMs
|
|
182
|
+
]);
|
|
183
|
+
const blockAnimationMetaResult = useMemo(() => {
|
|
184
|
+
const nextBlockCharDelay = /* @__PURE__ */ new Map();
|
|
185
|
+
const blockAnimationMeta = /* @__PURE__ */ new Map();
|
|
186
|
+
for (const [index, block] of blocks.entries()) {
|
|
187
|
+
const state = getBlockState(index);
|
|
188
|
+
const timelineElapsedMs = timelineForRender.get(block.startOffset) ?? 0;
|
|
189
|
+
const animationMeta = resolveBlockAnimationMeta({
|
|
190
|
+
blockCharCount: countChars(block.content),
|
|
191
|
+
currentCharDelay: charDelay,
|
|
192
|
+
fadeDuration: STREAM_FADE_DURATION,
|
|
193
|
+
previousCharDelay: blockCharDelayRef.current.get(block.startOffset),
|
|
194
|
+
state,
|
|
195
|
+
timelineElapsedMs
|
|
196
|
+
});
|
|
197
|
+
nextBlockCharDelay.set(block.startOffset, animationMeta.charDelay);
|
|
198
|
+
blockAnimationMeta.set(block.startOffset, animationMeta);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
blockAnimationMeta,
|
|
202
|
+
blockCharDelay: nextBlockCharDelay
|
|
203
|
+
};
|
|
204
|
+
}, [
|
|
205
|
+
blocks,
|
|
206
|
+
charDelay,
|
|
207
|
+
getBlockState,
|
|
208
|
+
timelineForRender
|
|
116
209
|
]);
|
|
117
210
|
useEffect(() => {
|
|
118
211
|
const nextCharCount = /* @__PURE__ */ new Map();
|
|
119
212
|
for (const block of blocks) nextCharCount.set(block.startOffset, countChars(block.content));
|
|
213
|
+
blockCharDelayRef.current = blockAnimationMetaResult.blockCharDelay;
|
|
120
214
|
prevBlockCharCountRef.current = nextCharCount;
|
|
121
215
|
blockTimelineRef.current = timelineForRender;
|
|
122
|
-
lastRenderTsRef.current =
|
|
123
|
-
}, [
|
|
124
|
-
|
|
216
|
+
lastRenderTsRef.current = getNow();
|
|
217
|
+
}, [
|
|
218
|
+
blockAnimationMetaResult.blockCharDelay,
|
|
219
|
+
blocks,
|
|
220
|
+
timelineForRender
|
|
221
|
+
]);
|
|
222
|
+
const handleRootRender = useCallback((_, phase, actualDuration, baseDuration) => {
|
|
223
|
+
profiler?.recordRootCommit({
|
|
224
|
+
actualDuration,
|
|
225
|
+
baseDuration,
|
|
226
|
+
blockCount: blocks.length,
|
|
227
|
+
phase,
|
|
228
|
+
textLength: processedContent.length
|
|
229
|
+
});
|
|
230
|
+
}, [
|
|
231
|
+
blocks.length,
|
|
232
|
+
processedContent.length,
|
|
233
|
+
profiler
|
|
234
|
+
]);
|
|
235
|
+
const handleBlockRender = useCallback((id, phase, actualDuration, baseDuration) => {
|
|
236
|
+
if (!profiler) return;
|
|
237
|
+
const [, indexText, offsetText] = id.split(":");
|
|
238
|
+
const blockIndex = Number(indexText);
|
|
239
|
+
if (!Number.isFinite(blockIndex)) return;
|
|
240
|
+
const block = blocks[blockIndex];
|
|
241
|
+
if (!block) return;
|
|
242
|
+
profiler.recordBlockCommit({
|
|
243
|
+
actualDuration,
|
|
244
|
+
baseDuration,
|
|
245
|
+
blockChars: countChars(block.content),
|
|
246
|
+
blockIndex,
|
|
247
|
+
blockKey: offsetText ?? String(block.startOffset),
|
|
248
|
+
phase,
|
|
249
|
+
state: getBlockState(blockIndex)
|
|
250
|
+
});
|
|
251
|
+
}, [
|
|
252
|
+
blocks,
|
|
253
|
+
getBlockState,
|
|
254
|
+
profiler
|
|
255
|
+
]);
|
|
256
|
+
const content = /* @__PURE__ */ jsx("div", {
|
|
125
257
|
className: styles.animated,
|
|
126
258
|
children: blocks.map((block, index) => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
charDelay,
|
|
259
|
+
if (getBlockState(index) === "queued") return null;
|
|
260
|
+
const animationMeta = blockAnimationMetaResult.blockAnimationMeta.get(block.startOffset);
|
|
261
|
+
if (!animationMeta) return null;
|
|
262
|
+
const plugins = animationMeta.settled ? [...baseRehypePlugins, REVEALED_STREAM_PLUGIN] : [...baseRehypePlugins, [rehypeStreamAnimated, {
|
|
263
|
+
charDelay: animationMeta.charDelay,
|
|
133
264
|
fadeDuration: STREAM_FADE_DURATION,
|
|
134
|
-
timelineElapsedMs
|
|
135
|
-
}]];
|
|
136
|
-
else if (state === "animating") plugins = [...baseRehypePlugins, [rehypeStreamAnimated, {
|
|
137
|
-
charDelay,
|
|
138
|
-
fadeDuration: STREAM_FADE_DURATION,
|
|
139
|
-
timelineElapsedMs
|
|
265
|
+
timelineElapsedMs: animationMeta.timelineElapsedMs
|
|
140
266
|
}]];
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
fadeDuration: STREAM_FADE_DURATION,
|
|
144
|
-
timelineElapsedMs
|
|
145
|
-
}]];
|
|
146
|
-
return /* @__PURE__ */ createElement(StreamdownBlock, {
|
|
267
|
+
const key = `${generatedId}-${block.startOffset}`;
|
|
268
|
+
const blockNode = /* @__PURE__ */ jsx(StreamdownBlock, {
|
|
147
269
|
...rest,
|
|
148
270
|
components,
|
|
149
|
-
|
|
271
|
+
rehypePlugins: plugins,
|
|
272
|
+
remarkPlugins,
|
|
273
|
+
children: block.content
|
|
274
|
+
});
|
|
275
|
+
if (!profiler) return /* @__PURE__ */ createElement(StreamdownBlock, {
|
|
276
|
+
...rest,
|
|
277
|
+
components,
|
|
278
|
+
key,
|
|
150
279
|
rehypePlugins: plugins,
|
|
151
280
|
remarkPlugins
|
|
152
281
|
}, block.content);
|
|
282
|
+
return /* @__PURE__ */ jsx(Profiler, {
|
|
283
|
+
id: `streamdown-block:${index}:${block.startOffset}`,
|
|
284
|
+
onRender: handleBlockRender,
|
|
285
|
+
children: blockNode
|
|
286
|
+
}, key);
|
|
153
287
|
})
|
|
154
288
|
});
|
|
289
|
+
if (!profiler) return content;
|
|
290
|
+
return /* @__PURE__ */ jsx(Profiler, {
|
|
291
|
+
id: "streamdown-root",
|
|
292
|
+
onRender: handleRootRender,
|
|
293
|
+
children: content
|
|
294
|
+
});
|
|
155
295
|
});
|
|
156
296
|
StreamdownRender.displayName = "StreamdownRender";
|
|
157
297
|
var StreamdownRender_default = StreamdownRender;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamdownRender.mjs","names":["blocks: BlockInfo[]","plugins: Pluggable[]"],"sources":["../../../src/Markdown/SyntaxMarkdown/StreamdownRender.tsx"],"sourcesContent":["'use client';\n\nimport { marked } from 'marked';\nimport { memo, useEffect, useId, useMemo, useRef } 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';\n\nimport { styles } from './style';\nimport { useSmoothStreamContent } from './useSmoothStreamContent';\nimport { type BlockInfo, useStreamQueue } from './useStreamQueue';\n\nconst STREAM_FADE_DURATION = 280;\n\nfunction countChars(text: string): number {\n return [...text].length;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null;\n\nconst isDeepEqualValue = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!isDeepEqualValue(a[i], b[i])) return false;\n }\n return true;\n }\n\n if (!isRecord(a) || !isRecord(b)) return false;\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (!isDeepEqualValue(a[key], b[key])) return false;\n }\n\n return true;\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 isDeepEqualValue(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 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 processedContent = useMemo(() => {\n return remend(smoothedContent);\n }, [smoothedContent]);\n\n const blocks: BlockInfo[] = useMemo(() => {\n const tokens = marked.lexer(processedContent);\n let offset = 0;\n return tokens.map((token) => {\n const block = { content: token.raw, startOffset: offset };\n offset += token.raw.length;\n return block;\n });\n }, [processedContent]);\n\n const { getBlockState, charDelay } = useStreamQueue(blocks);\n const prevBlockCharCountRef = useRef<Map<number, number>>(new Map());\n const blockTimelineRef = useRef<Map<number, number>>(new Map());\n const lastRenderTsRef = useRef<number | null>(null);\n\n const renderTs = typeof performance === 'undefined' ? Date.now() : performance.now();\n const frameDt =\n lastRenderTsRef.current === null\n ? 0\n : Math.max(0, Math.min(renderTs - lastRenderTsRef.current, 120));\n\n const timelineForRender = useMemo(() => {\n const next = new Map<number, number>();\n const prevTimeline = blockTimelineRef.current;\n const prevCharCounts = prevBlockCharCountRef.current;\n\n for (const block of blocks) {\n const blockCharCount = countChars(block.content);\n const prevCharCount = prevCharCounts.get(block.startOffset) ?? 0;\n const prevElapsed = prevTimeline.get(block.startOffset);\n const latestCharStart = Math.max(0, (blockCharCount - 1) * charDelay);\n\n if (prevElapsed === undefined || blockCharCount < prevCharCount) {\n next.set(block.startOffset, latestCharStart);\n continue;\n }\n\n const elapsedByTime = prevElapsed + frameDt;\n // Avoid huge hidden backlog when stream updates in bursts.\n const minElapsed = Math.max(0, latestCharStart - charDelay * 2);\n next.set(block.startOffset, Math.max(elapsedByTime, minElapsed));\n }\n\n return next;\n }, [blocks, charDelay, frameDt]);\n\n useEffect(() => {\n const nextCharCount = new Map<number, number>();\n for (const block of blocks) {\n nextCharCount.set(block.startOffset, countChars(block.content));\n }\n prevBlockCharCountRef.current = nextCharCount;\n blockTimelineRef.current = timelineForRender;\n lastRenderTsRef.current = typeof performance === 'undefined' ? Date.now() : performance.now();\n }, [blocks, timelineForRender]);\n\n return (\n <div className={styles.animated}>\n {blocks.map((block, index) => {\n const state = getBlockState(index);\n if (state === 'queued') return null;\n const timelineElapsedMs = timelineForRender.get(block.startOffset) ?? 0;\n\n let plugins: Pluggable[];\n if (state === 'streaming') {\n plugins = [\n ...baseRehypePlugins,\n [\n rehypeStreamAnimated,\n { charDelay, fadeDuration: STREAM_FADE_DURATION, timelineElapsedMs },\n ],\n ];\n } else if (state === 'animating') {\n // Continue from previously rendered progress instead of restarting\n // or force-switching to fully revealed.\n plugins = [\n ...baseRehypePlugins,\n [\n rehypeStreamAnimated,\n { charDelay, fadeDuration: STREAM_FADE_DURATION, timelineElapsedMs },\n ],\n ];\n } else {\n // Keep fade continuity for just-finished chars; avoid instant class\n // switch to stream-char-revealed that would cancel in-flight fades.\n plugins = [\n ...baseRehypePlugins,\n [\n rehypeStreamAnimated,\n { charDelay, fadeDuration: STREAM_FADE_DURATION, timelineElapsedMs },\n ],\n ];\n }\n\n return (\n <StreamdownBlock\n {...rest}\n components={components}\n key={`${generatedId}-${block.startOffset}`}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n );\n })}\n </div>\n );\n});\n\nStreamdownRender.displayName = 'StreamdownRender';\n\nexport default StreamdownRender;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"StreamdownRender.mjs","names":["REVEALED_STREAM_PLUGIN: Pluggable","blocks: BlockInfo[]","plugins: Pluggable[]"],"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';\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 isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null;\n\nconst isDeepEqualValue = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!isDeepEqualValue(a[i], b[i])) return false;\n }\n return true;\n }\n\n if (!isRecord(a) || !isRecord(b)) return false;\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (!isDeepEqualValue(a[key], b[key])) return false;\n }\n\n return true;\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 isDeepEqualValue(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 prevBlockCharCountRef = useRef<Map<number, number>>(new Map());\n const blockCharDelayRef = useRef<Map<number, number>>(new Map());\n const blockTimelineRef = useRef<Map<number, number>>(new Map());\n const lastRenderTsRef = useRef<number | null>(null);\n\n const renderTs = getNow();\n const frameDt =\n lastRenderTsRef.current === null\n ? 0\n : Math.max(0, Math.min(renderTs - lastRenderTsRef.current, 120));\n\n const timelineResult = useMemo(() => {\n const start = profiler ? getNow() : 0;\n const next = new Map<number, number>();\n const prevTimeline = blockTimelineRef.current;\n const prevCharCounts = prevBlockCharCountRef.current;\n\n for (const block of blocks) {\n const blockCharCount = countChars(block.content);\n const prevCharCount = prevCharCounts.get(block.startOffset) ?? 0;\n const prevElapsed = prevTimeline.get(block.startOffset);\n const latestCharStart = Math.max(0, (blockCharCount - 1) * charDelay);\n\n if (prevElapsed === undefined || blockCharCount < prevCharCount) {\n next.set(block.startOffset, latestCharStart);\n continue;\n }\n\n const elapsedByTime = prevElapsed + frameDt;\n // Avoid huge hidden backlog when stream updates in bursts.\n const minElapsed = Math.max(0, latestCharStart - charDelay * 2);\n next.set(block.startOffset, Math.max(elapsedByTime, minElapsed));\n }\n\n return {\n durationMs: profiler ? getNow() - start : 0,\n value: next,\n };\n }, [blocks, charDelay, frameDt, profiler]);\n const timelineForRender = timelineResult.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: timelineResult.durationMs,\n itemCount: blocks.length,\n name: 'block-timeline',\n textLength: processedContent.length,\n });\n }, [blocks.length, processedContent.length, profiler, timelineResult.durationMs]);\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 timelineElapsedMs = timelineForRender.get(block.startOffset) ?? 0;\n const animationMeta = resolveBlockAnimationMeta({\n blockCharCount: countChars(block.content),\n currentCharDelay: charDelay,\n fadeDuration: STREAM_FADE_DURATION,\n previousCharDelay: blockCharDelayRef.current.get(block.startOffset),\n state,\n timelineElapsedMs,\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 }, [blocks, charDelay, getBlockState, timelineForRender]);\n\n useEffect(() => {\n const nextCharCount = new Map<number, number>();\n for (const block of blocks) {\n nextCharCount.set(block.startOffset, countChars(block.content));\n }\n blockCharDelayRef.current = blockAnimationMetaResult.blockCharDelay;\n prevBlockCharCountRef.current = nextCharCount;\n blockTimelineRef.current = timelineForRender;\n lastRenderTsRef.current = getNow();\n }, [blockAnimationMetaResult.blockCharDelay, blocks, timelineForRender]);\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 plugins: Pluggable[] = animationMeta.settled\n ? [...baseRehypePlugins, REVEALED_STREAM_PLUGIN]\n : [\n ...baseRehypePlugins,\n [\n rehypeStreamAnimated,\n {\n charDelay: animationMeta.charDelay,\n fadeDuration: STREAM_FADE_DURATION,\n timelineElapsedMs: animationMeta.timelineElapsedMs,\n },\n ],\n ];\n\n const key = `${generatedId}-${block.startOffset}`;\n const blockNode = (\n <StreamdownBlock\n {...rest}\n components={components}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n );\n\n if (!profiler) {\n return (\n <StreamdownBlock\n {...rest}\n components={components}\n key={key}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n );\n }\n\n return (\n <Profiler\n id={`streamdown-block:${index}:${block.startOffset}`}\n key={key}\n onRender={handleBlockRender}\n >\n {blockNode}\n </Profiler>\n );\n })}\n </div>\n );\n\n if (!profiler) return content;\n\n return (\n <Profiler id={'streamdown-root'} onRender={handleRootRender}>\n {content}\n </Profiler>\n );\n});\n\nStreamdownRender.displayName = 'StreamdownRender';\n\nexport default StreamdownRender;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/Markdown/SyntaxMarkdown/streamAnimationMeta.ts
|
|
2
|
+
const isActiveBlock = (state) => {
|
|
3
|
+
return state === "animating" || state === "streaming";
|
|
4
|
+
};
|
|
5
|
+
const resolveBlockAnimationMeta = ({ blockCharCount, currentCharDelay, fadeDuration, previousCharDelay, state, timelineElapsedMs }) => {
|
|
6
|
+
const charDelay = isActiveBlock(state) ? currentCharDelay : previousCharDelay ?? currentCharDelay;
|
|
7
|
+
const latestCharStart = Math.max(0, (blockCharCount - 1) * charDelay);
|
|
8
|
+
const settled = state === "revealed" && timelineElapsedMs >= latestCharStart + fadeDuration;
|
|
9
|
+
return {
|
|
10
|
+
charDelay,
|
|
11
|
+
settled,
|
|
12
|
+
timelineElapsedMs: settled ? latestCharStart + fadeDuration : timelineElapsedMs
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { resolveBlockAnimationMeta };
|
|
18
|
+
//# sourceMappingURL=streamAnimationMeta.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamAnimationMeta.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/streamAnimationMeta.ts"],"sourcesContent":["import { type BlockState } from './useStreamQueue';\n\nexport interface ResolveBlockAnimationMetaOptions {\n blockCharCount: number;\n currentCharDelay: number;\n fadeDuration: number;\n previousCharDelay?: number;\n state: BlockState;\n timelineElapsedMs: number;\n}\n\nexport interface BlockAnimationMeta {\n charDelay: number;\n settled: boolean;\n timelineElapsedMs: number;\n}\n\nconst isActiveBlock = (state: BlockState) => {\n return state === 'animating' || state === 'streaming';\n};\n\nexport const resolveBlockAnimationMeta = ({\n blockCharCount,\n currentCharDelay,\n fadeDuration,\n previousCharDelay,\n state,\n timelineElapsedMs,\n}: ResolveBlockAnimationMetaOptions): BlockAnimationMeta => {\n const charDelay = isActiveBlock(state)\n ? currentCharDelay\n : (previousCharDelay ?? currentCharDelay);\n const latestCharStart = Math.max(0, (blockCharCount - 1) * charDelay);\n const settled = state === 'revealed' && timelineElapsedMs >= latestCharStart + fadeDuration;\n\n return {\n charDelay,\n settled,\n timelineElapsedMs: settled ? latestCharStart + fadeDuration : timelineElapsedMs,\n };\n};\n"],"mappings":";AAiBA,MAAM,iBAAiB,UAAsB;AAC3C,QAAO,UAAU,eAAe,UAAU;;AAG5C,MAAa,6BAA6B,EACxC,gBACA,kBACA,cACA,mBACA,OACA,wBAC0D;CAC1D,MAAM,YAAY,cAAc,MAAM,GAClC,mBACC,qBAAqB;CAC1B,MAAM,kBAAkB,KAAK,IAAI,IAAI,iBAAiB,KAAK,UAAU;CACrE,MAAM,UAAU,UAAU,cAAc,qBAAqB,kBAAkB;AAE/E,QAAO;EACL;EACA;EACA,mBAAmB,UAAU,kBAAkB,eAAe;EAC/D"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useStreamdownProfiler } from "../streamProfiler/StreamdownProfilerProvider.mjs";
|
|
1
2
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
3
|
|
|
3
4
|
//#region src/Markdown/SyntaxMarkdown/useSmoothStreamContent.ts
|
|
@@ -51,11 +52,15 @@ const PRESET_CONFIG = {
|
|
|
51
52
|
const clamp = (value, min, max) => {
|
|
52
53
|
return Math.min(max, Math.max(min, value));
|
|
53
54
|
};
|
|
55
|
+
const getNow = () => {
|
|
56
|
+
return typeof performance === "undefined" ? Date.now() : performance.now();
|
|
57
|
+
};
|
|
54
58
|
const countChars = (text) => {
|
|
55
59
|
return [...text].length;
|
|
56
60
|
};
|
|
57
61
|
const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" } = {}) => {
|
|
58
62
|
const config = PRESET_CONFIG[preset];
|
|
63
|
+
const profiler = useStreamdownProfiler();
|
|
59
64
|
const [displayedContent, setDisplayedContent] = useState(content);
|
|
60
65
|
const displayedContentRef = useRef(content);
|
|
61
66
|
const displayedCountRef = useRef(countChars(content));
|
|
@@ -69,6 +74,13 @@ const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" }
|
|
|
69
74
|
const arrivalCpsEmaRef = useRef(config.defaultCps);
|
|
70
75
|
const rafRef = useRef(null);
|
|
71
76
|
const lastFrameTsRef = useRef(null);
|
|
77
|
+
const wakeTimerRef = useRef(null);
|
|
78
|
+
const clearWakeTimer = useCallback(() => {
|
|
79
|
+
if (wakeTimerRef.current !== null) {
|
|
80
|
+
clearTimeout(wakeTimerRef.current);
|
|
81
|
+
wakeTimerRef.current = null;
|
|
82
|
+
}
|
|
83
|
+
}, []);
|
|
72
84
|
const stopFrameLoop = useCallback(() => {
|
|
73
85
|
if (rafRef.current !== null) {
|
|
74
86
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -76,10 +88,22 @@ const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" }
|
|
|
76
88
|
}
|
|
77
89
|
lastFrameTsRef.current = null;
|
|
78
90
|
}, []);
|
|
79
|
-
const
|
|
91
|
+
const stopScheduling = useCallback(() => {
|
|
80
92
|
stopFrameLoop();
|
|
93
|
+
clearWakeTimer();
|
|
94
|
+
}, [clearWakeTimer, stopFrameLoop]);
|
|
95
|
+
const startFrameLoopRef = useRef(() => {});
|
|
96
|
+
const scheduleFrameWake = useCallback((delayMs) => {
|
|
97
|
+
clearWakeTimer();
|
|
98
|
+
wakeTimerRef.current = setTimeout(() => {
|
|
99
|
+
wakeTimerRef.current = null;
|
|
100
|
+
startFrameLoopRef.current();
|
|
101
|
+
}, Math.max(1, Math.ceil(delayMs)));
|
|
102
|
+
}, [clearWakeTimer]);
|
|
103
|
+
const syncImmediate = useCallback((nextContent) => {
|
|
104
|
+
stopScheduling();
|
|
81
105
|
const chars = [...nextContent];
|
|
82
|
-
const now =
|
|
106
|
+
const now = getNow();
|
|
83
107
|
targetContentRef.current = nextContent;
|
|
84
108
|
targetCharsRef.current = chars;
|
|
85
109
|
targetCountRef.current = chars.length;
|
|
@@ -91,16 +115,19 @@ const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" }
|
|
|
91
115
|
arrivalCpsEmaRef.current = config.defaultCps;
|
|
92
116
|
lastInputTsRef.current = now;
|
|
93
117
|
lastInputCountRef.current = chars.length;
|
|
94
|
-
}, [config.defaultCps,
|
|
118
|
+
}, [config.defaultCps, stopScheduling]);
|
|
95
119
|
const startFrameLoop = useCallback(() => {
|
|
120
|
+
clearWakeTimer();
|
|
96
121
|
if (rafRef.current !== null) return;
|
|
97
122
|
const tick = (ts) => {
|
|
123
|
+
const frameStart = getNow();
|
|
98
124
|
if (lastFrameTsRef.current === null) {
|
|
99
125
|
lastFrameTsRef.current = ts;
|
|
100
126
|
rafRef.current = requestAnimationFrame(tick);
|
|
101
127
|
return;
|
|
102
128
|
}
|
|
103
|
-
const
|
|
129
|
+
const frameIntervalMs = Math.max(0, ts - lastFrameTsRef.current);
|
|
130
|
+
const dtSeconds = Math.max(.001, Math.min(frameIntervalMs / 1e3, .05));
|
|
104
131
|
lastFrameTsRef.current = ts;
|
|
105
132
|
const targetCount = targetCountRef.current;
|
|
106
133
|
const displayedCount = displayedCountRef.current;
|
|
@@ -109,7 +136,7 @@ const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" }
|
|
|
109
136
|
stopFrameLoop();
|
|
110
137
|
return;
|
|
111
138
|
}
|
|
112
|
-
const idleMs =
|
|
139
|
+
const idleMs = getNow() - lastInputTsRef.current;
|
|
113
140
|
const inputActive = idleMs <= config.activeInputWindowMs;
|
|
114
141
|
const settling = !inputActive && idleMs >= config.settleAfterMs;
|
|
115
142
|
const baseCps = clamp(emaCpsRef.current, config.minCps, config.maxCps);
|
|
@@ -136,7 +163,16 @@ const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" }
|
|
|
136
163
|
if (inputActive) {
|
|
137
164
|
const shortfall = desiredDisplayed - displayedCount;
|
|
138
165
|
if (shortfall <= 0) {
|
|
139
|
-
|
|
166
|
+
stopFrameLoop();
|
|
167
|
+
scheduleFrameWake(config.activeInputWindowMs - idleMs);
|
|
168
|
+
profiler?.recordAnimationFrame({
|
|
169
|
+
backlog,
|
|
170
|
+
durationMs: getNow() - frameStart,
|
|
171
|
+
frameIntervalMs,
|
|
172
|
+
inputActive,
|
|
173
|
+
revealChars: 0,
|
|
174
|
+
settling
|
|
175
|
+
});
|
|
140
176
|
return;
|
|
141
177
|
}
|
|
142
178
|
revealChars = Math.min(revealChars, shortfall, backlog);
|
|
@@ -153,10 +189,19 @@ const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" }
|
|
|
153
189
|
displayedCountRef.current = targetCount;
|
|
154
190
|
setDisplayedContent(targetContentRef.current);
|
|
155
191
|
}
|
|
192
|
+
profiler?.recordAnimationFrame({
|
|
193
|
+
backlog,
|
|
194
|
+
durationMs: getNow() - frameStart,
|
|
195
|
+
frameIntervalMs,
|
|
196
|
+
inputActive,
|
|
197
|
+
revealChars: segment ? revealChars : backlog,
|
|
198
|
+
settling
|
|
199
|
+
});
|
|
156
200
|
rafRef.current = requestAnimationFrame(tick);
|
|
157
201
|
};
|
|
158
202
|
rafRef.current = requestAnimationFrame(tick);
|
|
159
203
|
}, [
|
|
204
|
+
clearWakeTimer,
|
|
160
205
|
config.activeInputWindowMs,
|
|
161
206
|
config.flushCps,
|
|
162
207
|
config.maxActiveCps,
|
|
@@ -167,8 +212,10 @@ const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" }
|
|
|
167
212
|
config.settleDrainMaxMs,
|
|
168
213
|
config.settleDrainMinMs,
|
|
169
214
|
config.targetBufferMs,
|
|
215
|
+
scheduleFrameWake,
|
|
170
216
|
stopFrameLoop
|
|
171
217
|
]);
|
|
218
|
+
startFrameLoopRef.current = startFrameLoop;
|
|
172
219
|
useEffect(() => {
|
|
173
220
|
if (!enabled) {
|
|
174
221
|
syncImmediate(content);
|
|
@@ -176,13 +223,17 @@ const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" }
|
|
|
176
223
|
}
|
|
177
224
|
const prevTargetContent = targetContentRef.current;
|
|
178
225
|
if (content === prevTargetContent) return;
|
|
179
|
-
const now =
|
|
226
|
+
const now = getNow();
|
|
180
227
|
if (!content.startsWith(prevTargetContent)) {
|
|
181
228
|
syncImmediate(content);
|
|
182
229
|
return;
|
|
183
230
|
}
|
|
184
231
|
const appendedChars = [...content.slice(prevTargetContent.length)];
|
|
185
232
|
const appendedCount = appendedChars.length;
|
|
233
|
+
profiler?.recordInputAppend({
|
|
234
|
+
appendedChars: appendedCount,
|
|
235
|
+
contentLength: countChars(content)
|
|
236
|
+
});
|
|
186
237
|
if (appendedCount > config.largeAppendChars) {
|
|
187
238
|
syncImmediate(content);
|
|
188
239
|
return;
|
|
@@ -214,13 +265,14 @@ const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" }
|
|
|
214
265
|
content,
|
|
215
266
|
enabled,
|
|
216
267
|
startFrameLoop,
|
|
217
|
-
syncImmediate
|
|
268
|
+
syncImmediate,
|
|
269
|
+
profiler
|
|
218
270
|
]);
|
|
219
271
|
useEffect(() => {
|
|
220
272
|
return () => {
|
|
221
|
-
|
|
273
|
+
stopScheduling();
|
|
222
274
|
};
|
|
223
|
-
}, [
|
|
275
|
+
}, [stopScheduling]);
|
|
224
276
|
return displayedContent;
|
|
225
277
|
};
|
|
226
278
|
|