Render diagrams with Mermaid
Draw the mermaid fences a model writes as diagrams, while the reply streams, with @aparte/plugin-mermaid.
<aparte-chat-bubble message-id="a1" data-role="assistant" streaming></aparte-chat-bubble>
<script>
// Every fence in a reply is a code segment: a mermaid one is redrawn as it arrives.
const bubble = document.querySelector('aparte-chat-bubble');
bubble.setSegments([{ id: 's1', type: 'code', language: 'mermaid', content: '', isStreaming: true }]);
const source = 'flowchart LR\n accTitle: How a reply reaches the screen\n A[Model] -->|tokens| B[Markdown provider]\n B --> C{A code language?}\n C -->|math| D[KaTeX]\n C -->|mermaid| E[Mermaid]\n C -->|anything else| F[Highlighter]';
let i = 0;
const tick = setInterval(() => {
bubble.appendToSegment('s1', source.slice(i, i + 4));
i += 4;
if (i >= source.length) {
clearInterval(tick);
bubble.updateSegment('s1', { isStreaming: false });
bubble.removeAttribute('streaming');
}
}, 60);
</script>Install and register
Section titled “Install and register”npm install @aparte/plugin-mermaid mermaidimport { setupMermaid } from '@aparte/plugin-mermaid';
setupMermaid();Every ```mermaid fence in a reply is drawn by Mermaid — with
any Markdown provider, or none, because every fence in a streamed reply is already a block of its
own. Mermaid is loaded on the first diagram, and a diagram is drawn only once it comes near the
screen, so reopening a long conversation stays fast.
While the reply streams
Section titled “While the reply streams”The diagram is drawn as its fence arrives, and redrawn at most every 300 ms — or as long as the last drawing took, if longer — keeping the last version that parsed. Until the first version parses, the source shows. Once the fence closes, a diagram that does not parse shows a short note and its source. The copy button on the block always copies the source.
An image, so nothing in it reaches the page
Section titled “An image, so nothing in it reaches the page”Each diagram is an <img> of its SVG. A browser draws an image’s SVG with no scripts and no access
to the page, so neither a label a model wrote nor a style inside the diagram can act on the chat
around it. Mermaid also runs with securityLevel: 'strict' and HTML labels off, and a diagram’s
own %%{init}%% cannot turn either back on.
The cost: the text in a diagram cannot be selected, and it is set in the system fonts, since an image cannot load the page’s.
Colours and words
Section titled “Colours and words”The diagram takes its colours from the chat’s theme — --aparte-bg, --aparte-text,
--aparte-primary and their neighbours — and is drawn again when the theme changes. Its alt text
is the diagram’s own accTitle or accDescr, else “Diagram”. To translate the fallback and the
error note:
import { aparteGlobalConfig } from '@aparte/core';
aparteGlobalConfig.extendLocale({ diagramAlt: 'Diagramme', diagramError: 'Ce diagramme ne peut pas être dessiné.' });Bring your own Mermaid
Section titled “Bring your own Mermaid”@aparte/plugin-mermaid/core imports nothing from mermaid: hand it the instance, or a function
that loads it.
import mermaid from 'mermaid';
import { setupMermaidWith } from '@aparte/plugin-mermaid/core';
setupMermaidWith(mermaid, { progressive: false, mermaid: { flowchart: { curve: 'basis' } } });MermaidOptions takes progressive (false draws a diagram once its fence has closed) and
mermaid, Mermaid’s own configuration under the keys the plugin keeps. Any object shaped like
MermaidLike will do. Both setups return a function that removes the renderer.
Mermaid keeps one configuration for the whole page, and the plugin calls initialize with its
own before every diagram it draws. If your page also draws diagrams of its own with the same
Mermaid, call mermaid.initialize(yourConfig) again before your own render() or run():
import mermaid from 'mermaid';
mermaid.initialize({ startOnLoad: false, theme: 'dark' });
await mermaid.run({ querySelector: '.my-diagrams' });