Feat/add mermaid chart rendering (#5377)

Signed-off-by: Arya Pratap Singh <notaryasingh@gmail.com>
This commit is contained in:
Arya Pratap Singh
2025-10-28 05:23:50 +05:30
committed by GitHub
parent 786e7084b7
commit 1156cbcf9d
4 changed files with 2294 additions and 0 deletions
@@ -387,6 +387,13 @@ pub struct ShowChartParams {
pub data: ChartData,
}
/// Parameters for render_mermaid tool
#[derive(Debug, Serialize, Deserialize, rmcp::schemars::JsonSchema)]
pub struct RenderMermaidParams {
/// The Mermaid diagram code to render
pub mermaid_code: String,
}
/// An extension for automatic data visualization and UI generation
#[derive(Clone)]
pub struct AutoVisualiserRouter {
@@ -448,6 +455,7 @@ impl AutoVisualiserRouter {
- **render_treemap**: Creates interactive treemap visualizations for hierarchical data
- **render_chord**: Creates interactive chord diagrams for relationship/flow visualization
- **render_map**: Creates interactive map visualizations with location markers
- **render_mermaid**: Creates interactive Mermaid diagrams from Mermaid syntax
- **show_chart**: Creates interactive line, scatter, or bar charts for data visualization
"#};
@@ -977,6 +985,61 @@ Example:
.with_audience(vec![Role::User])]))
}
/// show a Mermaid diagram from Mermaid syntax
#[tool(
name = "render_mermaid",
description = r#"show a Mermaid diagram from Mermaid syntax
Provide the Mermaid code as a string. Supports flowcharts, sequence diagrams, Gantt charts, etc.
Example:
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
"#
)]
pub async fn render_mermaid(
&self,
params: Parameters<RenderMermaidParams>,
) -> Result<CallToolResult, ErrorData> {
let mermaid_code = params.0.mermaid_code;
// Load all resources at compile time using include_str!
const TEMPLATE: &str = include_str!("templates/mermaid_template.html");
const MERMAID_MIN: &str = include_str!("templates/assets/mermaid.min.js");
// Replace all placeholders with actual content
let html_content = TEMPLATE
.replace("{{MERMAID_MIN}}", MERMAID_MIN)
.replace("{{MERMAID_CODE}}", &mermaid_code);
// Save to /tmp/mermaid.html for debugging
let debug_path = std::path::Path::new("/tmp/mermaid.html");
if let Err(e) = std::fs::write(debug_path, &html_content) {
tracing::warn!("Failed to write debug HTML to /tmp/mermaid.html: {}", e);
} else {
tracing::info!("Debug HTML saved to /tmp/mermaid.html");
}
// Use BlobResourceContents with base64 encoding to avoid JSON string escaping issues
let html_bytes = html_content.as_bytes();
let base64_encoded = STANDARD.encode(html_bytes);
let resource_contents = ResourceContents::BlobResourceContents {
uri: "ui://mermaid/diagram".to_string(),
mime_type: Some("text/html".to_string()),
blob: base64_encoded,
meta: None,
};
Ok(CallToolResult::success(vec![Content::resource(
resource_contents,
)
.with_audience(vec![Role::User])]))
}
/// show interactive line, scatter, or bar charts
#[tool(
name = "show_chart",
@@ -1472,4 +1535,32 @@ mod tests {
&vec![Role::User]
);
}
#[tokio::test]
async fn test_render_mermaid() {
let router = AutoVisualiserRouter::new();
let params = Parameters(RenderMermaidParams {
mermaid_code: r#"graph TD;
A-->B;
A-->C;
B-->D;
C-->D;"#
.to_string(),
});
let result = router.render_mermaid(params).await;
if let Err(e) = &result {
eprintln!("Error in test_render_mermaid: {:?}", e);
}
assert!(result.is_ok());
let tool_result = result.unwrap();
assert_eq!(tool_result.content.len(), 1);
// Check the audience is set to User
assert!(tool_result.content[0].audience().is_some());
assert_eq!(
tool_result.content[0].audience().unwrap(),
&vec![Role::User]
);
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,173 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mermaid Diagram</title>
<script>
{{MERMAID_MIN}}
</script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
overflow: hidden;
padding: 30px;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
margin: 0 0 10px 0;
font-size: 2em;
font-weight: 300;
color: #333;
}
.mermaid {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
/* Mermaid specific styles */
.mermaid .node {
fill: #667eea !important;
stroke: #4a5fc1 !important;
stroke-width: 2px !important;
}
.mermaid .node text {
fill: white !important;
font-weight: 500 !important;
}
.mermaid .edgePath path {
stroke: #667eea !important;
stroke-width: 2px !important;
}
.mermaid .edgeLabel text {
fill: #333 !important;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Mermaid Diagram</h1>
</div>
<div class="mermaid">
{{MERMAID_CODE}}
</div>
</div>
<script>
// Initialize Mermaid
mermaid.initialize({
startOnLoad: true,
theme: 'default',
themeVariables: {
primaryColor: '#667eea',
primaryTextColor: '#fff',
primaryBorderColor: '#4a5fc1',
lineColor: '#667eea',
sectionBkgColor: '#f8f9fa',
altSectionBkgColor: '#e9ecef',
gridColor: '#e9ecef',
tertiaryColor: '#f8f9fa'
},
flowchart: {
useMaxWidth: true,
htmlLabels: true,
curve: 'basis'
},
sequence: {
useMaxWidth: true,
htmlLabels: true,
diagramMarginX: 50,
diagramMarginY: 10,
actorMargin: 50,
width: 150,
height: 65,
boxMargin: 10,
boxTextMargin: 5,
noteMargin: 10,
messageMargin: 35,
mirrorActors: true,
bottomMarginAdj: 1,
useMaxWidth: true
},
gantt: {
useMaxWidth: true,
titleTopMargin: 25,
barHeight: 20,
barGap: 4,
topPadding: 50,
leftPadding: 75,
gridLineStartPadding: 35,
fontSize: 11,
fontFamily: '"Open Sans", sans-serif',
numberSectionStyles: 4,
axisFormat: '%Y-%m-%d'
}
});
function reportContentSize() {
const contentHeight = Math.max(
document.body.scrollHeight,
document.body.offsetHeight,
document.documentElement.clientHeight,
document.documentElement.scrollHeight,
document.documentElement.offsetHeight
);
// Send size change message to parent window (for MCP-UI iframe auto-resize)
if (window.parent !== window) {
window.parent.postMessage({
type: 'ui-size-change',
payload: {
height: contentHeight
}
}, '*');
}
}
// Initialize on load
window.onload = function() {
// Report initial size
setTimeout(reportContentSize, 100);
// Watch for size changes using ResizeObserver if available
if (typeof ResizeObserver !== 'undefined') {
const resizeObserver = new ResizeObserver(() => {
reportContentSize();
});
resizeObserver.observe(document.body);
resizeObserver.observe(document.documentElement);
}
// Fallback: also report on window resize
window.addEventListener('resize', reportContentSize);
};
</script>
</body>
</html></content>
<parameter name="filePath">c:\Users\ARYA SINGH\Dropbox\PC\Desktop\goose\crates\goose-mcp\src\autovisualiser\templates\mermaid_template.html
@@ -61,6 +61,7 @@ The Auto Visualiser is a powerful extension that integrates with Goose's MCP-UI
| **Treemap Visualizations** | Hierarchical data with proportional area representation | Hierarchical data <br/>(nested categories, organizational structures) |
| **Chord Diagrams** | Relationship and flow visualization between entities | Relationship matrices <br/>(network connections, cross-references) |
| **Interactive Maps** | Geographic data visualization with location markers using Leaflet | Geographic information <br/>(location data, coordinates, addresses) |
| **Mermaid Diagrams** | Flowcharts, sequence diagrams, Gantt charts, and other diagram types using Mermaid syntax | Diagram creation <br/>(flowcharts, sequence diagrams, architecture diagrams) |
| **Line/Bar/Scatter Charts** | Traditional chart types for data analysis | Time series data <br/>(historical data, trends over time) |
### Example Visualizations