Ver código fonte

Merge pull request #842 from Kilo-Org/mark/fix-mermaid-syntax-errors

fix mermaid syntax errors
Mark IJbema 7 meses atrás
pai
commit
1ddacf4463
33 arquivos alterados com 839 adições e 12 exclusões
  1. 5 0
      .changeset/empty-ghosts-carry.md
  2. 31 0
      src/core/prompts/utilities/mermaid.ts
  3. 29 0
      src/core/webview/webviewMessageHandler.ts
  4. 2 0
      src/shared/ExtensionMessage.ts
  5. 2 0
      src/shared/WebviewMessage.ts
  6. 84 12
      webview-ui/src/components/common/MermaidBlock.tsx
  7. 39 0
      webview-ui/src/components/common/MermaidFixButton.tsx
  8. 11 0
      webview-ui/src/i18n/locales/ca/common.json
  9. 11 0
      webview-ui/src/i18n/locales/cs/common.json
  10. 11 0
      webview-ui/src/i18n/locales/de/common.json
  11. 11 0
      webview-ui/src/i18n/locales/el/common.json
  12. 11 0
      webview-ui/src/i18n/locales/en/common.json
  13. 11 0
      webview-ui/src/i18n/locales/es/common.json
  14. 11 0
      webview-ui/src/i18n/locales/fil/common.json
  15. 11 0
      webview-ui/src/i18n/locales/fr/common.json
  16. 11 0
      webview-ui/src/i18n/locales/hi/common.json
  17. 11 0
      webview-ui/src/i18n/locales/id/common.json
  18. 11 0
      webview-ui/src/i18n/locales/it/common.json
  19. 11 0
      webview-ui/src/i18n/locales/ja/common.json
  20. 11 0
      webview-ui/src/i18n/locales/ko/common.json
  21. 11 0
      webview-ui/src/i18n/locales/nl/common.json
  22. 11 0
      webview-ui/src/i18n/locales/pl/common.json
  23. 11 0
      webview-ui/src/i18n/locales/pt-BR/common.json
  24. 11 0
      webview-ui/src/i18n/locales/ru/common.json
  25. 11 0
      webview-ui/src/i18n/locales/sv/common.json
  26. 11 0
      webview-ui/src/i18n/locales/th/common.json
  27. 11 0
      webview-ui/src/i18n/locales/tr/common.json
  28. 11 0
      webview-ui/src/i18n/locales/uk/common.json
  29. 11 0
      webview-ui/src/i18n/locales/vi/common.json
  30. 11 0
      webview-ui/src/i18n/locales/zh-CN/common.json
  31. 11 0
      webview-ui/src/i18n/locales/zh-TW/common.json
  32. 228 0
      webview-ui/src/services/__tests__/mermaidSyntaxFixer.spec.ts
  33. 155 0
      webview-ui/src/services/mermaidSyntaxFixer.ts

+ 5 - 0
.changeset/empty-ghosts-carry.md

@@ -0,0 +1,5 @@
+---
+"kilo-code": patch
+---
+
+add a button to fix mermaid syntax errors by calling the LLM

+ 31 - 0
src/core/prompts/utilities/mermaid.ts

@@ -0,0 +1,31 @@
+/**
+ * Prompts for Mermaid diagram-related tasks
+ */
+
+/**
+ * Generate a prompt for fixing invalid Mermaid diagram syntax
+ * @param error - The error message from Mermaid parser
+ * @param invalidCode - The invalid Mermaid code that needs fixing
+ * @returns The formatted prompt for the AI to fix the Mermaid syntax
+ */
+export const mermaidFixPrompt = (error: string, invalidCode: string): string => {
+	return `You are a Mermaid diagram syntax expert. Fix the following invalid Mermaid diagram syntax and return ONLY the corrected Mermaid code without any explanations or markdown formatting.
+
+Error: ${error}
+
+Invalid Mermaid code:
+\`\`\`
+${invalidCode}
+\`\`\`
+
+Requirements:
+1. Return ONLY the corrected Mermaid syntax
+2. Do not include markdown code blocks or explanations
+3. Ensure the syntax is valid according to Mermaid specifications
+4. Enclose labels and edge label within double quotes even when you do not think it necessary to ensure the syntax is robust
+5. Do not point to multiple nodes with one edge, use multiple edges instead
+6. Preserve the original intent and structure as much as possible
+7. If the diagram type is unclear, default to a flowchart
+
+Corrected Mermaid code:`
+}

+ 29 - 0
src/core/webview/webviewMessageHandler.ts

@@ -41,6 +41,7 @@ import { GetModelsOptions } from "../../shared/api"
 import { generateSystemPrompt } from "./generateSystemPrompt"
 import { getCommand } from "../../utils/commands"
 import { toggleWorkflow, toggleRule, createRuleFile, deleteRuleFile } from "./kilorules"
+import { mermaidFixPrompt } from "../prompts/utilities/mermaid" // kilocode_change
 
 const ALLOWED_VSCODE_SETTINGS = new Set(["terminal.integrated.inheritEnv"])
 
@@ -1811,6 +1812,34 @@ export const webviewMessageHandler = async (
 				await provider.toggleTaskFavorite(message.text)
 			}
 			break
+		case "fixMermaidSyntax":
+			if (message.text && message.requestId) {
+				try {
+					const { apiConfiguration } = await provider.getState()
+
+					const prompt = mermaidFixPrompt(message.values?.error || "Unknown syntax error", message.text)
+
+					const fixedCode = await singleCompletionHandler(apiConfiguration, prompt)
+
+					provider.postMessageToWebview({
+						type: "mermaidFixResponse",
+						requestId: message.requestId,
+						success: true,
+						fixedCode: fixedCode?.trim() || null,
+					})
+				} catch (error) {
+					const errorMessage = error instanceof Error ? error.message : "Failed to fix Mermaid syntax"
+					provider.log(`Error fixing Mermaid syntax: ${errorMessage}`)
+
+					provider.postMessageToWebview({
+						type: "mermaidFixResponse",
+						requestId: message.requestId,
+						success: false,
+						error: errorMessage,
+					})
+				}
+			}
+			break
 		// kilocode_change end
 		case "focusPanelRequest": {
 			// Execute the focusPanel command to focus the WebView

+ 2 - 0
src/shared/ExtensionMessage.ts

@@ -107,6 +107,7 @@ export interface ExtensionMessage {
 		| "rulesData" // kilocode_change
 		| "marketplaceInstallResult"
 		| "marketplaceData"
+		| "mermaidFixResponse" // kilocode_change
 	text?: string
 	payload?: ProfileDataResponsePayload | BalanceDataResponsePayload // kilocode_change: Add payload for profile and balance data
 	action?:
@@ -169,6 +170,7 @@ export interface ExtensionMessage {
 	localWorkflows?: ClineRulesToggles
 	marketplaceItems?: MarketplaceItem[]
 	marketplaceInstalledMetadata?: MarketplaceInstalledMetadata
+	fixedCode?: string | null // For mermaidFixResponse // kilocode_change
 }
 
 export type ExtensionState = Pick<

+ 2 - 0
src/shared/WebviewMessage.ts

@@ -192,6 +192,8 @@ export interface WebviewMessage {
 		| "setHistoryPreviewCollapsed"
 		| "showTaskTimeline" // kilocode_change
 		| "toggleTaskFavorite" // kilocode_change
+		| "fixMermaidSyntax" // kilocode_change
+		| "mermaidFixResponse" // kilocode_change
 		| "openExternal"
 		| "filterMarketplaceItems"
 		| "marketplaceButtonClicked"

+ 84 - 12
webview-ui/src/components/common/MermaidBlock.tsx

@@ -5,8 +5,10 @@ import { useDebounceEffect } from "@src/utils/useDebounceEffect"
 import { vscode } from "@src/utils/vscode"
 import { useAppTranslation } from "@src/i18n/TranslationContext"
 import { useCopyToClipboard } from "@src/utils/clipboard"
+import { MermaidSyntaxFixer } from "@src/services/mermaidSyntaxFixer" // kilocode_change
 import CodeBlock from "./CodeBlock"
 import { MermaidButton } from "@/components/common/MermaidButton"
+import { MermaidFixButton } from "@/components/common/MermaidFixButton" // kilocode_change
 
 // Removed previous attempts at static imports for individual diagram types
 // as the paths were incorrect for Mermaid v11.4.1 and caused errors.
@@ -86,11 +88,17 @@ interface MermaidBlockProps {
 	code: string
 }
 
-export default function MermaidBlock({ code }: MermaidBlockProps) {
+// kilocode_change next line rename to originalCode to keep the difference with Roo smaller
+export default function MermaidBlock({ code: originalCode }: MermaidBlockProps) {
 	const containerRef = useRef<HTMLDivElement>(null)
 	const [isLoading, setIsLoading] = useState(false)
 	const [error, setError] = useState<string | null>(null)
 	const [isErrorExpanded, setIsErrorExpanded] = useState(false)
+	// kilocode_change start
+	const [svgContent, setSvgContent] = useState<string>("")
+	const [isFixing, setIsFixing] = useState(false)
+	const [code, setCode] = useState("")
+	// kilocode_change end
 	const { showCopyFeedback, copyWithFeedback } = useCopyToClipboard()
 	const { t } = useAppTranslation()
 
@@ -98,14 +106,41 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
 	useEffect(() => {
 		setIsLoading(true)
 		setError(null)
-	}, [code])
+		// kilocode_change start
+		setCode(originalCode)
+		setIsFixing(false)
+		// kilocode_change end
+	}, [originalCode]) // kilocode_change originalCode instead of code
+
+	// kilocode_change start
+	const handleSyntaxFix = async () => {
+		if (isFixing) return
+
+		setIsLoading(true)
+		setIsFixing(true)
+		const result = await MermaidSyntaxFixer.autoFixSyntax(code)
+		if (result.fixedCode) {
+			// Use the improved code even if not completely successful
+			setCode(result.fixedCode)
+		}
+
+		if (!result.success) {
+			setError(result.error || t("common:mermaid.errors.fix_failed"))
+		}
+
+		setIsFixing(false)
+		setIsLoading(false)
+	}
+	// kilocode_change end
 
 	// 2) Debounce the actual parse/render
+	// the LLM is still 'typing', and we do not want to start rendering and/or autofixing before it is fully done.
 	useDebounceEffect(
 		() => {
-			if (containerRef.current) {
-				containerRef.current.innerHTML = ""
-			}
+			//kilocode_change start
+			if (isFixing) return
+			setIsLoading(true)
+			//kilocode_change end
 
 			mermaid
 				.parse(code)
@@ -114,20 +149,24 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
 					return mermaid.render(id, code)
 				})
 				.then(({ svg }) => {
-					if (containerRef.current) {
-						containerRef.current.innerHTML = svg
-					}
+					//kilocode_change start
+					setError(null)
+					setSvgContent(svg)
+					// kilocode_change end
 				})
 				.catch((err) => {
 					console.warn("Mermaid parse/render failed:", err)
-					setError(err.message || "Failed to render Mermaid diagram")
+					// kilocode_change start
+					const errorMessage = err instanceof Error ? err.message : t("common:mermaid.render_error")
+					setError(errorMessage)
+					// kilocode_change end
 				})
 				.finally(() => {
 					setIsLoading(false)
 				})
 		},
 		500, // Delay 500ms
-		[code], // Dependencies for scheduling
+		[code, isFixing, originalCode, t], // Dependencies for scheduling // kilocode_change added isFixing, originalCode and t
 	)
 
 	/**
@@ -154,7 +193,11 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
 
 	return (
 		<MermaidBlockContainer>
-			{isLoading && <LoadingMessage>{t("common:mermaid.loading")}</LoadingMessage>}
+			{isLoading && (
+				<LoadingMessage>
+					{isFixing /* kilocode_change */ ? t("common:mermaid.fixing_syntax") : t("common:mermaid.loading")}
+				</LoadingMessage>
+			)}
 
 			{error ? (
 				<div style={{ marginTop: "0px", overflow: "hidden", marginBottom: "8px" }}>
@@ -188,6 +231,19 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
 							<span style={{ fontWeight: "bold" }}>{t("common:mermaid.render_error")}</span>
 						</div>
 						<div style={{ display: "flex", alignItems: "center" }}>
+							{/* kilocode_change start */}
+							{!!error && (
+								<MermaidFixButton
+									onClick={(e) => {
+										e.stopPropagation()
+										handleSyntaxFix()
+									}}
+									disabled={isFixing}
+									title={t("common:mermaid.fix_syntax_button")}>
+									<span className={`codicon codicon-${isFixing ? "loading" : "wand"}`}></span>
+								</MermaidFixButton>
+							)}
+							{/* kilocode_change end */}
 							<CopyButton
 								onClick={(e) => {
 									e.stopPropagation()
@@ -210,12 +266,28 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
 								{error}
 							</div>
 							<CodeBlock language="mermaid" source={code} />
+							{/* kilocode_change start */}
+							{code !== originalCode && (
+								<div style={{ marginTop: "8px" }}>
+									<div style={{ marginBottom: "4px", fontSize: "0.9em", fontWeight: "bold" }}>
+										{t("common:mermaid.original_code")}
+									</div>
+									<CodeBlock language="mermaid" source={originalCode} />
+								</div>
+							)}
+							{/* kilocode_change end */}
 						</div>
 					)}
 				</div>
 			) : (
 				<MermaidButton containerRef={containerRef} code={code} isLoading={isLoading} svgToPng={svgToPng}>
-					<SvgContainer onClick={handleClick} ref={containerRef} $isLoading={isLoading}></SvgContainer>
+					{/* kilocode_change start switched from ref to dangerouslySetInnerHTML */}
+					<SvgContainer
+						onClick={handleClick}
+						$isLoading={isLoading}
+						dangerouslySetInnerHTML={{ __html: svgContent }}
+					/>
+					{/* kilocode_change end */}
 				</MermaidButton>
 			)}
 		</MermaidBlockContainer>

+ 39 - 0
webview-ui/src/components/common/MermaidFixButton.tsx

@@ -0,0 +1,39 @@
+// kilocode_change file added
+import styled from "styled-components"
+
+export const MermaidFixButton = styled.button`
+	padding: 3px;
+	height: 24px;
+	margin-right: 4px;
+	color: var(--vscode-editor-foreground);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	background: transparent;
+	border: none;
+	cursor: pointer;
+
+	&:hover {
+		opacity: 0.8;
+		color: var(--vscode-button-foreground);
+		background: var(--vscode-button-hoverBackground);
+	}
+
+	&:disabled {
+		opacity: 0.5;
+		cursor: not-allowed;
+	}
+
+	.codicon-loading {
+		animation: spin 1s linear infinite;
+	}
+
+	@keyframes spin {
+		from {
+			transform: rotate(0deg);
+		}
+		to {
+			transform: rotate(360deg);
+		}
+	}
+`

+ 11 - 0
webview-ui/src/i18n/locales/ca/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Generant diagrama mermaid...",
 		"render_error": "No es pot renderitzar el diagrama",
+		"fixing_syntax": "Corregint la sintaxi de Mermaid...",
+		"fix_syntax_button": "Corregir sintaxi amb IA",
+		"original_code": "Codi original:",
+		"errors": {
+			"unknown_syntax": "Error de sintaxi desconegut",
+			"fix_timeout": "La sol·licitud de correcció de l'IA ha esgotat el temps",
+			"fix_failed": "La correcció de l'IA ha fallat",
+			"fix_attempts": "No s'ha pogut corregir la sintaxi després de {{attempts}} intents. Últim error: {{error}}",
+			"no_fix_provided": "L'IA no ha pogut proporcionar una correcció",
+			"fix_request_failed": "La sol·licitud de correcció ha fallat"
+		},
 		"buttons": {
 			"zoom": "Zoom",
 			"zoomIn": "Ampliar",

+ 11 - 0
webview-ui/src/i18n/locales/cs/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Vytváření diagramu mermaid...",
 		"render_error": "Nepodařilo se vykreslit diagram",
+		"fixing_syntax": "Opravuji syntaxi Mermaid...",
+		"fix_syntax_button": "Opravit syntaxi pomocí AI",
+		"original_code": "Původní kód:",
+		"errors": {
+			"unknown_syntax": "Neznámá chyba syntaxe",
+			"fix_timeout": "Vypršel časový limit požadavku na opravu LLM",
+			"fix_failed": "Oprava LLM selhala",
+			"fix_attempts": "Nepodařilo se opravit syntaxi po {{attempts}} pokusech. Poslední chyba: {{error}}",
+			"no_fix_provided": "LLM neposkytl opravu",
+			"fix_request_failed": "Požadavek na opravu selhal"
+		},
 		"buttons": {
 			"zoom": "Přiblížení",
 			"zoomIn": "Přiblížit",

+ 11 - 0
webview-ui/src/i18n/locales/de/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Mermaid-Diagramm wird generiert...",
 		"render_error": "Diagramm kann nicht gerendert werden",
+		"fixing_syntax": "Mermaid-Syntax wird korrigiert...",
+		"fix_syntax_button": "Syntax mit KI korrigieren",
+		"original_code": "Ursprünglicher Code:",
+		"errors": {
+			"unknown_syntax": "Unbekannter Syntaxfehler",
+			"fix_timeout": "KI-Korrekturanfrage hat das Zeitlimit überschritten",
+			"fix_failed": "KI-Korrektur fehlgeschlagen",
+			"fix_attempts": "Korrektur nach {{attempts}} Versuchen fehlgeschlagen. Letzter Fehler: {{error}}",
+			"no_fix_provided": "KI konnte keine Korrektur bereitstellen",
+			"fix_request_failed": "Korrekturanfrage fehlgeschlagen"
+		},
 		"buttons": {
 			"zoom": "Zoom",
 			"zoomIn": "Vergrößern",

+ 11 - 0
webview-ui/src/i18n/locales/el/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Δημιουργία διαγράμματος mermaid...",
 		"render_error": "Αδυναμία απόδοσης διαγράμματος",
+		"fixing_syntax": "Διόρθωση σύνταξης Mermaid...",
+		"fix_syntax_button": "Διόρθωση σύνταξης με AI",
+		"original_code": "Αρχικός κώδικας:",
+		"errors": {
+			"unknown_syntax": "Άγνωστο συντακτικό σφάλμα",
+			"fix_timeout": "Το αίτημα διόρθωσης LLM έληξε",
+			"fix_failed": "Η διόρθωση LLM απέτυχε",
+			"fix_attempts": "Αποτυχία διόρθωσης σύνταξης μετά από {{attempts}} προσπάθειες. Τελευταίο σφάλμα: {{error}}",
+			"no_fix_provided": "Το LLM απέτυχε να παρέχει διόρθωση",
+			"fix_request_failed": "Το αίτημα διόρθωσης απέτυχε"
+		},
 		"buttons": {
 			"zoom": "Ζουμ",
 			"zoomIn": "Μεγέθυνση",

+ 11 - 0
webview-ui/src/i18n/locales/en/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Generating mermaid diagram...",
 		"render_error": "Unable to Render Diagram",
+		"fixing_syntax": "Fixing Mermaid syntax...",
+		"fix_syntax_button": "Fix syntax with AI",
+		"original_code": "Original code:",
+		"errors": {
+			"unknown_syntax": "Unknown syntax error",
+			"fix_timeout": "LLM fix request timed out",
+			"fix_failed": "LLM fix failed",
+			"fix_attempts": "Failed to fix syntax after {{attempts}} attempts. Last error: {{error}}",
+			"no_fix_provided": "LLM failed to provide a fix",
+			"fix_request_failed": "Fix request failed"
+		},
 		"buttons": {
 			"zoom": "Zoom",
 			"zoomIn": "Zoom In",

+ 11 - 0
webview-ui/src/i18n/locales/es/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Generando diagrama mermaid...",
 		"render_error": "No se puede renderizar el diagrama",
+		"fixing_syntax": "Corrigiendo sintaxis de Mermaid...",
+		"fix_syntax_button": "Corregir sintaxis con IA",
+		"original_code": "Código original:",
+		"errors": {
+			"unknown_syntax": "Error de sintaxis desconocido",
+			"fix_timeout": "La solicitud de corrección de IA agotó el tiempo de espera",
+			"fix_failed": "La corrección de IA falló",
+			"fix_attempts": "No se pudo corregir la sintaxis después de {{attempts}} intentos. Último error: {{error}}",
+			"no_fix_provided": "La IA no pudo proporcionar una corrección",
+			"fix_request_failed": "La solicitud de corrección falló"
+		},
 		"buttons": {
 			"zoom": "Zoom",
 			"zoomIn": "Ampliar",

+ 11 - 0
webview-ui/src/i18n/locales/fil/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Ginagawa ang mermaid diagram...",
 		"render_error": "Hindi Maipakita ang Diagram",
+		"fixing_syntax": "Inaayos ang Mermaid syntax...",
+		"fix_syntax_button": "Ayusin ang syntax gamit ang AI",
+		"original_code": "Orihinal na code:",
+		"errors": {
+			"unknown_syntax": "Hindi kilalang syntax error",
+			"fix_timeout": "Nag-timeout ang LLM fix request",
+			"fix_failed": "Nabigo ang LLM fix",
+			"fix_attempts": "Hindi naayos ang syntax pagkatapos ng {{attempts}} na pagtatangka. Huling error: {{error}}",
+			"no_fix_provided": "Hindi nakapagbigay ng fix ang LLM",
+			"fix_request_failed": "Nabigo ang fix request"
+		},
 		"buttons": {
 			"zoom": "Zoom",
 			"zoomIn": "Palakihin",

+ 11 - 0
webview-ui/src/i18n/locales/fr/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Génération du diagramme mermaid...",
 		"render_error": "Impossible de rendre le diagramme",
+		"fixing_syntax": "Correction de la syntaxe Mermaid...",
+		"fix_syntax_button": "Corriger la syntaxe avec l'IA",
+		"original_code": "Code original :",
+		"errors": {
+			"unknown_syntax": "Erreur de syntaxe inconnue",
+			"fix_timeout": "La demande de correction par l'IA a expiré",
+			"fix_failed": "La correction par l'IA a échoué",
+			"fix_attempts": "Échec de la correction de la syntaxe après {{attempts}} tentatives. Dernière erreur : {{error}}",
+			"no_fix_provided": "L'IA n'a pas pu fournir de correction",
+			"fix_request_failed": "La demande de correction a échoué"
+		},
 		"buttons": {
 			"zoom": "Zoom",
 			"zoomIn": "Agrandir",

+ 11 - 0
webview-ui/src/i18n/locales/hi/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "मरमेड डायग्राम जनरेट हो रहा है...",
 		"render_error": "डायग्राम रेंडर नहीं किया जा सकता",
+		"fixing_syntax": "मरमेड सिंटैक्स ठीक किया जा रहा है...",
+		"fix_syntax_button": "AI से सिंटैक्स ठीक करें",
+		"original_code": "मूल कोड:",
+		"errors": {
+			"unknown_syntax": "अज्ञात सिंटैक्स त्रुटि",
+			"fix_timeout": "LLM फिक्स अनुरोध का समय समाप्त हो गया",
+			"fix_failed": "LLM फिक्स विफल हुआ",
+			"fix_attempts": "{{attempts}} प्रयासों के बाद सिंटैक्स ठीक करने में विफल। अंतिम त्रुटि: {{error}}",
+			"no_fix_provided": "LLM फिक्स प्रदान करने में विफल रहा",
+			"fix_request_failed": "फिक्स अनुरोध विफल हुआ"
+		},
 		"buttons": {
 			"zoom": "ज़ूम",
 			"zoomIn": "बड़ा करें",

+ 11 - 0
webview-ui/src/i18n/locales/id/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Membuat diagram mermaid...",
 		"render_error": "Tidak Dapat Merender Diagram",
+		"fixing_syntax": "Memperbaiki sintaks Mermaid...",
+		"fix_syntax_button": "Perbaiki sintaks dengan AI",
+		"original_code": "Kode asli:",
+		"errors": {
+			"unknown_syntax": "Error sintaks tidak dikenal",
+			"fix_timeout": "Permintaan perbaikan LLM habis waktu",
+			"fix_failed": "Perbaikan LLM gagal",
+			"fix_attempts": "Gagal memperbaiki sintaks setelah {{attempts}} percobaan. Error terakhir: {{error}}",
+			"no_fix_provided": "LLM gagal memberikan perbaikan",
+			"fix_request_failed": "Permintaan perbaikan gagal"
+		},
 		"buttons": {
 			"zoom": "Zoom",
 			"zoomIn": "Perbesar",

+ 11 - 0
webview-ui/src/i18n/locales/it/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Generazione del diagramma mermaid...",
 		"render_error": "Impossibile renderizzare il diagramma",
+		"fixing_syntax": "Correzione della sintassi Mermaid...",
+		"fix_syntax_button": "Correggi sintassi con AI",
+		"original_code": "Codice originale:",
+		"errors": {
+			"unknown_syntax": "Errore di sintassi sconosciuto",
+			"fix_timeout": "Richiesta di correzione LLM scaduta",
+			"fix_failed": "Correzione LLM fallita",
+			"fix_attempts": "Impossibile correggere la sintassi dopo {{attempts}} tentativi. Ultimo errore: {{error}}",
+			"no_fix_provided": "LLM non è riuscito a fornire una correzione",
+			"fix_request_failed": "Richiesta di correzione fallita"
+		},
 		"buttons": {
 			"zoom": "Zoom",
 			"zoomIn": "Ingrandisci",

+ 11 - 0
webview-ui/src/i18n/locales/ja/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Mermaidダイアグラムを生成中...",
 		"render_error": "ダイアグラムをレンダリングできません",
+		"fixing_syntax": "Mermaid構文を修正中...",
+		"fix_syntax_button": "AIで構文を修正",
+		"original_code": "元のコード:",
+		"errors": {
+			"unknown_syntax": "不明な構文エラー",
+			"fix_timeout": "AI修正リクエストがタイムアウトしました",
+			"fix_failed": "AI修正に失敗しました",
+			"fix_attempts": "{{attempts}}回の試行後に構文の修正に失敗しました。最後のエラー:{{error}}",
+			"no_fix_provided": "AIが修正を提供できませんでした",
+			"fix_request_failed": "修正リクエストに失敗しました"
+		},
 		"buttons": {
 			"zoom": "ズーム",
 			"zoomIn": "拡大",

+ 11 - 0
webview-ui/src/i18n/locales/ko/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "머메이드 다이어그램 생성 중...",
 		"render_error": "다이어그램을 렌더링할 수 없음",
+		"fixing_syntax": "머메이드 구문 수정 중...",
+		"fix_syntax_button": "AI로 구문 수정하기",
+		"original_code": "원본 코드:",
+		"errors": {
+			"unknown_syntax": "알 수 없는 구문 오류",
+			"fix_timeout": "LLM 수정 요청 시간 초과",
+			"fix_failed": "LLM 수정 실패",
+			"fix_attempts": "{{attempts}}번의 시도 후 구문 수정 실패. 마지막 오류: {{error}}",
+			"no_fix_provided": "LLM이 수정을 제공하지 못함",
+			"fix_request_failed": "수정 요청 실패"
+		},
 		"buttons": {
 			"zoom": "줌",
 			"zoomIn": "확대",

+ 11 - 0
webview-ui/src/i18n/locales/nl/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Mermaid-diagram genereren...",
 		"render_error": "Kan diagram niet weergeven",
+		"fixing_syntax": "Mermaid-syntax corrigeren...",
+		"fix_syntax_button": "Syntax corrigeren met AI",
+		"original_code": "Originele code:",
+		"errors": {
+			"unknown_syntax": "Onbekende syntaxfout",
+			"fix_timeout": "LLM-correctieverzoek is verlopen",
+			"fix_failed": "LLM-correctie mislukt",
+			"fix_attempts": "Syntax kon niet worden gecorrigeerd na {{attempts}} pogingen. Laatste fout: {{error}}",
+			"no_fix_provided": "LLM kon geen correctie leveren",
+			"fix_request_failed": "Correctieverzoek mislukt"
+		},
 		"buttons": {
 			"zoom": "Zoom",
 			"zoomIn": "Inzoomen",

+ 11 - 0
webview-ui/src/i18n/locales/pl/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Generowanie diagramu mermaid...",
 		"render_error": "Nie można renderować diagramu",
+		"fixing_syntax": "Naprawianie składni Mermaid...",
+		"fix_syntax_button": "Napraw składnię za pomocą AI",
+		"original_code": "Oryginalny kod:",
+		"errors": {
+			"unknown_syntax": "Nieznany błąd składni",
+			"fix_timeout": "Upłynął limit czasu żądania naprawy LLM",
+			"fix_failed": "Naprawa LLM nie powiodła się",
+			"fix_attempts": "Nie udało się naprawić składni po {{attempts}} próbach. Ostatni błąd: {{error}}",
+			"no_fix_provided": "LLM nie dostarczył naprawy",
+			"fix_request_failed": "Żądanie naprawy nie powiodło się"
+		},
 		"buttons": {
 			"zoom": "Powiększenie",
 			"zoomIn": "Powiększ",

+ 11 - 0
webview-ui/src/i18n/locales/pt-BR/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Gerando diagrama mermaid...",
 		"render_error": "Não foi possível renderizar o diagrama",
+		"fixing_syntax": "Corrigindo sintaxe do Mermaid...",
+		"fix_syntax_button": "Corrigir sintaxe com IA",
+		"original_code": "Código original:",
+		"errors": {
+			"unknown_syntax": "Erro de sintaxe desconhecido",
+			"fix_timeout": "Tempo limite da solicitação de correção LLM esgotado",
+			"fix_failed": "Falha na correção LLM",
+			"fix_attempts": "Falha ao corrigir sintaxe após {{attempts}} tentativas. Último erro: {{error}}",
+			"no_fix_provided": "LLM falhou em fornecer uma correção",
+			"fix_request_failed": "Falha na solicitação de correção"
+		},
 		"buttons": {
 			"zoom": "Zoom",
 			"zoomIn": "Ampliar",

+ 11 - 0
webview-ui/src/i18n/locales/ru/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Создание диаграммы mermaid...",
 		"render_error": "Не удалось отобразить диаграмму",
+		"fixing_syntax": "Исправление синтаксиса Mermaid...",
+		"fix_syntax_button": "Исправить синтаксис с помощью ИИ",
+		"original_code": "Исходный код:",
+		"errors": {
+			"unknown_syntax": "Неизвестная ошибка синтаксиса",
+			"fix_timeout": "Истекло время ожидания запроса исправления LLM",
+			"fix_failed": "Не удалось исправить с помощью LLM",
+			"fix_attempts": "Не удалось исправить синтаксис после {{attempts}} попыток. Последняя ошибка: {{error}}",
+			"no_fix_provided": "LLM не смог предоставить исправление",
+			"fix_request_failed": "Запрос на исправление не выполнен"
+		},
 		"buttons": {
 			"zoom": "Масштаб",
 			"zoomIn": "Увеличить",

+ 11 - 0
webview-ui/src/i18n/locales/sv/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Skapar mermaid-diagram...",
 		"render_error": "Kunde inte rendera diagram",
+		"fixing_syntax": "Fixar Mermaid-syntax...",
+		"fix_syntax_button": "Fixa syntax med AI",
+		"original_code": "Ursprunglig kod:",
+		"errors": {
+			"unknown_syntax": "Okänt syntaxfel",
+			"fix_timeout": "LLM-fixförfrågan tog för lång tid",
+			"fix_failed": "LLM-fix misslyckades",
+			"fix_attempts": "Misslyckades med att fixa syntax efter {{attempts}} försök. Senaste fel: {{error}}",
+			"no_fix_provided": "LLM kunde inte tillhandahålla en fix",
+			"fix_request_failed": "Fixförfrågan misslyckades"
+		},
 		"buttons": {
 			"zoom": "Zoom",
 			"zoomIn": "Zooma in",

+ 11 - 0
webview-ui/src/i18n/locales/th/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "กำลังสร้างไดอะแกรม mermaid...",
 		"render_error": "ไม่สามารถแสดงไดอะแกรม",
+		"fixing_syntax": "กำลังแก้ไขไวยากรณ์ Mermaid...",
+		"fix_syntax_button": "แก้ไขไวยากรณ์ด้วย AI",
+		"original_code": "โค้ดต้นฉบับ:",
+		"errors": {
+			"unknown_syntax": "ข้อผิดพลาดทางไวยากรณ์ที่ไม่รู้จัก",
+			"fix_timeout": "คำขอแก้ไข LLM หมดเวลา",
+			"fix_failed": "การแก้ไข LLM ล้มเหลว",
+			"fix_attempts": "ไม่สามารถแก้ไขไวยากรณ์หลังจากพยายาม {{attempts}} ครั้ง ข้อผิดพลาดล่าสุด: {{error}}",
+			"no_fix_provided": "LLM ไม่สามารถให้การแก้ไขได้",
+			"fix_request_failed": "คำขอแก้ไขล้มเหลว"
+		},
 		"buttons": {
 			"zoom": "ซูม",
 			"zoomIn": "ซูมเข้า",

+ 11 - 0
webview-ui/src/i18n/locales/tr/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Mermaid diyagramı oluşturuluyor...",
 		"render_error": "Diyagram render edilemiyor",
+		"fixing_syntax": "Mermaid sözdizimi düzeltiliyor...",
+		"fix_syntax_button": "Sözdizimini AI ile düzelt",
+		"original_code": "Orijinal kod:",
+		"errors": {
+			"unknown_syntax": "Bilinmeyen sözdizimi hatası",
+			"fix_timeout": "LLM düzeltme isteği zaman aşımına uğradı",
+			"fix_failed": "LLM düzeltme başarısız oldu",
+			"fix_attempts": "{{attempts}} denemeden sonra sözdizimi düzeltilemedi. Son hata: {{error}}",
+			"no_fix_provided": "LLM düzeltme sağlayamadı",
+			"fix_request_failed": "Düzeltme isteği başarısız oldu"
+		},
 		"buttons": {
 			"zoom": "Yakınlaştır",
 			"zoomIn": "Büyüt",

+ 11 - 0
webview-ui/src/i18n/locales/uk/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Створення діаграми mermaid...",
 		"render_error": "Не вдалося відобразити діаграму",
+		"fixing_syntax": "Виправлення синтаксису Mermaid...",
+		"fix_syntax_button": "Виправити синтаксис за допомогою AI",
+		"original_code": "Оригінальний код:",
+		"errors": {
+			"unknown_syntax": "Невідома синтаксична помилка",
+			"fix_timeout": "Час очікування запиту на виправлення LLM вичерпано",
+			"fix_failed": "Виправлення LLM не вдалося",
+			"fix_attempts": "Не вдалося виправити синтаксис після {{attempts}} спроб. Остання помилка: {{error}}",
+			"no_fix_provided": "LLM не надав виправлення",
+			"fix_request_failed": "Запит на виправлення не вдався"
+		},
 		"buttons": {
 			"zoom": "Масштаб",
 			"zoomIn": "Збільшити",

+ 11 - 0
webview-ui/src/i18n/locales/vi/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "Đang tạo biểu đồ mermaid...",
 		"render_error": "Không thể hiển thị biểu đồ",
+		"fixing_syntax": "Đang sửa cú pháp Mermaid...",
+		"fix_syntax_button": "Sửa cú pháp bằng AI",
+		"original_code": "Mã gốc:",
+		"errors": {
+			"unknown_syntax": "Lỗi cú pháp không xác định",
+			"fix_timeout": "Yêu cầu sửa LLM đã hết thời gian",
+			"fix_failed": "Sửa LLM thất bại",
+			"fix_attempts": "Không thể sửa cú pháp sau {{attempts}} lần thử. Lỗi cuối cùng: {{error}}",
+			"no_fix_provided": "LLM không cung cấp được bản sửa",
+			"fix_request_failed": "Yêu cầu sửa thất bại"
+		},
 		"buttons": {
 			"zoom": "Thu phóng",
 			"zoomIn": "Phóng to",

+ 11 - 0
webview-ui/src/i18n/locales/zh-CN/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "生成 Mermaid 图表中...",
 		"render_error": "无法渲染图表",
+		"fixing_syntax": "修复 Mermaid 语法中...",
+		"fix_syntax_button": "使用 AI 修复语法",
+		"original_code": "原始代码:",
+		"errors": {
+			"unknown_syntax": "未知语法错误",
+			"fix_timeout": "AI 修复请求超时",
+			"fix_failed": "AI 修复失败",
+			"fix_attempts": "在 {{attempts}} 次尝试后修复语法失败。最后错误:{{error}}",
+			"no_fix_provided": "AI 未能提供修复",
+			"fix_request_failed": "修复请求失败"
+		},
 		"buttons": {
 			"zoom": "缩放",
 			"zoomIn": "放大",

+ 11 - 0
webview-ui/src/i18n/locales/zh-TW/common.json

@@ -25,6 +25,17 @@
 	"mermaid": {
 		"loading": "產生 Mermaid 圖表中...",
 		"render_error": "無法渲染圖表",
+		"fixing_syntax": "修復 Mermaid 語法中...",
+		"fix_syntax_button": "使用 AI 修復語法",
+		"original_code": "原始程式碼:",
+		"errors": {
+			"unknown_syntax": "未知的語法錯誤",
+			"fix_timeout": "LLM 修復請求超時",
+			"fix_failed": "LLM 修復失敗",
+			"fix_attempts": "嘗試 {{attempts}} 次後無法修復語法。最後錯誤:{{error}}",
+			"no_fix_provided": "LLM 未能提供修復方案",
+			"fix_request_failed": "修復請求失敗"
+		},
 		"buttons": {
 			"zoom": "縮放",
 			"zoomIn": "放大",

+ 228 - 0
webview-ui/src/services/__tests__/mermaidSyntaxFixer.spec.ts

@@ -0,0 +1,228 @@
+import { MermaidSyntaxFixer } from "../mermaidSyntaxFixer"
+import { vi, beforeEach, afterEach } from "vitest"
+
+// Mock the mermaid library
+vi.mock("mermaid", () => ({
+	default: {
+		parse: vi.fn(),
+	},
+}))
+
+// Mock i18next
+vi.mock("i18next", () => ({
+	default: {
+		t: (key: string, options?: any) => {
+			// Return a simple translation based on the key
+			if (key === "common:mermaid.errors.unknown_syntax") return "Unknown syntax error"
+			if (key === "common:mermaid.errors.fix_timeout") return "Fix timeout"
+			if (key === "common:mermaid.errors.fix_request_failed") return "Fix request failed"
+			if (key === "common:mermaid.errors.fix_attempts")
+				return `Failed to fix syntax after ${options?.attempts} attempts: ${options?.error}`
+			if (key === "common:mermaid.errors.no_fix_provided") return "LLM failed to provide a fix"
+			return key
+		},
+	},
+}))
+
+describe("MermaidSyntaxFixer", () => {
+	describe("applyDeterministicFixes", () => {
+		it("should replace --&gt; with -->", () => {
+			const input = "A --&gt; B"
+			const expected = "A --> B"
+			const result = MermaidSyntaxFixer.applyDeterministicFixes(input)
+			expect(result).toBe(expected)
+		})
+
+		it("should replace multiple instances of --&gt; with -->", () => {
+			const input = "A --&gt; B\nB --&gt; C\nC --&gt; D"
+			const expected = "A --> B\nB --> C\nC --> D"
+			const result = MermaidSyntaxFixer.applyDeterministicFixes(input)
+			expect(result).toBe(expected)
+		})
+
+		it("should handle complex mermaid diagrams with --&gt; errors", () => {
+			const input = `graph TD
+    A[Start] --&gt; B{Decision}
+    B --&gt; C[Option 1]
+    B --&gt; D[Option 2]
+    C --&gt; E[End]
+    D --&gt; E`
+			const expected = `graph TD
+    A[Start] --> B{Decision}
+    B --> C[Option 1]
+    B --> D[Option 2]
+    C --> E[End]
+    D --> E`
+			const result = MermaidSyntaxFixer.applyDeterministicFixes(input)
+			expect(result).toBe(expected)
+		})
+
+		it("should not modify code that does not contain --&gt;", () => {
+			const input = "A --> B\nB --> C"
+			const result = MermaidSyntaxFixer.applyDeterministicFixes(input)
+			expect(result).toBe(input)
+		})
+
+		it("should handle empty string", () => {
+			const input = ""
+			const result = MermaidSyntaxFixer.applyDeterministicFixes(input)
+			expect(result).toBe("")
+		})
+
+		it("should handle string with only --&gt;", () => {
+			const input = "--&gt;"
+			const expected = "-->"
+			const result = MermaidSyntaxFixer.applyDeterministicFixes(input)
+			expect(result).toBe(expected)
+		})
+
+		it("should preserve other HTML entities that are not --&gt;", () => {
+			const input = "A --&gt; B &amp; C &lt; D"
+			const expected = "A --> B &amp; C &lt; D"
+			const result = MermaidSyntaxFixer.applyDeterministicFixes(input)
+			expect(result).toBe(expected)
+		})
+
+		it("should handle mixed content with --&gt; in different contexts", () => {
+			const input = `flowchart LR
+    A[User Input] --&gt; B[Process]
+    B --&gt; C{Valid?}
+    C --&gt;|Yes| D[Success]
+    C --&gt;|No| E[Error]`
+			const expected = `flowchart LR
+    A[User Input] --> B[Process]
+    B --> C{Valid?}
+    C -->|Yes| D[Success]
+    C -->|No| E[Error]`
+			const result = MermaidSyntaxFixer.applyDeterministicFixes(input)
+			expect(result).toBe(expected)
+		})
+
+		it("should handle --&gt; at the beginning and end of lines", () => {
+			const input = "--&gt; start\nmiddle --&gt; middle\nend --&gt;"
+			const expected = "--> start\nmiddle --> middle\nend -->"
+			const result = MermaidSyntaxFixer.applyDeterministicFixes(input)
+			expect(result).toBe(expected)
+		})
+
+		it("should handle --&gt; with surrounding whitespace", () => {
+			const input = "A   --&gt;   B"
+			const expected = "A   -->   B"
+			const result = MermaidSyntaxFixer.applyDeterministicFixes(input)
+			expect(result).toBe(expected)
+		})
+
+		describe("autoFixSyntax", () => {
+			let validateSyntaxSpy: any
+			let requestLLMFixSpy: any
+			beforeEach(() => {
+				validateSyntaxSpy = vi.spyOn(MermaidSyntaxFixer, "validateSyntax")
+				requestLLMFixSpy = vi.spyOn(MermaidSyntaxFixer as any, "requestLLMFix")
+			})
+
+			afterEach(() => {
+				vi.restoreAllMocks()
+			})
+
+			it("should return success when deterministic fixes are sufficient", async () => {
+				// Mock successful validation after deterministic fixes
+				validateSyntaxSpy.mockResolvedValue({ isValid: true })
+
+				const result = await MermaidSyntaxFixer.autoFixSyntax("A --&gt; B")
+
+				expect(result.success).toBe(true)
+				expect(result.fixedCode).toBe("A --> B")
+				expect(result.attempts).toBe(0)
+				// requestLLMFix should NOT be called when validation passes after deterministic fixes
+				expect(requestLLMFixSpy).not.toHaveBeenCalled()
+			})
+
+			it("should return success and fixed code when LLM validation succeeds", async () => {
+				const applyDeterministicFixesSpy = vi.spyOn(MermaidSyntaxFixer, "applyDeterministicFixes")
+				applyDeterministicFixesSpy.mockReturnValueOnce("original code") // First call
+				applyDeterministicFixesSpy.mockReturnValueOnce("deterministically fixed code") // Second call after LLM fix
+
+				validateSyntaxSpy.mockResolvedValueOnce({ isValid: false, error: "error" }) // First validation fails
+				validateSyntaxSpy.mockResolvedValueOnce({ isValid: true }) // Second validation succeeds
+				requestLLMFixSpy.mockResolvedValue({ fixedCode: "fixed code" })
+
+				const result = await MermaidSyntaxFixer.autoFixSyntax("original code")
+
+				expect(result.success).toBe(true)
+				expect(result.fixedCode).toBe("deterministically fixed code")
+				expect(result.attempts).toBe(1)
+				expect(applyDeterministicFixesSpy).toHaveBeenCalledWith("fixed code")
+			})
+
+			it("should return the best attempt even when fix is not successful", async () => {
+				// Mock failed validation for initial and both LLM attempts
+				validateSyntaxSpy.mockResolvedValueOnce({ isValid: false, error: "initial error" })
+				validateSyntaxSpy.mockResolvedValueOnce({ isValid: false, error: "error 1" })
+				validateSyntaxSpy.mockResolvedValueOnce({ isValid: false, error: "error 2" })
+
+				// Mock LLM fix attempts
+				requestLLMFixSpy.mockResolvedValueOnce({ fixedCode: "first attempt" })
+				requestLLMFixSpy.mockResolvedValueOnce({ fixedCode: "second attempt" })
+
+				// Mock applyDeterministicFixes
+				const applyDeterministicFixesSpy = vi.spyOn(MermaidSyntaxFixer, "applyDeterministicFixes")
+				applyDeterministicFixesSpy.mockReturnValueOnce("original code") // Initial deterministic fix
+				applyDeterministicFixesSpy.mockReturnValueOnce("deterministically fixed first attempt")
+				applyDeterministicFixesSpy.mockReturnValueOnce("deterministically fixed second attempt")
+
+				const result = await MermaidSyntaxFixer.autoFixSyntax("original code")
+
+				expect(result.success).toBe(false)
+				expect(result.fixedCode).toBe("deterministically fixed second attempt") // Should return the deterministically fixed last attempt
+				expect(result.attempts).toBe(2)
+				expect(result.error).toContain("Failed to fix syntax after 2 attempts")
+
+				expect(applyDeterministicFixesSpy).toHaveBeenCalledTimes(3) // Initial + 2 LLM attempts
+				expect(applyDeterministicFixesSpy).toHaveBeenNthCalledWith(2, "first attempt")
+				expect(applyDeterministicFixesSpy).toHaveBeenNthCalledWith(3, "second attempt")
+			})
+
+			it("should return the best attempt when LLM request fails", async () => {
+				// Mock failed initial validation
+				validateSyntaxSpy.mockResolvedValueOnce({ isValid: false, error: "initial error" })
+
+				// Mock successful first attempt but failed second attempt
+				requestLLMFixSpy.mockResolvedValueOnce({ fixedCode: "first attempt" })
+				requestLLMFixSpy.mockResolvedValueOnce({ requestError: "LLM request failed" })
+
+				// Mock failed validation for first attempt
+				validateSyntaxSpy.mockResolvedValueOnce({ isValid: false, error: "still invalid" })
+
+				// Mock applyDeterministicFixes
+				const applyDeterministicFixesSpy = vi.spyOn(MermaidSyntaxFixer, "applyDeterministicFixes")
+				applyDeterministicFixesSpy.mockReturnValueOnce("original code") // Initial
+				applyDeterministicFixesSpy.mockReturnValueOnce("deterministically fixed first attempt")
+
+				const result = await MermaidSyntaxFixer.autoFixSyntax("original code")
+
+				expect(result.success).toBe(false)
+				expect(result.fixedCode).toBe("deterministically fixed first attempt") // Should return the deterministically fixed best attempt
+				expect(result.error).toContain("LLM request failed")
+				expect(applyDeterministicFixesSpy).toHaveBeenNthCalledWith(2, "first attempt")
+			})
+
+			it("should return the original code when LLM fails to provide a fix", async () => {
+				// Mock failed initial validation
+				validateSyntaxSpy.mockResolvedValueOnce({ isValid: false, error: "error" })
+
+				// Mock LLM returning null (no fix provided)
+				requestLLMFixSpy.mockResolvedValueOnce({ fixedCode: "" })
+
+				// Mock applyDeterministicFixes to return the original code
+				const applyDeterministicFixesSpy = vi.spyOn(MermaidSyntaxFixer, "applyDeterministicFixes")
+				applyDeterministicFixesSpy.mockReturnValue("original code")
+
+				const result = await MermaidSyntaxFixer.autoFixSyntax("original code")
+
+				expect(result.success).toBe(false)
+				expect(result.fixedCode).toBe("original code") // Should return the original code after deterministic fixes
+				expect(result.error).toBe("LLM failed to provide a fix")
+			})
+		})
+	})
+})

+ 155 - 0
webview-ui/src/services/mermaidSyntaxFixer.ts

@@ -0,0 +1,155 @@
+// kilocode_change file added
+import { vscode } from "@src/utils/vscode"
+import i18next from "i18next"
+
+export interface MermaidFixResult {
+	success: boolean
+	fixedCode?: string
+	error?: string
+	attempts?: number
+}
+
+export interface MermaidValidationResult {
+	isValid: boolean
+	error?: string
+}
+
+/**
+ * Service for validating and fixing Mermaid syntax using LLM assistance
+ */
+export class MermaidSyntaxFixer {
+	private static readonly MAX_FIX_ATTEMPTS = 2
+	private static readonly FIX_TIMEOUT = 30000 // 30 seconds
+
+	/**
+	 * Applies deterministic fixes for common LLM errors before validation
+	 */
+	static applyDeterministicFixes(code: string): string {
+		// Fix HTML entity encoding: --&gt; should be -->;
+		// surprisingly, this does most of the heavy lifting in the MermaidSyntaxFixer
+		// sometimes the llm prepends ```mermaid, remove that
+		return code.replace(/--&gt;/g, "-->").replace(/```mermaid/, "")
+	}
+
+	/**
+	 * Validates Mermaid syntax using the mermaid library
+	 */
+	static async validateSyntax(code: string): Promise<MermaidValidationResult> {
+		try {
+			const mermaid = (await import("mermaid")).default
+			await mermaid.parse(code)
+			return { isValid: true }
+		} catch (error) {
+			return {
+				isValid: false,
+				error: error instanceof Error ? error.message : i18next.t("common:mermaid.errors.unknown_syntax"),
+			}
+		}
+	}
+
+	/**
+	 * Requests the LLM to fix the Mermaid syntax via the extension
+	 */
+	private static requestLLMFix(
+		code: string,
+		error: string,
+	): Promise<{ fixedCode: string } | { requestError: string }> {
+		return new Promise((resolve, _reject) => {
+			const requestId = `mermaid-fix-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`
+
+			const timeout = setTimeout(() => {
+				cleanup()
+				resolve({ requestError: i18next.t("common:mermaid.errors.fix_timeout") })
+			}, this.FIX_TIMEOUT)
+
+			const messageListener = (event: MessageEvent) => {
+				const message = event.data
+				if (message.type === "mermaidFixResponse" && message.requestId === requestId) {
+					cleanup()
+
+					if (message.success) {
+						resolve({ fixedCode: message.fixedCode })
+					} else {
+						resolve({
+							requestError: message.error || i18next.t("common:mermaid.errors.fix_request_failed"),
+						})
+					}
+				}
+			}
+
+			const cleanup = () => {
+				clearTimeout(timeout)
+				window.removeEventListener("message", messageListener)
+			}
+
+			window.addEventListener("message", messageListener)
+
+			vscode.postMessage({
+				type: "fixMermaidSyntax",
+				requestId,
+				text: code,
+				values: { error },
+			})
+		})
+	}
+
+	/**
+	 * Attempts to fix Mermaid syntax with automatic retry and fallback
+	 * Always returns the best attempt at fixing the code, even if not completely successful
+	 */
+	static async autoFixSyntax(code: string): Promise<MermaidFixResult> {
+		let currentCode = code
+		let llmAttempts = 0
+		let finalError: string | undefined
+
+		while (true) {
+			console.info("attempt ", llmAttempts)
+			currentCode = this.applyDeterministicFixes(currentCode)
+
+			// Validate the current code
+			const validation = await this.validateSyntax(currentCode)
+			if (validation.isValid) {
+				return {
+					success: true,
+					fixedCode: currentCode,
+					attempts: llmAttempts,
+				}
+			}
+
+			const lastError = validation.error || i18next.t("common:mermaid.errors.unknown_syntax")
+
+			// break in the middle so we start and finish the loop with a deterministic fix
+			if (llmAttempts >= this.MAX_FIX_ATTEMPTS) {
+				finalError = i18next.t("common:mermaid.errors.fix_attempts", {
+					attempts: this.MAX_FIX_ATTEMPTS,
+					error: lastError,
+				})
+				break
+			}
+
+			llmAttempts++
+			const result = await this.requestLLMFix(currentCode, lastError)
+
+			if ("requestError" in result) {
+				finalError = result.requestError
+				break
+			} else if (!result.fixedCode) {
+				finalError = i18next.t("common:mermaid.errors.no_fix_provided")
+				break
+			} else {
+				currentCode = result.fixedCode
+			}
+		}
+
+		if (finalError) {
+			console.info(finalError)
+		}
+
+		return {
+			success: false,
+			fixedCode: currentCode,
+			error: finalError,
+			attempts: llmAttempts,
+		}
+	}
+}