@@ -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.
@@ -87,26 +89,50 @@ interface MermaidBlockProps {
8789 code : string
8890}
8991
90- export default function MermaidBlock ( { code } : MermaidBlockProps ) {
92+ export default function MermaidBlock ( { code : originalCode } : MermaidBlockProps ) {
9193 const containerRef = useRef < HTMLDivElement > ( null )
9294 const [ isLoading , setIsLoading ] = useState ( false )
9395 const [ error , setError ] = useState < string | null > ( null )
9496 const [ isErrorExpanded , setIsErrorExpanded ] = useState ( false )
97+ const [ svgContent , setSvgContent ] = useState < string > ( "" )
98+ const [ isFixing , setIsFixing ] = useState ( false )
99+ const [ code , setCode ] = useState ( "" )
95100 const { showCopyFeedback, copyWithFeedback } = useCopyToClipboard ( )
96101 const { t } = useAppTranslation ( )
97102
98103 // 1) Whenever `code` changes, mark that we need to re-render a new chart
99104 useEffect ( ( ) => {
100105 setIsLoading ( true )
101106 setError ( null )
102- } , [ code ] )
107+ setCode ( originalCode )
108+ setIsFixing ( false )
109+ } , [ originalCode ] )
110+
111+ const handleSyntaxFix = async ( ) => {
112+ if ( isFixing ) return
113+
114+ setIsLoading ( true )
115+ setIsFixing ( true )
116+ const result = await MermaidSyntaxFixer . autoFixSyntax ( code )
117+ if ( result . fixedCode ) {
118+ // Use the improved code even if not completely successful
119+ setCode ( result . fixedCode )
120+ }
121+
122+ if ( ! result . success ) {
123+ setError ( result . error || t ( "common:mermaid.errors.fix_failed" ) )
124+ }
125+
126+ setIsFixing ( false )
127+ setIsLoading ( false )
128+ }
103129
104130 // 2) Debounce the actual parse/render
131+ // the LLM is still 'typing', and we do not want to start rendering and/or autofixing before it is fully done.
105132 useDebounceEffect (
106133 ( ) => {
107- if ( containerRef . current ) {
108- containerRef . current . innerHTML = ""
109- }
134+ if ( isFixing ) return
135+ setIsLoading ( true )
110136
111137 mermaid
112138 . parse ( code )
@@ -115,20 +141,20 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
115141 return mermaid . render ( id , code )
116142 } )
117143 . then ( ( { svg } ) => {
118- if ( containerRef . current ) {
119- containerRef . current . innerHTML = svg
120- }
144+ setError ( null )
145+ setSvgContent ( svg )
121146 } )
122147 . catch ( ( err ) => {
123148 console . warn ( "Mermaid parse/render failed:" , err )
124- setError ( err . message || "Failed to render Mermaid diagram" )
149+ const errorMessage = err instanceof Error ? err . message : t ( "common:mermaid.render_error" )
150+ setError ( errorMessage )
125151 } )
126152 . finally ( ( ) => {
127153 setIsLoading ( false )
128154 } )
129155 } ,
130156 500 , // Delay 500ms
131- [ code ] , // Dependencies for scheduling
157+ [ code , isFixing , originalCode , t ] , // Dependencies for scheduling
132158 )
133159
134160 /**
@@ -155,7 +181,11 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
155181
156182 return (
157183 < MermaidBlockContainer >
158- { isLoading && < LoadingMessage > { t ( "common:mermaid.loading" ) } </ LoadingMessage > }
184+ { isLoading && (
185+ < LoadingMessage >
186+ { isFixing ? t ( "common:mermaid.fixing_syntax" ) : t ( "common:mermaid.loading" ) }
187+ </ LoadingMessage >
188+ ) }
159189
160190 { error ? (
161191 < div style = { { marginTop : "0px" , overflow : "hidden" , marginBottom : "8px" } } >
@@ -189,6 +219,17 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
189219 < span style = { { fontWeight : "bold" } } > { t ( "common:mermaid.render_error" ) } </ span >
190220 </ div >
191221 < div style = { { display : "flex" , alignItems : "center" } } >
222+ { ! ! error && (
223+ < MermaidFixButton
224+ onClick = { ( e ) => {
225+ e . stopPropagation ( )
226+ handleSyntaxFix ( )
227+ } }
228+ disabled = { isFixing }
229+ title = { t ( "common:mermaid.fix_syntax_button" ) } >
230+ < span className = { `codicon codicon-${ isFixing ? "loading" : "wand" } ` } > </ span >
231+ </ MermaidFixButton >
232+ ) }
192233 < CopyButton
193234 onClick = { ( e ) => {
194235 e . stopPropagation ( )
@@ -211,12 +252,24 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
211252 { error }
212253 </ div >
213254 < CodeBlock language = "mermaid" source = { code } />
255+ { code !== originalCode && (
256+ < div style = { { marginTop : "8px" } } >
257+ < div style = { { marginBottom : "4px" , fontSize : "0.9em" , fontWeight : "bold" } } >
258+ { t ( "common:mermaid.original_code" ) }
259+ </ div >
260+ < CodeBlock language = "mermaid" source = { originalCode } />
261+ </ div >
262+ ) }
214263 </ div >
215264 ) }
216265 </ div >
217266 ) : (
218267 < MermaidButton containerRef = { containerRef } code = { code } isLoading = { isLoading } svgToPng = { svgToPng } >
219- < SvgContainer onClick = { handleClick } ref = { containerRef } $isLoading = { isLoading } > </ SvgContainer >
268+ < SvgContainer
269+ onClick = { handleClick }
270+ $isLoading = { isLoading }
271+ dangerouslySetInnerHTML = { { __html : svgContent } }
272+ />
220273 </ MermaidButton >
221274 ) }
222275 </ MermaidBlockContainer >
0 commit comments