mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
Feat/add mermaid chart rendering (#5377)
Signed-off-by: Arya Pratap Singh <notaryasingh@gmail.com>
This commit is contained in:
committed by
GitHub
parent
786e7084b7
commit
1156cbcf9d
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user