You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.tsx 2.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import ReactMarkdown from 'react-markdown'
  2. import 'katex/dist/katex.min.css'
  3. import RemarkMath from 'remark-math'
  4. import RemarkBreaks from 'remark-breaks'
  5. import RehypeKatex from 'rehype-katex'
  6. import RemarkGfm from 'remark-gfm'
  7. import RehypeRaw from 'rehype-raw'
  8. import { flow } from 'lodash-es'
  9. import cn from '@/utils/classnames'
  10. import { customUrlTransform, preprocessLaTeX, preprocessThinkTag } from './markdown-utils'
  11. import {
  12. AudioBlock,
  13. CodeBlock,
  14. Img,
  15. Link,
  16. MarkdownButton,
  17. MarkdownForm,
  18. Paragraph,
  19. ScriptBlock,
  20. ThinkBlock,
  21. VideoBlock,
  22. } from '@/app/components/base/markdown-blocks'
  23. /**
  24. * @fileoverview Main Markdown rendering component.
  25. * This file was refactored to extract individual block renderers and utility functions
  26. * into separate modules for better organization and maintainability as of [Date of refactor].
  27. * Further refactoring candidates (custom block components not fitting general categories)
  28. * are noted in their respective files if applicable.
  29. */
  30. export type MarkdownProps = {
  31. content: string
  32. className?: string
  33. customDisallowedElements?: string[]
  34. customComponents?: Record<string, React.ComponentType<any>>
  35. }
  36. export const Markdown = (props: MarkdownProps) => {
  37. const { customComponents = {} } = props
  38. const latexContent = flow([
  39. preprocessThinkTag,
  40. preprocessLaTeX,
  41. ])(props.content)
  42. return (
  43. <div className={cn('markdown-body', '!text-text-primary', props.className)}>
  44. <ReactMarkdown
  45. remarkPlugins={[
  46. RemarkGfm,
  47. [RemarkMath, { singleDollarTextMath: false }],
  48. RemarkBreaks,
  49. ]}
  50. rehypePlugins={[
  51. RehypeKatex,
  52. RehypeRaw as any,
  53. // The Rehype plug-in is used to remove the ref attribute of an element
  54. () => {
  55. return (tree: any) => {
  56. const iterate = (node: any) => {
  57. if (node.type === 'element' && node.properties?.ref)
  58. delete node.properties.ref
  59. if (node.type === 'element' && !/^[a-z][a-z0-9]*$/i.test(node.tagName)) {
  60. node.type = 'text'
  61. node.value = `<${node.tagName}`
  62. }
  63. if (node.children)
  64. node.children.forEach(iterate)
  65. }
  66. tree.children.forEach(iterate)
  67. }
  68. },
  69. ]}
  70. urlTransform={customUrlTransform}
  71. disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', ...(props.customDisallowedElements || [])]}
  72. components={{
  73. code: CodeBlock,
  74. img: Img,
  75. video: VideoBlock,
  76. audio: AudioBlock,
  77. a: Link,
  78. p: Paragraph,
  79. button: MarkdownButton,
  80. form: MarkdownForm,
  81. script: ScriptBlock as any,
  82. details: ThinkBlock,
  83. ...customComponents,
  84. }}
  85. >
  86. {/* Markdown detect has problem. */}
  87. {latexContent}
  88. </ReactMarkdown>
  89. </div>
  90. )
  91. }