{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "streaming-markdown",
  "dependencies": [
    "react-markdown",
    "remark-gfm",
    "marked"
  ],
  "registryDependencies": [
    "https://turnui.xyz/r/citations.json",
    "https://turnui.xyz/r/code-block.json",
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/streaming-markdown.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport { marked } from \"marked\";\nimport { cn } from \"@/lib/utils\";\nimport { CodeBlock } from \"./code-block\";\nimport { CitationMarker, type Source } from \"./citations\";\n\ntype MdNode = {\n  type: string;\n  value?: string;\n  children?: MdNode[];\n  data?: Record<string, unknown>;\n};\n\n/** Turns \"[1]\" in text into <citation index=\"1\"> elements. */\nfunction remarkCitations() {\n  const walk = (node: MdNode) => {\n    if (!node.children || node.type === \"link\") return;\n    const next: MdNode[] = [];\n    for (const child of node.children) {\n      if (child.type !== \"text\" || !child.value) {\n        walk(child);\n        next.push(child);\n        continue;\n      }\n      const parts = child.value.split(/\\[(\\d{1,3})\\]/);\n      parts.forEach((part, i) => {\n        if (i % 2 === 0) {\n          if (part) next.push({ type: \"text\", value: part });\n        } else {\n          next.push({\n            type: \"citation\",\n            data: { hName: \"citation\", hProperties: { index: part } },\n          });\n        }\n      });\n    }\n    node.children = next;\n  };\n  return (tree: MdNode) => walk(tree);\n}\n\nconst baseComponents = {\n  code({ className, children, ...props }: React.ComponentProps<\"code\">) {\n    const lang = /language-(\\w+)/.exec(className ?? \"\")?.[1];\n    const text = String(children).replace(/\\n$/, \"\");\n    if (!lang && !text.includes(\"\\n\")) {\n      return (\n        <code\n          className=\"rounded-sm bg-muted px-1.5 py-0.5 font-mono text-[0.9em]\"\n          {...props}\n        >\n          {children}\n        </code>\n      );\n    }\n    return <CodeBlock code={text} lang={lang} className=\"my-3\" />;\n  },\n  pre: ({ children }: React.ComponentProps<\"pre\">) => <>{children}</>,\n  a: (props: React.ComponentProps<\"a\">) => (\n    <a\n      className=\"underline underline-offset-4 decoration-border hover:decoration-foreground\"\n      target=\"_blank\"\n      rel=\"noreferrer\"\n      {...props}\n    />\n  ),\n};\n\nconst Block = React.memo(function Block({\n  source,\n  sources,\n}: {\n  source: string;\n  sources?: Source[];\n}) {\n  const components = React.useMemo(\n    () => ({\n      ...baseComponents,\n      citation: ({ index }: { index?: string }) => {\n        const n = Number(index);\n        const src = sources?.[n - 1];\n        return src ? <CitationMarker index={n} source={src} /> : <>[{index}]</>;\n      },\n    }),\n    [sources],\n  );\n  return (\n    <ReactMarkdown\n      remarkPlugins={sources ? [remarkGfm, remarkCitations] : [remarkGfm]}\n      components={components as never}\n    >\n      {source}\n    </ReactMarkdown>\n  );\n});\n\n/**\n * Splits markdown into top-level blocks and memoizes each one, so only the\n * block currently being streamed re-renders (no flicker, no re-highlighting).\n */\nexport function StreamingMarkdown({\n  children,\n  sources,\n  streaming = false,\n  className,\n}: {\n  children: string;\n  /** When provided, \"[n]\" in the text renders as a citation marker for sources[n - 1]. */\n  sources?: Source[];\n  /** Shows a caret after the last block while text is still arriving. */\n  streaming?: boolean;\n  className?: string;\n}) {\n  const blocks = React.useMemo(\n    () => marked.lexer(children).map((t) => t.raw),\n    [children],\n  );\n  return (\n    <div\n      className={cn(\n        \"space-y-3 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5 [&_h1]:text-xl [&_h1]:font-semibold [&_h2]:text-lg [&_h2]:font-semibold [&_h3]:font-semibold [&_table]:w-full [&_th]:text-left [&_td]:border-t [&_td]:py-1.5\",\n        streaming &&\n          \"[&>:last-child]:after:ml-1 [&>:last-child]:after:inline-block [&>:last-child]:after:h-[1.05em] [&>:last-child]:after:w-[2px] [&>:last-child]:after:translate-y-[3px] [&>:last-child]:after:bg-foreground [&>:last-child]:after:content-[''] [&>:last-child]:after:animate-pulse motion-reduce:[&>:last-child]:after:animate-none\",\n        className,\n      )}\n      aria-busy={streaming || undefined}\n    >\n      {blocks.map((raw, i) => (\n        <Block key={i} source={raw} sources={sources} />\n      ))}\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}