@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.
Files changed (167) hide show
  1. package/es/Accordion/Accordion.d.mts +2 -2
  2. package/es/Accordion/AccordionItem.d.mts +2 -2
  3. package/es/ActionIcon/ActionIcon.d.mts +2 -2
  4. package/es/Alert/Alert.d.mts +2 -2
  5. package/es/AutoComplete/Select.d.mts +2 -2
  6. package/es/Avatar/AvatarGroup/index.d.mts +2 -2
  7. package/es/CodeDiff/CodeDiff.d.mts +2 -2
  8. package/es/CodeDiff/PatchDiff.d.mts +2 -2
  9. package/es/CodeEditor/CodeEditor.d.mts +2 -2
  10. package/es/Collapse/Collapse.d.mts +2 -2
  11. package/es/ConfigProvider/index.d.mts +2 -2
  12. package/es/CopyButton/CopyButton.d.mts +2 -2
  13. package/es/DatePicker/DatePicker.d.mts +2 -2
  14. package/es/DraggablePanel/components/DraggablePanelBody.d.mts +2 -2
  15. package/es/DraggablePanel/components/DraggablePanelContainer.d.mts +2 -2
  16. package/es/DraggablePanel/components/DraggablePanelFooter.d.mts +2 -2
  17. package/es/DraggablePanel/components/DraggablePanelHeader.d.mts +2 -2
  18. package/es/DraggableSideNav/DraggableSideNav.d.mts +2 -2
  19. package/es/Drawer/Drawer.d.mts +2 -2
  20. package/es/Dropdown/Dropdown.d.mts +2 -2
  21. package/es/EditableText/EditableText.d.mts +2 -2
  22. package/es/EmojiPicker/EmojiPicker.d.mts +2 -2
  23. package/es/Flex/FlexBasic.d.mts +2 -2
  24. package/es/FontLoader/index.d.mts +2 -2
  25. package/es/Footer/Footer.d.mts +2 -2
  26. package/es/Form/components/FormGroup.d.mts +2 -2
  27. package/es/Form/components/FormItem.d.mts +2 -2
  28. package/es/Form/components/FormSubmitFooter.d.mts +2 -2
  29. package/es/FormModal/FormModal.d.mts +2 -2
  30. package/es/Freeze/Freeze.d.mts +2 -2
  31. package/es/GuideCard/GuideCard.d.mts +2 -2
  32. package/es/Header/Header.d.mts +2 -2
  33. package/es/Highlighter/Highlighter.d.mts +2 -2
  34. package/es/Highlighter/SyntaxHighlighter/index.d.mts +2 -2
  35. package/es/Hotkey/Hotkey.d.mts +2 -2
  36. package/es/HotkeyInput/HotkeyInput.d.mts +2 -2
  37. package/es/Icon/Icon.d.mts +2 -2
  38. package/es/Icon/components/IconProvider.d.mts +3 -3
  39. package/es/Image/PreviewGroup.d.mts +2 -2
  40. package/es/ImageSelect/ImageSelect.d.mts +2 -2
  41. package/es/Input/Input.d.mts +2 -2
  42. package/es/Input/InputNumber.d.mts +2 -2
  43. package/es/Input/InputPassword.d.mts +2 -2
  44. package/es/Input/TextArea.d.mts +2 -2
  45. package/es/Layout/components/LayoutFooter.d.mts +2 -2
  46. package/es/Layout/components/LayoutHeader.d.mts +2 -2
  47. package/es/Layout/components/LayoutMain.d.mts +2 -2
  48. package/es/Layout/components/LayoutSidebar.d.mts +2 -2
  49. package/es/Layout/components/LayoutSidebarInner.d.mts +2 -2
  50. package/es/Layout/components/LayoutToc.d.mts +2 -2
  51. package/es/List/ListItem/index.d.mts +2 -2
  52. package/es/Markdown/Markdown.d.mts +2 -2
  53. package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs +173 -33
  54. package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs.map +1 -1
  55. package/es/Markdown/SyntaxMarkdown/streamAnimationMeta.mjs +18 -0
  56. package/es/Markdown/SyntaxMarkdown/streamAnimationMeta.mjs.map +1 -0
  57. package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs +62 -10
  58. package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs.map +1 -1
  59. package/es/Markdown/Typography.d.mts +2 -2
  60. package/es/Markdown/components/SearchResultCards/index.d.mts +2 -2
  61. package/es/Markdown/streamProfiler/StreamdownProfilerProvider.mjs +21 -0
  62. package/es/Markdown/streamProfiler/StreamdownProfilerProvider.mjs.map +1 -0
  63. package/es/MaskShadow/MaskShadow.d.mts +2 -2
  64. package/es/Menu/Menu.d.mts +2 -2
  65. package/es/Mermaid/Mermaid.d.mts +2 -2
  66. package/es/Mermaid/SyntaxMermaid/index.d.mts +2 -2
  67. package/es/Modal/Modal.d.mts +2 -2
  68. package/es/Modal/ModalProvider.d.mts +2 -2
  69. package/es/Modal/imperative.d.mts +2 -2
  70. package/es/MotionProvider/index.d.mts +2 -2
  71. package/es/SearchBar/SearchBar.d.mts +2 -2
  72. package/es/Segmented/Segmented.d.mts +2 -2
  73. package/es/Select/Select.d.mts +2 -2
  74. package/es/SideNav/SideNav.d.mts +2 -2
  75. package/es/SliderWithInput/SliderWithInput.d.mts +2 -2
  76. package/es/SortableList/components/DragHandle.d.mts +2 -2
  77. package/es/SortableList/components/SortableItem.d.mts +2 -2
  78. package/es/ThemeProvider/ThemeProvider.d.mts +2 -2
  79. package/es/Toc/Toc.d.mts +2 -2
  80. package/es/Video/index.d.mts +2 -2
  81. package/es/awesome/AuroraBackground/AuroraBackground.d.mts +2 -2
  82. package/es/awesome/BottomGradientButton/BottomGradientButton.d.mts +2 -2
  83. package/es/awesome/Features/Features.d.mts +2 -2
  84. package/es/awesome/Giscus/Giscus.d.mts +2 -2
  85. package/es/awesome/GradientButton/GradientButton.d.mts +2 -2
  86. package/es/awesome/GridBackground/GridBackground.d.mts +2 -2
  87. package/es/awesome/GridBackground/GridShowcase.d.mts +2 -2
  88. package/es/awesome/Hero/Hero.d.mts +2 -2
  89. package/es/awesome/Spline/Spine.d.mts +2 -2
  90. package/es/awesome/Spotlight/Spotlight.d.mts +2 -2
  91. package/es/awesome/SpotlightCard/SpotlightCard.d.mts +2 -2
  92. package/es/awesome/TypewriterEffect/TypewriterEffect.d.mts +2 -2
  93. package/es/base-ui/ContextMenu/ContextMenuHost.d.mts +2 -2
  94. package/es/base-ui/DropdownMenu/DropdownMenu.d.mts +2 -2
  95. package/es/base-ui/DropdownMenu/atoms.d.mts +18 -18
  96. package/es/base-ui/Modal/atoms.d.mts +12 -12
  97. package/es/base-ui/Modal/context.d.mts +2 -2
  98. package/es/base-ui/Modal/imperative.d.mts +2 -2
  99. package/es/base-ui/Popover/ArrowIcon.d.mts +2 -2
  100. package/es/base-ui/Popover/atoms.d.mts +9 -9
  101. package/es/base-ui/Popover/context.d.mts +2 -2
  102. package/es/base-ui/ScrollArea/atoms.d.mts +7 -7
  103. package/es/base-ui/Select/Select.d.mts +2 -2
  104. package/es/base-ui/Select/atoms.d.mts +19 -19
  105. package/es/base-ui/Switch/Switch.d.mts +2 -2
  106. package/es/base-ui/Switch/atoms.d.mts +4 -4
  107. package/es/base-ui/Toast/imperative.d.mts +4 -3
  108. package/es/base-ui/Toast/imperative.mjs +1 -1
  109. package/es/base-ui/Toast/imperative.mjs.map +1 -1
  110. package/es/brand/LobeChat/index.d.mts +2 -2
  111. package/es/brand/LobeHub/index.d.mts +2 -2
  112. package/es/brand/LogoThree/LogoSpline.d.mts +2 -2
  113. package/es/brand/LogoThree/index.d.mts +2 -2
  114. package/es/chat/BackBottom/BackBottom.d.mts +2 -2
  115. package/es/chat/ChatInputArea/components/ChatInputAreaInner.d.mts +2 -2
  116. package/es/chat/ChatItem/ChatItem.d.mts +2 -2
  117. package/es/chat/ChatList/ChatList.d.mts +2 -2
  118. package/es/chat/EditableMessage/EditableMessage.d.mts +2 -2
  119. package/es/chat/EditableMessageList/EditableMessageList.d.mts +2 -2
  120. package/es/chat/MessageInput/MessageInput.d.mts +2 -2
  121. package/es/chat/MessageModal/MessageModal.d.mts +2 -2
  122. package/es/color/ColorScales/index.d.mts +2 -2
  123. package/es/color/CssVar/index.d.mts +2 -2
  124. package/es/i18n/context.d.mts +2 -2
  125. package/es/icons/lucideExtra/AndroidIcon.d.mts +2 -2
  126. package/es/icons/lucideExtra/AppleIcon.d.mts +2 -2
  127. package/es/icons/lucideExtra/AppstoreIcon.d.mts +3 -3
  128. package/es/icons/lucideExtra/BotPromptIcon.d.mts +2 -2
  129. package/es/icons/lucideExtra/BrainOffIcon.d.mts +2 -2
  130. package/es/icons/lucideExtra/ChromeIcon.d.mts +2 -2
  131. package/es/icons/lucideExtra/CodepenIcon.d.mts +3 -3
  132. package/es/icons/lucideExtra/CodesandboxIcon.d.mts +2 -2
  133. package/es/icons/lucideExtra/CreateBotIcon.d.mts +2 -2
  134. package/es/icons/lucideExtra/DiscordIcon.d.mts +2 -2
  135. package/es/icons/lucideExtra/FacebookIcon.d.mts +3 -3
  136. package/es/icons/lucideExtra/FigmaIcon.d.mts +3 -3
  137. package/es/icons/lucideExtra/FramerIcon.d.mts +3 -3
  138. package/es/icons/lucideExtra/GithubIcon.d.mts +3 -3
  139. package/es/icons/lucideExtra/GitlabIcon.d.mts +2 -2
  140. package/es/icons/lucideExtra/GlobeOffIcon.d.mts +3 -3
  141. package/es/icons/lucideExtra/GooglePlayIcon.d.mts +3 -3
  142. package/es/icons/lucideExtra/GroupBotIcon.d.mts +3 -3
  143. package/es/icons/lucideExtra/GroupBotSquareIcon.d.mts +3 -3
  144. package/es/icons/lucideExtra/InstagramIcon.d.mts +3 -3
  145. package/es/icons/lucideExtra/LeftClickIcon.d.mts +3 -3
  146. package/es/icons/lucideExtra/LeftDoubleClickIcon.d.mts +2 -2
  147. package/es/icons/lucideExtra/LinkedinIcon.d.mts +2 -2
  148. package/es/icons/lucideExtra/McpIcon.d.mts +2 -2
  149. package/es/icons/lucideExtra/NotionIcon.d.mts +2 -2
  150. package/es/icons/lucideExtra/PocketIcon.d.mts +2 -2
  151. package/es/icons/lucideExtra/ProviderIcon.d.mts +2 -2
  152. package/es/icons/lucideExtra/RailSymbolIcon.d.mts +2 -2
  153. package/es/icons/lucideExtra/RedditIcon.d.mts +2 -2
  154. package/es/icons/lucideExtra/RightClickIcon.d.mts +2 -2
  155. package/es/icons/lucideExtra/RightDoubleClickIcon.d.mts +2 -2
  156. package/es/icons/lucideExtra/ShapesUploadIcon.d.mts +2 -2
  157. package/es/icons/lucideExtra/SkillsIcon.d.mts +2 -2
  158. package/es/icons/lucideExtra/SlackIcon.d.mts +2 -2
  159. package/es/icons/lucideExtra/ThinkIcon.d.mts +2 -2
  160. package/es/icons/lucideExtra/TreeDownRightIcon.d.mts +2 -2
  161. package/es/icons/lucideExtra/TreeUpDownRightIcon.d.mts +3 -3
  162. package/es/mdx/Mdx/index.d.mts +2 -2
  163. package/es/mobile/ChatHeader/ChatHeaderTitle.d.mts +2 -2
  164. package/es/mobile/ChatInputArea/components/ChatSendButton.d.mts +2 -2
  165. package/es/mobile/TabBar/TabBar.d.mts +2 -2
  166. package/es/storybook/StoryBook/index.d.mts +2 -2
  167. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  import { LayoutTocProps } from "../type.mjs";
2
- import * as react31 from "react";
2
+ import * as react25 from "react";
3
3
 
4
4
  //#region src/Layout/components/LayoutToc.d.ts
5
- declare const LayoutToc: react31.NamedExoticComponent<LayoutTocProps>;
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 react27 from "react";
2
+ import * as react10 from "react";
3
3
 
4
4
  //#region src/List/ListItem/index.d.ts
5
- declare const ListItem: react27.NamedExoticComponent<ListItemProps>;
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 react26 from "react";
2
+ import * as react12 from "react";
3
3
 
4
4
  //#region src/Markdown/Markdown.d.ts
5
- declare const Markdown: react26.NamedExoticComponent<MarkdownProps>;
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 processedContent = useMemo(() => {
74
- return remend(smoothedContent);
75
- }, [smoothedContent]);
76
- const blocks = useMemo(() => {
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
- return tokens.map((token) => {
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
- }, [processedContent]);
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 = typeof performance === "undefined" ? Date.now() : performance.now();
112
+ const renderTs = getNow();
93
113
  const frameDt = lastRenderTsRef.current === null ? 0 : Math.max(0, Math.min(renderTs - lastRenderTsRef.current, 120));
94
- const timelineForRender = useMemo(() => {
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 next;
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 = typeof performance === "undefined" ? Date.now() : performance.now();
123
- }, [blocks, timelineForRender]);
124
- return /* @__PURE__ */ jsx("div", {
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
- const state = getBlockState(index);
128
- if (state === "queued") return null;
129
- const timelineElapsedMs = timelineForRender.get(block.startOffset) ?? 0;
130
- let plugins;
131
- if (state === "streaming") plugins = [...baseRehypePlugins, [rehypeStreamAnimated, {
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
- else plugins = [...baseRehypePlugins, [rehypeStreamAnimated, {
142
- charDelay,
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
- key: `${generatedId}-${block.startOffset}`,
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 syncImmediate = useCallback((nextContent) => {
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 = performance.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, stopFrameLoop]);
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 dtSeconds = Math.max(.001, Math.min((ts - lastFrameTsRef.current) / 1e3, .05));
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 = performance.now() - lastInputTsRef.current;
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
- rafRef.current = requestAnimationFrame(tick);
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 = performance.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
- stopFrameLoop();
273
+ stopScheduling();
222
274
  };
223
- }, [stopFrameLoop]);
275
+ }, [stopScheduling]);
224
276
  return displayedContent;
225
277
  };
226
278