From 2e08bda19d12f79a0e7ded8733552c14665035a3 Mon Sep 17 00:00:00 2001 From: _00_ <131402327+rgaricano@users.noreply.github.com> Date: Sat, 4 Oct 2025 14:17:04 +0200 Subject: [PATCH 1/4] UPD: Add Validators & Error Toast for Mermaid & Vega diagrams ### UPD: Feat: Add Validators & Error Toast for Mermaid & Vega diagrams Description: As many time the diagrams generated or entered have syntax errors the diagrams are not rendered due to that errors, but as there isn't any notification is difficult to know what happend. This PR add validator and toast notification when error on Mermaid and Vega/Vega-Lite diagrams, helping the user to fix its. --- .../components/chat/Messages/CodeBlock.svelte | 4 +- src/lib/utils/index.ts | 44 +++++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/lib/components/chat/Messages/CodeBlock.svelte b/src/lib/components/chat/Messages/CodeBlock.svelte index 13bc7ec2a1..d2e3a6323d 100644 --- a/src/lib/components/chat/Messages/CodeBlock.svelte +++ b/src/lib/components/chat/Messages/CodeBlock.svelte @@ -326,12 +326,12 @@ const render = async () => { onUpdate(token); if (lang === 'mermaid' && (token?.raw ?? '').slice(-4).includes('```')) { - mermaidHtml = await renderMermaidDiagram(code); + mermaidHtml = await renderMermaidDiagram(code, $i18n); } else if ( (lang === 'vega' || lang === 'vega-lite') && (token?.raw ?? '').slice(-4).includes('```') ) { - vegaHtml = await renderVegaVisualization(code); + vegaHtml = await renderVegaVisualization(code, $i18n); } }; diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index fb239e87b3..7d05c34135 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -22,6 +22,8 @@ import markedExtension from '$lib/utils/marked/extension'; import markedKatexExtension from '$lib/utils/marked/katex-extension'; import hljs from 'highlight.js'; +import { toast } from 'svelte-sonner'; + ////////////////////////// // Helper functions ////////////////////////// @@ -1579,40 +1581,56 @@ export const decodeString = (str: string) => { } }; -export const renderMermaidDiagram = async (code: string) => { +export const renderMermaidDiagram = async (code: string, i18n?: any) => { try { const { default: mermaid } = await import('mermaid'); mermaid.initialize({ - startOnLoad: true, + startOnLoad: false, // Should be false when using render API theme: document.documentElement.classList.contains('dark') ? 'dark' : 'default', securityLevel: 'loose' }); - if (await mermaid.parse(code)) { + const parseResult = await mermaid.parse(code, { suppressErrors: false }); + if (parseResult) { const { svg } = await mermaid.render(`mermaid-${uuidv4()}`, code); return svg; } + return ''; } catch (error) { - console.log('Failed to render mermaid diagram:', error); + console.error('Failed to render mermaid diagram:', error); + const errorMsg = error instanceof Error ? error.message : String(error); + toast.error(i18n.t('Failed to render diagram') + `: ${errorMsg}`); return ''; } }; -export const renderVegaVisualization = async (spec: string) => { +export const renderVegaVisualization = async (spec: string, i18n?: any) => { try { const vega = await import('vega'); const parsedSpec = JSON.parse(spec); + if (!parsedSpec.$schema) { + const errorMsg = 'Specification missing $schema property'; + toast.error(i18n.t('Failed to render diagram') + `: ${errorMsg}`); + throw new Error(errorMsg); + } let vegaSpec; - if (parsedSpec.$schema && parsedSpec.$schema.includes('vega-lite')) { - const vegaLite = await import('vega-lite'); - vegaSpec = vegaLite.compile(parsedSpec).spec; - } else { - vegaSpec = parsedSpec; - } - const view = new vega.View(vega.parse(vegaSpec), {renderer: 'none'}); + if (parsedSpec.$schema.includes('vega-lite')) { + const vegaLite = await import('vega-lite'); + vegaSpec = vegaLite.compile(parsedSpec).spec; + } else if (parsedSpec.$schema.includes('vega')) { + vegaSpec = parsedSpec; + } else { + const errorMsg = 'Unknown schema format: ' + parsedSpec.$schema; + toast.error(i18n.t('Failed to render diagram') + `: ${errorMsg}`); + throw new Error(errorMsg); + } + const runtime = vega.parse(vegaSpec); + const view = new vega.View(runtime, {renderer: 'none'}); const svg = await view.toSVG(); return svg; } catch (error) { - console.log('Failed to render Vega visualization:', error); + console.error('Failed to render Vega visualization:', error); + const errorMsg = error instanceof Error ? error.message : String(error); + toast.error(i18n.t('Failed to render diagram') + `: ${errorMsg}`); return ''; } }; From e6cc7db3c1a3788ed55b41229b0db7306000fe6c Mon Sep 17 00:00:00 2001 From: _00_ <131402327+rgaricano@users.noreply.github.com> Date: Sat, 4 Oct 2025 14:45:49 +0200 Subject: [PATCH 2/4] Fix Code Format --- src/lib/utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 7d05c34135..856f7fa028 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1624,7 +1624,7 @@ export const renderVegaVisualization = async (spec: string, i18n?: any) => { throw new Error(errorMsg); } const runtime = vega.parse(vegaSpec); - const view = new vega.View(runtime, {renderer: 'none'}); + const view = new vega.View(runtime, { renderer: 'none' }); const svg = await view.toSVG(); return svg; } catch (error) { From 8538d1b2cb21144df7f4504a9354f77dee14b7f1 Mon Sep 17 00:00:00 2001 From: _00_ <131402327+rgaricano@users.noreply.github.com> Date: Mon, 6 Oct 2025 00:09:17 +0200 Subject: [PATCH 3/4] UPD: Add Validators & Error Toast for Mermaid & Vega diagrams ### UPD: Feat: Add Validators & Error Toast for Mermaid & Vega diagrams Description: As many time the diagrams generated or entered have syntax errors the diagrams are not rendered due to that errors, but as there isn't any notification is difficult to know what happend. This PR add validator and toast notification when error on Mermaid and Vega/Vega-Lite diagrams, helping the user to fix its. Note: Another possibility of integrating this Graph Visualizer is through its svelte component: https://github.com/vega/svelte-vega/tree/main/packages/svelte-vega --- .../components/chat/Messages/CodeBlock.svelte | 18 ++++- src/lib/utils/index.ts | 68 ++++++------------- 2 files changed, 37 insertions(+), 49 deletions(-) diff --git a/src/lib/components/chat/Messages/CodeBlock.svelte b/src/lib/components/chat/Messages/CodeBlock.svelte index d2e3a6323d..7e6e72b6bb 100644 --- a/src/lib/components/chat/Messages/CodeBlock.svelte +++ b/src/lib/components/chat/Messages/CodeBlock.svelte @@ -326,12 +326,26 @@ const render = async () => { onUpdate(token); if (lang === 'mermaid' && (token?.raw ?? '').slice(-4).includes('```')) { - mermaidHtml = await renderMermaidDiagram(code, $i18n); + try { + mermaidHtml = await renderMermaidDiagram(code); + } catch (error) { + console.error('Failed to render mermaid diagram:', error); + const errorMsg = error instanceof Error ? error.message : String(error); + toast.error($i18n.t('Failed to render diagram') + `: ${errorMsg}`); + mermaidHtml = null; + } } else if ( (lang === 'vega' || lang === 'vega-lite') && (token?.raw ?? '').slice(-4).includes('```') ) { - vegaHtml = await renderVegaVisualization(code, $i18n); + try { + vegaHtml = await renderVegaVisualization(code); + } catch (error) { + console.error('Failed to render Vega visualization:', error); + const errorMsg = error instanceof Error ? error.message : String(error); + toast.error($i18n.t('Failed to render diagram') + `: ${errorMsg}`); + vegaHtml = null; + } } }; diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 856f7fa028..f1e44920a9 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1581,56 +1581,30 @@ export const decodeString = (str: string) => { } }; -export const renderMermaidDiagram = async (code: string, i18n?: any) => { - try { - const { default: mermaid } = await import('mermaid'); - mermaid.initialize({ - startOnLoad: false, // Should be false when using render API - theme: document.documentElement.classList.contains('dark') ? 'dark' : 'default', - securityLevel: 'loose' - }); - const parseResult = await mermaid.parse(code, { suppressErrors: false }); - if (parseResult) { - const { svg } = await mermaid.render(`mermaid-${uuidv4()}`, code); - return svg; - } - return ''; - } catch (error) { - console.error('Failed to render mermaid diagram:', error); - const errorMsg = error instanceof Error ? error.message : String(error); - toast.error(i18n.t('Failed to render diagram') + `: ${errorMsg}`); - return ''; +export const renderMermaidDiagram = async (code: string) => { + const { default: mermaid } = await import('mermaid'); + mermaid.initialize({ + startOnLoad: false, // Should be false when using render API + theme: document.documentElement.classList.contains('dark') ? 'dark' : 'default', + securityLevel: 'loose' + }); + const parseResult = await mermaid.parse(code, { suppressErrors: false }); + if (parseResult) { + const { svg } = await mermaid.render(`mermaid-${uuidv4()}`, code); + return svg; } + return ''; }; export const renderVegaVisualization = async (spec: string, i18n?: any) => { - try { - const vega = await import('vega'); - const parsedSpec = JSON.parse(spec); - if (!parsedSpec.$schema) { - const errorMsg = 'Specification missing $schema property'; - toast.error(i18n.t('Failed to render diagram') + `: ${errorMsg}`); - throw new Error(errorMsg); - } - let vegaSpec; - if (parsedSpec.$schema.includes('vega-lite')) { - const vegaLite = await import('vega-lite'); - vegaSpec = vegaLite.compile(parsedSpec).spec; - } else if (parsedSpec.$schema.includes('vega')) { - vegaSpec = parsedSpec; - } else { - const errorMsg = 'Unknown schema format: ' + parsedSpec.$schema; - toast.error(i18n.t('Failed to render diagram') + `: ${errorMsg}`); - throw new Error(errorMsg); - } - const runtime = vega.parse(vegaSpec); - const view = new vega.View(runtime, { renderer: 'none' }); - const svg = await view.toSVG(); - return svg; - } catch (error) { - console.error('Failed to render Vega visualization:', error); - const errorMsg = error instanceof Error ? error.message : String(error); - toast.error(i18n.t('Failed to render diagram') + `: ${errorMsg}`); - return ''; + const vega = await import('vega'); + const parsedSpec = JSON.parse(spec); + let vegaSpec = parsedSpec; + if (parsedSpec.$schema && parsedSpec.$schema.includes('vega-lite')) { + const vegaLite = await import('vega-lite'); + vegaSpec = vegaLite.compile(parsedSpec).spec; } + const view = new vega.View(vega.parse(vegaSpec), { renderer: 'none' }); + const svg = await view.toSVG(); + return svg; }; From fa2534a52996c8ce229bda4de9f512112bd4f2bf Mon Sep 17 00:00:00 2001 From: _00_ <131402327+rgaricano@users.noreply.github.com> Date: Mon, 6 Oct 2025 00:16:01 +0200 Subject: [PATCH 4/4] Removed unused toast import & Code Format --- src/lib/components/chat/Messages/CodeBlock.svelte | 12 ++++++------ src/lib/utils/index.ts | 2 -- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/lib/components/chat/Messages/CodeBlock.svelte b/src/lib/components/chat/Messages/CodeBlock.svelte index 7e6e72b6bb..7322773d80 100644 --- a/src/lib/components/chat/Messages/CodeBlock.svelte +++ b/src/lib/components/chat/Messages/CodeBlock.svelte @@ -330,9 +330,9 @@ mermaidHtml = await renderMermaidDiagram(code); } catch (error) { console.error('Failed to render mermaid diagram:', error); - const errorMsg = error instanceof Error ? error.message : String(error); - toast.error($i18n.t('Failed to render diagram') + `: ${errorMsg}`); - mermaidHtml = null; + const errorMsg = error instanceof Error ? error.message : String(error); + toast.error($i18n.t('Failed to render diagram') + `: ${errorMsg}`); + mermaidHtml = null; } } else if ( (lang === 'vega' || lang === 'vega-lite') && @@ -342,9 +342,9 @@ vegaHtml = await renderVegaVisualization(code); } catch (error) { console.error('Failed to render Vega visualization:', error); - const errorMsg = error instanceof Error ? error.message : String(error); - toast.error($i18n.t('Failed to render diagram') + `: ${errorMsg}`); - vegaHtml = null; + const errorMsg = error instanceof Error ? error.message : String(error); + toast.error($i18n.t('Failed to render diagram') + `: ${errorMsg}`); + vegaHtml = null; } } }; diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index f1e44920a9..85000b4448 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -22,8 +22,6 @@ import markedExtension from '$lib/utils/marked/extension'; import markedKatexExtension from '$lib/utils/marked/katex-extension'; import hljs from 'highlight.js'; -import { toast } from 'svelte-sonner'; - ////////////////////////// // Helper functions //////////////////////////