fix(ui): add KaTeX CSS and normalize LaTeX math delimiters for proper formula rendering

Import KaTeX CSS in layout.tsx to ensure math formulas display correctly
in both dev and production modes. Convert LaTeX-style `\[...\]` / `\(...\)`
delimiters to `$$...$$` / `$...$` since remark-math only supports dollar
sign delimiters, fixing formula rendering for agents like Codex.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
xintaofei
2026-04-08 23:27:40 +08:00
parent 0b866eddb4
commit c367117392
4 changed files with 47 additions and 6 deletions

View File

@@ -329,8 +329,41 @@ export type MessageResponseProps = ComponentProps<typeof Streamdown>
const math = createMathPlugin({ singleDollarTextMath: true })
const streamdownPlugins = { cjk, code, math, mermaid }
function MessageResponseImpl({ className, ...props }: MessageResponseProps) {
// remark-math only supports `$` delimiters. Convert LaTeX-style
// `\[...\]` / `\(...\)` to `$$...$$` / `$...$` so they are recognized.
// Code blocks and inline code are preserved to avoid false positives.
export function normalizeMathDelimiters(text: string): string {
const saved: string[] = []
const placeholder = (m: string) => {
saved.push(m)
return `\0CBLK${saved.length - 1}\0`
}
const masked = text.replace(
/`{3,}[\s\S]*?`{3,}|~{3,}[\s\S]*?~{3,}|`[^`\n]+`/g,
placeholder
)
const normalized = masked
.replace(/\\\[([\s\S]*?)\\\]/g, (_m, inner: string) => `$$${inner}$$`)
.replace(/\\\(([\s\S]*?)\\\)/g, (_m, inner: string) => `$${inner}$`)
return normalized.replace(
/\0CBLK(\d+)\0/g,
(_m, i: string) => saved[Number(i)]
)
}
function MessageResponseImpl({
className,
children,
...props
}: MessageResponseProps) {
const linkSafety = useStreamdownLinkSafety()
const normalized = useMemo(
() =>
typeof children === "string"
? normalizeMathDelimiters(children)
: children,
[children]
)
return (
<Streamdown
@@ -341,7 +374,9 @@ function MessageResponseImpl({ className, ...props }: MessageResponseProps) {
linkSafety={linkSafety}
plugins={streamdownPlugins}
{...props}
/>
>
{normalized}
</Streamdown>
)
}

View File

@@ -29,6 +29,7 @@ import { Streamdown } from "streamdown"
import { Shimmer } from "./shimmer"
import { useStreamdownLinkSafety } from "./link-safety"
import { normalizeMathDelimiters } from "./message"
interface ReasoningContextValue {
isStreaming: boolean
@@ -218,6 +219,10 @@ const streamdownPlugins = { cjk, code, math, mermaid }
export const ReasoningContent = memo(
({ className, children, ...props }: ReasoningContentProps) => {
const linkSafety = useStreamdownLinkSafety()
const normalized = useMemo(
() => normalizeMathDelimiters(children),
[children]
)
return (
<CollapsibleContent
@@ -233,7 +238,7 @@ export const ReasoningContent = memo(
plugins={streamdownPlugins}
{...props}
>
{children}
{normalized}
</Streamdown>
</CollapsibleContent>
)

View File

@@ -22,6 +22,7 @@ import { createMathPlugin } from "@streamdown/math"
import { mermaid } from "@streamdown/mermaid"
import { Streamdown } from "streamdown"
import { readFileBase64 } from "@/lib/api"
import { normalizeMathDelimiters } from "@/components/ai-elements/message"
import { defineMonacoThemes, useMonacoThemeSync } from "@/lib/monaco-themes"
import "@/lib/monaco-local"
@@ -1354,9 +1355,8 @@ export function FileWorkspacePanel() {
const relativeFileDir = activeFileTab.path?.includes("/")
? activeFileTab.path.replace(/\/[^/]*$/, "")
: ""
const preprocessedContent = preprocessMarkdownPaths(
renderedContent,
relativeFileDir
const preprocessedContent = normalizeMathDelimiters(
preprocessMarkdownPaths(renderedContent, relativeFileDir)
)
return (