@lobehub/ui 5.3.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) 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/Burger/Burger.d.mts +2 -2
  8. package/es/CodeDiff/CodeDiff.d.mts +2 -2
  9. package/es/CodeDiff/PatchDiff.d.mts +2 -2
  10. package/es/CodeEditor/CodeEditor.d.mts +2 -2
  11. package/es/Collapse/Collapse.d.mts +2 -2
  12. package/es/ConfigProvider/index.d.mts +2 -2
  13. package/es/CopyButton/CopyButton.d.mts +2 -2
  14. package/es/DatePicker/DatePicker.d.mts +2 -2
  15. package/es/DraggablePanel/components/DraggablePanelBody.d.mts +2 -2
  16. package/es/DraggablePanel/components/DraggablePanelContainer.d.mts +2 -2
  17. package/es/DraggablePanel/components/DraggablePanelFooter.d.mts +2 -2
  18. package/es/DraggablePanel/components/DraggablePanelHeader.d.mts +2 -2
  19. package/es/DraggableSideNav/DraggableSideNav.d.mts +2 -2
  20. package/es/Drawer/Drawer.d.mts +2 -2
  21. package/es/Dropdown/Dropdown.d.mts +2 -2
  22. package/es/EditableText/EditableText.d.mts +2 -2
  23. package/es/EditorSlashMenu/atoms.d.mts +13 -13
  24. package/es/EmojiPicker/EmojiPicker.d.mts +2 -2
  25. package/es/Flex/FlexBasic.d.mts +2 -2
  26. package/es/FontLoader/index.d.mts +2 -2
  27. package/es/Footer/Footer.d.mts +2 -2
  28. package/es/Form/components/FormGroup.d.mts +2 -2
  29. package/es/Form/components/FormItem.d.mts +2 -2
  30. package/es/Form/components/FormSubmitFooter.d.mts +2 -2
  31. package/es/FormModal/FormModal.d.mts +2 -2
  32. package/es/Freeze/Freeze.d.mts +2 -2
  33. package/es/GuideCard/GuideCard.d.mts +2 -2
  34. package/es/Header/Header.d.mts +2 -2
  35. package/es/Highlighter/Highlighter.d.mts +2 -2
  36. package/es/Highlighter/SyntaxHighlighter/index.d.mts +2 -2
  37. package/es/Hotkey/Hotkey.d.mts +2 -2
  38. package/es/HotkeyInput/HotkeyInput.d.mts +2 -2
  39. package/es/HotkeyInput/HotkeyInput.mjs +30 -8
  40. package/es/HotkeyInput/HotkeyInput.mjs.map +1 -1
  41. package/es/HotkeyInput/type.d.mts +3 -0
  42. package/es/Icon/Icon.d.mts +2 -2
  43. package/es/Icon/components/IconProvider.d.mts +3 -3
  44. package/es/Image/PreviewGroup.d.mts +2 -2
  45. package/es/ImageSelect/ImageSelect.d.mts +2 -2
  46. package/es/Input/Input.d.mts +2 -2
  47. package/es/Input/InputNumber.d.mts +2 -2
  48. package/es/Input/InputOPT.d.mts +2 -2
  49. package/es/Input/InputPassword.d.mts +2 -2
  50. package/es/Input/TextArea.d.mts +2 -2
  51. package/es/Layout/components/LayoutFooter.d.mts +2 -2
  52. package/es/Layout/components/LayoutHeader.d.mts +2 -2
  53. package/es/Layout/components/LayoutMain.d.mts +2 -2
  54. package/es/Layout/components/LayoutSidebar.d.mts +2 -2
  55. package/es/Layout/components/LayoutSidebarInner.d.mts +2 -2
  56. package/es/Layout/components/LayoutToc.d.mts +2 -2
  57. package/es/List/ListItem/index.d.mts +2 -2
  58. package/es/Markdown/Markdown.d.mts +2 -2
  59. package/es/Markdown/Markdown.mjs +2 -1
  60. package/es/Markdown/Markdown.mjs.map +1 -1
  61. package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs +56 -30
  62. package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs.map +1 -1
  63. package/es/Markdown/SyntaxMarkdown/style.mjs +2 -2
  64. package/es/Markdown/SyntaxMarkdown/style.mjs.map +1 -1
  65. package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs +229 -0
  66. package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs.map +1 -0
  67. package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs +8 -6
  68. package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs.map +1 -1
  69. package/es/Markdown/Typography.d.mts +2 -2
  70. package/es/Markdown/components/SearchResultCards/index.d.mts +2 -2
  71. package/es/Markdown/index.d.mts +2 -2
  72. package/es/Markdown/plugins/rehypeStreamAnimated.d.mts +2 -0
  73. package/es/Markdown/plugins/rehypeStreamAnimated.mjs +17 -5
  74. package/es/Markdown/plugins/rehypeStreamAnimated.mjs.map +1 -1
  75. package/es/Markdown/type.d.mts +3 -1
  76. package/es/MaskShadow/MaskShadow.d.mts +2 -2
  77. package/es/Menu/Menu.d.mts +2 -2
  78. package/es/Mermaid/Mermaid.d.mts +2 -2
  79. package/es/Mermaid/SyntaxMermaid/index.d.mts +2 -2
  80. package/es/Modal/Modal.d.mts +2 -2
  81. package/es/Modal/ModalProvider.d.mts +2 -2
  82. package/es/Modal/imperative.d.mts +2 -2
  83. package/es/MotionProvider/index.d.mts +2 -2
  84. package/es/Popover/ArrowIcon.d.mts +2 -2
  85. package/es/Popover/atoms.d.mts +9 -9
  86. package/es/Popover/context.d.mts +2 -2
  87. package/es/SearchBar/SearchBar.d.mts +2 -2
  88. package/es/Segmented/Segmented.d.mts +2 -2
  89. package/es/Select/Select.d.mts +2 -2
  90. package/es/SideNav/SideNav.d.mts +2 -2
  91. package/es/SliderWithInput/SliderWithInput.d.mts +2 -2
  92. package/es/SortableList/components/DragHandle.d.mts +2 -2
  93. package/es/SortableList/components/SortableItem.d.mts +2 -2
  94. package/es/ThemeProvider/ThemeProvider.d.mts +2 -2
  95. package/es/Toc/Toc.d.mts +2 -2
  96. package/es/Video/index.d.mts +2 -2
  97. package/es/awesome/AuroraBackground/AuroraBackground.d.mts +2 -2
  98. package/es/awesome/BottomGradientButton/BottomGradientButton.d.mts +2 -2
  99. package/es/awesome/Features/Features.d.mts +2 -2
  100. package/es/awesome/Giscus/Giscus.d.mts +2 -2
  101. package/es/awesome/GradientButton/GradientButton.d.mts +2 -2
  102. package/es/awesome/GridBackground/GridBackground.d.mts +2 -2
  103. package/es/awesome/GridBackground/GridShowcase.d.mts +2 -2
  104. package/es/awesome/Hero/Hero.d.mts +2 -2
  105. package/es/awesome/Spline/Spine.d.mts +2 -2
  106. package/es/awesome/Spotlight/Spotlight.d.mts +2 -2
  107. package/es/awesome/SpotlightCard/SpotlightCard.d.mts +2 -2
  108. package/es/awesome/TypewriterEffect/TypewriterEffect.d.mts +2 -2
  109. package/es/base-ui/Modal/atoms.d.mts +12 -12
  110. package/es/base-ui/Modal/context.d.mts +2 -2
  111. package/es/base-ui/Modal/imperative.d.mts +2 -2
  112. package/es/base-ui/Select/Select.d.mts +2 -2
  113. package/es/base-ui/Select/atoms.d.mts +19 -19
  114. package/es/base-ui/Switch/Switch.d.mts +2 -2
  115. package/es/base-ui/Switch/atoms.d.mts +4 -4
  116. package/es/base-ui/Toast/imperative.d.mts +2 -2
  117. package/es/brand/LobeChat/index.d.mts +2 -2
  118. package/es/brand/LobeHub/index.d.mts +2 -2
  119. package/es/brand/LogoThree/LogoSpline.d.mts +2 -2
  120. package/es/brand/LogoThree/index.d.mts +2 -2
  121. package/es/chat/BackBottom/BackBottom.d.mts +2 -2
  122. package/es/chat/ChatInputArea/components/ChatInputAreaInner.d.mts +2 -2
  123. package/es/chat/ChatItem/ChatItem.d.mts +2 -2
  124. package/es/chat/ChatList/ChatList.d.mts +2 -2
  125. package/es/chat/EditableMessage/EditableMessage.d.mts +2 -2
  126. package/es/chat/EditableMessageList/EditableMessageList.d.mts +2 -2
  127. package/es/chat/MessageInput/MessageInput.d.mts +2 -2
  128. package/es/chat/MessageModal/MessageModal.d.mts +2 -2
  129. package/es/color/ColorScales/index.d.mts +2 -2
  130. package/es/color/CssVar/index.d.mts +2 -2
  131. package/es/i18n/context.d.mts +2 -2
  132. package/es/i18n/resources/en/hotkey.d.mts +1 -0
  133. package/es/i18n/resources/en/hotkey.mjs +1 -0
  134. package/es/i18n/resources/en/hotkey.mjs.map +1 -1
  135. package/es/i18n/resources/zhCn/hotkey.d.mts +1 -0
  136. package/es/i18n/resources/zhCn/hotkey.mjs +1 -0
  137. package/es/i18n/resources/zhCn/hotkey.mjs.map +1 -1
  138. package/es/icons/lucideExtra/BotPromptIcon.d.mts +2 -2
  139. package/es/icons/lucideExtra/CreateBotIcon.d.mts +3 -3
  140. package/es/icons/lucideExtra/DiscordIcon.d.mts +2 -2
  141. package/es/icons/lucideExtra/GlobeOffIcon.d.mts +2 -2
  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/LeftClickIcon.d.mts +3 -3
  145. package/es/icons/lucideExtra/LeftDoubleClickIcon.d.mts +3 -3
  146. package/es/icons/lucideExtra/McpIcon.d.mts +3 -3
  147. package/es/icons/lucideExtra/ProviderIcon.d.mts +3 -3
  148. package/es/icons/lucideExtra/RightClickIcon.d.mts +3 -3
  149. package/es/icons/lucideExtra/RightDoubleClickIcon.d.mts +3 -3
  150. package/es/icons/lucideExtra/ShapesUploadIcon.d.mts +3 -3
  151. package/es/icons/lucideExtra/SkillsIcon.d.mts +3 -3
  152. package/es/icons/lucideExtra/TreeDownRightIcon.d.mts +2 -2
  153. package/es/icons/lucideExtra/TreeUpDownRightIcon.d.mts +3 -3
  154. package/es/mdx/Mdx/index.d.mts +2 -2
  155. package/es/mobile/ChatHeader/ChatHeaderTitle.d.mts +2 -2
  156. package/es/mobile/ChatInputArea/components/ChatSendButton.d.mts +2 -2
  157. package/es/mobile/TabBar/TabBar.d.mts +2 -2
  158. package/es/storybook/StoryBook/index.d.mts +2 -2
  159. package/package.json +1 -1
@@ -12,11 +12,11 @@ import { jsx, jsxs } from "react/jsx-runtime";
12
12
  import { cx, useThemeMode } from "antd-style";
13
13
  import useMergeState from "use-merge-value";
14
14
  import { isEqual } from "es-toolkit/compat";
15
- import { Undo2Icon } from "lucide-react";
15
+ import { Undo2Icon, XIcon } from "lucide-react";
16
16
  import { useHotkeys, useRecordHotkeys } from "react-hotkeys-hook";
17
17
 
18
18
  //#region src/HotkeyInput/HotkeyInput.tsx
19
- const HotkeyInput = memo(({ value = "", defaultValue = "", resetValue = "", onChange, onConflict, placeholder, disabled, shadow, allowReset = true, style, className, hotkeyConflicts = [], variant, texts, isApple, onBlur, onReset, onFocus }) => {
19
+ const HotkeyInput = memo(({ value = "", defaultValue = "", resetValue = "", onChange, onClear, onConflict, placeholder, disabled, shadow, allowClear, allowReset = true, style, className, hotkeyConflicts = [], variant, texts, isApple, onBlur, onReset, onFocus }) => {
20
20
  const [isFocused, setIsFocused] = useState(false);
21
21
  const [hasConflict, setHasConflict] = useState(false);
22
22
  const [hasInvalidCombination, setHasInvalidCombination] = useState(false);
@@ -106,6 +106,17 @@ const HotkeyInput = memo(({ value = "", defaultValue = "", resetValue = "", onCh
106
106
  stop();
107
107
  onBlur?.(e);
108
108
  };
109
+ const handleClear = (e) => {
110
+ e.preventDefault();
111
+ e.stopPropagation();
112
+ setHotkeyValue?.("");
113
+ resetKeys();
114
+ setHasConflict(false);
115
+ setHasInvalidCombination(false);
116
+ setIsFocused(false);
117
+ stop();
118
+ onClear?.(hotkeyValue);
119
+ };
109
120
  const handleReset = (e) => {
110
121
  e.preventDefault();
111
122
  e.stopPropagation();
@@ -125,6 +136,7 @@ const HotkeyInput = memo(({ value = "", defaultValue = "", resetValue = "", onCh
125
136
  };
126
137
  const placeholderText = placeholder ?? t("hotkey.placeholder");
127
138
  const resetTitle = texts?.reset ?? t("hotkey.reset");
139
+ const clearTitle = texts?.clear ?? t("hotkey.clear");
128
140
  const conflictText = texts?.conflicts ?? t("hotkey.conflict");
129
141
  const invalidText = texts?.invalidCombination ?? t("hotkey.invalidCombination");
130
142
  return /* @__PURE__ */ jsxs(FlexBasic_default, {
@@ -167,12 +179,22 @@ const HotkeyInput = memo(({ value = "", defaultValue = "", resetValue = "", onCh
167
179
  onBlur: handleBlur,
168
180
  onFocus: handleFocus
169
181
  }),
170
- !isFocused && allowReset && hotkeyValue && hotkeyValue !== resetValue && !disabled && /* @__PURE__ */ jsx(ActionIcon_default, {
171
- icon: Undo2Icon,
172
- size: "small",
173
- title: resetTitle,
174
- variant: "filled",
175
- onClick: handleReset
182
+ !isFocused && hotkeyValue && !disabled && (allowReset || allowClear) && /* @__PURE__ */ jsxs(FlexBasic_default, {
183
+ horizontal: true,
184
+ gap: 4,
185
+ children: [allowReset && hotkeyValue !== resetValue && /* @__PURE__ */ jsx(ActionIcon_default, {
186
+ icon: Undo2Icon,
187
+ size: "small",
188
+ title: resetTitle,
189
+ variant: "filled",
190
+ onClick: handleReset
191
+ }), allowClear && /* @__PURE__ */ jsx(ActionIcon_default, {
192
+ icon: XIcon,
193
+ size: "small",
194
+ title: clearTitle,
195
+ variant: "filled",
196
+ onClick: handleClear
197
+ })]
176
198
  })
177
199
  ]
178
200
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"HotkeyInput.mjs","names":["hotkeyMessages","useControlledState","modifiers: string[]","normalKeys: string[]","normalizedKey: any","Flexbox","Hotkey","ActionIcon"],"sources":["../../src/HotkeyInput/HotkeyInput.tsx"],"sourcesContent":["'use client';\n\nimport { type InputRef } from 'antd';\nimport { cx, useThemeMode } from 'antd-style';\nimport { isEqual } from 'es-toolkit/compat';\nimport { Undo2Icon } from 'lucide-react';\nimport {\n type FocusEvent,\n memo,\n type MouseEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useHotkeys, useRecordHotkeys } from 'react-hotkeys-hook';\nimport useControlledState from 'use-merge-value';\n\nimport ActionIcon from '@/ActionIcon';\nimport { Flexbox } from '@/Flex';\nimport Hotkey from '@/Hotkey';\nimport { checkIsAppleDevice, NORMATIVE_MODIFIER, splitKeysByPlus } from '@/Hotkey/utils';\nimport hotkeyMessages from '@/i18n/resources/en/hotkey';\nimport { useTranslation } from '@/i18n/useTranslation';\n\nimport { styles, variants } from './style';\nimport { type HotkeyInputProps } from './type';\n\nconst HotkeyInput = memo<HotkeyInputProps>(\n ({\n value = '',\n defaultValue = '',\n resetValue = '',\n onChange,\n onConflict,\n placeholder,\n disabled,\n shadow,\n allowReset = true,\n style,\n className,\n hotkeyConflicts = [],\n variant,\n texts,\n isApple,\n onBlur,\n onReset,\n onFocus,\n }) => {\n const [isFocused, setIsFocused] = useState(false);\n const [hasConflict, setHasConflict] = useState(false);\n const [hasInvalidCombination, setHasInvalidCombination] = useState(false);\n const inputRef = useRef<InputRef>(null);\n const { isDarkMode } = useThemeMode();\n const { t } = useTranslation(hotkeyMessages);\n const isAppleDevice = useMemo(() => checkIsAppleDevice(isApple), [isApple]);\n const [hotkeyValue, setHotkeyValue] = useControlledState(defaultValue, {\n defaultValue,\n onChange,\n value,\n });\n\n // 使用 useRecordHotkeys 处理快捷键录入\n const [recordedKeys, { start, stop, isRecording, resetKeys }] = useRecordHotkeys();\n\n useHotkeys(\n '*',\n () => {\n inputRef.current?.blur();\n },\n {\n enableOnContentEditable: true,\n enableOnFormTags: true,\n enabled: isRecording && !disabled,\n keydown: false,\n keyup: true,\n preventDefault: true,\n },\n );\n\n // 处理按键,保证格式正确:修饰键在前,最多一个非修饰键在后\n const formatKeys = useCallback((keysSet: Set<string>) => {\n const modifiers: string[] = [];\n const normalKeys: string[] = [];\n\n for (const key of keysSet) {\n // 处理不同表示的修饰键\n const normalizedKey: any = key.toLowerCase();\n if (NORMATIVE_MODIFIER.includes(normalizedKey)) {\n // 统一修饰键表示\n if (\n (!isAppleDevice && normalizedKey === 'ctrl') ||\n (isAppleDevice && normalizedKey === 'meta')\n ) {\n if (!modifiers.includes('mod')) modifiers.push('mod');\n } else if (!modifiers.includes(normalizedKey)) {\n modifiers.push(normalizedKey);\n }\n } else {\n normalKeys.push(key);\n }\n }\n\n // 至少需要一个修饰键\n if (modifiers.length === 0 && normalKeys.length > 0) {\n return { isValid: false, keys: [] };\n }\n\n // 只允许一个非修饰键,如果有多个,只保留最后一个\n const finalKey = normalKeys.length > 0 ? [normalKeys.at(-1)] : [];\n const shortcuts = [modifiers, finalKey];\n\n return {\n // 组合必须包含至少一个按键\n isValid: shortcuts.every((k) => k.length > 0),\n keys: shortcuts.flat(),\n };\n }, []);\n\n // 获取格式化后的按键字符串\n const { isValid, keys } = formatKeys(recordedKeys);\n const keysString = keys.join('+');\n\n // 检查快捷键冲突\n const checkHotkeyConflict = useCallback(\n (newHotkey: string): boolean => {\n return hotkeyConflicts\n .filter((conflictKey) => conflictKey !== resetValue)\n .some((conflictKey) => {\n const newKeys = splitKeysByPlus(newHotkey);\n const conflictKeys = splitKeysByPlus(conflictKey);\n return isEqual(newKeys, conflictKeys);\n });\n },\n [hotkeyConflicts],\n );\n\n // 当按键组合完成时处理结果\n useEffect(() => {\n if (recordedKeys.size > 0 && !isRecording) {\n if (!isValid) {\n setHasInvalidCombination(true);\n setHasConflict(false);\n return;\n }\n\n setHasInvalidCombination(false);\n const newKeysString = keysString;\n\n // 检查冲突\n const conflict = checkHotkeyConflict(newKeysString);\n if (conflict) {\n setHasConflict(true);\n onConflict?.(newKeysString);\n } else {\n setHasConflict(false);\n setHotkeyValue?.(newKeysString);\n }\n }\n }, [\n recordedKeys,\n isRecording,\n isValid,\n keysString,\n checkHotkeyConflict,\n setHotkeyValue,\n onConflict,\n ]);\n\n // 处理输入框焦点\n const handleFocus = (e: FocusEvent<HTMLInputElement>) => {\n if (disabled) return;\n setIsFocused(true);\n setHasConflict(false);\n setHasInvalidCombination(false);\n start(); // 开始记录\n onFocus?.(e);\n };\n\n const handleBlur = (e: FocusEvent<HTMLInputElement>) => {\n setIsFocused(false);\n stop(); // 停止记录\n onBlur?.(e);\n };\n\n // 重置功能\n const handleReset = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setHotkeyValue?.(resetValue);\n resetKeys();\n setHasConflict(false);\n setHasInvalidCombination(false);\n setIsFocused(false);\n stop(); // 停止记录\n onReset?.(hotkeyValue, resetValue);\n };\n\n const handleClick = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n if (disabled || isFocused) return;\n inputRef.current?.focus();\n };\n\n const placeholderText = placeholder ?? t('hotkey.placeholder');\n const resetTitle = texts?.reset ?? t('hotkey.reset');\n const conflictText = texts?.conflicts ?? t('hotkey.conflict');\n const invalidText = texts?.invalidCombination ?? t('hotkey.invalidCombination');\n\n return (\n <Flexbox\n className={className}\n gap={8}\n style={{\n position: 'relative',\n ...style,\n }}\n >\n <Flexbox\n horizontal\n align={'center'}\n justify={'space-between'}\n className={cx(\n variants({\n disabled,\n error: hasConflict || hasInvalidCombination,\n focused: isFocused,\n shadow,\n variant: variant || (isDarkMode ? 'filled' : 'outlined'),\n }),\n )}\n onClick={handleClick}\n >\n <div style={{ pointerEvents: 'none' }}>\n {isRecording ? (\n <span className={styles.placeholder}>\n {keys.length > 0 ? <Hotkey keys={keysString} /> : placeholderText}\n </span>\n ) : hotkeyValue ? (\n <Hotkey keys={hotkeyValue} />\n ) : (\n <span className={styles.placeholder}>{placeholderText}</span>\n )}\n </div>\n\n {/* 隐藏的输入框,用于接收焦点 */}\n <input\n readOnly\n className={styles.hiddenInput}\n disabled={disabled}\n ref={inputRef as any}\n style={{ pointerEvents: 'none' }}\n onBlur={handleBlur}\n onFocus={handleFocus}\n />\n\n {!isFocused && allowReset && hotkeyValue && hotkeyValue !== resetValue && !disabled && (\n <ActionIcon\n icon={Undo2Icon}\n size={'small'}\n title={resetTitle}\n variant={'filled'}\n onClick={handleReset}\n />\n )}\n </Flexbox>\n {hasConflict && <div className={styles.errorText}>{conflictText}</div>}\n {hasInvalidCombination && <div className={styles.errorText}>{invalidText}</div>}\n </Flexbox>\n );\n },\n);\n\nHotkeyInput.displayName = 'HotkeyInput';\n\nexport default HotkeyInput;\n"],"mappings":"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"}
1
+ {"version":3,"file":"HotkeyInput.mjs","names":["hotkeyMessages","useControlledState","modifiers: string[]","normalKeys: string[]","normalizedKey: any","Flexbox","Hotkey","ActionIcon"],"sources":["../../src/HotkeyInput/HotkeyInput.tsx"],"sourcesContent":["'use client';\n\nimport { type InputRef } from 'antd';\nimport { cx, useThemeMode } from 'antd-style';\nimport { isEqual } from 'es-toolkit/compat';\nimport { Undo2Icon, XIcon } from 'lucide-react';\nimport {\n type FocusEvent,\n memo,\n type MouseEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useHotkeys, useRecordHotkeys } from 'react-hotkeys-hook';\nimport useControlledState from 'use-merge-value';\n\nimport ActionIcon from '@/ActionIcon';\nimport { Flexbox } from '@/Flex';\nimport Hotkey from '@/Hotkey';\nimport { checkIsAppleDevice, NORMATIVE_MODIFIER, splitKeysByPlus } from '@/Hotkey/utils';\nimport hotkeyMessages from '@/i18n/resources/en/hotkey';\nimport { useTranslation } from '@/i18n/useTranslation';\n\nimport { styles, variants } from './style';\nimport { type HotkeyInputProps } from './type';\n\nconst HotkeyInput = memo<HotkeyInputProps>(\n ({\n value = '',\n defaultValue = '',\n resetValue = '',\n onChange,\n onClear,\n onConflict,\n placeholder,\n disabled,\n shadow,\n allowClear,\n allowReset = true,\n style,\n className,\n hotkeyConflicts = [],\n variant,\n texts,\n isApple,\n onBlur,\n onReset,\n onFocus,\n }) => {\n const [isFocused, setIsFocused] = useState(false);\n const [hasConflict, setHasConflict] = useState(false);\n const [hasInvalidCombination, setHasInvalidCombination] = useState(false);\n const inputRef = useRef<InputRef>(null);\n const { isDarkMode } = useThemeMode();\n const { t } = useTranslation(hotkeyMessages);\n const isAppleDevice = useMemo(() => checkIsAppleDevice(isApple), [isApple]);\n const [hotkeyValue, setHotkeyValue] = useControlledState(defaultValue, {\n defaultValue,\n onChange,\n value,\n });\n\n // 使用 useRecordHotkeys 处理快捷键录入\n const [recordedKeys, { start, stop, isRecording, resetKeys }] = useRecordHotkeys();\n\n useHotkeys(\n '*',\n () => {\n inputRef.current?.blur();\n },\n {\n enableOnContentEditable: true,\n enableOnFormTags: true,\n enabled: isRecording && !disabled,\n keydown: false,\n keyup: true,\n preventDefault: true,\n },\n );\n\n // 处理按键,保证格式正确:修饰键在前,最多一个非修饰键在后\n const formatKeys = useCallback((keysSet: Set<string>) => {\n const modifiers: string[] = [];\n const normalKeys: string[] = [];\n\n for (const key of keysSet) {\n // 处理不同表示的修饰键\n const normalizedKey: any = key.toLowerCase();\n if (NORMATIVE_MODIFIER.includes(normalizedKey)) {\n // 统一修饰键表示\n if (\n (!isAppleDevice && normalizedKey === 'ctrl') ||\n (isAppleDevice && normalizedKey === 'meta')\n ) {\n if (!modifiers.includes('mod')) modifiers.push('mod');\n } else if (!modifiers.includes(normalizedKey)) {\n modifiers.push(normalizedKey);\n }\n } else {\n normalKeys.push(key);\n }\n }\n\n // 至少需要一个修饰键\n if (modifiers.length === 0 && normalKeys.length > 0) {\n return { isValid: false, keys: [] };\n }\n\n // 只允许一个非修饰键,如果有多个,只保留最后一个\n const finalKey = normalKeys.length > 0 ? [normalKeys.at(-1)] : [];\n const shortcuts = [modifiers, finalKey];\n\n return {\n // 组合必须包含至少一个按键\n isValid: shortcuts.every((k) => k.length > 0),\n keys: shortcuts.flat(),\n };\n }, []);\n\n // 获取格式化后的按键字符串\n const { isValid, keys } = formatKeys(recordedKeys);\n const keysString = keys.join('+');\n\n // 检查快捷键冲突\n const checkHotkeyConflict = useCallback(\n (newHotkey: string): boolean => {\n return hotkeyConflicts\n .filter((conflictKey) => conflictKey !== resetValue)\n .some((conflictKey) => {\n const newKeys = splitKeysByPlus(newHotkey);\n const conflictKeys = splitKeysByPlus(conflictKey);\n return isEqual(newKeys, conflictKeys);\n });\n },\n [hotkeyConflicts],\n );\n\n // 当按键组合完成时处理结果\n useEffect(() => {\n if (recordedKeys.size > 0 && !isRecording) {\n if (!isValid) {\n setHasInvalidCombination(true);\n setHasConflict(false);\n return;\n }\n\n setHasInvalidCombination(false);\n const newKeysString = keysString;\n\n // 检查冲突\n const conflict = checkHotkeyConflict(newKeysString);\n if (conflict) {\n setHasConflict(true);\n onConflict?.(newKeysString);\n } else {\n setHasConflict(false);\n setHotkeyValue?.(newKeysString);\n }\n }\n }, [\n recordedKeys,\n isRecording,\n isValid,\n keysString,\n checkHotkeyConflict,\n setHotkeyValue,\n onConflict,\n ]);\n\n // 处理输入框焦点\n const handleFocus = (e: FocusEvent<HTMLInputElement>) => {\n if (disabled) return;\n setIsFocused(true);\n setHasConflict(false);\n setHasInvalidCombination(false);\n start(); // 开始记录\n onFocus?.(e);\n };\n\n const handleBlur = (e: FocusEvent<HTMLInputElement>) => {\n setIsFocused(false);\n stop(); // 停止记录\n onBlur?.(e);\n };\n\n const handleClear = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setHotkeyValue?.('');\n resetKeys();\n setHasConflict(false);\n setHasInvalidCombination(false);\n setIsFocused(false);\n stop();\n onClear?.(hotkeyValue);\n };\n\n // 重置功能\n const handleReset = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setHotkeyValue?.(resetValue);\n resetKeys();\n setHasConflict(false);\n setHasInvalidCombination(false);\n setIsFocused(false);\n stop(); // 停止记录\n onReset?.(hotkeyValue, resetValue);\n };\n\n const handleClick = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n if (disabled || isFocused) return;\n inputRef.current?.focus();\n };\n\n const placeholderText = placeholder ?? t('hotkey.placeholder');\n const resetTitle = texts?.reset ?? t('hotkey.reset');\n const clearTitle = texts?.clear ?? t('hotkey.clear');\n const conflictText = texts?.conflicts ?? t('hotkey.conflict');\n const invalidText = texts?.invalidCombination ?? t('hotkey.invalidCombination');\n\n return (\n <Flexbox\n className={className}\n gap={8}\n style={{\n position: 'relative',\n ...style,\n }}\n >\n <Flexbox\n horizontal\n align={'center'}\n justify={'space-between'}\n className={cx(\n variants({\n disabled,\n error: hasConflict || hasInvalidCombination,\n focused: isFocused,\n shadow,\n variant: variant || (isDarkMode ? 'filled' : 'outlined'),\n }),\n )}\n onClick={handleClick}\n >\n <div style={{ pointerEvents: 'none' }}>\n {isRecording ? (\n <span className={styles.placeholder}>\n {keys.length > 0 ? <Hotkey keys={keysString} /> : placeholderText}\n </span>\n ) : hotkeyValue ? (\n <Hotkey keys={hotkeyValue} />\n ) : (\n <span className={styles.placeholder}>{placeholderText}</span>\n )}\n </div>\n\n {/* 隐藏的输入框,用于接收焦点 */}\n <input\n readOnly\n className={styles.hiddenInput}\n disabled={disabled}\n ref={inputRef as any}\n style={{ pointerEvents: 'none' }}\n onBlur={handleBlur}\n onFocus={handleFocus}\n />\n\n {!isFocused && hotkeyValue && !disabled && (allowReset || allowClear) && (\n <Flexbox horizontal gap={4}>\n {allowReset && hotkeyValue !== resetValue && (\n <ActionIcon\n icon={Undo2Icon}\n size={'small'}\n title={resetTitle}\n variant={'filled'}\n onClick={handleReset}\n />\n )}\n {allowClear && (\n <ActionIcon\n icon={XIcon}\n size={'small'}\n title={clearTitle}\n variant={'filled'}\n onClick={handleClear}\n />\n )}\n </Flexbox>\n )}\n </Flexbox>\n {hasConflict && <div className={styles.errorText}>{conflictText}</div>}\n {hasInvalidCombination && <div className={styles.errorText}>{invalidText}</div>}\n </Flexbox>\n );\n },\n);\n\nHotkeyInput.displayName = 'HotkeyInput';\n\nexport default HotkeyInput;\n"],"mappings":"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"}
@@ -3,6 +3,7 @@ import { InputProps } from "antd";
3
3
 
4
4
  //#region src/HotkeyInput/type.d.ts
5
5
  interface HotkeyInputProps {
6
+ allowClear?: boolean;
6
7
  allowReset?: boolean;
7
8
  className?: string;
8
9
  defaultValue?: string;
@@ -11,6 +12,7 @@ interface HotkeyInputProps {
11
12
  isApple?: boolean;
12
13
  onBlur?: InputProps['onBlur'];
13
14
  onChange?: (value: string) => void;
15
+ onClear?: (currentValue: string) => void;
14
16
  onConflict?: (conflictKey: string) => void;
15
17
  onFocus?: InputProps['onFocus'];
16
18
  onReset?: (currentValue: string, resetValue: string) => void;
@@ -19,6 +21,7 @@ interface HotkeyInputProps {
19
21
  shadow?: boolean;
20
22
  style?: CSSProperties;
21
23
  texts?: {
24
+ clear?: string;
22
25
  conflicts?: string;
23
26
  invalidCombination?: string;
24
27
  reset?: string;
@@ -1,8 +1,8 @@
1
1
  import { IconProps } from "./type.mjs";
2
- import * as react21 from "react";
2
+ import * as react25 from "react";
3
3
 
4
4
  //#region src/Icon/Icon.d.ts
5
- declare const Icon: react21.NamedExoticComponent<IconProps>;
5
+ declare const Icon: react25.NamedExoticComponent<IconProps>;
6
6
  //#endregion
7
7
  export { Icon };
8
8
  //# sourceMappingURL=Icon.d.mts.map
@@ -1,11 +1,11 @@
1
1
  import { IconProps } from "../type.mjs";
2
- import * as react19 from "react";
2
+ import * as react23 from "react";
3
3
  import { ReactNode } from "react";
4
4
 
5
5
  //#region src/Icon/components/IconProvider.d.ts
6
6
  type IconContentConfig = Omit<IconProps, 'icon' | 'ref'>;
7
- declare const IconContext: react19.Context<IconContentConfig>;
8
- declare const IconProvider: react19.NamedExoticComponent<{
7
+ declare const IconContext: react23.Context<IconContentConfig>;
8
+ declare const IconProvider: react23.NamedExoticComponent<{
9
9
  children: ReactNode;
10
10
  config?: IconContentConfig;
11
11
  }>;
@@ -1,8 +1,8 @@
1
1
  import { PreviewGroupProps } from "./type.mjs";
2
- import * as react69 from "react";
2
+ import * as react35 from "react";
3
3
 
4
4
  //#region src/Image/PreviewGroup.d.ts
5
- declare const PreviewGroup: react69.NamedExoticComponent<PreviewGroupProps>;
5
+ declare const PreviewGroup: react35.NamedExoticComponent<PreviewGroupProps>;
6
6
  //#endregion
7
7
  export { PreviewGroup };
8
8
  //# sourceMappingURL=PreviewGroup.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { ImageSelectProps } from "./type.mjs";
2
- import * as react81 from "react";
2
+ import * as react118 from "react";
3
3
 
4
4
  //#region src/ImageSelect/ImageSelect.d.ts
5
- declare const ImageSelect: react81.NamedExoticComponent<ImageSelectProps>;
5
+ declare const ImageSelect: react118.NamedExoticComponent<ImageSelectProps>;
6
6
  //#endregion
7
7
  export { ImageSelect };
8
8
  //# sourceMappingURL=ImageSelect.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { InputProps } from "./type.mjs";
2
- import * as react121 from "react";
2
+ import * as react153 from "react";
3
3
 
4
4
  //#region src/Input/Input.d.ts
5
- declare const Input: react121.NamedExoticComponent<InputProps>;
5
+ declare const Input: react153.NamedExoticComponent<InputProps>;
6
6
  //#endregion
7
7
  export { Input };
8
8
  //# sourceMappingURL=Input.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { InputNumberProps } from "./type.mjs";
2
- import * as react123 from "react";
2
+ import * as react154 from "react";
3
3
 
4
4
  //#region src/Input/InputNumber.d.ts
5
- declare const InputNumber: react123.NamedExoticComponent<InputNumberProps>;
5
+ declare const InputNumber: react154.NamedExoticComponent<InputNumberProps>;
6
6
  //#endregion
7
7
  export { InputNumber };
8
8
  //# sourceMappingURL=InputNumber.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { InputOPTProps } from "./type.mjs";
2
- import * as react124 from "react";
2
+ import * as react155 from "react";
3
3
 
4
4
  //#region src/Input/InputOPT.d.ts
5
- declare const InputOPT: react124.NamedExoticComponent<InputOPTProps>;
5
+ declare const InputOPT: react155.NamedExoticComponent<InputOPTProps>;
6
6
  //#endregion
7
7
  export { InputOPT };
8
8
  //# sourceMappingURL=InputOPT.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { InputPasswordProps } from "./type.mjs";
2
- import * as react125 from "react";
2
+ import * as react156 from "react";
3
3
 
4
4
  //#region src/Input/InputPassword.d.ts
5
- declare const InputPassword: react125.NamedExoticComponent<InputPasswordProps>;
5
+ declare const InputPassword: react156.NamedExoticComponent<InputPasswordProps>;
6
6
  //#endregion
7
7
  export { InputPassword };
8
8
  //# sourceMappingURL=InputPassword.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { TextAreaProps } from "./type.mjs";
2
- import * as react126 from "react";
2
+ import * as react157 from "react";
3
3
 
4
4
  //#region src/Input/TextArea.d.ts
5
- declare const TextArea: react126.NamedExoticComponent<TextAreaProps>;
5
+ declare const TextArea: react157.NamedExoticComponent<TextAreaProps>;
6
6
  //#endregion
7
7
  export { TextArea };
8
8
  //# sourceMappingURL=TextArea.d.mts.map
@@ -1,9 +1,9 @@
1
1
  import { DivProps } from "../../types/index.mjs";
2
2
  import "../../index.mjs";
3
- import * as react62 from "react";
3
+ import * as react28 from "react";
4
4
 
5
5
  //#region src/Layout/components/LayoutFooter.d.ts
6
- declare const LayoutFooter: react62.NamedExoticComponent<DivProps>;
6
+ declare const LayoutFooter: react28.NamedExoticComponent<DivProps>;
7
7
  //#endregion
8
8
  export { LayoutFooter };
9
9
  //# sourceMappingURL=LayoutFooter.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { LayoutHeaderProps } from "../type.mjs";
2
- import * as react64 from "react";
2
+ import * as react29 from "react";
3
3
 
4
4
  //#region src/Layout/components/LayoutHeader.d.ts
5
- declare const LayoutHeader: react64.NamedExoticComponent<LayoutHeaderProps>;
5
+ declare const LayoutHeader: react29.NamedExoticComponent<LayoutHeaderProps>;
6
6
  //#endregion
7
7
  export { LayoutHeader };
8
8
  //# sourceMappingURL=LayoutHeader.d.mts.map
@@ -1,9 +1,9 @@
1
1
  import { DivProps } from "../../types/index.mjs";
2
2
  import "../../index.mjs";
3
- import * as react63 from "react";
3
+ import * as react30 from "react";
4
4
 
5
5
  //#region src/Layout/components/LayoutMain.d.ts
6
- declare const LayoutMain: react63.NamedExoticComponent<DivProps>;
6
+ declare const LayoutMain: react30.NamedExoticComponent<DivProps>;
7
7
  //#endregion
8
8
  export { LayoutMain };
9
9
  //# sourceMappingURL=LayoutMain.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { LayoutSidebarProps } from "../type.mjs";
2
- import * as react66 from "react";
2
+ import * as react31 from "react";
3
3
 
4
4
  //#region src/Layout/components/LayoutSidebar.d.ts
5
- declare const LayoutSidebar: react66.NamedExoticComponent<LayoutSidebarProps>;
5
+ declare const LayoutSidebar: react31.NamedExoticComponent<LayoutSidebarProps>;
6
6
  //#endregion
7
7
  export { LayoutSidebar };
8
8
  //# sourceMappingURL=LayoutSidebar.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { LayoutSidebarInnerProps } from "../type.mjs";
2
- import * as react65 from "react";
2
+ import * as react32 from "react";
3
3
 
4
4
  //#region src/Layout/components/LayoutSidebarInner.d.ts
5
- declare const LayoutSidebarInner: react65.NamedExoticComponent<LayoutSidebarInnerProps>;
5
+ declare const LayoutSidebarInner: react32.NamedExoticComponent<LayoutSidebarInnerProps>;
6
6
  //#endregion
7
7
  export { LayoutSidebarInner };
8
8
  //# sourceMappingURL=LayoutSidebarInner.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { LayoutTocProps } from "../type.mjs";
2
- import * as react67 from "react";
2
+ import * as react33 from "react";
3
3
 
4
4
  //#region src/Layout/components/LayoutToc.d.ts
5
- declare const LayoutToc: react67.NamedExoticComponent<LayoutTocProps>;
5
+ declare const LayoutToc: react33.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 react68 from "react";
2
+ import * as react27 from "react";
3
3
 
4
4
  //#region src/List/ListItem/index.d.ts
5
- declare const ListItem: react68.NamedExoticComponent<ListItemProps>;
5
+ declare const ListItem: react27.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 react58 from "react";
2
+ import * as react37 from "react";
3
3
 
4
4
  //#region src/Markdown/Markdown.d.ts
5
- declare const Markdown: react58.NamedExoticComponent<MarkdownProps>;
5
+ declare const Markdown: react37.NamedExoticComponent<MarkdownProps>;
6
6
  //#endregion
7
7
  export { Markdown };
8
8
  //# sourceMappingURL=Markdown.d.mts.map
@@ -13,7 +13,7 @@ import { cx } from "antd-style";
13
13
 
14
14
  //#region src/Markdown/Markdown.tsx
15
15
  const Markdown = memo((props) => {
16
- const { ref, children = "", className, style, fullFeaturedCodeBlock, onDoubleClick, animated, enableLatex = true, enableMermaid = true, enableImageGallery, enableCustomFootnotes, enableGithubAlert, enableStream = true, componentProps, rehypePluginsAhead, allowHtml, borderRadius, fontSize = props.variant === "chat" ? 14 : void 0, headerMultiple = props.variant === "chat" ? .25 : void 0, marginMultiple = props.variant === "chat" ? 1 : void 0, variant = "default", reactMarkdownProps, lineHeight = props.variant === "chat" ? 1.6 : void 0, rehypePlugins, remarkPlugins, remarkPluginsAhead, components = {}, customRender, showFootnotes = true, citations, ...rest } = props;
16
+ const { ref, children = "", className, style, fullFeaturedCodeBlock, onDoubleClick, animated, enableLatex = true, enableMermaid = true, enableImageGallery, enableCustomFootnotes, enableGithubAlert, enableStream = true, componentProps, rehypePluginsAhead, allowHtml, borderRadius, fontSize = props.variant === "chat" ? 14 : void 0, headerMultiple = props.variant === "chat" ? .25 : void 0, marginMultiple = props.variant === "chat" ? 1 : void 0, variant = "default", reactMarkdownProps, lineHeight = props.variant === "chat" ? 1.6 : void 0, rehypePlugins, remarkPlugins, remarkPluginsAhead, components = {}, customRender, showFootnotes = true, streamSmoothingPreset, citations, ...rest } = props;
17
17
  const delayedAnimated = useDelayedAnimated(animated);
18
18
  const Render = useCallback(({ enableStream: enableStream$1, children: children$1, reactMarkdownProps: reactMarkdownProps$1 }) => {
19
19
  const defaultDOM = /* @__PURE__ */ jsx(enableStream$1 ? StreamdownRender_default : MarkdownRender_default, {
@@ -55,6 +55,7 @@ const Markdown = memo((props) => {
55
55
  remarkPlugins,
56
56
  remarkPluginsAhead,
57
57
  showFootnotes,
58
+ streamSmoothingPreset,
58
59
  variant,
59
60
  children: /* @__PURE__ */ jsx(Render, {
60
61
  enableStream: enableStream && delayedAnimated,
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.mjs","names":["enableStream","StreamdownRender","MarkdownRender","reactMarkdownProps","children","PreviewGroup","Typography"],"sources":["../../src/Markdown/Markdown.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'antd-style';\nimport { memo, useCallback } from 'react';\n\nimport { PreviewGroup } from '@/Image';\n\nimport { MarkdownProvider } from './components/MarkdownProvider';\nimport { useDelayedAnimated } from './components/useDelayedAnimated';\nimport { variants } from './style';\nimport { MarkdownRender, StreamdownRender } from './SyntaxMarkdown';\nimport { type MarkdownProps } from './type';\nimport Typography from './Typography';\n\nconst Markdown = memo<MarkdownProps>((props) => {\n const {\n ref,\n children = '',\n className,\n style,\n fullFeaturedCodeBlock,\n onDoubleClick,\n animated,\n enableLatex = true,\n enableMermaid = true,\n enableImageGallery,\n enableCustomFootnotes,\n enableGithubAlert,\n enableStream = true,\n componentProps,\n rehypePluginsAhead,\n allowHtml,\n borderRadius,\n fontSize = props.variant === 'chat' ? 14 : undefined,\n headerMultiple = props.variant === 'chat' ? 0.25 : undefined,\n marginMultiple = props.variant === 'chat' ? 1 : undefined,\n variant = 'default',\n reactMarkdownProps,\n lineHeight = props.variant === 'chat' ? 1.6 : undefined,\n rehypePlugins,\n remarkPlugins,\n remarkPluginsAhead,\n components = {},\n customRender,\n showFootnotes = true,\n citations,\n ...rest\n } = props;\n\n const delayedAnimated = useDelayedAnimated(animated);\n\n const Render = useCallback(\n ({\n enableStream,\n children,\n reactMarkdownProps,\n }: Pick<MarkdownProps, 'children' | 'enableStream' | 'reactMarkdownProps'>) => {\n const DefaultRender = enableStream ? StreamdownRender : MarkdownRender;\n const defaultDOM = <DefaultRender {...reactMarkdownProps}>{children}</DefaultRender>;\n return customRender ? customRender(defaultDOM, { text: children }) : defaultDOM;\n },\n [customRender],\n );\n\n return (\n <PreviewGroup enable={enableImageGallery}>\n <Typography\n borderRadius={borderRadius}\n className={cx(variants({ enableLatex, variant }), className)}\n data-code-type=\"markdown\"\n fontSize={fontSize}\n headerMultiple={headerMultiple}\n lineHeight={lineHeight}\n marginMultiple={marginMultiple}\n ref={ref}\n style={style}\n onDoubleClick={onDoubleClick}\n {...rest}\n >\n <MarkdownProvider\n allowHtml={allowHtml}\n animated={delayedAnimated}\n citations={citations}\n componentProps={componentProps}\n components={components}\n enableCustomFootnotes={enableCustomFootnotes}\n enableGithubAlert={enableGithubAlert}\n enableLatex={enableLatex}\n enableMermaid={enableMermaid}\n fullFeaturedCodeBlock={fullFeaturedCodeBlock}\n rehypePlugins={rehypePlugins}\n rehypePluginsAhead={rehypePluginsAhead}\n remarkPlugins={remarkPlugins}\n remarkPluginsAhead={remarkPluginsAhead}\n showFootnotes={showFootnotes}\n variant={variant}\n >\n <Render\n enableStream={enableStream && delayedAnimated}\n reactMarkdownProps={reactMarkdownProps}\n >\n {children}\n </Render>\n </MarkdownProvider>\n </Typography>\n </PreviewGroup>\n );\n});\n\nMarkdown.displayName = 'Markdown';\n\nexport default Markdown;\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAM,WAAW,MAAqB,UAAU;CAC9C,MAAM,EACJ,KACA,WAAW,IACX,WACA,OACA,uBACA,eACA,UACA,cAAc,MACd,gBAAgB,MAChB,oBACA,uBACA,mBACA,eAAe,MACf,gBACA,oBACA,WACA,cACA,WAAW,MAAM,YAAY,SAAS,KAAK,QAC3C,iBAAiB,MAAM,YAAY,SAAS,MAAO,QACnD,iBAAiB,MAAM,YAAY,SAAS,IAAI,QAChD,UAAU,WACV,oBACA,aAAa,MAAM,YAAY,SAAS,MAAM,QAC9C,eACA,eACA,oBACA,aAAa,EAAE,EACf,cACA,gBAAgB,MAChB,WACA,GAAG,SACD;CAEJ,MAAM,kBAAkB,mBAAmB,SAAS;CAEpD,MAAM,SAAS,aACZ,EACC,8BACA,sBACA,+CAC6E;EAE7E,MAAM,aAAa,oBADGA,iBAAeC,2BAAmBC;GACtB,GAAIC;aAAqBC;IAAyB;AACpF,SAAO,eAAe,aAAa,YAAY,EAAE,MAAMA,YAAU,CAAC,GAAG;IAEvE,CAAC,aAAa,CACf;AAED,QACE,oBAACC;EAAa,QAAQ;YACpB,oBAACC;GACe;GACd,WAAW,GAAG,SAAS;IAAE;IAAa;IAAS,CAAC,EAAE,UAAU;GAC5D,kBAAe;GACL;GACM;GACJ;GACI;GACX;GACE;GACQ;GACf,GAAI;aAEJ,oBAAC;IACY;IACX,UAAU;IACC;IACK;IACJ;IACW;IACJ;IACN;IACE;IACQ;IACR;IACK;IACL;IACK;IACL;IACN;cAET,oBAAC;KACC,cAAc,gBAAgB;KACV;KAEnB;MACM;KACQ;IACR;GACA;EAEjB;AAEF,SAAS,cAAc;AAEvB,uBAAe"}
1
+ {"version":3,"file":"Markdown.mjs","names":["enableStream","StreamdownRender","MarkdownRender","reactMarkdownProps","children","PreviewGroup","Typography"],"sources":["../../src/Markdown/Markdown.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'antd-style';\nimport { memo, useCallback } from 'react';\n\nimport { PreviewGroup } from '@/Image';\n\nimport { MarkdownProvider } from './components/MarkdownProvider';\nimport { useDelayedAnimated } from './components/useDelayedAnimated';\nimport { variants } from './style';\nimport { MarkdownRender, StreamdownRender } from './SyntaxMarkdown';\nimport { type MarkdownProps } from './type';\nimport Typography from './Typography';\n\nconst Markdown = memo<MarkdownProps>((props) => {\n const {\n ref,\n children = '',\n className,\n style,\n fullFeaturedCodeBlock,\n onDoubleClick,\n animated,\n enableLatex = true,\n enableMermaid = true,\n enableImageGallery,\n enableCustomFootnotes,\n enableGithubAlert,\n enableStream = true,\n componentProps,\n rehypePluginsAhead,\n allowHtml,\n borderRadius,\n fontSize = props.variant === 'chat' ? 14 : undefined,\n headerMultiple = props.variant === 'chat' ? 0.25 : undefined,\n marginMultiple = props.variant === 'chat' ? 1 : undefined,\n variant = 'default',\n reactMarkdownProps,\n lineHeight = props.variant === 'chat' ? 1.6 : undefined,\n rehypePlugins,\n remarkPlugins,\n remarkPluginsAhead,\n components = {},\n customRender,\n showFootnotes = true,\n streamSmoothingPreset,\n citations,\n ...rest\n } = props;\n\n const delayedAnimated = useDelayedAnimated(animated);\n\n const Render = useCallback(\n ({\n enableStream,\n children,\n reactMarkdownProps,\n }: Pick<MarkdownProps, 'children' | 'enableStream' | 'reactMarkdownProps'>) => {\n const DefaultRender = enableStream ? StreamdownRender : MarkdownRender;\n const defaultDOM = <DefaultRender {...reactMarkdownProps}>{children}</DefaultRender>;\n return customRender ? customRender(defaultDOM, { text: children }) : defaultDOM;\n },\n [customRender],\n );\n\n return (\n <PreviewGroup enable={enableImageGallery}>\n <Typography\n borderRadius={borderRadius}\n className={cx(variants({ enableLatex, variant }), className)}\n data-code-type=\"markdown\"\n fontSize={fontSize}\n headerMultiple={headerMultiple}\n lineHeight={lineHeight}\n marginMultiple={marginMultiple}\n ref={ref}\n style={style}\n onDoubleClick={onDoubleClick}\n {...rest}\n >\n <MarkdownProvider\n allowHtml={allowHtml}\n animated={delayedAnimated}\n citations={citations}\n componentProps={componentProps}\n components={components}\n enableCustomFootnotes={enableCustomFootnotes}\n enableGithubAlert={enableGithubAlert}\n enableLatex={enableLatex}\n enableMermaid={enableMermaid}\n fullFeaturedCodeBlock={fullFeaturedCodeBlock}\n rehypePlugins={rehypePlugins}\n rehypePluginsAhead={rehypePluginsAhead}\n remarkPlugins={remarkPlugins}\n remarkPluginsAhead={remarkPluginsAhead}\n showFootnotes={showFootnotes}\n streamSmoothingPreset={streamSmoothingPreset}\n variant={variant}\n >\n <Render\n enableStream={enableStream && delayedAnimated}\n reactMarkdownProps={reactMarkdownProps}\n >\n {children}\n </Render>\n </MarkdownProvider>\n </Typography>\n </PreviewGroup>\n );\n});\n\nMarkdown.displayName = 'Markdown';\n\nexport default Markdown;\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAM,WAAW,MAAqB,UAAU;CAC9C,MAAM,EACJ,KACA,WAAW,IACX,WACA,OACA,uBACA,eACA,UACA,cAAc,MACd,gBAAgB,MAChB,oBACA,uBACA,mBACA,eAAe,MACf,gBACA,oBACA,WACA,cACA,WAAW,MAAM,YAAY,SAAS,KAAK,QAC3C,iBAAiB,MAAM,YAAY,SAAS,MAAO,QACnD,iBAAiB,MAAM,YAAY,SAAS,IAAI,QAChD,UAAU,WACV,oBACA,aAAa,MAAM,YAAY,SAAS,MAAM,QAC9C,eACA,eACA,oBACA,aAAa,EAAE,EACf,cACA,gBAAgB,MAChB,uBACA,WACA,GAAG,SACD;CAEJ,MAAM,kBAAkB,mBAAmB,SAAS;CAEpD,MAAM,SAAS,aACZ,EACC,8BACA,sBACA,+CAC6E;EAE7E,MAAM,aAAa,oBADGA,iBAAeC,2BAAmBC;GACtB,GAAIC;aAAqBC;IAAyB;AACpF,SAAO,eAAe,aAAa,YAAY,EAAE,MAAMA,YAAU,CAAC,GAAG;IAEvE,CAAC,aAAa,CACf;AAED,QACE,oBAACC;EAAa,QAAQ;YACpB,oBAACC;GACe;GACd,WAAW,GAAG,SAAS;IAAE;IAAa;IAAS,CAAC,EAAE,UAAU;GAC5D,kBAAe;GACL;GACM;GACJ;GACI;GACX;GACE;GACQ;GACf,GAAI;aAEJ,oBAAC;IACY;IACX,UAAU;IACC;IACK;IACJ;IACW;IACJ;IACN;IACE;IACQ;IACR;IACK;IACL;IACK;IACL;IACQ;IACd;cAET,oBAAC;KACC,cAAc,gBAAgB;KACV;KAEnB;MACM;KACQ;IACR;GACA;EAEjB;AAEF,SAAS,cAAc;AAEvB,uBAAe"}
@@ -1,11 +1,13 @@
1
1
  'use client';
2
2
 
3
+ import { useMarkdownContext } from "../components/MarkdownProvider.mjs";
3
4
  import { useMarkdownComponents } from "../../hooks/useMarkdown/useMarkdownComponents.mjs";
4
5
  import { useMarkdownContent } from "../../hooks/useMarkdown/useMarkdownContent.mjs";
5
6
  import { useMarkdownRehypePlugins } from "../../hooks/useMarkdown/useMarkdownRehypePlugins.mjs";
6
7
  import { useMarkdownRemarkPlugins } from "../../hooks/useMarkdown/useMarkdownRemarkPlugins.mjs";
7
8
  import { rehypeStreamAnimated } from "../plugins/rehypeStreamAnimated.mjs";
8
9
  import { styles } from "./style.mjs";
10
+ import { useSmoothStreamContent } from "./useSmoothStreamContent.mjs";
9
11
  import { useStreamQueue } from "./useStreamQueue.mjs";
10
12
  import { createElement, memo, useEffect, useId, useMemo, useRef } from "react";
11
13
  import { jsx } from "react/jsx-runtime";
@@ -14,7 +16,7 @@ import { marked } from "marked";
14
16
  import remend from "remend";
15
17
 
16
18
  //#region src/Markdown/SyntaxMarkdown/StreamdownRender.tsx
17
- const STREAM_CHAR_DELAY = 15;
19
+ const STREAM_FADE_DURATION = 280;
18
20
  function countChars(text) {
19
21
  return [...text].length;
20
22
  }
@@ -61,14 +63,16 @@ const StreamdownBlock = memo(({ children, ...rest }) => {
61
63
  }, (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.components === nextProps.components && isSamePlugins(prevProps.rehypePlugins, nextProps.rehypePlugins) && isSamePlugins(prevProps.remarkPlugins, nextProps.remarkPlugins));
62
64
  StreamdownBlock.displayName = "StreamdownBlock";
63
65
  const StreamdownRender = memo(({ children, ...rest }) => {
66
+ const { streamSmoothingPreset = "balanced" } = useMarkdownContext();
64
67
  const escapedContent = useMarkdownContent(children || "");
65
68
  const components = useMarkdownComponents();
66
69
  const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());
67
70
  const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());
68
71
  const generatedId = useId();
72
+ const smoothedContent = useSmoothStreamContent(typeof escapedContent === "string" ? escapedContent : "", { preset: streamSmoothingPreset });
69
73
  const processedContent = useMemo(() => {
70
- return remend(typeof escapedContent === "string" ? escapedContent : "");
71
- }, [escapedContent]);
74
+ return remend(smoothedContent);
75
+ }, [smoothedContent]);
72
76
  const blocks = useMemo(() => {
73
77
  const tokens = marked.lexer(processedContent);
74
78
  let offset = 0;
@@ -82,41 +86,63 @@ const StreamdownRender = memo(({ children, ...rest }) => {
82
86
  });
83
87
  }, [processedContent]);
84
88
  const { getBlockState, charDelay } = useStreamQueue(blocks);
85
- const staggerPlugins = useMemo(() => [...baseRehypePlugins, [rehypeStreamAnimated, {
86
- baseCharCount: 0,
87
- charDelay
88
- }]], [baseRehypePlugins, charDelay]);
89
- const revealedPlugins = useMemo(() => [...baseRehypePlugins, [rehypeStreamAnimated, { revealed: true }]], [baseRehypePlugins]);
90
- const prevCharCountRef = useRef(0);
91
- const prevStreamOffsetRef = useRef(-1);
92
- useEffect(() => {
93
- const tailIdx = blocks.length - 1;
94
- if (tailIdx >= 0) {
95
- const tail = blocks[tailIdx];
96
- if (tail.startOffset !== prevStreamOffsetRef.current) {
97
- prevStreamOffsetRef.current = tail.startOffset;
98
- prevCharCountRef.current = 0;
89
+ const prevBlockCharCountRef = useRef(/* @__PURE__ */ new Map());
90
+ const blockTimelineRef = useRef(/* @__PURE__ */ new Map());
91
+ const lastRenderTsRef = useRef(null);
92
+ const renderTs = typeof performance === "undefined" ? Date.now() : performance.now();
93
+ const frameDt = lastRenderTsRef.current === null ? 0 : Math.max(0, Math.min(renderTs - lastRenderTsRef.current, 120));
94
+ const timelineForRender = useMemo(() => {
95
+ const next = /* @__PURE__ */ new Map();
96
+ const prevTimeline = blockTimelineRef.current;
97
+ const prevCharCounts = prevBlockCharCountRef.current;
98
+ for (const block of blocks) {
99
+ const blockCharCount = countChars(block.content);
100
+ const prevCharCount = prevCharCounts.get(block.startOffset) ?? 0;
101
+ const prevElapsed = prevTimeline.get(block.startOffset);
102
+ const latestCharStart = Math.max(0, (blockCharCount - 1) * charDelay);
103
+ if (prevElapsed === void 0 || blockCharCount < prevCharCount) {
104
+ next.set(block.startOffset, latestCharStart);
105
+ continue;
99
106
  }
100
- prevCharCountRef.current = countChars(tail.content);
101
- } else {
102
- prevCharCountRef.current = 0;
103
- prevStreamOffsetRef.current = -1;
107
+ const elapsedByTime = prevElapsed + frameDt;
108
+ const minElapsed = Math.max(0, latestCharStart - charDelay * 2);
109
+ next.set(block.startOffset, Math.max(elapsedByTime, minElapsed));
104
110
  }
105
- }, [blocks]);
111
+ return next;
112
+ }, [
113
+ blocks,
114
+ charDelay,
115
+ frameDt
116
+ ]);
117
+ useEffect(() => {
118
+ const nextCharCount = /* @__PURE__ */ new Map();
119
+ for (const block of blocks) nextCharCount.set(block.startOffset, countChars(block.content));
120
+ prevBlockCharCountRef.current = nextCharCount;
121
+ blockTimelineRef.current = timelineForRender;
122
+ lastRenderTsRef.current = typeof performance === "undefined" ? Date.now() : performance.now();
123
+ }, [blocks, timelineForRender]);
106
124
  return /* @__PURE__ */ jsx("div", {
107
125
  className: styles.animated,
108
126
  children: blocks.map((block, index) => {
109
127
  const state = getBlockState(index);
110
128
  if (state === "queued") return null;
129
+ const timelineElapsedMs = timelineForRender.get(block.startOffset) ?? 0;
111
130
  let plugins;
112
- if (state === "streaming") {
113
- const baseCharCount = block.startOffset === prevStreamOffsetRef.current ? prevCharCountRef.current : 0;
114
- plugins = [...baseRehypePlugins, [rehypeStreamAnimated, {
115
- baseCharCount,
116
- charDelay: STREAM_CHAR_DELAY
117
- }]];
118
- } else if (state === "animating") plugins = staggerPlugins;
119
- else plugins = revealedPlugins;
131
+ if (state === "streaming") plugins = [...baseRehypePlugins, [rehypeStreamAnimated, {
132
+ charDelay,
133
+ fadeDuration: STREAM_FADE_DURATION,
134
+ timelineElapsedMs
135
+ }]];
136
+ else if (state === "animating") plugins = [...baseRehypePlugins, [rehypeStreamAnimated, {
137
+ charDelay,
138
+ fadeDuration: STREAM_FADE_DURATION,
139
+ timelineElapsedMs
140
+ }]];
141
+ else plugins = [...baseRehypePlugins, [rehypeStreamAnimated, {
142
+ charDelay,
143
+ fadeDuration: STREAM_FADE_DURATION,
144
+ timelineElapsedMs
145
+ }]];
120
146
  return /* @__PURE__ */ createElement(StreamdownBlock, {
121
147
  ...rest,
122
148
  components,
@@ -1 +1 @@
1
- {"version":3,"file":"StreamdownRender.mjs","names":["blocks: BlockInfo[]","staggerPlugins: Pluggable[]","revealedPlugins: Pluggable[]","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 { rehypeStreamAnimated } from '@/Markdown/plugins/rehypeStreamAnimated';\n\nimport { styles } from './style';\nimport { type BlockInfo, useStreamQueue } from './useStreamQueue';\n\nconst STREAM_CHAR_DELAY = 15;\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 escapedContent = useMarkdownContent(children || '');\n const components = useMarkdownComponents();\n const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());\n const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());\n const generatedId = useId();\n\n const processedContent = useMemo(() => {\n const content = typeof escapedContent === 'string' ? escapedContent : '';\n return remend(content);\n }, [escapedContent]);\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\n const staggerPlugins: Pluggable[] = useMemo(\n () => [...baseRehypePlugins, [rehypeStreamAnimated, { baseCharCount: 0, charDelay }]],\n [baseRehypePlugins, charDelay],\n );\n\n const revealedPlugins: Pluggable[] = useMemo(\n () => [...baseRehypePlugins, [rehypeStreamAnimated, { revealed: true }]],\n [baseRehypePlugins],\n );\n\n // prevCharCount tracks the PREVIOUS render's streaming char count.\n // Updated in useEffect (after render) to avoid stale reads during\n // synchronous re-renders (e.g. useLayoutEffect auto-reveal).\n const prevCharCountRef = useRef(0);\n const prevStreamOffsetRef = useRef(-1);\n\n useEffect(() => {\n const tailIdx = blocks.length - 1;\n if (tailIdx >= 0) {\n const tail = blocks[tailIdx];\n if (tail.startOffset !== prevStreamOffsetRef.current) {\n prevStreamOffsetRef.current = tail.startOffset;\n prevCharCountRef.current = 0;\n }\n prevCharCountRef.current = countChars(tail.content);\n } else {\n prevCharCountRef.current = 0;\n prevStreamOffsetRef.current = -1;\n }\n }, [blocks]);\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\n let plugins: Pluggable[];\n if (state === 'streaming') {\n const baseCharCount =\n block.startOffset === prevStreamOffsetRef.current ? prevCharCountRef.current : 0;\n plugins = [\n ...baseRehypePlugins,\n [rehypeStreamAnimated, { baseCharCount, charDelay: STREAM_CHAR_DELAY }],\n ];\n } else if (state === 'animating') {\n plugins = staggerPlugins;\n } else {\n plugins = revealedPlugins;\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":["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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"}
@@ -8,8 +8,8 @@ const styles = createStaticStyles(({ css: css$1 }) => {
8
8
  opacity: 0;
9
9
 
10
10
  animation-name: ${fadeIn};
11
- animation-duration: 150ms;
12
- animation-timing-function: ease-out;
11
+ animation-duration: 280ms;
12
+ animation-timing-function: cubic-bezier(0.33, 0, 0.67, 1);
13
13
  animation-fill-mode: forwards;
14
14
  }
15
15