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:
parent
8826d2f892
commit
17dd56e41c
1 changed files with 35 additions and 21 deletions
|
|
@ -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}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue