{"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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"}