@@ -20,10 +20,26 @@ import {
2020import { css } from '@emotion/react'
2121import DOMPurify from 'dompurify'
2222import hljs from 'highlight.js/lib/core'
23- import { marked } from 'marked'
23+ import { Marked , RendererObject , Tokens } from 'marked'
2424import * as React from 'react'
25- import { useEffect } from 'react'
26- import { $ , $$ } from 'select-dom'
25+ import { useEffect , useMemo } from 'react'
26+
27+ // Create the marked instance once globally (renderer never changes)
28+ const createMarkedInstance = ( ) => {
29+ const renderer : RendererObject = {
30+ code ( { text, lang } : Tokens . Code ) : string {
31+ const highlighted = lang ? hljs . highlight ( text , { language : lang } ) . value : hljs . highlightAuto ( text ) . value ;
32+ return `<div class="highlight">
33+ <pre>
34+ <code class="language-${ lang } ">${ highlighted } </code>
35+ </pre>
36+ </div>` ;
37+ }
38+ }
39+ return new Marked ( { renderer } )
40+ }
41+
42+ const markedInstance = createMarkedInstance ( ) // Created once globally
2743
2844interface ChatMessageProps {
2945 message : ChatMessageType
@@ -157,25 +173,12 @@ export const ChatMessage = ({
157173 : message . content
158174
159175 const hasError = message . status === 'error' || ! ! error
160-
161- const html = marked . parse ( content )
162- const sanitized = DOMPurify . sanitize ( html as string )
163- const tempDiv = document . createElement ( 'div' )
164- tempDiv . innerHTML = sanitized
165-
166- // Add highlight wrappers and highlight code blocks
167- $$ ( 'pre' , tempDiv ) . forEach ( ( preEl ) => {
168- const wrapper = document . createElement ( 'div' )
169- wrapper . className = 'highlight'
170- preEl . parentNode ?. insertBefore ( wrapper , preEl )
171- wrapper . appendChild ( preEl )
172- const codeEl = $ ( 'code' , preEl )
173- if ( codeEl ) {
174- hljs . highlightElement ( codeEl )
175- }
176- } )
177-
178- const parsed = tempDiv . innerHTML
176+
177+ // Memoize the parsed HTML to avoid re-parsing on every render
178+ const parsed = useMemo ( ( ) => {
179+ const html = markedInstance . parse ( content ) as string
180+ return DOMPurify . sanitize ( html )
181+ } , [ content ] )
179182 const ref = React . useRef < HTMLDivElement > ( null )
180183
181184 // Initialize copy buttons after DOM is updated
@@ -195,7 +198,7 @@ export const ChatMessage = ({
195198 } , 100 )
196199 return ( ) => clearTimeout ( timer )
197200 }
198- } , [ parsed , isComplete ] )
201+ } , [ isComplete ] ) // Only depend on isComplete, not parsed
199202
200203 return (
201204 < EuiFlexGroup
0 commit comments