@@ -5,8 +5,10 @@ import { useDebounceEffect } from "@src/utils/useDebounceEffect"
55import { vscode } from "@src/utils/vscode"
66import { useAppTranslation } from "@src/i18n/TranslationContext"
77import { useCopyToClipboard } from "@src/utils/clipboard"
8+ import { MermaidSyntaxFixer } from "@src/services/mermaidSyntaxFixer"
89import CodeBlock from "./CodeBlock"
910import { MermaidButton } from "@/components/common/MermaidButton"
11+ import { MermaidFixButton } from "@/components/common/MermaidFixButton"
1012
1113// Removed previous attempts at static imports for individual diagram types
1214// as the paths were incorrect for Mermaid v11.4.1 and caused errors.
@@ -86,26 +88,50 @@ interface MermaidBlockProps {
8688 code : string
8789}
8890
89- export default function MermaidBlock ( { code } : MermaidBlockProps ) {
91+ export default function MermaidBlock ( { code : originalCode } : MermaidBlockProps ) {
9092 const containerRef = useRef < HTMLDivElement > ( null )
9193 const [ isLoading , setIsLoading ] = useState ( false )
9294 const [ error , setError ] = useState < string | null > ( null )
9395 const [ isErrorExpanded , setIsErrorExpanded ] = useState ( false )
96+ const [ svgContent , setSvgContent ] = useState < string > ( "" )
97+ const [ isFixing , setIsFixing ] = useState ( false )
98+ const [ code , setCode ] = useState ( "" )
9499 const { showCopyFeedback, copyWithFeedback } = useCopyToClipboard ( )
95100 const { t } = useAppTranslation ( )
96101
97102 // 1) Whenever `code` changes, mark that we need to re-render a new chart
98103 useEffect ( ( ) => {
99104 setIsLoading ( true )
100105 setError ( null )
101- } , [ code ] )
106+ setCode ( originalCode )
107+ setIsFixing ( false )
108+ } , [ originalCode ] )
109+
110+ const handleSyntaxFix = async ( ) => {
111+ if ( isFixing ) return
112+
113+ setIsLoading ( true )
114+ setIsFixing ( true )
115+ const result = await MermaidSyntaxFixer . autoFixSyntax ( code )
116+ if ( result . fixedCode ) {
117+ // Use the improved code even if not completely successful
118+ setCode ( result . fixedCode )
119+ }
120+
121+ if ( ! result . success ) {
122+ setError ( result . error || t ( "common:mermaid.errors.fix_failed" ) )
123+ }
124+
125+ setIsFixing ( false )
126+ setIsLoading ( false )
127+ }
102128
103129 // 2) Debounce the actual parse/render
130+ // the LLM is still 'typing', and we do not want to start rendering and/or autofixing before it is fully done.
104131 useDebounceEffect (
105132 ( ) => {
106- if ( containerRef . current ) {
107- containerRef . current . innerHTML = ""
108- }
133+ if ( isFixing ) return
134+ setIsLoading ( true )
109135
110136 mermaid
111137 . parse ( code )
@@ -114,20 +140,20 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
114140 return mermaid . render ( id , code )
115141 } )
116142 . then ( ( { svg } ) => {
117- if ( containerRef . current ) {
118- containerRef . current . innerHTML = svg
119- }
143+ setError ( null )
144+ setSvgContent ( svg )
120145 } )
121146 . catch ( ( err ) => {
122147 console . warn ( "Mermaid parse/render failed:" , err )
123- setError ( err . message || "Failed to render Mermaid diagram" )
148+ const errorMessage = err instanceof Error ? err . message : t ( "common:mermaid.render_error" )
149+ setError ( errorMessage )
124150 } )
125151 . finally ( ( ) => {
126152 setIsLoading ( false )
127153 } )
128154 } ,
129155 500 , // Delay 500ms
130- [ code ] , // Dependencies for scheduling
156+ [ code , isFixing , originalCode , t ] , // Dependencies for scheduling
131157 )
132158
133159 /**
@@ -154,7 +180,11 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
154180
155181 return (
156182 < MermaidBlockContainer >
157- { isLoading && < LoadingMessage > { t ( "common:mermaid.loading" ) } </ LoadingMessage > }
183+ { isLoading && (
184+ < LoadingMessage >
185+ { isFixing ? t ( "common:mermaid.fixing_syntax" ) : t ( "common:mermaid.loading" ) }
186+ </ LoadingMessage >
187+ ) }
158188
159189 { error ? (
160190 < div style = { { marginTop : "0px" , overflow : "hidden" , marginBottom : "8px" } } >
@@ -188,6 +218,17 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
188218 < span style = { { fontWeight : "bold" } } > { t ( "common:mermaid.render_error" ) } </ span >
189219 </ div >
190220 < div style = { { display : "flex" , alignItems : "center" } } >
221+ { ! ! error && (
222+ < MermaidFixButton
223+ onClick = { ( e ) => {
224+ e . stopPropagation ( )
225+ handleSyntaxFix ( )
226+ } }
227+ disabled = { isFixing }
228+ title = { t ( "common:mermaid.fix_syntax_button" ) } >
229+ < span className = { `codicon codicon-${ isFixing ? "loading" : "wand" } ` } > </ span >
230+ </ MermaidFixButton >
231+ ) }
191232 < CopyButton
192233 onClick = { ( e ) => {
193234 e . stopPropagation ( )
@@ -210,12 +251,24 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
210251 { error }
211252 </ div >
212253 < CodeBlock language = "mermaid" source = { code } />
254+ { code !== originalCode && (
255+ < div style = { { marginTop : "8px" } } >
256+ < div style = { { marginBottom : "4px" , fontSize : "0.9em" , fontWeight : "bold" } } >
257+ { t ( "common:mermaid.original_code" ) }
258+ </ div >
259+ < CodeBlock language = "mermaid" source = { originalCode } />
260+ </ div >
261+ ) }
213262 </ div >
214263 ) }
215264 </ div >
216265 ) : (
217266 < MermaidButton containerRef = { containerRef } code = { code } isLoading = { isLoading } svgToPng = { svgToPng } >
218- < SvgContainer onClick = { handleClick } ref = { containerRef } $isLoading = { isLoading } > </ SvgContainer >
267+ < SvgContainer
268+ onClick = { handleClick }
269+ $isLoading = { isLoading }
270+ dangerouslySetInnerHTML = { { __html : svgContent } }
271+ />
219272 </ MermaidButton >
220273 ) }
221274 </ MermaidBlockContainer >
0 commit comments