Repository navigation
Conversation
Mermaid code blocks render as diagrams once streaming completes, with a copy button for the source and a toggle between the diagram and the source.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9011 +/- ##
==========================================
+ Coverage 79.46% 79.54% +0.07%
==========================================
Files 475 475
Lines 26028 26076 +48
Branches 6938 6950 +12
==========================================
+ Hits 20684 20741 +57
+ Misses 5344 5335 -9
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… speed up streaming Add copy as SVG, copy as image and download PNG to rendered mermaid blocks, using the shared ff-btn styles. Diagrams render as soon as their closing fence has streamed in, and text streams several words per tick.
…d of bundling mermaid The Expert now sends each rendered diagram's SVG alongside the message, so the frontend no longer needs the mermaid dependency. The SVG is sanitized, matched to its code block by source, and recoloured with the FlowFuse theme variables.
… whitespace Streaming collapses runs of whitespace in code, so the indented source of a block no longer equalled the source the diagram was rendered from.
The diagrams take their colours from variables, so one set of values follows the FlowFuse theme instead of overriding the diagram's own classes.
… highlight mermaid code Mermaid blocks are now a plain code block variant. The Code toggle and export buttons are added once the diagram is shown and the toggle uses the hidden attribute, which removes the state-driven CSS and leaves other code blocks as they were. Mermaid sources get a highlight.js grammar.
Streaming split the message on whitespace and joined the words with single spaces, so code blocks kept collapsed indentation after streaming, and copying a streamed block gave flattened code. Each word now keeps the whitespace before it, so the streamed text ends identical to the message.
…n the page theme Diagrams are sanitized once per message instead of on every streaming tick, and the copy and export buttons use the sanitized SVG. Ids are removed from diagrams because nothing in them refers to the ids and they repeat across the diagrams on a page. Copy SVG, Copy image and Download PNG write the theme colours of the page onto the SVG, so exports match the diagram on screen in light and dark mode. The PNG download URL is revoked after a delay, as revoking it straight after the click can cancel the download in Safari and Firefox.
n-lark
left a comment
There was a problem hiding this comment.
Hey from local testing with mocked mermaid responses this does seem to work, but I couldn't get my local setup connected to Expert dev, so I haven't tested it end to end. Worth having someone else run it against dev before it merges.
That said, the approach gives me some pause. Most of the frontend doesn't render features with vanilla DOM manipulation. Here, though:
- Diagrams and buttons are added by hand after
v-htmlrenders, usingappendChild,innerHTML,setAttribute,querySelector, and buttons built as HTML strings. - Finished blocks are found by searching the streamed HTML for the class name and
</pre>. - It depends on the class names from
Markdown.jsstaying exactly the same.
Could we potentially split the content with markedInstance.lexer()? Each mermaid block could then be its own MermaidDiagram component that just renders the SVG from the backend, no mermaid library needed. It would only be built once, state could live in refs, and we'd get ff-button / ff-kebab-menu.
Extending the current pattern would be a concern. Each new interactive piece would add more class-name coupling and more state that gets wiped on every rebuild, and it's a bit of an outlier in the codebase.
| const svg = this.findDiagramSvg(source) | ||
| if (!svg) return | ||
| const theme = this.diagramTheme(block) | ||
| const label = btn.textContent |
There was a problem hiding this comment.
| this.renderDiagrams() | ||
| }, | ||
| updated () { | ||
| this.renderDiagrams() |
There was a problem hiding this comment.
So while streaming, :key="text" rebuilds the whole content every tick and updated() re-injects the diagrams each time, so the diagram buttons don't work until streaming ends. None of my clicks on Code / export registered.
Could each diagram be its own Vue component instead of going through v-html? Then it wouldn't rebuild every tick, and we could use ff-button / ff-kebab-menu, which would also fix the export buttons wrapping in the drawer on smaller screens.

Closes #9010
Closes FlowFuse/engineering#577
Closes FlowFuse/engineering#578
Open question
Testing