webui: Add Mermaid diagram support to markdown
* Installed mermaid library
* Extended markdown renderer to support mermaid code blocks
* Added CSS styling for mermaid diagrams
* Added a demo page for testing mermaid diagrams
Co-Authored-By: sketch <hello@sketch.dev>
diff --git a/webui/src/web-components/demo/mermaid-test/index.html b/webui/src/web-components/demo/mermaid-test/index.html
new file mode 100644
index 0000000..e2fd202
--- /dev/null
+++ b/webui/src/web-components/demo/mermaid-test/index.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Mermaid Diagram Test</title>
+ <script type="module" src="./mermaid-test.ts"></script>
+ <style>
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ padding: 20px;
+ max-width: 1000px;
+ margin: 0 auto;
+ }
+ h1 {
+ color: #333;
+ }
+ .container {
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ padding: 20px;
+ margin: 20px 0;
+ }
+ </style>
+</head>
+<body>
+ <h1>Mermaid Diagram Testing</h1>
+ <p>This page tests the integration of Mermaid diagrams in the markdown renderer.</p>
+
+ <div class="container">
+ <mermaid-test-component></mermaid-test-component>
+ </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/webui/src/web-components/demo/mermaid-test/mermaid-test.ts b/webui/src/web-components/demo/mermaid-test/mermaid-test.ts
new file mode 100644
index 0000000..7d8e04f
--- /dev/null
+++ b/webui/src/web-components/demo/mermaid-test/mermaid-test.ts
@@ -0,0 +1,136 @@
+import { html, css, LitElement } from 'lit';
+import { customElement } from 'lit/decorators.js';
+import '../../sketch-timeline-message.ts';
+// Using simple objects matching the AgentMessage interface
+
+@customElement('mermaid-test-component')
+export class MermaidTestComponent extends LitElement {
+ static styles = css`
+ :host {
+ display: block;
+ }
+ .test-section {
+ margin-bottom: 30px;
+ }
+ h2 {
+ margin-top: 0;
+ color: #444;
+ }
+ `;
+
+ render() {
+ // Create a sample message with Mermaid diagrams
+ const flowchartMessage = {
+ id: 'test-1',
+ type: 'agent',
+ content: `# Mermaid Flowchart Example
+
+This is a test of a flowchart diagram in Mermaid syntax:
+
+\`\`\`mermaid
+graph TD
+ A[Start] --> B{Is it working?}
+ B -->|Yes| C[Great!]
+ B -->|No| D[Try again]
+ C --> E[Continue]
+ D --> B
+\`\`\`
+
+The above should render as a Mermaid diagram.`,
+ timestamp: new Date().toISOString(),
+ };
+
+ const sequenceDiagramMessage = {
+ id: 'test-2',
+ type: 'agent',
+ content: `# Mermaid Sequence Diagram Example
+
+Here's a sequence diagram showing a typical HTTP request:
+
+\`\`\`mermaid
+sequenceDiagram
+ participant Browser
+ participant Server
+ Browser->>Server: GET /index.html
+ Server-->>Browser: HTML Response
+ Browser->>Server: GET /style.css
+ Server-->>Browser: CSS Response
+ Browser->>Server: GET /script.js
+ Server-->>Browser: JS Response
+\`\`\`
+
+Complex diagrams should render properly.`,
+ timestamp: new Date().toISOString(),
+ };
+
+ const classDiagramMessage = {
+ id: 'test-3',
+ type: 'agent',
+ content: `# Mermaid Class Diagram Example
+
+A simple class diagram in Mermaid:
+
+\`\`\`mermaid
+classDiagram
+ class Animal {
+ +string name
+ +makeSound() void
+ }
+ class Dog {
+ +bark() void
+ }
+ class Cat {
+ +meow() void
+ }
+ Animal <|-- Dog
+ Animal <|-- Cat
+\`\`\`
+
+This represents a basic inheritance diagram.`,
+ timestamp: new Date().toISOString(),
+ };
+
+ const normalMarkdownMessage = {
+ id: 'test-4',
+ type: 'agent',
+ content: `# Regular Markdown
+
+This is regular markdown with:
+
+- A bullet list
+- **Bold text**
+- *Italic text*
+
+\`\`\`javascript
+// Regular code block
+const x = 10;
+console.log('This is not Mermaid');
+\`\`\`
+
+Regular markdown should continue to work properly.`,
+ timestamp: new Date().toISOString(),
+ };
+
+ return html`
+ <div class="test-section">
+ <h2>Flowchart Diagram Test</h2>
+ <sketch-timeline-message .message=${flowchartMessage}></sketch-timeline-message>
+ </div>
+
+ <div class="test-section">
+ <h2>Sequence Diagram Test</h2>
+ <sketch-timeline-message .message=${sequenceDiagramMessage}></sketch-timeline-message>
+ </div>
+
+ <div class="test-section">
+ <h2>Class Diagram Test</h2>
+ <sketch-timeline-message .message=${classDiagramMessage}></sketch-timeline-message>
+ </div>
+
+ <div class="test-section">
+ <h2>Normal Markdown Test</h2>
+ <sketch-timeline-message .message=${normalMarkdownMessage}></sketch-timeline-message>
+ </div>
+ `;
+ }
+}
diff --git a/webui/src/web-components/demo/sketch-timeline-message.demo.html b/webui/src/web-components/demo/sketch-timeline-message.demo.html
index 3c5d77e..add83a1 100644
--- a/webui/src/web-components/demo/sketch-timeline-message.demo.html
+++ b/webui/src/web-components/demo/sketch-timeline-message.demo.html
@@ -19,6 +19,21 @@
content: "a tool use message",
},
{
+ type:"agent",
+ content: `Mermaid Sequence Diagram Example
+\`\`\`mermaid
+sequenceDiagram
+ participant Browser
+ participant Server
+ Browser->>Server: GET /index.html
+ Server-->>Browser: HTML Response
+ Browser->>Server: GET /style.css
+ Server-->>Browser: CSS Response
+ Browser->>Server: GET /script.js
+ Server-->>Browser: JS Response
+\`\`\``,
+ },
+ {
type: "commit",
end_of_turn: false,
content: "",
diff --git a/webui/src/web-components/sketch-timeline-message.ts b/webui/src/web-components/sketch-timeline-message.ts
index 0e16c3f..b9c1455 100644
--- a/webui/src/web-components/sketch-timeline-message.ts
+++ b/webui/src/web-components/sketch-timeline-message.ts
@@ -2,7 +2,8 @@
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { customElement, property } from "lit/decorators.js";
import { AgentMessage } from "../types";
-import { marked, MarkedOptions } from "marked";
+import { marked, MarkedOptions, Renderer, Tokens } from "marked";
+import mermaid from "mermaid";
import "./sketch-tool-calls";
@customElement("sketch-timeline-message")
export class SketchTimelineMessage extends LitElement {
@@ -417,16 +418,82 @@
margin-block-start: 0.5em;
margin-block-end: 0.5em;
}
+
+ /* Mermaid diagram styling */
+ .mermaid-container {
+ margin: 1em 0;
+ padding: 0.5em;
+ background-color: #f8f8f8;
+ border-radius: 4px;
+ overflow-x: auto;
+ }
+
+ .mermaid {
+ text-align: center;
+ }
`;
+ // Track mermaid diagrams that need rendering
+ private mermaidDiagrams = new Map();
+
constructor() {
super();
+ // Initialize mermaid with specific config
+ mermaid.initialize({
+ startOnLoad: false,
+ theme: 'default',
+ securityLevel: 'loose', // Allows more flexibility but be careful with user-generated content
+ fontFamily: 'monospace'
+ });
}
// See https://lit.dev/docs/components/lifecycle/
connectedCallback() {
super.connectedCallback();
}
+
+ // After the component is updated and rendered, render any mermaid diagrams
+ updated(changedProperties: Map<string, unknown>) {
+ super.updated(changedProperties);
+ this.renderMermaidDiagrams();
+ }
+
+ // Render mermaid diagrams after the component is updated
+ renderMermaidDiagrams() {
+ // Add a small delay to ensure the DOM is fully rendered
+ setTimeout(() => {
+ // Find all mermaid containers in our shadow root
+ const containers = this.shadowRoot?.querySelectorAll('.mermaid');
+ if (!containers || containers.length === 0) return;
+
+ // Process each mermaid diagram
+ containers.forEach(container => {
+ const id = container.id;
+ const code = container.textContent || '';
+ if (!code || !id) return; // Use return for forEach instead of continue
+
+ try {
+ // Clear any previous content
+ container.innerHTML = code;
+
+ // Render the mermaid diagram using promise
+ mermaid.render(`${id}-svg`, code)
+ .then(({ svg }) => {
+ container.innerHTML = svg;
+ })
+ .catch(err => {
+ console.error('Error rendering mermaid diagram:', err);
+ // Show the original code as fallback
+ container.innerHTML = `<pre>${code}</pre>`;
+ });
+ } catch (err) {
+ console.error('Error processing mermaid diagram:', err);
+ // Show the original code as fallback
+ container.innerHTML = `<pre>${code}</pre>`;
+ }
+ });
+ }, 100); // Small delay to ensure DOM is ready
+ }
// See https://lit.dev/docs/components/lifecycle/
disconnectedCallback() {
@@ -435,11 +502,31 @@
renderMarkdown(markdownContent: string): string {
try {
+ // Create a custom renderer
+ const renderer = new Renderer();
+ const originalCodeRenderer = renderer.code.bind(renderer);
+
+ // Override the code renderer to handle mermaid diagrams
+ renderer.code = function({ text, lang, escaped }: Tokens.Code): string {
+ if (lang === 'mermaid') {
+ // Generate a unique ID for this diagram
+ const id = `mermaid-diagram-${Math.random().toString(36).substring(2, 10)}`;
+
+ // Just create the container and mermaid div - we'll render it in the updated() lifecycle method
+ return `<div class="mermaid-container">
+ <div class="mermaid" id="${id}">${text}</div>
+ </div>`;
+ }
+ // Default rendering for other code blocks
+ return originalCodeRenderer({ text, lang, escaped });
+ };
+
// Set markdown options for proper code block highlighting and safety
const markedOptions: MarkedOptions = {
gfm: true, // GitHub Flavored Markdown
breaks: true, // Convert newlines to <br>
async: false,
+ renderer: renderer
// DOMPurify is recommended for production, but not included in this implementation
};
return marked.parse(markdownContent, markedOptions) as string;