'use client'
import { memo, useMemo } from 'react'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import rehypeRaw from 'rehype-raw'
import 'katex/dist/katex.min.css'
import { NoteChartFromCode } from './note-chart'
// Memoized wrapper to prevent infinite re-renders
const ChartWrapper = memo(function ChartWrapper({ code }: { code: string }) {
return
})
interface MarkdownContentProps {
content: string
className?: string
}
export const MarkdownContent = memo(function MarkdownContent({ content, className }: MarkdownContentProps) {
// Strip ... and ... blocks produced by reasoning models
// (MiniMax, DeepSeek R1, etc.) before passing to ReactMarkdown — rehypeRaw would otherwise
// try to render them as HTML elements causing React warnings.
// Also handles partial/unclosed blocks during streaming (e.g. without yet).
const safeContent = useMemo(
() => content
.replace(/[\s\S]*?<\/think>/gi, '') // complete think blocks
.replace(/[\s\S]*/i, '') // unclosed think block (still streaming)
.replace(/[\s\S]*?<\/thinking>/gi, '') // complete thinking blocks
.replace(/[\s\S]*/i, '') // unclosed thinking block
.trim(),
[content]
)
return (
(
),
code({ node, inline, className, children, ...props }: any) {
const match = /language-(\w+)/.exec(className || '')
const language = match ? match[1] : ''
// Chart code blocks - memoized to prevent infinite loops
if (language === 'chart') {
const chartCode = String(children).replace(/\n$/, '')
return
}
// Other code blocks
if (!inline && match) {
return (
{children}
)
}
return (
{children}
)
},
}}
>
{safeContent}
)
})