-
- {props.children}
-
- )
-}
-
// **Add code block
// Avoid error #185 (Maximum update depth exceeded.
// This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
@@ -444,150 +385,4 @@ const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any
})
CodeBlock.displayName = 'CodeBlock'
-const VideoBlock: any = memo(({ node }: any) => {
- const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src)
- if (srcs.length === 0) {
- const src = node.properties?.src
- if (src)
- return {paragraph.children}
-} - -const Img = ({ src }: any) => { - returntags in Markdown. + * Extracted from the main markdown renderer for modularity. + * Handles special rendering for paragraphs that directly contain an image. + */ +import React from 'react' +import ImageGallery from '@/app/components/base/image-gallery' + +const Paragraph = (paragraph: any) => { + const { node }: any = paragraph + const children_node = node.children + if (children_node && children_node[0] && 'tagName' in children_node[0] && children_node[0].tagName === 'img') { + return ( +
{paragraph.children}
+} + +export default Paragraph diff --git a/web/app/components/base/markdown-blocks/pre-code.tsx b/web/app/components/base/markdown-blocks/pre-code.tsx new file mode 100644 index 000000000..a9d0cfb9a --- /dev/null +++ b/web/app/components/base/markdown-blocks/pre-code.tsx @@ -0,0 +1,21 @@ +/** + * @fileoverview PreCode component for rendering tags in Markdown.
+ * Extracted from the main markdown renderer for modularity.
+ * This is a simple wrapper around the HTML element.
+ */
+import React, { useRef } from 'react'
+
+function PreCode(props: { children: any }) {
+ const ref = useRef(null)
+
+ return (
+
+
+ {props.children}
+
+ )
+}
+
+export default PreCode
diff --git a/web/app/components/base/markdown-blocks/script-block.tsx b/web/app/components/base/markdown-blocks/script-block.tsx
new file mode 100644
index 000000000..921e2bf04
--- /dev/null
+++ b/web/app/components/base/markdown-blocks/script-block.tsx
@@ -0,0 +1,15 @@
+/**
+ * @fileoverview ScriptBlock component for handling `
+})
+ScriptBlock.displayName = 'ScriptBlock'
+
+export default ScriptBlock
diff --git a/web/app/components/base/markdown-blocks/video-block.tsx b/web/app/components/base/markdown-blocks/video-block.tsx
new file mode 100644
index 000000000..9f1a36f67
--- /dev/null
+++ b/web/app/components/base/markdown-blocks/video-block.tsx
@@ -0,0 +1,21 @@
+/**
+ * @fileoverview VideoBlock component for rendering video elements in Markdown.
+ * Extracted from the main markdown renderer for modularity.
+ * Uses the VideoGallery component to display videos.
+ */
+import React, { memo } from 'react'
+import VideoGallery from '@/app/components/base/video-gallery'
+
+const VideoBlock: any = memo(({ node }: any) => {
+ const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src)
+ if (srcs.length === 0) {
+ const src = node.properties?.src
+ if (src)
+ return
+ return null
+ }
+ return
+})
+VideoBlock.displayName = 'VideoBlock'
+
+export default VideoBlock
diff --git a/web/app/components/base/markdown/error-boundary.tsx b/web/app/components/base/markdown/error-boundary.tsx
new file mode 100644
index 000000000..0e6876191
--- /dev/null
+++ b/web/app/components/base/markdown/error-boundary.tsx
@@ -0,0 +1,33 @@
+/**
+ * @fileoverview ErrorBoundary component for React.
+ * This component was extracted from the main markdown renderer.
+ * It catches JavaScript errors anywhere in its child component tree,
+ * logs those errors, and displays a fallback UI instead of the crashed component tree.
+ * Primarily used around complex rendering logic like ECharts or SVG within Markdown.
+ */
+import React, { Component } from 'react'
+// **Add an ECharts runtime error handler
+// Avoid error #7832 (Crash when ECharts accesses undefined objects)
+// This can happen when a component attempts to access an undefined object that references an unregistered map, causing the program to crash.
+
+export default class ErrorBoundary extends Component {
+ constructor(props: any) {
+ super(props)
+ this.state = { hasError: false }
+ }
+
+ componentDidCatch(error: any, errorInfo: any) {
+ this.setState({ hasError: true })
+ console.error(error, errorInfo)
+ }
+
+ render() {
+ // eslint-disable-next-line ts/ban-ts-comment
+ // @ts-expect-error
+ if (this.state.hasError)
+ return Oops! An error occurred. This could be due to an ECharts runtime error or invalid SVG content.
(see the browser console for more information)
+ // eslint-disable-next-line ts/ban-ts-comment
+ // @ts-expect-error
+ return this.props.children
+ }
+}
diff --git a/web/app/components/base/markdown/index.tsx b/web/app/components/base/markdown/index.tsx
new file mode 100644
index 000000000..0e0dc41cf
--- /dev/null
+++ b/web/app/components/base/markdown/index.tsx
@@ -0,0 +1,87 @@
+import ReactMarkdown from 'react-markdown'
+import 'katex/dist/katex.min.css'
+import RemarkMath from 'remark-math'
+import RemarkBreaks from 'remark-breaks'
+import RehypeKatex from 'rehype-katex'
+import RemarkGfm from 'remark-gfm'
+import RehypeRaw from 'rehype-raw'
+import { flow } from 'lodash-es'
+import cn from '@/utils/classnames'
+import { preprocessLaTeX, preprocessThinkTag } from './markdown-utils'
+import {
+ AudioBlock,
+ CodeBlock,
+ Img,
+ Link,
+ MarkdownButton,
+ MarkdownForm,
+ Paragraph,
+ ScriptBlock,
+ ThinkBlock,
+ VideoBlock,
+} from '@/app/components/base/markdown-blocks'
+
+/**
+ * @fileoverview Main Markdown rendering component.
+ * This file was refactored to extract individual block renderers and utility functions
+ * into separate modules for better organization and maintainability as of [Date of refactor].
+ * Further refactoring candidates (custom block components not fitting general categories)
+ * are noted in their respective files if applicable.
+ */
+
+export function Markdown(props: { content: string; className?: string; customDisallowedElements?: string[] }) {
+ const latexContent = flow([
+ preprocessThinkTag,
+ preprocessLaTeX,
+ ])(props.content)
+
+ return (
+
+ {
+ return (tree: any) => {
+ const iterate = (node: any) => {
+ if (node.type === 'element' && node.properties?.ref)
+ delete node.properties.ref
+
+ if (node.type === 'element' && !/^[a-z][a-z0-9]*$/i.test(node.tagName)) {
+ node.type = 'text'
+ node.value = `<${node.tagName}`
+ }
+
+ if (node.children)
+ node.children.forEach(iterate)
+ }
+ tree.children.forEach(iterate)
+ }
+ },
+ ]}
+ disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', ...(props.customDisallowedElements || [])]}
+ components={{
+ code: CodeBlock,
+ img: Img,
+ video: VideoBlock,
+ audio: AudioBlock,
+ a: Link,
+ p: Paragraph,
+ button: MarkdownButton,
+ form: MarkdownForm,
+ script: ScriptBlock as any,
+ details: ThinkBlock,
+ }}
+ >
+ {/* Markdown detect has problem. */}
+ {latexContent}
+
+
+ )
+}
diff --git a/web/app/components/base/markdown/markdown-utils.ts b/web/app/components/base/markdown/markdown-utils.ts
new file mode 100644
index 000000000..ff7dd5db0
--- /dev/null
+++ b/web/app/components/base/markdown/markdown-utils.ts
@@ -0,0 +1,37 @@
+/**
+ * @fileoverview Utility functions for preprocessing Markdown content.
+ * These functions were extracted from the main markdown renderer for better separation of concerns.
+ * Includes preprocessing for LaTeX and custom "think" tags.
+ */
+import { flow } from 'lodash-es'
+
+export const preprocessLaTeX = (content: string) => {
+ if (typeof content !== 'string')
+ return content
+
+ const codeBlockRegex = /```[\s\S]*?```/g
+ const codeBlocks = content.match(codeBlockRegex) || []
+ let processedContent = content.replace(codeBlockRegex, 'CODE_BLOCK_PLACEHOLDER')
+
+ processedContent = flow([
+ (str: string) => str.replace(/\\\[(.*?)\\\]/g, (_, equation) => `$$${equation}$$`),
+ (str: string) => str.replace(/\\\[([\s\S]*?)\\\]/g, (_, equation) => `$$${equation}$$`),
+ (str: string) => str.replace(/\\\((.*?)\\\)/g, (_, equation) => `$$${equation}$$`),
+ (str: string) => str.replace(/(^|[^\\])\$(.+?)\$/g, (_, prefix, equation) => `${prefix}$${equation}$`),
+ ])(processedContent)
+
+ codeBlocks.forEach((block) => {
+ processedContent = processedContent.replace('CODE_BLOCK_PLACEHOLDER', block)
+ })
+
+ return processedContent
+}
+
+export const preprocessThinkTag = (content: string) => {
+ const thinkOpenTagRegex = /\n/g
+ const thinkCloseTagRegex = /\n<\/think>/g
+ return flow([
+ (str: string) => str.replace(thinkOpenTagRegex, '\n'),
+ (str: string) => str.replace(thinkCloseTagRegex, '\n[ENDTHINKFLAG]'),
+ ])(content)
+}