From 77950e48c76f4a3b29d01831d43039caba29888a Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期二, 18 十一月 2025 14:12:42 +0800
Subject: [PATCH] 修改
---
app/components/base/markdown.tsx | 218 ++++++++++++++++++++----------------------------------
1 files changed, 81 insertions(+), 137 deletions(-)
diff --git a/app/components/base/markdown.tsx b/app/components/base/markdown.tsx
index bc6fe0e..b26d9df 100644
--- a/app/components/base/markdown.tsx
+++ b/app/components/base/markdown.tsx
@@ -7,28 +7,20 @@
import RemarkGfm from 'remark-gfm'
import RehypeRaw from 'rehype-raw'
import SyntaxHighlighter from 'react-syntax-highlighter'
-import {
- atelierHeathDark,
- atelierHeathLight,
-} from 'react-syntax-highlighter/dist/esm/styles/hljs'
+import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs'
import { Component, memo, useMemo, useRef, useState } from 'react'
-import { flow } from 'lodash-es'
-import ActionButton from '@/app/components/base/action-button'
-import CopyIcon from '@/app/components/base/copy-icon'
+import type { CodeComponent } from 'react-markdown/lib/ast-to-react'
+import cn from '@/utils/classnames'
+import CopyBtn from '@/app/components/base/copy-btn'
import SVGBtn from '@/app/components/base/svg'
import Flowchart from '@/app/components/base/mermaid'
import ImageGallery from '@/app/components/base/image-gallery'
import { useChatContext } from '@/app/components/base/chat/chat/context'
import VideoGallery from '@/app/components/base/video-gallery'
import AudioGallery from '@/app/components/base/audio-gallery'
+import SVGRenderer from '@/app/components/base/svg-gallery'
import MarkdownButton from '@/app/components/base/markdown-blocks/button'
import MarkdownForm from '@/app/components/base/markdown-blocks/form'
-import MarkdownMusic from '@/app/components/base/markdown-blocks/music'
-import ThinkBlock from '@/app/components/base/markdown-blocks/think-block'
-import { Theme } from '@/types/app'
-import useTheme from '@/hooks/use-theme'
-import cn from '@/utils/classnames'
-import SVGRenderer from './svg-gallery'
// Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD
const capitalizationLanguageNameMap: Record<string, string> = {
@@ -52,7 +44,6 @@
json: 'JSON',
latex: 'Latex',
svg: 'SVG',
- abc: 'ABC',
}
const getCorrectCapitalizationLanguageName = (language: string) => {
if (!language)
@@ -67,32 +58,9 @@
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(/\\\[(.*?)\\\]/gs, (_, 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
-}
-
-const preprocessThinkTag = (content: string) => {
- const thinkOpenTagRegex = /<think>\n/g
- const thinkCloseTagRegex = /\n<\/think>/g
- return flow([
- (str: string) => str.replace(thinkOpenTagRegex, '<details data-think=true>\n'),
- (str: string) => str.replace(thinkCloseTagRegex, '\n[ENDTHINKFLAG]</details>'),
- ])(content)
+ return content.replace(/\\\[(.*?)\\\]/g, (_, equation) => `$$${equation}$$`)
+ .replace(/\\\((.*?)\\\)/g, (_, equation) => `$$${equation}$$`)
+ .replace(/(^|[^\\])\$(.+?)\$/g, (_, prefix, equation) => `${prefix}$${equation}$`)
}
export function PreCode(props: { children: any }) {
@@ -121,91 +89,80 @@
// visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message
// or use the non-minified dev environment for full errors and additional helpful warnings.
-const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any) => {
- const { theme } = useTheme()
+const CodeBlock: CodeComponent = memo(({ inline, className, children, ...props }) => {
const [isSVG, setIsSVG] = useState(true)
const match = /language-(\w+)/.exec(className || '')
const language = match?.[1]
const languageShowName = getCorrectCapitalizationLanguageName(language || '')
const chartData = useMemo(() => {
- const str = String(children).replace(/\n$/, '')
if (language === 'echarts') {
try {
- return JSON.parse(str)
+ return JSON.parse(String(children).replace(/\n$/, ''))
}
- catch { }
- try {
- // eslint-disable-next-line no-new-func, sonarjs/code-eval
- return new Function(`return ${str}`)()
- }
- catch { }
+ catch (error) { }
}
- return JSON.parse('{"title":{"text":"ECharts error - Wrong option."}}')
+ return JSON.parse('{"title":{"text":"ECharts error - Wrong JSON format."}}')
}, [language, children])
const renderCodeContent = useMemo(() => {
const content = String(children).replace(/\n$/, '')
- switch (language) {
- case 'mermaid':
- if (isSVG)
- return <Flowchart PrimitiveCode={content} />
- break
- case 'echarts':
- return (
- <div style={{ minHeight: '350px', minWidth: '100%', overflowX: 'scroll' }}>
- <ErrorBoundary>
- <ReactEcharts option={chartData} style={{ minWidth: '700px' }} />
- </ErrorBoundary>
- </div>
- )
- case 'svg':
- if (isSVG) {
- return (
- <ErrorBoundary>
- <SVGRenderer content={content} />
- </ErrorBoundary>
- )
- }
- break
- case 'abc':
- return (
- <ErrorBoundary>
- <MarkdownMusic children={content} />
- </ErrorBoundary>
- )
- default:
- return (
- <SyntaxHighlighter
- {...props}
- style={theme === Theme.light ? atelierHeathLight : atelierHeathDark}
- customStyle={{
- paddingLeft: 12,
- borderBottomLeftRadius: '10px',
- borderBottomRightRadius: '10px',
- backgroundColor: 'var(--color-components-input-bg-normal)',
- }}
- language={match?.[1]}
- showLineNumbers
- PreTag="div"
- >
- {content}
- </SyntaxHighlighter>
- )
+ if (language === 'mermaid' && isSVG) {
+ return <Flowchart PrimitiveCode={content} />
}
- }, [children, language, isSVG, chartData, props, theme, match])
+ else if (language === 'echarts') {
+ return (
+ <div style={{ minHeight: '350px', minWidth: '100%', overflowX: 'scroll' }}>
+ <ErrorBoundary>
+ <ReactEcharts option={chartData} style={{ minWidth: '700px' }} />
+ </ErrorBoundary>
+ </div>
+ )
+ }
+ else if (language === 'svg' && isSVG) {
+ return (
+ <ErrorBoundary>
+ <SVGRenderer content={content} />
+ </ErrorBoundary>
+ )
+ }
+ else {
+ return (
+ <SyntaxHighlighter
+ {...props}
+ style={atelierHeathLight}
+ customStyle={{
+ paddingLeft: 12,
+ backgroundColor: '#fff',
+ }}
+ language={match?.[1]}
+ showLineNumbers
+ PreTag="div"
+ >
+ {content}
+ </SyntaxHighlighter>
+ )
+ }
+ }, [language, match, props, children, chartData, isSVG])
if (inline || !match)
return <code {...props} className={className}>{children}</code>
return (
- <div className='relative'>
- <div className='flex h-8 items-center justify-between rounded-t-[10px] border-b border-divider-subtle bg-components-input-bg-normal p-1 pl-3'>
- <div className='system-xs-semibold-uppercase text-text-secondary'>{languageShowName}</div>
- <div className='flex items-center gap-1'>
+ <div>
+ <div
+ className='flex justify-between h-8 items-center p-1 pl-3 border-b'
+ style={{
+ borderColor: 'rgba(0, 0, 0, 0.05)',
+ }}
+ >
+ <div className='text-[13px] text-gray-500 font-normal'>{languageShowName}</div>
+ <div style={{ display: 'flex' }}>
{(['mermaid', 'svg']).includes(language!) && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />}
- <ActionButton>
- <CopyIcon content={String(children).replace(/\n$/, '')} />
- </ActionButton>
+ <CopyBtn
+ className='mr-1'
+ value={String(children).replace(/\n$/, '')}
+ isPlain
+ />
</div>
</div>
{renderCodeContent}
@@ -214,16 +171,16 @@
})
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)
+const VideoBlock: CodeComponent = memo(({ node }) => {
+ const srcs = node.children.filter(child => 'properties' in child).map(child => (child as any).properties.src)
if (srcs.length === 0)
return null
return <VideoGallery key={srcs.join()} srcs={srcs} />
})
VideoBlock.displayName = 'VideoBlock'
-const AudioBlock: any = memo(({ node }: any) => {
- const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src)
+const AudioBlock: CodeComponent = memo(({ node }) => {
+ const srcs = node.children.filter(child => 'properties' in child).map(child => (child as any).properties.src)
if (srcs.length === 0)
return null
return <AudioGallery key={srcs.join()} srcs={srcs} />
@@ -241,44 +198,36 @@
const children_node = node.children
if (children_node && children_node[0] && 'tagName' in children_node[0] && children_node[0].tagName === 'img') {
return (
- <div className="markdown-img-wrapper">
+ <>
<ImageGallery srcs={[children_node[0].properties.src]} />
- {
- Array.isArray(paragraph.children) && paragraph.children.length > 1 && (
- <div className="mt-2">{paragraph.children.slice(1)}</div>
- )
- }
- </div>
+ <p>{paragraph.children.slice(1)}</p>
+ </>
)
}
return <p>{paragraph.children}</p>
}
const Img = ({ src }: any) => {
- return <div className="markdown-img-wrapper"><ImageGallery srcs={[src]} /></div>
+ return (<ImageGallery srcs={[src]} />)
}
-const Link = ({ node, children, ...props }: any) => {
+const Link = ({ node, ...props }: any) => {
if (node.properties?.href && node.properties.href?.toString().startsWith('abbr')) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { onSend } = useChatContext()
const hidden_text = decodeURIComponent(node.properties.href.toString().split('abbr:')[1])
- return <abbr className="cursor-pointer underline !decoration-primary-700 decoration-dashed" onClick={() => onSend?.(hidden_text)} title={node.children[0]?.value || ''}>{node.children[0]?.value || ''}</abbr>
+ return <abbr className="underline decoration-dashed !decoration-primary-700 cursor-pointer" onClick={() => onSend?.(hidden_text)} title={node.children[0]?.value}>{node.children[0]?.value}</abbr>
}
else {
- return <a {...props} target="_blank" className="cursor-pointer underline !decoration-primary-700 decoration-dashed">{children || 'Download'}</a>
+ return <a {...props} target="_blank" className="underline decoration-dashed !decoration-primary-700 cursor-pointer">{node.children[0] ? node.children[0]?.value : 'Download'}</a>
}
}
-export function Markdown(props: { content: string; className?: string; customDisallowedElements?: string[] }) {
- const latexContent = flow([
- preprocessThinkTag,
- preprocessLaTeX,
- ])(props.content)
-
+export function Markdown(props: { content: string; className?: string }) {
+ const latexContent = preprocessLaTeX(props.content)
return (
- <div className={cn('markdown-body', '!text-text-primary', props.className)}>
+ <div className={cn(props.className, 'markdown-body')}>
<ReactMarkdown
remarkPlugins={[
RemarkGfm,
@@ -295,11 +244,6 @@
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)
}
@@ -307,7 +251,7 @@
}
},
]}
- disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', ...(props.customDisallowedElements || [])]}
+ disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body']}
components={{
code: CodeBlock,
img: Img,
@@ -317,9 +261,9 @@
p: Paragraph,
button: MarkdownButton,
form: MarkdownForm,
- script: ScriptBlock as any,
- details: ThinkBlock,
+ script: ScriptBlock,
}}
+ linkTarget='_blank'
>
{/* Markdown detect has problem. */}
{latexContent}
@@ -344,11 +288,11 @@
}
render() {
- // eslint-disable-next-line ts/ban-ts-comment
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
if (this.state.hasError)
return <div>Oops! An error occurred. This could be due to an ECharts runtime error or invalid SVG content. <br />(see the browser console for more information)</div>
- // eslint-disable-next-line ts/ban-ts-comment
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
return this.props.children
}
--
Gitblit v1.8.0