fix text label bugs

This commit is contained in:
yctimlin
2025-07-12 09:01:51 +00:00
parent afa145bd73
commit 86232d9c94
5 changed files with 74 additions and 24 deletions
+8
View File
@@ -29,6 +29,14 @@ For the most stable experience, we recommend using the local development setup.
- **🔄 WebSocket Updates**: Live synchronization across multiple connected clients
- **🏗️ Production Ready**: Clean, minimal UI suitable for end users
## 🎥 Demo Video
> **See MCP Excalidraw in Action!**
[![MCP Excalidraw Demo](https://img.youtube.com/vi/YOUR_VIDEO_ID/maxresdefault.jpg)](https://www.youtube.com/watch?v=YOUR_VIDEO_ID)
*Watch how AI agents create and manipulate diagrams in real-time on the live canvas*
## 🏛️ Architecture Overview
```
+21 -5
View File
@@ -2,6 +2,17 @@ import React, { useState, useEffect, useRef } from 'react'
import { Excalidraw, convertToExcalidrawElements } from '@excalidraw/excalidraw'
import '@excalidraw/excalidraw/index.css'
// Helper function to clean elements for Excalidraw
const cleanElementForExcalidraw = (element) => {
const {
createdAt,
updatedAt,
version,
...cleanElement
} = element;
return cleanElement;
}
function App() {
const [excalidrawAPI, setExcalidrawAPI] = useState(null)
const [isConnected, setIsConnected] = useState(false)
@@ -35,7 +46,8 @@ function App() {
const result = await response.json()
if (result.success && result.elements && result.elements.length > 0) {
const convertedElements = convertToExcalidrawElements(result.elements)
const cleanedElements = result.elements.map(cleanElementForExcalidraw)
const convertedElements = convertToExcalidrawElements(cleanedElements)
excalidrawAPI.updateScene({ elements: convertedElements })
}
} catch (error) {
@@ -96,19 +108,22 @@ function App() {
switch (data.type) {
case 'initial_elements':
if (data.elements && data.elements.length > 0) {
const convertedElements = convertToExcalidrawElements(data.elements)
const cleanedElements = data.elements.map(cleanElementForExcalidraw)
const convertedElements = convertToExcalidrawElements(cleanedElements)
excalidrawAPI.updateScene({ elements: convertedElements })
}
break
case 'element_created':
const newElement = convertToExcalidrawElements([data.element])
const cleanedNewElement = cleanElementForExcalidraw(data.element)
const newElement = convertToExcalidrawElements([cleanedNewElement])
const updatedElementsAfterCreate = [...currentElements, ...newElement]
excalidrawAPI.updateScene({ elements: updatedElementsAfterCreate })
break
case 'element_updated':
const convertedUpdatedElement = convertToExcalidrawElements([data.element])[0]
const cleanedUpdatedElement = cleanElementForExcalidraw(data.element)
const convertedUpdatedElement = convertToExcalidrawElements([cleanedUpdatedElement])[0]
const updatedElements = currentElements.map(el =>
el.id === data.element.id ? convertedUpdatedElement : el
)
@@ -121,7 +136,8 @@ function App() {
break
case 'elements_batch_created':
const batchElements = convertToExcalidrawElements(data.elements)
const cleanedBatchElements = data.elements.map(cleanElementForExcalidraw)
const batchElements = convertToExcalidrawElements(cleanedBatchElements)
const updatedElementsAfterBatch = [...currentElements, ...batchElements]
excalidrawAPI.updateScene({ elements: updatedElementsAfterBatch })
break
+36 -17
View File
@@ -397,6 +397,18 @@ const server = new Server(
}
);
// Helper function to convert text property to label format for Excalidraw
function convertTextToLabel(element) {
const { text, ...rest } = element;
if (text) {
return {
...rest,
label: { text }
};
}
return element;
}
// Set up request handler for tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
@@ -417,23 +429,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
version: 1
};
// Store locally (MCP server storage)
elements.set(id, element);
// Convert text to label format for Excalidraw
const excalidrawElement = convertTextToLabel(element);
// Store the converted element locally (MCP server storage)
elements.set(id, excalidrawElement);
// Sync to canvas (Express server + WebSocket broadcast)
const canvasElement = await createElementOnCanvas(element);
const canvasElement = await createElementOnCanvas(excalidrawElement);
const result = canvasElement || element;
logger.info('Element created via MCP and synced to canvas', {
id: result.id,
type: result.type,
id: excalidrawElement.id,
type: excalidrawElement.type,
synced: !!canvasElement
});
return {
content: [{
type: 'text',
text: `Element created successfully!\n\n${JSON.stringify(result, null, 2)}\n\n${canvasElement ? '✅ Synced to canvas' : '⚠️ Canvas sync failed (element still created locally)'}`
text: `Element created successfully!\n\n${JSON.stringify(excalidrawElement, null, 2)}\n\n${canvasElement ? '✅ Synced to canvas' : '⚠️ Canvas sync failed (element still created locally)'}`
}]
};
}
@@ -457,22 +471,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
version: existingElement.version + 1
};
// Store locally (MCP server storage)
elements.set(id, updatedElement);
// Convert text to label format for Excalidraw
const excalidrawElement = convertTextToLabel(updatedElement);
// Store the converted element locally (MCP server storage)
elements.set(id, excalidrawElement);
// Sync to canvas (Express server + WebSocket broadcast)
const canvasElement = await updateElementOnCanvas(updatedElement);
const canvasElement = await updateElementOnCanvas(excalidrawElement);
const result = canvasElement || updatedElement;
logger.info('Element updated via MCP and synced to canvas', {
id: result.id,
id: excalidrawElement.id,
synced: !!canvasElement
});
return {
content: [{
type: 'text',
text: `Element updated successfully!\n\n${JSON.stringify(result, null, 2)}\n\n${canvasElement ? '✅ Synced to canvas' : '⚠️ Canvas sync failed (element still updated locally)'}`
text: `Element updated successfully!\n\n${JSON.stringify(excalidrawElement, null, 2)}\n\n${canvasElement ? '✅ Synced to canvas' : '⚠️ Canvas sync failed (element still updated locally)'}`
}]
};
}
@@ -668,9 +684,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
version: 1
};
// Store locally (MCP server storage)
elements.set(id, element);
createdElements.push(element);
// Convert text to label format for Excalidraw
const excalidrawElement = convertTextToLabel(element);
// Store the converted element locally (MCP server storage)
elements.set(id, excalidrawElement);
createdElements.push(excalidrawElement);
}
// Sync all elements to canvas at once (Express server + WebSocket broadcast)
@@ -678,7 +697,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const result = {
success: true,
elements: canvasElements || createdElements,
elements: createdElements,
count: createdElements.length,
syncedToCanvas: !!canvasElements
};
+8
View File
@@ -30,6 +30,8 @@ app.use(express.json());
// Serve static files from the build directory
const staticDir = path.join(__dirname, '../dist');
app.use(express.static(staticDir));
// Also serve frontend assets
app.use(express.static(path.join(__dirname, '../dist/frontend')));
// WebSocket connections
const clients = new Set();
@@ -79,6 +81,9 @@ const CreateElementSchema = z.object({
roughness: z.number().optional(),
opacity: z.number().optional(),
text: z.string().optional(),
label: z.object({
text: z.string()
}).optional(),
fontSize: z.number().optional(),
fontFamily: z.string().optional()
});
@@ -96,6 +101,9 @@ const UpdateElementSchema = z.object({
roughness: z.number().optional(),
opacity: z.number().optional(),
text: z.string().optional(),
label: z.object({
text: z.string()
}).optional(),
fontSize: z.number().optional(),
fontFamily: z.string().optional()
});
+1 -2
View File
@@ -7,8 +7,7 @@ export const EXCALIDRAW_ELEMENT_TYPES = {
TEXT: 'text',
LABEL: 'label',
FREEDRAW: 'freedraw',
LINE: 'line',
ARROW_LABEL: 'arrowLabel'
LINE: 'line'
};
// In-memory storage for Excalidraw elements