Improve code highlighting with context-aware styling and inline detection

• Add messageRole prop to CodeHighlight
• Remove unused Element type import
• Replace node-based inline detection
• Add theme-aware inline code styles
• Update dependency arrays in useMemo
This commit is contained in:
yangdx 2025-09-22 01:41:58 +08:00
parent 8826d2f892
commit 17dd56e41c

View file

@ -10,7 +10,6 @@ import rehypeReact from 'rehype-react'
import remarkMath from 'remark-math' import remarkMath from 'remark-math'
import mermaid from 'mermaid' import mermaid from 'mermaid'
import type { Element } from 'hast'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { oneLight, oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism' import { oneLight, oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'
@ -85,6 +84,7 @@ export const ChatMessage = ({ message }: { message: MessageWithError }) => { //
<CodeHighlight <CodeHighlight
{...props} {...props}
renderAsDiagram={message.mermaidRendered ?? false} renderAsDiagram={message.mermaidRendered ?? false}
messageRole={message.role}
/> />
), ),
p: ({ children }: { children?: ReactNode }) => <p className="my-2">{children}</p>, p: ({ children }: { children?: ReactNode }) => <p className="my-2">{children}</p>,
@ -95,11 +95,11 @@ export const ChatMessage = ({ message }: { message: MessageWithError }) => { //
ul: ({ children }: { children?: ReactNode }) => <ul className="list-disc pl-5 my-2">{children}</ul>, ul: ({ children }: { children?: ReactNode }) => <ul className="list-disc pl-5 my-2">{children}</ul>,
ol: ({ children }: { children?: ReactNode }) => <ol className="list-decimal pl-5 my-2">{children}</ol>, ol: ({ children }: { children?: ReactNode }) => <ol className="list-decimal pl-5 my-2">{children}</ol>,
li: ({ children }: { children?: ReactNode }) => <li className="my-1">{children}</li> li: ({ children }: { children?: ReactNode }) => <li className="my-1">{children}</li>
}), [message.mermaidRendered]); }), [message.mermaidRendered, message.role]);
const thinkingMarkdownComponents = useMemo(() => ({ const thinkingMarkdownComponents = useMemo(() => ({
code: (props: any) => (<CodeHighlight {...props} renderAsDiagram={message.mermaidRendered ?? false} />) code: (props: any) => (<CodeHighlight {...props} renderAsDiagram={message.mermaidRendered ?? false} messageRole={message.role} />)
}), [message.mermaidRendered]); }), [message.mermaidRendered, message.role]);
return ( return (
<div <div
@ -208,20 +208,10 @@ interface CodeHighlightProps {
inline?: boolean inline?: boolean
className?: string className?: string
children?: ReactNode children?: ReactNode
node?: Element // Keep node for inline check
renderAsDiagram?: boolean // Flag to indicate if rendering as diagram should be attempted renderAsDiagram?: boolean // Flag to indicate if rendering as diagram should be attempted
messageRole?: 'user' | 'assistant' // Message role for context-aware styling
} }
// Helper function remains the same
const isInlineCode = (node?: Element): boolean => {
if (!node || !node.children) return false;
const textContent = node.children
.filter((child) => child.type === 'text')
.map((child) => (child as any).value)
.join('');
// Consider inline if it doesn't contain newline or is very short
return !textContent.includes('\n') || textContent.length < 40;
};
// Check if it is a large JSON // Check if it is a large JSON
@ -231,12 +221,11 @@ const isLargeJson = (language: string | undefined, content: string | undefined):
}; };
// Memoize the CodeHighlight component // Memoize the CodeHighlight component
const CodeHighlight = memo(({ className, children, node, renderAsDiagram = false, ...props }: CodeHighlightProps) => { const CodeHighlight = memo(({ inline, className, children, renderAsDiagram = false, messageRole, ...props }: CodeHighlightProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
const [hasRendered, setHasRendered] = useState(false); // State to track successful render const [hasRendered, setHasRendered] = useState(false); // State to track successful render
const match = className?.match(/language-(\w+)/); const match = className?.match(/language-(\w+)/);
const language = match ? match[1] : undefined; const language = match ? match[1] : undefined;
const inline = isInlineCode(node); // Use the helper function
const mermaidRef = useRef<HTMLDivElement>(null); const mermaidRef = useRef<HTMLDivElement>(null);
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); // Use ReturnType for better typing const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); // Use ReturnType for better typing
@ -401,20 +390,45 @@ const CodeHighlight = memo(({ className, children, node, renderAsDiagram = false
} }
// ReactMarkdown determines inline vs block based on markdown syntax
// Inline code: `code` (no className with language)
// Block code: ```language (has className like "language-js")
// If there's no language className and no explicit inline prop, it's likely inline code
const isInline = inline ?? !className?.startsWith('language-');
// Generate dynamic inline code styles based on message role and theme
const getInlineCodeStyles = () => {
if (messageRole === 'user') {
// User messages have dark background (bg-primary), need light inline code
return theme === 'dark'
? 'bg-primary-foreground/20 text-primary-foreground border border-primary-foreground/30'
: 'bg-primary-foreground/20 text-primary-foreground border border-primary-foreground/30';
} else {
// Assistant messages have light background (bg-muted), need contrasting inline code
return theme === 'dark'
? 'bg-muted-foreground/20 text-muted-foreground border border-muted-foreground/30'
: 'bg-slate-200 text-slate-800 border border-slate-300';
}
};
// Handle non-Mermaid code blocks // Handle non-Mermaid code blocks
return !inline ? ( return !isInline ? (
<SyntaxHighlighter <SyntaxHighlighter
style={theme === 'dark' ? oneDark : oneLight} style={theme === 'dark' ? oneDark : oneLight}
PreTag="div" // Use div for block code PreTag="div"
language={language} language={language}
{...props} {...props}
> >
{contentStr} {contentStr}
</SyntaxHighlighter> </SyntaxHighlighter>
) : ( ) : (
// Handle inline code // Handle inline code with context-aware styling
<code <code
className={cn(className, 'mx-1 rounded-sm bg-muted px-1 py-0.5 font-mono text-sm')} // Add font-mono to ensure monospaced font is used className={cn(
className,
'mx-1 rounded-sm px-1 py-0.5 font-mono text-sm',
getInlineCodeStyles()
)}
{...props} {...props}
> >
{children} {children}