模型使用了deepseek r1,前端直接显示了<think>标签,没有展示成思考过程 #64

Closed
opened 2026-02-16 10:15:59 -05:00 by yindo · 11 comments
Owner

Originally created by @yuyuan1019 on GitHub (Feb 25, 2025).

这是webapp-conversation的web页面
Image
这是dify自带的页面
Image

Originally created by @yuyuan1019 on GitHub (Feb 25, 2025). 这是webapp-conversation的web页面 ![Image](https://github.com/user-attachments/assets/373ebe46-74f4-4865-b1a0-63c7db680d39) 这是dify自带的页面 ![Image](https://github.com/user-attachments/assets/af6ab7d7-484d-403a-9ef1-3232c334bc62)
yindo closed this issue 2026-02-16 10:15:59 -05:00
Author
Owner

@vip55zxc commented on GitHub (Mar 3, 2025):

+1

@vip55zxc commented on GitHub (Mar 3, 2025): +1
Author
Owner

@miRUAcleInjoker commented on GitHub (Mar 4, 2025):

+1

@miRUAcleInjoker commented on GitHub (Mar 4, 2025): +1
Author
Owner

@BroMachee commented on GitHub (Mar 6, 2025):

+1

@BroMachee commented on GitHub (Mar 6, 2025): +1
Author
Owner

@cuibty commented on GitHub (Mar 10, 2025):

+1

@cuibty commented on GitHub (Mar 10, 2025): +1
Author
Owner

@hello-lx-83 commented on GitHub (Mar 13, 2025):

+1

@hello-lx-83 commented on GitHub (Mar 13, 2025): +1
Author
Owner

@Chastex commented on GitHub (Mar 14, 2025):

太长时间没更新了

@Chastex commented on GitHub (Mar 14, 2025): 太长时间没更新了
Author
Owner

@marsDes commented on GitHub (Mar 23, 2025):

@yuyuan1019 @cuibty
https://github.com/marsDes/dify-conversation/

@marsDes commented on GitHub (Mar 23, 2025): @yuyuan1019 @cuibty https://github.com/marsDes/dify-conversation/
Author
Owner

@vip55zxc commented on GitHub (Mar 23, 2025):

Image

缺少“rehype-raw”库,加一下就好了

@vip55zxc commented on GitHub (Mar 23, 2025): <img width="1178" alt="Image" src="https://github.com/user-attachments/assets/1913c2a6-737a-4a53-8da0-a2a147560218" /> 缺少“rehype-raw”库,加一下就好了
Author
Owner

@mobuiin commented on GitHub (Apr 7, 2025):

Image 缺少“rehype-raw”库,加一下就好了

哥 可以帮忙看看吗 我加了rehype-raw了 为啥还是不行啊 刚从仓库里拉的main分支的代码

Image
@mobuiin commented on GitHub (Apr 7, 2025): > <img alt="Image" width="1178" src="https://private-user-images.githubusercontent.com/42274961/425892601-1913c2a6-737a-4a53-8da0-a2a147560218.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDQwODI1MTAsIm5iZiI6MTc0NDA4MjIxMCwicGF0aCI6Ii80MjI3NDk2MS80MjU4OTI2MDEtMTkxM2MyYTYtNzM3YS00YTUzLThkYTAtYTJhMTQ3NTYwMjE4LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA0MDglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNDA4VDAzMTY1MFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTE2Y2ExODA1NmU2ZTg2ZjM3NGQwY2E4NzNlYjFiNjM3YjQ2YzY0MTg3NGVlMTU3YTJjOGMxNTE5NjA4ZmRjNzMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.j67bfLT4iTv3V4Mr2EPY59agx3SQjGRtRRRyIwJQGUs"> > 缺少“rehype-raw”库,加一下就好了 哥 可以帮忙看看吗 我加了rehype-raw了 为啥还是不行啊 刚从仓库里拉的main分支的代码 <img width="2247" alt="Image" src="https://github.com/user-attachments/assets/dceb47ae-613c-4bde-a14c-2f0f9b5658fd" />
Author
Owner

@c137max commented on GitHub (Apr 14, 2025):

实际是代码太旧了,对着生产的版本去抄一下可以临时解决这个问题,但终究不是长久之计

生产版本的markdown.tsx 改一下放到app/components/base/markdown.tsx文件

Image

Image

Image

参考(下面的代码不完整,注释掉了几处视频、图片块,最好不要直接用):

import ReactMarkdown from 'react-markdown'
import ReactEcharts from 'echarts-for-react'
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 SyntaxHighlighter from 'react-syntax-highlighter'
import {
  atelierHeathDark,
  atelierHeathLight,
} from 'react-syntax-highlighter/dist/esm/styles/hljs'
import { Component, memo, useMemo, useRef, useState } from 'react'
import { flow } from 'lodash-es'
import cn from '@/utils/classnames'
import ThinkBlock from './markdown-blocks/think-block'


// Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD
const capitalizationLanguageNameMap: Record<string, string> = {
  sql: 'SQL',
  javascript: 'JavaScript',
  java: 'Java',
  typescript: 'TypeScript',
  vbscript: 'VBScript',
  css: 'CSS',
  html: 'HTML',
  xml: 'XML',
  php: 'PHP',
  python: 'Python',
  yaml: 'Yaml',
  mermaid: 'Mermaid',
  markdown: 'MarkDown',
  makefile: 'MakeFile',
  echarts: 'ECharts',
  shell: 'Shell',
  powershell: 'PowerShell',
  json: 'JSON',
  latex: 'Latex',
  svg: 'SVG',
}
const getCorrectCapitalizationLanguageName = (language: string) => {
  if (!language)
    return 'Plain'

  if (language in capitalizationLanguageNameMap)
    return capitalizationLanguageNameMap[language]

  return language.charAt(0).toUpperCase() + language.substring(1)
}

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) => {
  return flow([
    (str: string) => str.replace('<think>\n', '<details data-think=true>\n'),
    (str: string) => str.replace('\n</think>', '\n[ENDTHINKFLAG]</details>'),
  ])(content)
}

export function PreCode(props: { children: any }) {
  const ref = useRef<HTMLPreElement>(null)

  return (
    <pre ref={ref}>
      <span
        className="copy-code-button"
      ></span>
      {props.children}
    </pre>
  )
}

// **Add code block
// Avoid error #185 (Maximum update depth exceeded.
// This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate.
// React limits the number of nested updates to prevent infinite loops.)
// Reference A: https://reactjs.org/docs/error-decoder.html?invariant=185
// Reference B1: https://react.dev/reference/react/memo
// Reference B2: https://react.dev/reference/react/useMemo
// ****
// The original error that occurred in the streaming response during the conversation:
// Error: Minified React error 185;
// 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 [isSVG, setIsSVG] = useState(true)
  const match = /language-(\w+)/.exec(className || '')
  const language = match?.[1]
  const languageShowName = getCorrectCapitalizationLanguageName(language || '')
  const chartData = useMemo(() => {
    if (language === 'echarts') {
      try {
        return JSON.parse(String(children).replace(/\n$/, ''))
      }
      catch { }
    }
    return JSON.parse('{"title":{"text":"ECharts error - Wrong JSON format."}}')
  }, [language, children])

  const renderCodeContent = useMemo(() => {
    const content = String(children).replace(/\n$/, '')
    // if (language === 'mermaid' && isSVG) {
    //   return <Flowchart PrimitiveCode={content} />
    // }
    // 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,
            borderBottomLeftRadius: '10px',
            borderBottomRightRadius: '10px',
            backgroundColor: 'var(--color-components-input-bg-normal)',
          }}
          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'>
          {(['mermaid', 'svg']).includes(language!) && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />}
          <ActionButton>
            <CopyIcon content={String(children).replace(/\n$/, '')} />
          </ActionButton>
        </div> */}
      </div>
      {renderCodeContent}
    </div>
  )
})
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)
    return null
  // return <VideoGallery key={srcs.join()} srcs={srcs} />
  return <div>{srcs}</div>
})
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)
  if (srcs.length === 0)
    return null
  // return <AudioGallery key={srcs.join()} srcs={srcs} />
  return <div>{srcs}</div>
})
AudioBlock.displayName = 'AudioBlock'

const ScriptBlock = memo(({ node }: any) => {
  const scriptContent = node.children[0]?.value || ''
  return `<script>${scriptContent}</script>`
})
ScriptBlock.displayName = 'ScriptBlock'

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 (
      <div className="markdown-img-wrapper">
        {/* <ImageGallery srcs={[children_node[0].properties.src]} /> */}
        <div> {[children_node[0].properties.src]} </div>
        {
          Array.isArray(paragraph.children) && paragraph.children.length > 1 && (
            <div className="mt-2">{paragraph.children.slice(1)}</div>
          )
        }
      </div>
    )
  }
  return <p>{paragraph.children}</p>
}

const Img = ({ src }: any) => {
  return <div className="markdown-img-wrapper">
    {/* <ImageGallery srcs={[src]} /> */}
    {[src]}
  </div>
}

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

  return (
    <div className={cn('markdown-body', '!text-text-primary', props.className)}>
      <ReactMarkdown
        remarkPlugins={[
          RemarkGfm,
          [RemarkMath, { singleDollarTextMath: false }],
          RemarkBreaks,
        ]}
        rehypePlugins={[
          RehypeKatex,
          RehypeRaw as any,
          // The Rehype plug-in is used to remove the ref attribute of an element
          () => {
            return (tree) => {
              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}
      </ReactMarkdown>
    </div>
  )
}

// **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 <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
    // @ts-expect-error
    return this.props.children
  }
}
@c137max commented on GitHub (Apr 14, 2025): 实际是代码太旧了,对着生产的版本去抄一下可以临时解决这个问题,但终究不是长久之计 [生产版本的markdown.tsx](https://github.com/langgenius/dify/blob/main/web/app/components/base/markdown.tsx) 改一下放到`app/components/base/markdown.tsx`文件 ![Image](https://github.com/user-attachments/assets/6a7fc8dd-d885-4755-8f3f-35ea466724c7) ![Image](https://github.com/user-attachments/assets/c5cc4996-04a2-483c-9d53-c55a0d4cdd98) ![Image](https://github.com/user-attachments/assets/e872d903-884e-4615-bdbb-ad135ced019e) 参考(下面的代码不完整,注释掉了几处视频、图片块,**最好不要直接用**): ```tsx import ReactMarkdown from 'react-markdown' import ReactEcharts from 'echarts-for-react' 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 SyntaxHighlighter from 'react-syntax-highlighter' import { atelierHeathDark, atelierHeathLight, } from 'react-syntax-highlighter/dist/esm/styles/hljs' import { Component, memo, useMemo, useRef, useState } from 'react' import { flow } from 'lodash-es' import cn from '@/utils/classnames' import ThinkBlock from './markdown-blocks/think-block' // Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD const capitalizationLanguageNameMap: Record<string, string> = { sql: 'SQL', javascript: 'JavaScript', java: 'Java', typescript: 'TypeScript', vbscript: 'VBScript', css: 'CSS', html: 'HTML', xml: 'XML', php: 'PHP', python: 'Python', yaml: 'Yaml', mermaid: 'Mermaid', markdown: 'MarkDown', makefile: 'MakeFile', echarts: 'ECharts', shell: 'Shell', powershell: 'PowerShell', json: 'JSON', latex: 'Latex', svg: 'SVG', } const getCorrectCapitalizationLanguageName = (language: string) => { if (!language) return 'Plain' if (language in capitalizationLanguageNameMap) return capitalizationLanguageNameMap[language] return language.charAt(0).toUpperCase() + language.substring(1) } 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) => { return flow([ (str: string) => str.replace('<think>\n', '<details data-think=true>\n'), (str: string) => str.replace('\n</think>', '\n[ENDTHINKFLAG]</details>'), ])(content) } export function PreCode(props: { children: any }) { const ref = useRef<HTMLPreElement>(null) return ( <pre ref={ref}> <span className="copy-code-button" ></span> {props.children} </pre> ) } // **Add code block // Avoid error #185 (Maximum update depth exceeded. // This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. // React limits the number of nested updates to prevent infinite loops.) // Reference A: https://reactjs.org/docs/error-decoder.html?invariant=185 // Reference B1: https://react.dev/reference/react/memo // Reference B2: https://react.dev/reference/react/useMemo // **** // The original error that occurred in the streaming response during the conversation: // Error: Minified React error 185; // 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 [isSVG, setIsSVG] = useState(true) const match = /language-(\w+)/.exec(className || '') const language = match?.[1] const languageShowName = getCorrectCapitalizationLanguageName(language || '') const chartData = useMemo(() => { if (language === 'echarts') { try { return JSON.parse(String(children).replace(/\n$/, '')) } catch { } } return JSON.parse('{"title":{"text":"ECharts error - Wrong JSON format."}}') }, [language, children]) const renderCodeContent = useMemo(() => { const content = String(children).replace(/\n$/, '') // if (language === 'mermaid' && isSVG) { // return <Flowchart PrimitiveCode={content} /> // } // 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, borderBottomLeftRadius: '10px', borderBottomRightRadius: '10px', backgroundColor: 'var(--color-components-input-bg-normal)', }} 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'> {(['mermaid', 'svg']).includes(language!) && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />} <ActionButton> <CopyIcon content={String(children).replace(/\n$/, '')} /> </ActionButton> </div> */} </div> {renderCodeContent} </div> ) }) 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) return null // return <VideoGallery key={srcs.join()} srcs={srcs} /> return <div>{srcs}</div> }) 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) if (srcs.length === 0) return null // return <AudioGallery key={srcs.join()} srcs={srcs} /> return <div>{srcs}</div> }) AudioBlock.displayName = 'AudioBlock' const ScriptBlock = memo(({ node }: any) => { const scriptContent = node.children[0]?.value || '' return `<script>${scriptContent}</script>` }) ScriptBlock.displayName = 'ScriptBlock' 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 ( <div className="markdown-img-wrapper"> {/* <ImageGallery srcs={[children_node[0].properties.src]} /> */} <div> {[children_node[0].properties.src]} </div> { Array.isArray(paragraph.children) && paragraph.children.length > 1 && ( <div className="mt-2">{paragraph.children.slice(1)}</div> ) } </div> ) } return <p>{paragraph.children}</p> } const Img = ({ src }: any) => { return <div className="markdown-img-wrapper"> {/* <ImageGallery srcs={[src]} /> */} {[src]} </div> } 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) 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">{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) return ( <div className={cn('markdown-body', '!text-text-primary', props.className)}> <ReactMarkdown remarkPlugins={[ RemarkGfm, [RemarkMath, { singleDollarTextMath: false }], RemarkBreaks, ]} rehypePlugins={[ RehypeKatex, RehypeRaw as any, // The Rehype plug-in is used to remove the ref attribute of an element () => { return (tree) => { 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} </ReactMarkdown> </div> ) } // **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 <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 // @ts-expect-error return this.props.children } } ```
Author
Owner

@glzcc commented on GitHub (Aug 22, 2025):

半成品,这咋用?大家有推荐的其他AI前端框架吗?方便直接接入difyAPI的

@glzcc commented on GitHub (Aug 22, 2025): 半成品,这咋用?大家有推荐的其他AI前端框架吗?方便直接接入difyAPI的
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/webapp-conversation#64