Skip to content

Commit 9dbffb3

Browse files
committed
add a button to fix mermaid syntax errors by calling the LLM
1 parent 569b276 commit 9dbffb3

File tree

27 files changed

+752
-12
lines changed

27 files changed

+752
-12
lines changed

.changeset/empty-ghosts-carry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"kilo-code": patch
3+
---
4+
5+
add a button to fix mermaid syntax errors by calling the LLM
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Prompts for Mermaid diagram-related tasks
3+
*/
4+
5+
/**
6+
* Generate a prompt for fixing invalid Mermaid diagram syntax
7+
* @param error - The error message from Mermaid parser
8+
* @param invalidCode - The invalid Mermaid code that needs fixing
9+
* @returns The formatted prompt for the AI to fix the Mermaid syntax
10+
*/
11+
export const mermaidFixPrompt = (error: string, invalidCode: string): string => {
12+
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.
13+
14+
Error: ${error}
15+
16+
Invalid Mermaid code:
17+
\`\`\`
18+
${invalidCode}
19+
\`\`\`
20+
21+
Requirements:
22+
1. Return ONLY the corrected Mermaid syntax
23+
2. Do not include markdown code blocks or explanations
24+
3. Ensure the syntax is valid according to Mermaid specifications
25+
4. Enclose labels and edge label within double quotes even when you do not think it necessary to ensure the syntax is robust
26+
5. Do not point to multiple nodes with one edge, use multiple edges instead
27+
6. Preserve the original intent and structure as much as possible
28+
7. If the diagram type is unclear, default to a flowchart
29+
30+
Corrected Mermaid code:`
31+
}

src/core/webview/webviewMessageHandler.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { getModels, flushModels } from "../../api/providers/fetchers/modelCache"
3939
import { GetModelsOptions } from "../../shared/api"
4040
import { generateSystemPrompt } from "./generateSystemPrompt"
4141
import { getCommand } from "../../utils/commands"
42+
import { mermaidFixPrompt } from "../prompts/utilities/mermaid"
4243

4344
const ALLOWED_VSCODE_SETTINGS = new Set(["terminal.integrated.inheritEnv"])
4445

@@ -1628,6 +1629,34 @@ export const webviewMessageHandler = async (
16281629
}
16291630
break
16301631
}
1632+
case "fixMermaidSyntax":
1633+
if (message.text && message.requestId) {
1634+
try {
1635+
const { apiConfiguration } = await provider.getState()
1636+
1637+
const prompt = mermaidFixPrompt(message.values?.error || "Unknown syntax error", message.text)
1638+
1639+
const fixedCode = await singleCompletionHandler(apiConfiguration, prompt)
1640+
1641+
provider.postMessageToWebview({
1642+
type: "mermaidFixResponse",
1643+
requestId: message.requestId,
1644+
success: true,
1645+
fixedCode: fixedCode?.trim() || null,
1646+
})
1647+
} catch (error) {
1648+
const errorMessage = error instanceof Error ? error.message : "Failed to fix Mermaid syntax"
1649+
provider.log(`Error fixing Mermaid syntax: ${errorMessage}`)
1650+
1651+
provider.postMessageToWebview({
1652+
type: "mermaidFixResponse",
1653+
requestId: message.requestId,
1654+
success: false,
1655+
error: errorMessage,
1656+
})
1657+
}
1658+
}
1659+
break
16311660
case "focusPanelRequest": {
16321661
// Execute the focusPanel command to focus the WebView
16331662
await vscode.commands.executeCommand(getCommand("focusPanel"))

src/shared/ExtensionMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export interface ExtensionMessage {
9999
| "marketplaceInstallResult"
100100
| "marketplaceData"
101101
| "shareTaskSuccess"
102+
| "mermaidFixResponse"
102103
text?: string
103104
payload?: any // Add a generic payload for now, can refine later
104105
action?:
@@ -148,6 +149,7 @@ export interface ExtensionMessage {
148149
marketplaceItems?: MarketplaceItem[]
149150
marketplaceInstalledMetadata?: MarketplaceInstalledMetadata
150151
visibility?: ShareVisibility
152+
fixedCode?: string | null // For mermaidFixResponse
151153
}
152154

153155
export type ExtensionState = Pick<

src/shared/WebviewMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ export interface WebviewMessage {
163163
| "codebaseIndexConfig"
164164
| "profileThresholds"
165165
| "setHistoryPreviewCollapsed"
166+
| "fixMermaidSyntax"
167+
| "mermaidFixResponse"
166168
| "openExternal"
167169
| "filterMarketplaceItems"
168170
| "marketplaceButtonClicked"

webview-ui/src/components/common/MermaidBlock.tsx

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import { useDebounceEffect } from "@src/utils/useDebounceEffect"
55
import { vscode } from "@src/utils/vscode"
66
import { useAppTranslation } from "@src/i18n/TranslationContext"
77
import { useCopyToClipboard } from "@src/utils/clipboard"
8+
import { MermaidSyntaxFixer } from "@src/services/mermaidSyntaxFixer"
89
import CodeBlock from "./CodeBlock"
910
import { 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>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import styled from "styled-components"
2+
3+
export const MermaidFixButton = styled.button`
4+
padding: 3px;
5+
height: 24px;
6+
margin-right: 4px;
7+
color: var(--vscode-editor-foreground);
8+
display: flex;
9+
align-items: center;
10+
justify-content: center;
11+
background: transparent;
12+
border: none;
13+
cursor: pointer;
14+
15+
&:hover {
16+
opacity: 0.8;
17+
color: var(--vscode-button-foreground);
18+
background: var(--vscode-button-hoverBackground);
19+
}
20+
21+
&:disabled {
22+
opacity: 0.5;
23+
cursor: not-allowed;
24+
}
25+
26+
.codicon-loading {
27+
animation: spin 1s linear infinite;
28+
}
29+
30+
@keyframes spin {
31+
from {
32+
transform: rotate(0deg);
33+
}
34+
to {
35+
transform: rotate(360deg);
36+
}
37+
}
38+
`

webview-ui/src/i18n/locales/ca/common.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@
1717
"mermaid": {
1818
"loading": "Generant diagrama mermaid...",
1919
"render_error": "No es pot renderitzar el diagrama",
20+
"fixing_syntax": "Corregint la sintaxi de Mermaid...",
21+
"fix_syntax_button": "Corregir sintaxi amb IA",
22+
"original_code": "Codi original:",
23+
"errors": {
24+
"unknown_syntax": "Error de sintaxi desconegut",
25+
"fix_timeout": "La sol·licitud de correcció de l'IA ha esgotat el temps",
26+
"fix_failed": "La correcció de l'IA ha fallat",
27+
"fix_attempts": "No s'ha pogut corregir la sintaxi després de {{attempts}} intents. Últim error: {{error}}",
28+
"no_fix_provided": "L'IA no ha pogut proporcionar una correcció",
29+
"fix_request_failed": "La sol·licitud de correcció ha fallat"
30+
},
2031
"buttons": {
2132
"zoom": "Zoom",
2233
"zoomIn": "Ampliar",

webview-ui/src/i18n/locales/de/common.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@
1717
"mermaid": {
1818
"loading": "Mermaid-Diagramm wird generiert...",
1919
"render_error": "Diagramm kann nicht gerendert werden",
20+
"fixing_syntax": "Mermaid-Syntax wird korrigiert...",
21+
"fix_syntax_button": "Syntax mit KI korrigieren",
22+
"original_code": "Ursprünglicher Code:",
23+
"errors": {
24+
"unknown_syntax": "Unbekannter Syntaxfehler",
25+
"fix_timeout": "KI-Korrekturanfrage hat das Zeitlimit überschritten",
26+
"fix_failed": "KI-Korrektur fehlgeschlagen",
27+
"fix_attempts": "Korrektur nach {{attempts}} Versuchen fehlgeschlagen. Letzter Fehler: {{error}}",
28+
"no_fix_provided": "KI konnte keine Korrektur bereitstellen",
29+
"fix_request_failed": "Korrekturanfrage fehlgeschlagen"
30+
},
2031
"buttons": {
2132
"zoom": "Zoom",
2233
"zoomIn": "Vergrößern",

webview-ui/src/i18n/locales/en/common.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@
1717
"mermaid": {
1818
"loading": "Generating mermaid diagram...",
1919
"render_error": "Unable to Render Diagram",
20+
"fixing_syntax": "Fixing Mermaid syntax...",
21+
"fix_syntax_button": "Fix syntax with AI",
22+
"original_code": "Original code:",
23+
"errors": {
24+
"unknown_syntax": "Unknown syntax error",
25+
"fix_timeout": "LLM fix request timed out",
26+
"fix_failed": "LLM fix failed",
27+
"fix_attempts": "Failed to fix syntax after {{attempts}} attempts. Last error: {{error}}",
28+
"no_fix_provided": "LLM failed to provide a fix",
29+
"fix_request_failed": "Fix request failed"
30+
},
2031
"buttons": {
2132
"zoom": "Zoom",
2233
"zoomIn": "Zoom In",

0 commit comments

Comments
 (0)