diff --git a/src/components/MermaidDiagram.tsx b/src/components/MermaidDiagram.tsx new file mode 100644 index 0000000000..dded3f48de --- /dev/null +++ b/src/components/MermaidDiagram.tsx @@ -0,0 +1,135 @@ +import { useEffect, useRef, useState } from 'react'; +import mermaid from 'mermaid'; +import { AlertTriangle, Maximize2, X } from 'lucide-react'; + +// Initialize mermaid with dark theme +mermaid.initialize({ + startOnLoad: false, + theme: 'dark', + themeVariables: { + primaryColor: '#06b6d4', + primaryTextColor: '#e4e4ed', + primaryBorderColor: '#1e1e2a', + lineColor: '#3b3b54', + secondaryColor: '#1e1e2a', + tertiaryColor: '#0a0a10', + background: '#0a0a10', + mainBkg: '#0f0f18', + nodeBorder: '#3b3b54', + clusterBkg: '#1e1e2a', + titleColor: '#e4e4ed', + edgeLabelBackground: '#0f0f18', + nodeTextColor: '#e4e4ed', + }, + flowchart: { + curve: 'basis', + padding: 15, + nodeSpacing: 50, + rankSpacing: 50, + }, + sequence: { + actorMargin: 50, + boxMargin: 10, + boxTextMargin: 5, + noteMargin: 10, + messageMargin: 35, + }, + fontFamily: '"JetBrains Mono", "Fira Code", monospace', + fontSize: 13, +}); + +interface MermaidDiagramProps { + code: string; +} + +export const MermaidDiagram = ({ code }: MermaidDiagramProps) => { + const containerRef = useRef(null); + const [error, setError] = useState(null); + const [isExpanded, setIsExpanded] = useState(false); + const [svg, setSvg] = useState(''); + + useEffect(() => { + const renderDiagram = async () => { + if (!containerRef.current) return; + + try { + // Generate unique ID for this diagram + const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + + // Render the diagram + const { svg: renderedSvg } = await mermaid.render(id, code.trim()); + setSvg(renderedSvg); + setError(null); + } catch (err) { + console.error('Mermaid render error:', err); + setError(err instanceof Error ? err.message : 'Failed to render diagram'); + setSvg(''); + } + }; + + renderDiagram(); + }, [code]); + + if (error) { + return ( +
+
+ + Diagram Error +
+
{error}
+
+ + Show source + +
+            {code}
+          
+
+
+ ); + } + + return ( + <> + {/* Inline diagram with expand button */} +
+ {/* Expand button - top right */} + + + {/* Diagram container */} +
+
+ + {/* Fullscreen modal */} + {isExpanded && ( +
+ {/* Close button */} + + + {/* Expanded diagram */} +
+
+
+
+ )} + + ); +}; + diff --git a/src/components/MermaidRenderer.tsx b/src/components/MermaidRenderer.tsx index 355ecef6b1..e69de29bb2 100644 --- a/src/components/MermaidRenderer.tsx +++ b/src/components/MermaidRenderer.tsx @@ -1,263 +0,0 @@ -import { useEffect, useRef, useState, useCallback } from 'react'; -import mermaid from 'mermaid'; -import { AlertTriangle, Maximize2, X, RefreshCw } from 'lucide-react'; - -const cleanupGlobalMermaidErrors = () => { - if (typeof document === 'undefined') return; - // Mermaid may inject error blocks into the document on parse/render failure. - // Remove them so they don't blow up the page height / scrolling. - const selectors = [ - '.mermaid-error', - '.mermaidError', - '.mermaid-error-container', - '.mermaidTooltip', - ]; - try { - document.querySelectorAll(selectors.join(',')).forEach((el) => el.remove()); - } catch { - // no-op - } -}; - -// Initialize mermaid with dark theme -mermaid.initialize({ - startOnLoad: false, - theme: 'dark', - themeVariables: { - primaryColor: '#06b6d4', - primaryTextColor: '#e4e4ed', - primaryBorderColor: '#1e1e2a', - lineColor: '#3b3b54', - secondaryColor: '#1e1e2a', - tertiaryColor: '#0a0a10', - background: '#0a0a10', - mainBkg: '#0f0f18', - nodeBorder: '#3b3b54', - clusterBkg: '#1e1e2a', - titleColor: '#e4e4ed', - edgeLabelBackground: '#0f0f18', - nodeTextColor: '#e4e4ed', - }, - flowchart: { - curve: 'basis', - padding: 15, - nodeSpacing: 50, - rankSpacing: 50, - }, - sequence: { - actorMargin: 50, - boxMargin: 10, - boxTextMargin: 5, - noteMargin: 10, - messageMargin: 35, - }, - fontFamily: '"JetBrains Mono", "Fira Code", monospace', - fontSize: 13, - // Security: strict mode - securityLevel: 'strict', - // Avoid Mermaid injecting its own error UI into the document body - // (prevents the whole app becoming scrollable on syntax errors). - suppressErrorRendering: true as any, -}); -// Also override parseError hook (if supported by the installed Mermaid build) -// to ensure errors stay inside our component UI. -try { - (mermaid as any).parseError = () => { - // swallow; component handles errors explicitly - }; -} catch { - // ignore -} - -interface MermaidRendererProps { - code: string; - onError?: (error: string) => void; - className?: string; -} - -export const MermaidRenderer = ({ code, onError, className }: MermaidRendererProps) => { - const [error, setError] = useState(null); - const [svg, setSvg] = useState(''); - const [isExpanded, setIsExpanded] = useState(false); - const [isRendering, setIsRendering] = useState(true); // Start as rendering - const hasReportedError = useRef(false); - - useEffect(() => { - if (!code.trim()) { - setIsRendering(false); - return; - } - - let cancelled = false; - - const renderDiagram = async () => { - cleanupGlobalMermaidErrors(); - setIsRendering(true); - setError(null); - - try { - // Validate syntax first. This avoids Mermaid's global error renderer side-effects. - // `parse` throws on syntax errors in newer Mermaid versions. - if ((mermaid as any).parse) { - await (mermaid as any).parse(code.trim()); - } - - // Generate unique ID for this diagram - const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; - - // Attempt to render - const { svg: renderedSvg } = await mermaid.render(id, code.trim()); - - if (!cancelled) { - setSvg(renderedSvg); - setError(null); - hasReportedError.current = false; - } - } catch (err) { - if (!cancelled) { - console.error('Mermaid render error:', err); - const errorMessage = err instanceof Error ? err.message : 'Failed to render diagram'; - setError(errorMessage); - setSvg(''); // Clear invalid SVG - - // Propagate error to parent if callback provided (only once per code) - if (onError && !hasReportedError.current) { - hasReportedError.current = true; - onError(errorMessage); - } - } - } finally { - cleanupGlobalMermaidErrors(); - if (!cancelled) { - setIsRendering(false); - } - } - }; - - renderDiagram(); - - return () => { - cancelled = true; - }; - }, [code, onError]); // Re-render when code changes - - const retryRender = useCallback(() => { - hasReportedError.current = false; - // Trigger re-render by toggling a state - setError(null); - setSvg(''); - setIsRendering(true); - - const renderDiagram = async () => { - cleanupGlobalMermaidErrors(); - try { - if ((mermaid as any).parse) { - await (mermaid as any).parse(code.trim()); - } - const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; - const { svg: renderedSvg } = await mermaid.render(id, code.trim()); - setSvg(renderedSvg); - setError(null); - } catch (err) { - console.error('Mermaid render error:', err); - const errorMessage = err instanceof Error ? err.message : 'Failed to render diagram'; - setError(errorMessage); - setSvg(''); - } finally { - cleanupGlobalMermaidErrors(); - setIsRendering(false); - } - }; - - renderDiagram(); - }, [code]); - - if (error) { - return ( -
-
-
- - Diagram Syntax Error -
- -
-
-          {error}
-        
-
- - Show raw source - -
-            {code}
-          
-
-
- ); - } - - if (!svg && isRendering) { - return ( -
- - Rendering diagram... -
- ); - } - - if (!svg) return null; - - return ( - <> - {/* Inline diagram with expand button */} -
- {/* Toolbar - top right */} -
- -
- - {/* Diagram container */} -
-
- - {/* Fullscreen modal */} - {isExpanded && ( -
- {/* Close button */} - - - {/* Expanded diagram */} -
-
-
-
- )} - - ); -}; - diff --git a/src/components/RightPanel.tsx b/src/components/RightPanel.tsx index dadb65c454..43c7b6e964 100644 --- a/src/components/RightPanel.tsx +++ b/src/components/RightPanel.tsx @@ -8,7 +8,7 @@ import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import ReactMarkdown from 'react-markdown'; import { useAppState } from '../hooks/useAppState'; import { ToolCallCard } from './ToolCallCard'; -import { MermaidRenderer } from './MermaidRenderer'; +import { MermaidDiagram } from './MermaidDiagram'; import { isProviderConfigured } from '../core/llm/settings-service'; // Custom syntax theme @@ -51,27 +51,6 @@ export const RightPanel = () => { const [chatInput, setChatInput] = useState(''); const textareaRef = useRef(null); - // Track processed mermaid errors to prevent infinite loops or duplicate correction requests - const processedMermaidErrors = useRef>(new Set()); - - // Handle auto-correction of mermaid syntax errors - const handleMermaidFix = useCallback((error: string, code: string) => { - // Generate a simple hash of the code to track it - const hash = code.split('').reduce((a, b) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a }, 0).toString(); - - // If we've already processed this exact error for this exact code, skip - if (processedMermaidErrors.current.has(hash)) return; - - // Add to processed set - processedMermaidErrors.current.add(hash); - - // Send feedback to the agent - // We send this as a system alert message that the user "sees" but is clearly marked - const feedbackMessage = `System Alert: The Mermaid diagram you generated contains a syntax error:\n${error}\n\nPlease fix the syntax and regenerate the diagram.`; - - sendChatMessage(feedbackMessage); - }, [sendChatMessage]); - const resolveFilePathForUI = useCallback((requestedPath: string): string | null => { const req = requestedPath.replace(/\\/g, '/').replace(/^\.?\//, '').toLowerCase(); if (!req) return null; @@ -420,17 +399,6 @@ export const RightPanel = () => { } const language = match ? match[1] : 'text'; - - // Render mermaid diagrams - if (language === 'mermaid') { - return ( - handleMermaidFix(err, codeContent)} - /> - ); - } - return ( { // Render mermaid diagrams if (language === 'mermaid') { - return ( - handleMermaidFix(err, codeContent)} - /> - ); + return ; } return ( @@ -621,3 +584,6 @@ export const RightPanel = () => { ); }; + + + diff --git a/src/core/llm/agent.ts b/src/core/llm/agent.ts index 3353c9bd84..b8e385917a 100644 --- a/src/core/llm/agent.ts +++ b/src/core/llm/agent.ts @@ -43,7 +43,7 @@ You are not a one-shot query engine. You are an investigator. * *Did it fail?* -> Correct the query and retry. 4. **Visualize:** Use \`highlight_in_graph\` continuously as you find relevant nodes. 5. **Ground:** Construct your final answer with \`[[file:line]]\` citations. -6. **Compleatness check** If your research didnt find anything else worth checking and you are absolutely sure your answer is complete, stop else continue researching with tools. + ### 🛠️ TOOL STRATEGY - **Discovery:** Start with \`hybrid_search\` or \`semantic_search\` to find entry points. - **Structure:** Use \`execute_cypher\` to trace relationships (e.g., "What calls this?", "What does this inherit from?"). @@ -72,7 +72,6 @@ All nodes are in table \`CodeNode\`. All edges are in table \`CodeRelation\`. - **Iterative Depth:** Do not stop at the surface. If Function A calls Function B, **read Function B**. Trace the logic all the way to the source. - **Completeness:** Do not answer "I assume..." or "It likely does...". Keep calling tools until you **know**. - **Error Recovery:** If a tool fails, analyze the error, fix the input, and **retry**. Never give up after one error. -- **UI Feedback:** If the user (System Alert) reports a syntax error in your Mermaid diagram, **immediately fix the syntax** and regenerate the code block. **REMINDER:** Your unique value is the visual graph. If you talk about a node, **highlight it**.`; @@ -436,3 +435,4 @@ export const invokeAgent = async ( const lastMessage = result.messages[result.messages.length - 1]; return lastMessage?.content?.toString() ?? 'No response generated.'; }; +