feat: add Mermaid overlay support for agents to inject positioned diagrams

- Agents can send RenderMermaid with Mermaid source + canvas position
  to create SVG texture overlays that coexist with other elements
- MermaidOverlay struct holds source, rendered SVG, SvgRenderer, and
  lazy-loaded egui texture at a specific canvas position/size
- Server handles rendering via mermaid-rs, parses SVG for dimensions,
  sends overlay data through DrawingCommand channel to GUI thread
- Canvas renders overlays as positioned textures between base SVG and
  drawing elements, with proper pan/zoom transforms
- New MCP tool render_mermaid for agent access
- Overlays cleared on undo/redo/checkout to stay consistent with history
- 29 tests passing, clippy clean
This commit is contained in:
David Ibia
2026-02-10 10:44:39 +01:00
parent 5ca1e85209
commit 740fa2f5f9
6 changed files with 266 additions and 10 deletions

View File

@@ -187,9 +187,27 @@ pub struct BooleanOpParam {
pub stroke_width: Option<f32>,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct RenderMermaidParam {
#[schemars(description = "Session ID to target. If omitted, uses the active session.")]
pub session_id: Option<String>,
#[schemars(description = "Mermaid diagram source code (e.g., 'flowchart LR\\n A-->B')")]
pub mermaid_source: String,
#[schemars(description = "X position on canvas (default: 0)")]
pub x: Option<f32>,
#[schemars(description = "Y position on canvas (default: 0)")]
pub y: Option<f32>,
#[schemars(description = "Override width (default: natural SVG width)")]
pub width: Option<f32>,
#[schemars(description = "Override height (default: natural SVG height)")]
pub height: Option<f32>,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct BatchParam {
#[schemars(description = "JSON array of request objects. Each object must have a 'type' field and the same parameters as individual tool calls. Example: [{\"type\": \"CreateDrawingElement\", \"shape_type\": \"rectangle\", \"x\": 0, \"y\": 0, \"width\": 100, \"height\": 100}, {\"type\": \"CreateDrawingElement\", \"shape_type\": \"ellipse\", \"center_x\": 200, \"center_y\": 200, \"radius_x\": 50, \"radius_y\": 50}]")]
#[schemars(
description = "JSON array of request objects. Each object must have a 'type' field and the same parameters as individual tool calls. Example: [{\"type\": \"CreateDrawingElement\", \"shape_type\": \"rectangle\", \"x\": 0, \"y\": 0, \"width\": 100, \"height\": 100}, {\"type\": \"CreateDrawingElement\", \"shape_type\": \"ellipse\", \"center_x\": 200, \"center_y\": 200, \"radius_x\": 50, \"radius_y\": 50}]"
)]
pub requests_json: String,
}
@@ -527,6 +545,26 @@ impl AgCanvasServer {
self.call_agcanvas(&request).await
}
#[tool(
name = "render_mermaid",
description = "Render a Mermaid diagram (flowchart, sequence, etc.) as an SVG overlay at a specific position on the canvas. The diagram appears as a visual element that can coexist with other shapes and diagrams."
)]
async fn render_mermaid(
&self,
Parameters(params): Parameters<RenderMermaidParam>,
) -> Result<CallToolResult, McpError> {
let request = serde_json::json!({
"type": "RenderMermaid",
"session_id": params.session_id,
"mermaid_source": params.mermaid_source,
"x": params.x,
"y": params.y,
"width": params.width,
"height": params.height,
});
self.call_agcanvas(&request).await
}
#[tool(
description = "Send multiple Augmented Canvas operations in one request. Accepts a JSON array of request objects and returns one response per operation in a BatchResult."
)]