freellmapi / client /src /components /markdown.tsx
Nryn215's picture
Deploy: Enable and enforce pure ELO-based Intelligence routing strategy
ed57015
Raw
History Blame Contribute Delete
4.47 kB
import { memo, isValidElement, type ReactNode } from 'react'
import ReactMarkdown, { type Components } from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { cn } from '@/lib/utils'
import { CopyButton } from './copy-button'
// react-markdown hands <pre> its rendered <code> element, not the raw source,
// so pull the text back out of the React tree for the copy button.
function nodeText(node: ReactNode): string {
if (node == null || typeof node === 'boolean') return ''
if (typeof node === 'string' || typeof node === 'number') return String(node)
if (Array.isArray(node)) return node.map(nodeText).join('')
if (isValidElement(node)) return nodeText((node.props as { children?: ReactNode }).children)
return ''
}
function Pre({ children }: { children?: ReactNode }) {
return (
<pre className="group relative my-2 overflow-x-auto rounded-lg border bg-background/60 p-3 first:mt-0 last:mb-0">
<CopyButton
text={nodeText(children).replace(/\n$/, '')}
label="Copy code"
className="absolute right-2 top-2 size-7 opacity-0 transition-opacity focus-visible:opacity-100 group-hover:opacity-100"
/>
{children}
</pre>
)
}
const components: Components = {
p: ({ children }) => (
<p className="my-2 first:mt-0 last:mb-0 whitespace-pre-wrap wrap-break-word">
{children}
</p>
),
a: ({ children, href }) => (
<a
href={href}
target="_blank"
rel="noreferrer noopener"
className="underline underline-offset-2 decoration-foreground/40 hover:decoration-foreground wrap-break-word"
>
{children}
</a>
),
h1: ({ children }) => (
<h1 className="mt-4 mb-2 text-base font-semibold tracking-tight first:mt-0">{children}</h1>
),
h2: ({ children }) => (
<h2 className="mt-4 mb-2 text-sm font-semibold tracking-tight first:mt-0">{children}</h2>
),
h3: ({ children }) => (
<h3 className="mt-3 mb-1.5 text-sm font-semibold first:mt-0">{children}</h3>
),
h4: ({ children }) => (
<h4 className="mt-3 mb-1.5 text-sm font-semibold first:mt-0">{children}</h4>
),
ul: ({ children }) => (
<ul className="my-2 ml-5 list-disc space-y-1 [&_ul]:my-1 [&_ol]:my-1 first:mt-0 last:mb-0">
{children}
</ul>
),
ol: ({ children }) => (
<ol className="my-2 ml-5 list-decimal space-y-1 [&_ul]:my-1 [&_ol]:my-1 first:mt-0 last:mb-0">
{children}
</ol>
),
li: ({ children }) => <li className="leading-relaxed">{children}</li>,
blockquote: ({ children }) => (
<blockquote className="my-2 border-l-2 border-foreground/20 pl-3 italic text-foreground/80 first:mt-0 last:mb-0">
{children}
</blockquote>
),
hr: () => <hr className="my-3 border-foreground/15" />,
strong: ({ children }) => <strong className="font-semibold">{children}</strong>,
em: ({ children }) => <em className="italic">{children}</em>,
del: ({ children }) => <del className="opacity-70">{children}</del>,
table: ({ children }) => (
<div className="my-2 overflow-x-auto first:mt-0 last:mb-0">
<table className="w-full border-collapse text-xs">{children}</table>
</div>
),
thead: ({ children }) => <thead className="bg-background/40">{children}</thead>,
th: ({ children, style }) => (
<th
style={style}
className="border border-foreground/15 px-2 py-1 text-left font-semibold"
>
{children}
</th>
),
td: ({ children, style }) => (
<td style={style} className="border border-foreground/15 px-2 py-1 align-top">
{children}
</td>
),
code: ({ className, children, ...props }) => {
const isBlock = /language-/.test(className ?? '')
if (isBlock) {
return (
<code className={cn('font-mono text-[12.5px] leading-relaxed', className)} {...props}>
{children}
</code>
)
}
return (
<code
className="rounded bg-background/60 px-1 py-0.5 font-mono text-[0.85em] wrap-break-word"
{...props}
>
{children}
</code>
)
},
pre: ({ children }) => <Pre>{children}</Pre>,
}
interface MarkdownProps {
children: string
className?: string
}
function MarkdownInner({ children, className }: MarkdownProps) {
return (
<div className={cn('text-sm leading-relaxed', className)}>
<ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>
{children}
</ReactMarkdown>
</div>
)
}
export const Markdown = memo(MarkdownInner)