feat: implement WebSocket-based Mermaid conversion with auto-sync

- Add WebSocket message handler for mermaid_convert type
- Make handleWebSocketMessage async to support conversion
- Add automatic backend sync after diagram generation
- Integrate convertMermaidToExcalidraw utility
- Remove test button and handleMermaidTest function
- Fix: Add missing Excalidraw CSS import for proper UI rendering
- Enhance server endpoint with WebSocket broadcast support
- Add mermaid_convert to WebSocketMessageType union
This commit is contained in:
francisco
2025-11-01 13:58:23 -03:00
parent dce247f4d6
commit b8e2de76f1
4 changed files with 53 additions and 41 deletions
+34 -38
View File
@@ -215,7 +215,7 @@ function App(): JSX.Element {
}
}
const handleWebSocketMessage = (data: WebSocketMessage): void => {
const handleWebSocketMessage = async (data: WebSocketMessage): Promise<void> => {
if (!excalidrawAPI) {
return
}
@@ -294,6 +294,39 @@ function App(): JSX.Element {
console.log(`Server sync status: ${data.count} elements`)
break
case 'mermaid_convert':
console.log('Received Mermaid conversion request from MCP')
if ((data as any).mermaidDiagram) {
try {
const result = await convertMermaidToExcalidraw((data as any).mermaidDiagram, (data as any).config || DEFAULT_MERMAID_CONFIG)
if (result.error) {
console.error('Mermaid conversion error:', result.error)
return
}
if (result.elements && result.elements.length > 0) {
const convertedElements = convertToExcalidrawElements(result.elements as any, { regenerateIds: false })
excalidrawAPI.updateScene({
elements: convertedElements,
captureUpdate: CaptureUpdateAction.IMMEDIATELY
})
if (result.files) {
excalidrawAPI.addFiles(Object.values(result.files))
}
console.log('Mermaid diagram converted successfully:', result.elements.length, 'elements')
// Sync to backend automatically after creating elements
await syncToBackend()
}
} catch (error) {
console.error('Error converting Mermaid diagram from WebSocket:', error)
}
}
break
default:
console.log('Unknown WebSocket message type:', data.type)
}
@@ -400,42 +433,6 @@ function App(): JSX.Element {
}
}
const handleMermaidTest = async (): Promise<void> => {
if (!excalidrawAPI) return
const testMermaid = `graph TD
A[Start] --> B{Decision}
B -->|Yes| C[Do Something]
B -->|No| D[Do Something Else]
C --> E[End]
D --> E`
try {
const result = await convertMermaidToExcalidraw(testMermaid, DEFAULT_MERMAID_CONFIG)
if (result.error) {
console.error('Mermaid conversion error:', result.error)
return
}
if (result.elements && result.elements.length > 0) {
const convertedElements = convertToExcalidrawElements(result.elements as any, { regenerateIds: false })
excalidrawAPI.updateScene({
elements: convertedElements,
captureUpdate: CaptureUpdateAction.IMMEDIATELY
})
if (result.files) {
excalidrawAPI.addFiles(Object.values(result.files))
}
console.log('Mermaid diagram converted successfully:', result.elements.length, 'elements')
}
} catch (error) {
console.error('Error converting Mermaid diagram:', error)
}
}
return (
<div className="app">
{/* Header */}
@@ -474,7 +471,6 @@ function App(): JSX.Element {
</div>
</div>
<button className="btn-secondary" onClick={handleMermaidTest}>Test Mermaid</button>
<button className="btn-secondary" onClick={clearCanvas}>Clear Canvas</button>
</div>
</div>
+1
View File
@@ -1,6 +1,7 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import '@excalidraw/excalidraw/index.css'
const rootElement = document.getElementById('root');
if (!rootElement) {
+9 -2
View File
@@ -416,13 +416,20 @@ app.post('/api/elements/from-mermaid', (req: Request, res: Response) => {
hasConfig: !!config
});
// Broadcast to all WebSocket clients to process the Mermaid diagram
broadcast({
type: 'mermaid_convert',
mermaidDiagram,
config: config || {},
timestamp: new Date().toISOString()
});
// Return the diagram for frontend processing
// The frontend will handle the actual conversion using mermaid-to-excalidraw
res.json({
success: true,
mermaidDiagram,
config: config || {},
message: 'Mermaid diagram received. Process on frontend with DOM access.'
message: 'Mermaid diagram sent to frontend for conversion.'
});
} catch (error) {
logger.error('Error processing Mermaid diagram:', error);
+9 -1
View File
@@ -175,7 +175,8 @@ export type WebSocketMessageType =
| 'element_deleted'
| 'elements_batch_created'
| 'elements_synced'
| 'sync_status';
| 'sync_status'
| 'mermaid_convert';
export interface InitialElementsMessage extends WebSocketMessage {
type: 'initial_elements';
@@ -208,6 +209,13 @@ export interface SyncStatusMessage extends WebSocketMessage {
timestamp: string;
}
export interface MermaidConvertMessage extends WebSocketMessage {
type: 'mermaid_convert';
mermaidDiagram: string;
config?: MermaidConfig;
timestamp: string;
}
// Mermaid conversion types
export interface MermaidConfig {
startOnLoad?: boolean;