Files
excalidraw-mcp-sentinel/src/types.ts
T
francisco b8e2de76f1 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
2025-11-01 13:58:23 -03:00

265 lines
6.4 KiB
TypeScript

export interface ExcalidrawElementBase {
id: string;
type: ExcalidrawElementType;
x: number;
y: number;
width?: number;
height?: number;
angle?: number;
strokeColor?: string;
backgroundColor?: string;
fillStyle?: string;
strokeWidth?: number;
strokeStyle?: string;
roughness?: number;
opacity?: number;
groupIds?: string[];
frameId?: string | null;
roundness?: {
type: number;
value?: number;
} | null;
seed?: number;
versionNonce?: number;
isDeleted?: boolean;
locked?: boolean;
link?: string | null;
customData?: Record<string, any> | null;
boundElements?: readonly ExcalidrawBoundElement[] | null;
updated?: number;
containerId?: string | null;
}
export interface ExcalidrawTextElement extends ExcalidrawElementBase {
type: 'text';
text: string;
fontSize?: number;
fontFamily?: number;
textAlign?: string;
verticalAlign?: string;
baseline?: number;
lineHeight?: number;
}
export interface ExcalidrawRectangleElement extends ExcalidrawElementBase {
type: 'rectangle';
width: number;
height: number;
}
export interface ExcalidrawEllipseElement extends ExcalidrawElementBase {
type: 'ellipse';
width: number;
height: number;
}
export interface ExcalidrawDiamondElement extends ExcalidrawElementBase {
type: 'diamond';
width: number;
height: number;
}
export interface ExcalidrawArrowElement extends ExcalidrawElementBase {
type: 'arrow';
points: readonly [number, number][];
lastCommittedPoint?: readonly [number, number] | null;
startBinding?: ExcalidrawBinding | null;
endBinding?: ExcalidrawBinding | null;
startArrowhead?: string | null;
endArrowhead?: string | null;
}
export interface ExcalidrawLineElement extends ExcalidrawElementBase {
type: 'line';
points: readonly [number, number][];
lastCommittedPoint?: readonly [number, number] | null;
startBinding?: ExcalidrawBinding | null;
endBinding?: ExcalidrawBinding | null;
}
export interface ExcalidrawFreedrawElement extends ExcalidrawElementBase {
type: 'freedraw';
points: readonly [number, number][];
pressures?: readonly number[];
simulatePressure?: boolean;
lastCommittedPoint?: readonly [number, number] | null;
}
export type ExcalidrawElement =
| ExcalidrawTextElement
| ExcalidrawRectangleElement
| ExcalidrawEllipseElement
| ExcalidrawDiamondElement
| ExcalidrawArrowElement
| ExcalidrawLineElement
| ExcalidrawFreedrawElement;
export interface ExcalidrawBoundElement {
id: string;
type: 'text' | 'arrow';
}
export interface ExcalidrawBinding {
elementId: string;
focus: number;
gap: number;
fixedPoint?: readonly [number, number] | null;
}
export type ExcalidrawElementType = 'rectangle' | 'ellipse' | 'diamond' | 'arrow' | 'text' | 'line' | 'freedraw' | 'label';
// Excalidraw element types
export const EXCALIDRAW_ELEMENT_TYPES: Record<string, ExcalidrawElementType> = {
RECTANGLE: 'rectangle',
ELLIPSE: 'ellipse',
DIAMOND: 'diamond',
ARROW: 'arrow',
TEXT: 'text',
LABEL: 'label',
FREEDRAW: 'freedraw',
LINE: 'line'
} as const;
// Server-side element with metadata
export interface ServerElement extends Omit<ExcalidrawElementBase, 'id'> {
id: string;
type: ExcalidrawElementType;
createdAt?: string;
updatedAt?: string;
version?: number;
syncedAt?: string;
source?: string;
syncTimestamp?: string;
text?: string;
fontSize?: number;
fontFamily?: string | number;
label?: {
text: string;
};
}
// API Response types
export interface ApiResponse<T = any> {
success: boolean;
data?: T;
error?: string;
message?: string;
}
export interface ElementsResponse extends ApiResponse {
elements: ServerElement[];
count: number;
}
export interface ElementResponse extends ApiResponse {
element: ServerElement;
}
export interface SyncResponse extends ApiResponse {
count: number;
syncedAt: string;
beforeCount: number;
afterCount: number;
}
// WebSocket message types
export interface WebSocketMessage {
type: WebSocketMessageType;
[key: string]: any;
}
export type WebSocketMessageType =
| 'initial_elements'
| 'element_created'
| 'element_updated'
| 'element_deleted'
| 'elements_batch_created'
| 'elements_synced'
| 'sync_status'
| 'mermaid_convert';
export interface InitialElementsMessage extends WebSocketMessage {
type: 'initial_elements';
elements: ServerElement[];
}
export interface ElementCreatedMessage extends WebSocketMessage {
type: 'element_created';
element: ServerElement;
}
export interface ElementUpdatedMessage extends WebSocketMessage {
type: 'element_updated';
element: ServerElement;
}
export interface ElementDeletedMessage extends WebSocketMessage {
type: 'element_deleted';
elementId: string;
}
export interface BatchCreatedMessage extends WebSocketMessage {
type: 'elements_batch_created';
elements: ServerElement[];
}
export interface SyncStatusMessage extends WebSocketMessage {
type: 'sync_status';
elementCount: number;
timestamp: string;
}
export interface MermaidConvertMessage extends WebSocketMessage {
type: 'mermaid_convert';
mermaidDiagram: string;
config?: MermaidConfig;
timestamp: string;
}
// Mermaid conversion types
export interface MermaidConfig {
startOnLoad?: boolean;
flowchart?: {
curve?: 'linear' | 'basis';
};
themeVariables?: {
fontSize?: string;
};
maxEdges?: number;
maxTextSize?: number;
}
export interface MermaidConversionRequest {
mermaidDiagram: string;
config?: MermaidConfig;
}
export interface MermaidConversionResponse extends ApiResponse {
elements: ServerElement[];
files?: any;
count: number;
}
// In-memory storage for Excalidraw elements
export const elements = new Map<string, ServerElement>();
// Validation function for Excalidraw elements
export function validateElement(element: Partial<ServerElement>): element is ServerElement {
const requiredFields: (keyof ServerElement)[] = ['type', 'x', 'y'];
const hasRequiredFields = requiredFields.every(field => field in element);
if (!hasRequiredFields) {
throw new Error(`Missing required fields: ${requiredFields.join(', ')}`);
}
if (!Object.values(EXCALIDRAW_ELEMENT_TYPES).includes(element.type as ExcalidrawElementType)) {
throw new Error(`Invalid element type: ${element.type}`);
}
return true;
}
// Helper function to generate unique IDs
export function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substring(2);
}