Fixing eslint issues
Some checks failed
Main Workflow / Test and Build (20.x) (push) Successful in 3m58s
Main Workflow / Security Audit (push) Successful in 4m10s
Main Workflow / Test and Build (18.x) (push) Failing after 2m46s
Main Workflow / Build Release Artifacts (push) Has been skipped
Main Workflow / Code Quality Check (push) Failing after 1m51s
Main Workflow / Notification (push) Failing after 21s

This commit is contained in:
2025-07-23 05:20:31 +00:00
parent 6aa7e91874
commit 9d5dc9ae7d
12 changed files with 432 additions and 570 deletions

126
eslint.config.js Normal file
View File

@ -0,0 +1,126 @@
import js from '@eslint/js';
import tseslint from '@typescript-eslint/eslint-plugin';
import tsparser from '@typescript-eslint/parser';
export default [
// Base JavaScript configuration
js.configs.recommended,
// Configuration for TypeScript files
{
files: ['**/*.ts', '**/*.tsx'],
ignores: ['**/*.test.ts', '**/*.spec.ts', 'src/tests/**/*.ts'],
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
project: './tsconfig.json',
},
globals: {
console: 'readonly',
process: 'readonly',
Buffer: 'readonly',
global: 'readonly',
},
},
plugins: {
'@typescript-eslint': tseslint,
},
rules: {
// TypeScript recommended rules
...tseslint.configs.recommended.rules,
// Custom rules for the project
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-var-requires': 'error',
// General JavaScript/TypeScript rules
'no-console': 'warn',
'no-debugger': 'error',
'no-duplicate-imports': 'error',
'no-unused-expressions': 'error',
'prefer-const': 'error',
'no-var': 'error',
// Style rules
'indent': ['error', 2],
'quotes': ['error', 'single'],
'semi': ['error', 'always'],
'comma-dangle': ['error', 'always-multiline'],
'object-curly-spacing': ['error', 'always'],
'array-bracket-spacing': ['error', 'never'],
},
},
// Specific configuration for test files
{
files: ['**/*.test.ts', '**/*.spec.ts', 'src/tests/**/*.ts'],
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
globals: {
console: 'readonly',
process: 'readonly',
Buffer: 'readonly',
global: 'writable',
describe: 'readonly',
it: 'readonly',
test: 'readonly',
expect: 'readonly',
beforeEach: 'readonly',
afterEach: 'readonly',
beforeAll: 'readonly',
afterAll: 'readonly',
jest: 'readonly',
require: 'readonly',
},
},
plugins: {
'@typescript-eslint': tseslint,
},
rules: {
// Allow console.log in tests
'no-console': 'off',
// Allow any in tests for mocking
'@typescript-eslint/no-explicit-any': 'off',
// Allow unused vars in tests (for setup)
'@typescript-eslint/no-unused-vars': 'off',
},
},
// Configuration for JavaScript files (if any)
{
files: ['**/*.js', '**/*.mjs'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
rules: {
'no-console': 'warn',
'no-debugger': 'error',
'prefer-const': 'error',
'no-var': 'error',
},
},
// Files and directories to ignore
{
ignores: [
'node_modules/**',
'build/**',
'dist/**',
'*.config.js',
'coverage/**',
'.git/**',
'.vscode/**',
'*.min.js',
],
},
];

View File

@ -1,5 +1,5 @@
// index.d.ts // index.d.ts
declare module "*.gql" { declare module '*.gql' {
const content: any; const content: any;
export default content; export default content;
} }

View File

@ -6,98 +6,98 @@ const DIAGRAM_KEYWORDS: Record<DiagramType, string[]> = {
'proceso', 'flujo', 'workflow', 'aprobación', 'tarea', 'decisión', 'gateway', 'evento', 'proceso', 'flujo', 'workflow', 'aprobación', 'tarea', 'decisión', 'gateway', 'evento',
'actividad', 'secuencia', 'paralelo', 'exclusivo', 'inicio', 'fin', 'subprocess', 'actividad', 'secuencia', 'paralelo', 'exclusivo', 'inicio', 'fin', 'subprocess',
'process', 'flow', 'approval', 'task', 'decision', 'activity', 'sequence', 'parallel', 'process', 'flow', 'approval', 'task', 'decision', 'activity', 'sequence', 'parallel',
'exclusive', 'start', 'end', 'business process' 'exclusive', 'start', 'end', 'business process',
], ],
[DiagramType.BPMN_COLLABORATION]: [ [DiagramType.BPMN_COLLABORATION]: [
'colaboración', 'participante', 'pool', 'lane', 'mensaje', 'collaboration', 'colaboración', 'participante', 'pool', 'lane', 'mensaje', 'collaboration',
'participant', 'message', 'proceso', 'flujo', 'workflow' 'participant', 'message', 'proceso', 'flujo', 'workflow',
], ],
[DiagramType.BPMN_CHOREOGRAPHY]: [ [DiagramType.BPMN_CHOREOGRAPHY]: [
'coreografía', 'intercambio', 'mensaje', 'choreography', 'exchange', 'message', 'coreografía', 'intercambio', 'mensaje', 'choreography', 'exchange', 'message',
'comunicación', 'communication', 'protocolo', 'protocol' 'comunicación', 'communication', 'protocolo', 'protocol',
], ],
[DiagramType.ER_DIAGRAM]: [ [DiagramType.ER_DIAGRAM]: [
'base de datos', 'tabla', 'entidad', 'relación', 'campo', 'clave', 'foreign key', 'base de datos', 'tabla', 'entidad', 'relación', 'campo', 'clave', 'foreign key',
'primary key', 'atributo', 'cardinalidad', 'normalización', 'esquema', 'primary key', 'atributo', 'cardinalidad', 'normalización', 'esquema',
'database', 'table', 'entity', 'relationship', 'field', 'key', 'attribute', 'database', 'table', 'entity', 'relationship', 'field', 'key', 'attribute',
'cardinality', 'normalization', 'schema', 'sql', 'modelo de datos' 'cardinality', 'normalization', 'schema', 'sql', 'modelo de datos',
], ],
[DiagramType.DATABASE_SCHEMA]: [ [DiagramType.DATABASE_SCHEMA]: [
'esquema', 'schema', 'base de datos', 'database', 'tabla', 'table', 'sql' 'esquema', 'schema', 'base de datos', 'database', 'tabla', 'table', 'sql',
], ],
[DiagramType.CONCEPTUAL_MODEL]: [ [DiagramType.CONCEPTUAL_MODEL]: [
'modelo conceptual', 'conceptual model', 'concepto', 'concept', 'dominio', 'domain' 'modelo conceptual', 'conceptual model', 'concepto', 'concept', 'dominio', 'domain',
], ],
[DiagramType.SYSTEM_ARCHITECTURE]: [ [DiagramType.SYSTEM_ARCHITECTURE]: [
'arquitectura', 'sistema', 'componente', 'servicio', 'api', 'microservicio', 'capa', 'arquitectura', 'sistema', 'componente', 'servicio', 'api', 'microservicio', 'capa',
'módulo', 'interfaz', 'dependencia', 'infraestructura', 'servidor', 'módulo', 'interfaz', 'dependencia', 'infraestructura', 'servidor',
'architecture', 'system', 'component', 'service', 'microservice', 'layer', 'architecture', 'system', 'component', 'service', 'microservice', 'layer',
'module', 'interface', 'dependency', 'infrastructure', 'server', 'backend', 'frontend' 'module', 'interface', 'dependency', 'infrastructure', 'server', 'backend', 'frontend',
], ],
[DiagramType.MICROSERVICES]: [ [DiagramType.MICROSERVICES]: [
'microservicio', 'microservice', 'servicio', 'service', 'api', 'contenedor', 'container' 'microservicio', 'microservice', 'servicio', 'service', 'api', 'contenedor', 'container',
], ],
[DiagramType.LAYERED_ARCHITECTURE]: [ [DiagramType.LAYERED_ARCHITECTURE]: [
'capas', 'layers', 'arquitectura por capas', 'layered architecture', 'nivel', 'tier' 'capas', 'layers', 'arquitectura por capas', 'layered architecture', 'nivel', 'tier',
], ],
[DiagramType.C4_CONTEXT]: [ [DiagramType.C4_CONTEXT]: [
'c4', 'contexto', 'context', 'sistema', 'system', 'usuario', 'user', 'actor' 'c4', 'contexto', 'context', 'sistema', 'system', 'usuario', 'user', 'actor',
], ],
[DiagramType.C4_CONTAINER]: [ [DiagramType.C4_CONTAINER]: [
'c4', 'contenedor', 'container', 'aplicación', 'application', 'servicio', 'service' 'c4', 'contenedor', 'container', 'aplicación', 'application', 'servicio', 'service',
], ],
[DiagramType.C4_COMPONENT]: [ [DiagramType.C4_COMPONENT]: [
'c4', 'componente', 'component', 'módulo', 'module', 'clase', 'class' 'c4', 'componente', 'component', 'módulo', 'module', 'clase', 'class',
], ],
[DiagramType.UML_CLASS]: [ [DiagramType.UML_CLASS]: [
'clase', 'objeto', 'herencia', 'polimorfismo', 'encapsulación', 'método', 'atributo', 'clase', 'objeto', 'herencia', 'polimorfismo', 'encapsulación', 'método', 'atributo',
'class', 'object', 'inheritance', 'polymorphism', 'encapsulation', 'method', 'class', 'object', 'inheritance', 'polymorphism', 'encapsulation', 'method',
'uml', 'orientado a objetos', 'oop' 'uml', 'orientado a objetos', 'oop',
], ],
[DiagramType.UML_SEQUENCE]: [ [DiagramType.UML_SEQUENCE]: [
'secuencia', 'sequence', 'mensaje', 'message', 'tiempo', 'time', 'interacción', 'interaction' 'secuencia', 'sequence', 'mensaje', 'message', 'tiempo', 'time', 'interacción', 'interaction',
], ],
[DiagramType.UML_USE_CASE]: [ [DiagramType.UML_USE_CASE]: [
'caso de uso', 'use case', 'actor', 'funcionalidad', 'functionality', 'requisito', 'requirement' 'caso de uso', 'use case', 'actor', 'funcionalidad', 'functionality', 'requisito', 'requirement',
], ],
[DiagramType.UML_ACTIVITY]: [ [DiagramType.UML_ACTIVITY]: [
'actividad', 'activity', 'flujo', 'flow', 'acción', 'action', 'proceso', 'process' 'actividad', 'activity', 'flujo', 'flow', 'acción', 'action', 'proceso', 'process',
], ],
[DiagramType.UML_STATE]: [ [DiagramType.UML_STATE]: [
'estado', 'state', 'transición', 'transition', 'máquina de estados', 'state machine' 'estado', 'state', 'transición', 'transition', 'máquina de estados', 'state machine',
], ],
[DiagramType.UML_COMPONENT]: [ [DiagramType.UML_COMPONENT]: [
'componente', 'component', 'interfaz', 'interface', 'dependencia', 'dependency' 'componente', 'component', 'interfaz', 'interface', 'dependencia', 'dependency',
], ],
[DiagramType.UML_DEPLOYMENT]: [ [DiagramType.UML_DEPLOYMENT]: [
'despliegue', 'deployment', 'nodo', 'node', 'artefacto', 'artifact', 'hardware' 'despliegue', 'deployment', 'nodo', 'node', 'artefacto', 'artifact', 'hardware',
], ],
[DiagramType.NETWORK_TOPOLOGY]: [ [DiagramType.NETWORK_TOPOLOGY]: [
'red', 'router', 'switch', 'firewall', 'vlan', 'subred', 'ip', 'tcp', 'protocolo', 'red', 'router', 'switch', 'firewall', 'vlan', 'subred', 'ip', 'tcp', 'protocolo',
'network', 'topology', 'subnet', 'protocol', 'ethernet', 'wifi', 'lan', 'wan' 'network', 'topology', 'subnet', 'protocol', 'ethernet', 'wifi', 'lan', 'wan',
], ],
[DiagramType.INFRASTRUCTURE]: [ [DiagramType.INFRASTRUCTURE]: [
'infraestructura', 'infrastructure', 'servidor', 'server', 'hardware', 'datacenter' 'infraestructura', 'infrastructure', 'servidor', 'server', 'hardware', 'datacenter',
], ],
[DiagramType.CLOUD_ARCHITECTURE]: [ [DiagramType.CLOUD_ARCHITECTURE]: [
'nube', 'cloud', 'aws', 'azure', 'gcp', 'kubernetes', 'docker', 'contenedor' 'nube', 'cloud', 'aws', 'azure', 'gcp', 'kubernetes', 'docker', 'contenedor',
], ],
[DiagramType.FLOWCHART]: [ [DiagramType.FLOWCHART]: [
'diagrama de flujo', 'flowchart', 'algoritmo', 'lógica', 'condición', 'bucle', 'diagrama de flujo', 'flowchart', 'algoritmo', 'lógica', 'condición', 'bucle',
'algorithm', 'logic', 'condition', 'loop', 'if', 'while', 'for' 'algorithm', 'logic', 'condition', 'loop', 'if', 'while', 'for',
], ],
[DiagramType.ORGCHART]: [ [DiagramType.ORGCHART]: [
'organigrama', 'orgchart', 'jerarquía', 'hierarchy', 'organización', 'organization' 'organigrama', 'orgchart', 'jerarquía', 'hierarchy', 'organización', 'organization',
], ],
[DiagramType.MINDMAP]: [ [DiagramType.MINDMAP]: [
'mapa mental', 'mindmap', 'idea', 'concepto', 'brainstorming', 'lluvia de ideas' 'mapa mental', 'mindmap', 'idea', 'concepto', 'brainstorming', 'lluvia de ideas',
], ],
[DiagramType.WIREFRAME]: [ [DiagramType.WIREFRAME]: [
'wireframe', 'mockup', 'prototipo', 'prototype', 'interfaz', 'interface', 'ui', 'ux' 'wireframe', 'mockup', 'prototipo', 'prototype', 'interfaz', 'interface', 'ui', 'ux',
], ],
[DiagramType.GANTT]: [ [DiagramType.GANTT]: [
'gantt', 'cronograma', 'timeline', 'proyecto', 'project', 'tarea', 'task', 'tiempo' 'gantt', 'cronograma', 'timeline', 'proyecto', 'project', 'tarea', 'task', 'tiempo',
] ],
}; };
// Entity extraction patterns for different diagram types // Entity extraction patterns for different diagram types
@ -105,18 +105,18 @@ const ENTITY_PATTERNS: Partial<Record<DiagramType, Record<string, RegExp>>> = {
[DiagramType.BPMN_PROCESS]: { [DiagramType.BPMN_PROCESS]: {
tasks: /(?:tarea|task|actividad|activity|paso|step)\s*:?\s*([^,.\n]+)/gi, tasks: /(?:tarea|task|actividad|activity|paso|step)\s*:?\s*([^,.\n]+)/gi,
events: /(?:evento|event|inicio|start|fin|end)\s*:?\s*([^,.\n]+)/gi, events: /(?:evento|event|inicio|start|fin|end)\s*:?\s*([^,.\n]+)/gi,
decisions: /(?:decisión|decision|gateway|condición|condition)\s*:?\s*([^,.\n]+)/gi decisions: /(?:decisión|decision|gateway|condición|condition)\s*:?\s*([^,.\n]+)/gi,
}, },
[DiagramType.ER_DIAGRAM]: { [DiagramType.ER_DIAGRAM]: {
entities: /(?:tabla|table|entidad|entity)\s*:?\s*([^,.\n]+)/gi, entities: /(?:tabla|table|entidad|entity)\s*:?\s*([^,.\n]+)/gi,
attributes: /(?:campo|field|atributo|attribute|columna|column)\s*:?\s*([^,.\n]+)/gi, attributes: /(?:campo|field|atributo|attribute|columna|column)\s*:?\s*([^,.\n]+)/gi,
relationships: /(?:relación|relationship|conecta|connects?|relaciona)\s*([^,.\n]+)/gi relationships: /(?:relación|relationship|conecta|connects?|relaciona)\s*([^,.\n]+)/gi,
}, },
[DiagramType.SYSTEM_ARCHITECTURE]: { [DiagramType.SYSTEM_ARCHITECTURE]: {
components: /(?:componente|component|servicio|service|módulo|module)\s*:?\s*([^,.\n]+)/gi, components: /(?:componente|component|servicio|service|módulo|module)\s*:?\s*([^,.\n]+)/gi,
layers: /(?:capa|layer|nivel|tier)\s*:?\s*([^,.\n]+)/gi, layers: /(?:capa|layer|nivel|tier)\s*:?\s*([^,.\n]+)/gi,
services: /(?:api|servicio|service|microservicio|microservice)\s*:?\s*([^,.\n]+)/gi services: /(?:api|servicio|service|microservicio|microservice)\s*:?\s*([^,.\n]+)/gi,
} },
}; };
/** /**
@ -147,7 +147,7 @@ export const analyzeDiagramDescription = async (description: string): Promise<Di
entities, entities,
relationships, relationships,
keywords, keywords,
reasoning: generateReasoning(bestMatch, keywords, entities) reasoning: generateReasoning(bestMatch, keywords, entities),
}; };
}; };
@ -177,7 +177,7 @@ const getBestDiagramType = (scores: Record<DiagramType, number>): { type: Diagra
return { type: DiagramType.FLOWCHART, confidence: 0.3 }; return { type: DiagramType.FLOWCHART, confidence: 0.3 };
} }
const bestType = entries.find(([_, score]) => score === maxScore)?.[0] as DiagramType; const bestType = entries.find(([_category, score]) => score === maxScore)?.[0] as DiagramType;
const totalKeywords = DIAGRAM_KEYWORDS[bestType]?.length || 1; const totalKeywords = DIAGRAM_KEYWORDS[bestType]?.length || 1;
const confidence = Math.min(maxScore / totalKeywords, 1); const confidence = Math.min(maxScore / totalKeywords, 1);
@ -197,7 +197,7 @@ const extractEntities = (description: string, diagramType: DiagramType): string[
} }
// Extract entities using specific patterns // Extract entities using specific patterns
for (const [category, pattern] of Object.entries(patterns)) { for (const pattern of Object.values(patterns)) {
const matches = [...description.matchAll(pattern)]; const matches = [...description.matchAll(pattern)];
entities.push(...matches.map(match => match[1]?.trim()).filter(Boolean)); entities.push(...matches.map(match => match[1]?.trim()).filter(Boolean));
} }
@ -221,7 +221,7 @@ const extractGenericEntities = (description: string): string[] => {
const entities = [ const entities = [
...capitalizedWords, ...capitalizedWords,
...quotedStrings.map(s => s.replace(/"/g, '')) ...quotedStrings.map(s => s.replace(/"/g, '')),
]; ];
return [...new Set(entities)].slice(0, 10); // Limit to 10 entities return [...new Set(entities)].slice(0, 10); // Limit to 10 entities
@ -233,7 +233,7 @@ const extractGenericEntities = (description: string): string[] => {
const extractRelationships = ( const extractRelationships = (
description: string, description: string,
entities: string[], entities: string[],
diagramType: DiagramType diagramType: DiagramType,
): Array<{ from: string; to: string; type: string; label?: string }> => { ): Array<{ from: string; to: string; type: string; label?: string }> => {
const relationships: Array<{ from: string; to: string; type: string; label?: string }> = []; const relationships: Array<{ from: string; to: string; type: string; label?: string }> = [];
@ -241,7 +241,7 @@ const extractRelationships = (
const relationshipKeywords = [ const relationshipKeywords = [
'conecta', 'connects', 'relaciona', 'relates', 'depende', 'depends', 'conecta', 'connects', 'relaciona', 'relates', 'depende', 'depends',
'hereda', 'inherits', 'implementa', 'implements', 'usa', 'uses', 'hereda', 'inherits', 'implementa', 'implements', 'usa', 'uses',
'tiene', 'has', 'contiene', 'contains', 'pertenece', 'belongs' 'tiene', 'has', 'contiene', 'contains', 'pertenece', 'belongs',
]; ];
// Simple relationship extraction based on proximity and keywords // Simple relationship extraction based on proximity and keywords
@ -254,7 +254,7 @@ const extractRelationships = (
const pattern = new RegExp( const pattern = new RegExp(
`${entity1}.{0,50}(?:${relationshipKeywords.join('|')}).{0,50}${entity2}|` + `${entity1}.{0,50}(?:${relationshipKeywords.join('|')}).{0,50}${entity2}|` +
`${entity2}.{0,50}(?:${relationshipKeywords.join('|')}).{0,50}${entity1}`, `${entity2}.{0,50}(?:${relationshipKeywords.join('|')}).{0,50}${entity1}`,
'i' 'i',
); );
if (pattern.test(description)) { if (pattern.test(description)) {
@ -262,7 +262,7 @@ const extractRelationships = (
from: entity1, from: entity1,
to: entity2, to: entity2,
type: getRelationshipType(diagramType), type: getRelationshipType(diagramType),
label: extractRelationshipLabel(description, entity1, entity2) label: extractRelationshipLabel(description, entity1, entity2),
}); });
} }
} }
@ -276,16 +276,16 @@ const extractRelationships = (
*/ */
const getRelationshipType = (diagramType: DiagramType): string => { const getRelationshipType = (diagramType: DiagramType): string => {
switch (diagramType) { switch (diagramType) {
case DiagramType.BPMN_PROCESS: case DiagramType.BPMN_PROCESS:
return 'sequence'; return 'sequence';
case DiagramType.ER_DIAGRAM: case DiagramType.ER_DIAGRAM:
return 'relationship'; return 'relationship';
case DiagramType.SYSTEM_ARCHITECTURE: case DiagramType.SYSTEM_ARCHITECTURE:
return 'dependency'; return 'dependency';
case DiagramType.UML_CLASS: case DiagramType.UML_CLASS:
return 'association'; return 'association';
default: default:
return 'connection'; return 'connection';
} }
}; };
@ -313,7 +313,7 @@ const getMatchedKeywords = (text: string, diagramType: DiagramType): string[] =>
const generateReasoning = ( const generateReasoning = (
bestMatch: { type: DiagramType; confidence: number }, bestMatch: { type: DiagramType; confidence: number },
keywords: string[], keywords: string[],
entities: string[] entities: string[],
): string => { ): string => {
return `Detected ${bestMatch.type} with ${Math.round(bestMatch.confidence * 100)}% confidence. ` + return `Detected ${bestMatch.type} with ${Math.round(bestMatch.confidence * 100)}% confidence. ` +
`Found ${keywords.length} relevant keywords: ${keywords.slice(0, 5).join(', ')}. ` + `Found ${keywords.length} relevant keywords: ${keywords.slice(0, 5).join(', ')}. ` +

View File

@ -1,5 +1,4 @@
import { DiagramType, DiagramData, DiagramElement, DiagramConnection } from '../types/diagram-types.js'; import { DiagramType, DiagramData, DiagramElement, DiagramConnection, DiagramAnalysis } from '../types/diagram-types.js';
import { DiagramAnalysis } from '../types/diagram-types.js';
/** /**
* Generate intelligent system architecture diagram based on analysis * Generate intelligent system architecture diagram based on analysis
@ -7,7 +6,7 @@ import { DiagramAnalysis } from '../types/diagram-types.js';
export const generateSmartArchitectureDiagram = ( export const generateSmartArchitectureDiagram = (
description: string, description: string,
analysis: DiagramAnalysis, analysis: DiagramAnalysis,
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {} preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {},
): DiagramData => { ): DiagramData => {
const { entities, relationships } = analysis; const { entities, relationships } = analysis;
const complexity = preferences.complexity || 'detailed'; const complexity = preferences.complexity || 'detailed';
@ -55,7 +54,7 @@ export const generateSmartArchitectureDiagram = (
`component-${sourceComponent.name}`, `component-${sourceComponent.name}`,
`component-${targetComponent.name}`, `component-${targetComponent.name}`,
connection.type, connection.type,
connection.label connection.label,
); );
connectionElements.push(connectionElement); connectionElements.push(connectionElement);
} }
@ -69,8 +68,8 @@ export const generateSmartArchitectureDiagram = (
format: 'drawio' as any, format: 'drawio' as any,
created: new Date().toISOString(), created: new Date().toISOString(),
modified: new Date().toISOString(), modified: new Date().toISOString(),
version: '1.0' version: '1.0',
} },
}; };
}; };
@ -98,7 +97,7 @@ const extractArchitectureComponents = (entities: string[], description: string):
frontend: ['frontend', 'cliente', 'client', 'ui', 'interfaz', 'react', 'vue', 'angular'], frontend: ['frontend', 'cliente', 'client', 'ui', 'interfaz', 'react', 'vue', 'angular'],
backend: ['backend', 'servidor', 'server', 'node', 'express', 'spring'], backend: ['backend', 'servidor', 'server', 'node', 'express', 'spring'],
middleware: ['middleware', 'proxy', 'gateway', 'balanceador', 'load balancer'], middleware: ['middleware', 'proxy', 'gateway', 'balanceador', 'load balancer'],
external: ['externo', 'external', 'tercero', 'third party', 'integración', 'integration'] external: ['externo', 'external', 'tercero', 'third party', 'integración', 'integration'],
}; };
// Extract components from entities // Extract components from entities
@ -110,7 +109,7 @@ const extractArchitectureComponents = (entities: string[], description: string):
name: cleanComponentName(entity), name: cleanComponentName(entity),
type: componentType, type: componentType,
description: extractComponentDescription(entity, description), description: extractComponentDescription(entity, description),
technologies technologies,
}); });
}); });
@ -124,7 +123,7 @@ const extractArchitectureComponents = (entities: string[], description: string):
{ name: 'Frontend', type: 'frontend', technologies: ['React'] }, { name: 'Frontend', type: 'frontend', technologies: ['React'] },
{ name: 'API Gateway', type: 'middleware', technologies: ['Express'] }, { name: 'API Gateway', type: 'middleware', technologies: ['Express'] },
{ name: 'Backend Service', type: 'backend', technologies: ['Node.js'] }, { name: 'Backend Service', type: 'backend', technologies: ['Node.js'] },
{ name: 'Database', type: 'database', technologies: ['PostgreSQL'] } { name: 'Database', type: 'database', technologies: ['PostgreSQL'] },
); );
} }
@ -136,7 +135,7 @@ const extractArchitectureComponents = (entities: string[], description: string):
*/ */
const inferComponentType = ( const inferComponentType = (
componentName: string, componentName: string,
typeMap: Record<string, string[]> typeMap: Record<string, string[]>,
): 'service' | 'database' | 'api' | 'frontend' | 'backend' | 'middleware' | 'external' => { ): 'service' | 'database' | 'api' | 'frontend' | 'backend' | 'middleware' | 'external' => {
const name = componentName.toLowerCase(); const name = componentName.toLowerCase();
@ -157,7 +156,7 @@ const extractTechnologies = (componentName: string, description: string): string
const techKeywords = [ const techKeywords = [
'react', 'vue', 'angular', 'node', 'express', 'spring', 'django', 'react', 'vue', 'angular', 'node', 'express', 'spring', 'django',
'mysql', 'postgresql', 'mongodb', 'redis', 'docker', 'kubernetes', 'mysql', 'postgresql', 'mongodb', 'redis', 'docker', 'kubernetes',
'aws', 'azure', 'gcp', 'nginx', 'apache', 'java', 'python', 'javascript' 'aws', 'azure', 'gcp', 'nginx', 'apache', 'java', 'python', 'javascript',
]; ];
const text = description.toLowerCase(); const text = description.toLowerCase();
@ -177,7 +176,7 @@ const extractComponentDescription = (componentName: string, description: string)
// Look for descriptions near the component name // Look for descriptions near the component name
const patterns = [ const patterns = [
new RegExp(`${componentName}\\s+(?:es|is|se encarga de|handles?)\\s+([^.]{1,100})`, 'gi'), new RegExp(`${componentName}\\s+(?:es|is|se encarga de|handles?)\\s+([^.]{1,100})`, 'gi'),
new RegExp(`${componentName}\\s*:?\\s*([^.]{1,100})`, 'gi') new RegExp(`${componentName}\\s*:?\\s*([^.]{1,100})`, 'gi'),
]; ];
for (const pattern of patterns) { for (const pattern of patterns) {
@ -195,7 +194,7 @@ const extractComponentDescription = (componentName: string, description: string)
*/ */
const extractComponentsFromDescription = ( const extractComponentsFromDescription = (
description: string, description: string,
typeMap: Record<string, string[]> typeMap: Record<string, string[]>,
): Array<{ ): Array<{
name: string; name: string;
type: 'service' | 'database' | 'api' | 'frontend' | 'backend' | 'middleware' | 'external'; type: 'service' | 'database' | 'api' | 'frontend' | 'backend' | 'middleware' | 'external';
@ -212,7 +211,7 @@ const extractComponentsFromDescription = (
// Look for component patterns // Look for component patterns
const componentPatterns = [ const componentPatterns = [
/(?:componente|component|servicio|service|módulo|module)\s*:?\s*([^,.\n]+)/gi, /(?:componente|component|servicio|service|módulo|module)\s*:?\s*([^,.\n]+)/gi,
/(?:incluye|includes?|tiene|has)\s+(?:un|una|a|an)?\s*([^,.\n]+)/gi /(?:incluye|includes?|tiene|has)\s+(?:un|una|a|an)?\s*([^,.\n]+)/gi,
]; ];
for (const pattern of componentPatterns) { for (const pattern of componentPatterns) {
@ -226,7 +225,7 @@ const extractComponentsFromDescription = (
components.push({ components.push({
name, name,
type, type,
technologies technologies,
}); });
} }
} }
@ -240,7 +239,7 @@ const extractComponentsFromDescription = (
*/ */
const extractArchitectureLayers = ( const extractArchitectureLayers = (
description: string, description: string,
components: Array<{ name: string; type: string }> components: Array<{ name: string; type: string }>,
): Array<{ ): Array<{
name: string; name: string;
type: 'presentation' | 'business' | 'data' | 'integration'; type: 'presentation' | 'business' | 'data' | 'integration';
@ -256,33 +255,33 @@ const extractArchitectureLayers = (
const layerMappings = { const layerMappings = {
presentation: { presentation: {
name: 'Capa de Presentación', name: 'Capa de Presentación',
types: ['frontend', 'ui', 'client'] types: ['frontend', 'ui', 'client'],
}, },
business: { business: {
name: 'Capa de Negocio', name: 'Capa de Negocio',
types: ['service', 'backend', 'api'] types: ['service', 'backend', 'api'],
}, },
data: { data: {
name: 'Capa de Datos', name: 'Capa de Datos',
types: ['database', 'storage'] types: ['database', 'storage'],
}, },
integration: { integration: {
name: 'Capa de Integración', name: 'Capa de Integración',
types: ['middleware', 'external', 'gateway'] types: ['middleware', 'external', 'gateway'],
} },
}; };
// Organize components into layers // Organize components into layers
Object.entries(layerMappings).forEach(([layerType, layerConfig]) => { Object.entries(layerMappings).forEach(([layerType, layerConfig]) => {
const layerComponents = components.filter(component => const layerComponents = components.filter(component =>
layerConfig.types.some(type => component.type.includes(type)) layerConfig.types.some(type => component.type.includes(type)),
); );
if (layerComponents.length > 0) { if (layerComponents.length > 0) {
layers.push({ layers.push({
name: layerConfig.name, name: layerConfig.name,
type: layerType as any, type: layerType as any,
components: layerComponents components: layerComponents,
}); });
} }
}); });
@ -293,18 +292,18 @@ const extractArchitectureLayers = (
{ {
name: 'Capa de Presentación', name: 'Capa de Presentación',
type: 'presentation', type: 'presentation',
components: components.filter(c => c.type === 'frontend') components: components.filter(c => c.type === 'frontend'),
}, },
{ {
name: 'Capa de Negocio', name: 'Capa de Negocio',
type: 'business', type: 'business',
components: components.filter(c => ['service', 'backend', 'api'].includes(c.type)) components: components.filter(c => ['service', 'backend', 'api'].includes(c.type)),
}, },
{ {
name: 'Capa de Datos', name: 'Capa de Datos',
type: 'data', type: 'data',
components: components.filter(c => c.type === 'database') components: components.filter(c => c.type === 'database'),
} },
); );
} }
@ -317,7 +316,7 @@ const extractArchitectureLayers = (
const extractArchitectureConnections = ( const extractArchitectureConnections = (
relationships: Array<{ from: string; to: string; type: string; label?: string }>, relationships: Array<{ from: string; to: string; type: string; label?: string }>,
description: string, description: string,
components: Array<{ name: string; type: string }> components: Array<{ name: string; type: string }>,
): Array<{ from: string; to: string; type: string; label?: string }> => { ): Array<{ from: string; to: string; type: string; label?: string }> => {
const connections: Array<{ from: string; to: string; type: string; label?: string }> = []; const connections: Array<{ from: string; to: string; type: string; label?: string }> = [];
@ -325,7 +324,7 @@ const extractArchitectureConnections = (
relationships.forEach(rel => { relationships.forEach(rel => {
connections.push({ connections.push({
...rel, ...rel,
type: inferConnectionType(rel.from, rel.to, components) type: inferConnectionType(rel.from, rel.to, components),
}); });
}); });
@ -340,7 +339,7 @@ const extractArchitectureConnections = (
// Check if connection already exists // Check if connection already exists
const exists = connections.some(conn => const exists = connections.some(conn =>
(conn.from === comp1.name && conn.to === comp2.name) || (conn.from === comp1.name && conn.to === comp2.name) ||
(conn.from === comp2.name && conn.to === comp1.name) (conn.from === comp2.name && conn.to === comp1.name),
); );
if (!exists) { if (!exists) {
@ -359,7 +358,7 @@ const extractArchitectureConnections = (
const inferConnectionType = ( const inferConnectionType = (
from: string, from: string,
to: string, to: string,
components: Array<{ name: string; type: string }> components: Array<{ name: string; type: string }>,
): string => { ): string => {
const fromComp = components.find(c => c.name === from); const fromComp = components.find(c => c.name === from);
const toComp = components.find(c => c.name === to); const toComp = components.find(c => c.name === to);
@ -381,7 +380,7 @@ const inferConnectionType = (
const inferImplicitConnection = ( const inferImplicitConnection = (
comp1: { name: string; type: string }, comp1: { name: string; type: string },
comp2: { name: string; type: string }, comp2: { name: string; type: string },
description: string _description: string,
): { from: string; to: string; type: string; label?: string } | null => { ): { from: string; to: string; type: string; label?: string } | null => {
// Common architecture patterns // Common architecture patterns
const patterns = [ const patterns = [
@ -389,7 +388,7 @@ const inferImplicitConnection = (
{ from: 'api', to: 'database', label: 'Query' }, { from: 'api', to: 'database', label: 'Query' },
{ from: 'service', to: 'database', label: 'Data Access' }, { from: 'service', to: 'database', label: 'Data Access' },
{ from: 'middleware', to: 'service', label: 'Route' }, { from: 'middleware', to: 'service', label: 'Route' },
{ from: 'frontend', to: 'service', label: 'API Call' } { from: 'frontend', to: 'service', label: 'API Call' },
]; ];
for (const pattern of patterns) { for (const pattern of patterns) {
@ -399,7 +398,7 @@ const inferImplicitConnection = (
from: comp1.name, from: comp1.name,
to: comp2.name, to: comp2.name,
type: inferConnectionType(comp1.name, comp2.name, [comp1, comp2]), type: inferConnectionType(comp1.name, comp2.name, [comp1, comp2]),
label: pattern.label label: pattern.label,
}; };
} }
} }
@ -420,7 +419,7 @@ const cleanComponentName = (name: string): string => {
const findComponentInLayers = ( const findComponentInLayers = (
layers: Array<{ components: Array<{ name: string; type: string }> }>, layers: Array<{ components: Array<{ name: string; type: string }> }>,
componentName: string componentName: string,
): { name: string; type: string } | null => { ): { name: string; type: string } | null => {
for (const layer of layers) { for (const layer of layers) {
const component = layer.components.find(c => c.name === componentName); const component = layer.components.find(c => c.name === componentName);
@ -438,7 +437,7 @@ const createLayer = (
layer: { name: string; type: string; components: Array<{ name: string; type: string }> }, layer: { name: string; type: string; components: Array<{ name: string; type: string }> },
x: number, x: number,
y: number, y: number,
complexity: 'simple' | 'detailed' _complexity: 'simple' | 'detailed',
): DiagramElement => { ): DiagramElement => {
const width = 800; const width = 800;
const height = 150; const height = 150;
@ -449,7 +448,7 @@ const createLayer = (
label: layer.name, label: layer.name,
geometry: { x, y, width, height }, geometry: { x, y, width, height },
style: 'swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;fillColor=#f8f9fa;strokeColor=#dee2e6;', style: 'swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;fillColor=#f8f9fa;strokeColor=#dee2e6;',
properties: { layerType: layer.type, layerName: layer.name } properties: { layerType: layer.type, layerName: layer.name },
}; };
}; };
@ -460,7 +459,7 @@ const createComponent = (
component: { name: string; type: string; description?: string; technologies?: string[] }, component: { name: string; type: string; description?: string; technologies?: string[] },
x: number, x: number,
y: number, y: number,
complexity: 'simple' | 'detailed' complexity: 'simple' | 'detailed',
): DiagramElement => { ): DiagramElement => {
const width = complexity === 'detailed' ? 180 : 120; const width = complexity === 'detailed' ? 180 : 120;
const height = complexity === 'detailed' ? 100 : 80; const height = complexity === 'detailed' ? 100 : 80;
@ -483,7 +482,7 @@ const createComponent = (
api: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;', api: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;',
service: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;', service: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;',
middleware: 'rhombus;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;', middleware: 'rhombus;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;',
external: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;dashed=1;' external: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;dashed=1;',
}; };
const style = styleMap[component.type as keyof typeof styleMap] || styleMap.service; const style = styleMap[component.type as keyof typeof styleMap] || styleMap.service;
@ -498,8 +497,8 @@ const createComponent = (
componentType: component.type, componentType: component.type,
componentName: component.name, componentName: component.name,
technologies: component.technologies || [], technologies: component.technologies || [],
description: component.description || '' description: component.description || '',
} },
}; };
}; };
@ -510,7 +509,7 @@ const createArchitectureConnection = (
sourceId: string, sourceId: string,
targetId: string, targetId: string,
connectionType: string, connectionType: string,
label?: string label?: string,
): DiagramConnection => { ): DiagramConnection => {
// Choose style based on connection type // Choose style based on connection type
const styleMap = { const styleMap = {
@ -518,7 +517,7 @@ const createArchitectureConnection = (
data_access: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#82b366;', data_access: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#82b366;',
service_call: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#6c8ebf;', service_call: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#6c8ebf;',
proxy: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#d79b00;', proxy: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#d79b00;',
dependency: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#666666;' dependency: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#666666;',
}; };
const style = styleMap[connectionType as keyof typeof styleMap] || styleMap.dependency; const style = styleMap[connectionType as keyof typeof styleMap] || styleMap.dependency;
@ -529,6 +528,6 @@ const createArchitectureConnection = (
target: targetId, target: targetId,
label: label || '', label: label || '',
style, style,
properties: { connectionType } properties: { connectionType },
}; };
}; };

View File

@ -1,5 +1,4 @@
import { DiagramType, DiagramData, DiagramElement, DiagramConnection } from '../types/diagram-types.js'; import { DiagramType, DiagramData, DiagramElement, DiagramConnection, DiagramAnalysis } from '../types/diagram-types.js';
import { DiagramAnalysis } from '../types/diagram-types.js';
/** /**
* Generate intelligent BPMN diagram based on analysis * Generate intelligent BPMN diagram based on analysis
@ -7,9 +6,9 @@ import { DiagramAnalysis } from '../types/diagram-types.js';
export const generateSmartBPMNDiagram = ( export const generateSmartBPMNDiagram = (
description: string, description: string,
analysis: DiagramAnalysis, analysis: DiagramAnalysis,
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {} preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {},
): DiagramData => { ): DiagramData => {
const { entities, relationships } = analysis; const { entities } = analysis;
const complexity = preferences.complexity || 'detailed'; const complexity = preferences.complexity || 'detailed';
// Identify different types of BPMN elements from entities // Identify different types of BPMN elements from entities
@ -22,7 +21,7 @@ export const generateSmartBPMNDiagram = (
const connections: DiagramConnection[] = []; const connections: DiagramConnection[] = [];
let currentX = 100; let currentX = 100;
let currentY = 150; const currentY = 150;
const spacing = 200; const spacing = 200;
// Add start event // Add start event
@ -39,17 +38,17 @@ export const generateSmartBPMNDiagram = (
let element: DiagramElement; let element: DiagramElement;
switch (flowElement.type) { switch (flowElement.type) {
case 'task': case 'task':
element = createTask(flowElement.name, currentX, currentY, complexity); element = createTask(flowElement.name, currentX, currentY, complexity);
break; break;
case 'gateway': case 'gateway':
element = createGateway(flowElement.name, currentX, currentY, flowElement.gatewayType || 'exclusive'); element = createGateway(flowElement.name, currentX, currentY, flowElement.gatewayType || 'exclusive');
break; break;
case 'event': case 'event':
element = createIntermediateEvent(flowElement.name, currentX, currentY); element = createIntermediateEvent(flowElement.name, currentX, currentY);
break; break;
default: default:
element = createTask(flowElement.name, currentX, currentY, complexity); element = createTask(flowElement.name, currentX, currentY, complexity);
} }
elements.push(element); elements.push(element);
@ -68,7 +67,7 @@ export const generateSmartBPMNDiagram = (
currentX, currentX,
currentY, currentY,
spacing, spacing,
complexity complexity,
); );
elements.push(...branchResults.elements); elements.push(...branchResults.elements);
@ -93,8 +92,8 @@ export const generateSmartBPMNDiagram = (
format: 'drawio' as any, format: 'drawio' as any,
created: new Date().toISOString(), created: new Date().toISOString(),
modified: new Date().toISOString(), modified: new Date().toISOString(),
version: '1.0' version: '1.0',
} },
}; };
}; };
@ -136,7 +135,7 @@ const extractTasks = (entities: string[], description: string): string[] => {
/** /**
* Extract events from entities and description * Extract events from entities and description
*/ */
const extractEvents = (entities: string[], description: string): string[] => { const extractEvents = (entities: string[], _description: string): string[] => {
const eventKeywords = ['evento', 'event', 'inicio', 'start', 'fin', 'end', 'trigger', 'disparador']; const eventKeywords = ['evento', 'event', 'inicio', 'start', 'fin', 'end', 'trigger', 'disparador'];
const events: string[] = []; const events: string[] = [];
@ -175,7 +174,7 @@ const extractGateways = (entities: string[], description: string): Array<{ name:
/si\s+(.+?)\s+entonces/gi, /si\s+(.+?)\s+entonces/gi,
/if\s+(.+?)\s+then/gi, /if\s+(.+?)\s+then/gi,
/cuando\s+(.+?)\s+[,.]?/gi, /cuando\s+(.+?)\s+[,.]?/gi,
/en caso de\s+(.+?)\s+[,.]?/gi /en caso de\s+(.+?)\s+[,.]?/gi,
]; ];
for (const pattern of decisionPatterns) { for (const pattern of decisionPatterns) {
@ -196,7 +195,7 @@ const extractGateways = (entities: string[], description: string): Array<{ name:
const buildProcessFlow = ( const buildProcessFlow = (
tasks: string[], tasks: string[],
gateways: Array<{ name: string; type: 'exclusive' | 'parallel' | 'inclusive' }>, gateways: Array<{ name: string; type: 'exclusive' | 'parallel' | 'inclusive' }>,
events: string[] _events: string[],
): Array<{ type: string; name: string; gatewayType?: string; branches?: string[][] }> => { ): Array<{ type: string; name: string; gatewayType?: string; branches?: string[][] }> => {
const flow: Array<{ type: string; name: string; gatewayType?: string; branches?: string[][] }> = []; const flow: Array<{ type: string; name: string; gatewayType?: string; branches?: string[][] }> = [];
@ -221,7 +220,7 @@ const buildProcessFlow = (
type: 'gateway', type: 'gateway',
name: gateway.name, name: gateway.name,
gatewayType: gateway.type, gatewayType: gateway.type,
branches: branches branches: branches,
}); });
gatewayIndex++; gatewayIndex++;
@ -265,7 +264,7 @@ const createStartEvent = (x: number, y: number): DiagramElement => ({
label: 'Inicio', label: 'Inicio',
geometry: { x, y, width: 36, height: 36 }, geometry: { x, y, width: 36, height: 36 },
style: 'ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#d5e8d4;strokeColor=#82b366;', style: 'ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#d5e8d4;strokeColor=#82b366;',
properties: { eventType: 'start' } properties: { eventType: 'start' },
}); });
const createEndEvent = (x: number, y: number): DiagramElement => ({ const createEndEvent = (x: number, y: number): DiagramElement => ({
@ -274,7 +273,7 @@ const createEndEvent = (x: number, y: number): DiagramElement => ({
label: 'Fin', label: 'Fin',
geometry: { x, y, width: 36, height: 36 }, geometry: { x, y, width: 36, height: 36 },
style: 'ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#f8cecc;strokeColor=#b85450;strokeWidth=3;', style: 'ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#f8cecc;strokeColor=#b85450;strokeWidth=3;',
properties: { eventType: 'end' } properties: { eventType: 'end' },
}); });
const createTask = (name: string, x: number, y: number, complexity: 'simple' | 'detailed'): DiagramElement => { const createTask = (name: string, x: number, y: number, complexity: 'simple' | 'detailed'): DiagramElement => {
@ -287,7 +286,7 @@ const createTask = (name: string, x: number, y: number, complexity: 'simple' | '
label: name, label: name,
geometry: { x, y, width, height }, geometry: { x, y, width, height },
style: 'rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;', style: 'rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { taskType: 'user' } properties: { taskType: 'user' },
}; };
}; };
@ -297,7 +296,7 @@ const createGateway = (name: string, x: number, y: number, gatewayType: string):
label: name, label: name,
geometry: { x, y, width: 50, height: 50 }, geometry: { x, y, width: 50, height: 50 },
style: 'rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;', style: 'rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;',
properties: { gatewayType } properties: { gatewayType },
}); });
const createIntermediateEvent = (name: string, x: number, y: number): DiagramElement => ({ const createIntermediateEvent = (name: string, x: number, y: number): DiagramElement => ({
@ -306,7 +305,7 @@ const createIntermediateEvent = (name: string, x: number, y: number): DiagramEle
label: name, label: name,
geometry: { x, y, width: 36, height: 36 }, geometry: { x, y, width: 36, height: 36 },
style: 'ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#e1d5e7;strokeColor=#9673a6;', style: 'ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#e1d5e7;strokeColor=#9673a6;',
properties: { eventType: 'intermediate' } properties: { eventType: 'intermediate' },
}); });
const createSequenceFlow = (sourceId: string, targetId: string, label?: string): DiagramConnection => ({ const createSequenceFlow = (sourceId: string, targetId: string, label?: string): DiagramConnection => ({
@ -315,7 +314,7 @@ const createSequenceFlow = (sourceId: string, targetId: string, label?: string):
target: targetId, target: targetId,
label: label || '', label: label || '',
style: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;', style: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;',
properties: { flowType: 'sequence' } properties: { flowType: 'sequence' },
}); });
/** /**
@ -327,7 +326,7 @@ const createGatewayBranchElements = (
startX: number, startX: number,
startY: number, startY: number,
spacing: number, spacing: number,
complexity: 'simple' | 'detailed' complexity: 'simple' | 'detailed',
): { ): {
elements: DiagramElement[]; elements: DiagramElement[];
connections: DiagramConnection[]; connections: DiagramConnection[];
@ -350,7 +349,7 @@ const createGatewayBranchElements = (
let branchX = startX; let branchX = startX;
let previousId = gatewayId; let previousId = gatewayId;
branch.forEach((taskName, taskIndex) => { branch.forEach((taskName, _taskIndex) => {
branchX += spacing; branchX += spacing;
const task = createTask(taskName, branchX, branchY, complexity); const task = createTask(taskName, branchX, branchY, complexity);
elements.push(task); elements.push(task);
@ -370,6 +369,6 @@ const createGatewayBranchElements = (
elements, elements,
connections, connections,
nextX: maxX + spacing, nextX: maxX + spacing,
convergenceGatewayId: convergenceGateway.id convergenceGatewayId: convergenceGateway.id,
}; };
}; };

View File

@ -1,5 +1,4 @@
import { DiagramType, DiagramData, DiagramElement, DiagramConnection } from '../types/diagram-types.js'; import { DiagramType, DiagramData, DiagramElement, DiagramConnection, DiagramAnalysis } from '../types/diagram-types.js';
import { DiagramAnalysis } from '../types/diagram-types.js';
/** /**
* Generate intelligent ER diagram based on analysis * Generate intelligent ER diagram based on analysis
@ -7,7 +6,7 @@ import { DiagramAnalysis } from '../types/diagram-types.js';
export const generateSmartERDiagram = ( export const generateSmartERDiagram = (
description: string, description: string,
analysis: DiagramAnalysis, analysis: DiagramAnalysis,
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {} preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {},
): DiagramData => { ): DiagramData => {
const { entities, relationships } = analysis; const { entities, relationships } = analysis;
const complexity = preferences.complexity || 'detailed'; const complexity = preferences.complexity || 'detailed';
@ -47,7 +46,7 @@ export const generateSmartERDiagram = (
`entity-${sourceEntity.name}`, `entity-${sourceEntity.name}`,
`entity-${targetEntity.name}`, `entity-${targetEntity.name}`,
relationship.type, relationship.type,
relationship.label relationship.label,
); );
connections.push(connection); connections.push(connection);
} }
@ -61,8 +60,8 @@ export const generateSmartERDiagram = (
format: 'drawio' as any, format: 'drawio' as any,
created: new Date().toISOString(), created: new Date().toISOString(),
modified: new Date().toISOString(), modified: new Date().toISOString(),
version: '1.0' version: '1.0',
} },
}; };
}; };
@ -107,7 +106,7 @@ const extractDatabaseEntities = (entities: string[], description: string): Array
const attributes = generateEntityAttributes(entityName, description); const attributes = generateEntityAttributes(entityName, description);
dbEntities.push({ dbEntities.push({
name: entityName, name: entityName,
attributes attributes,
}); });
}); });
@ -119,7 +118,7 @@ const extractDatabaseEntities = (entities: string[], description: string): Array
*/ */
const generateEntityAttributes = ( const generateEntityAttributes = (
entityName: string, entityName: string,
description: string description: string,
): Array<{ name: string; type: string; isPrimaryKey?: boolean; isForeignKey?: boolean }> => { ): Array<{ name: string; type: string; isPrimaryKey?: boolean; isForeignKey?: boolean }> => {
const attributes: Array<{ name: string; type: string; isPrimaryKey?: boolean; isForeignKey?: boolean }> = []; const attributes: Array<{ name: string; type: string; isPrimaryKey?: boolean; isForeignKey?: boolean }> = [];
@ -127,7 +126,7 @@ const generateEntityAttributes = (
attributes.push({ attributes.push({
name: 'id', name: 'id',
type: 'INT', type: 'INT',
isPrimaryKey: true isPrimaryKey: true,
}); });
// Generate context-specific attributes based on entity name // Generate context-specific attributes based on entity name
@ -141,7 +140,7 @@ const generateEntityAttributes = (
// Add common attributes // Add common attributes
attributes.push( attributes.push(
{ name: 'created_at', type: 'TIMESTAMP' }, { name: 'created_at', type: 'TIMESTAMP' },
{ name: 'updated_at', type: 'TIMESTAMP' } { name: 'updated_at', type: 'TIMESTAMP' },
); );
return attributes.slice(0, 8); // Limit to 8 attributes for readability return attributes.slice(0, 8); // Limit to 8 attributes for readability
@ -156,55 +155,55 @@ const getContextualAttributes = (entityName: string): Array<{ name: string; type
{ name: 'nombre', type: 'VARCHAR(100)' }, { name: 'nombre', type: 'VARCHAR(100)' },
{ name: 'email', type: 'VARCHAR(255)' }, { name: 'email', type: 'VARCHAR(255)' },
{ name: 'password', type: 'VARCHAR(255)' }, { name: 'password', type: 'VARCHAR(255)' },
{ name: 'telefono', type: 'VARCHAR(20)' } { name: 'telefono', type: 'VARCHAR(20)' },
], ],
user: [ user: [
{ name: 'name', type: 'VARCHAR(100)' }, { name: 'name', type: 'VARCHAR(100)' },
{ name: 'email', type: 'VARCHAR(255)' }, { name: 'email', type: 'VARCHAR(255)' },
{ name: 'password', type: 'VARCHAR(255)' }, { name: 'password', type: 'VARCHAR(255)' },
{ name: 'phone', type: 'VARCHAR(20)' } { name: 'phone', type: 'VARCHAR(20)' },
], ],
producto: [ producto: [
{ name: 'nombre', type: 'VARCHAR(200)' }, { name: 'nombre', type: 'VARCHAR(200)' },
{ name: 'descripcion', type: 'TEXT' }, { name: 'descripcion', type: 'TEXT' },
{ name: 'precio', type: 'DECIMAL(10,2)' }, { name: 'precio', type: 'DECIMAL(10,2)' },
{ name: 'stock', type: 'INT' } { name: 'stock', type: 'INT' },
], ],
product: [ product: [
{ name: 'name', type: 'VARCHAR(200)' }, { name: 'name', type: 'VARCHAR(200)' },
{ name: 'description', type: 'TEXT' }, { name: 'description', type: 'TEXT' },
{ name: 'price', type: 'DECIMAL(10,2)' }, { name: 'price', type: 'DECIMAL(10,2)' },
{ name: 'stock', type: 'INT' } { name: 'stock', type: 'INT' },
], ],
pedido: [ pedido: [
{ name: 'numero', type: 'VARCHAR(50)' }, { name: 'numero', type: 'VARCHAR(50)' },
{ name: 'total', type: 'DECIMAL(10,2)' }, { name: 'total', type: 'DECIMAL(10,2)' },
{ name: 'estado', type: 'VARCHAR(50)' }, { name: 'estado', type: 'VARCHAR(50)' },
{ name: 'fecha', type: 'DATE' } { name: 'fecha', type: 'DATE' },
], ],
order: [ order: [
{ name: 'number', type: 'VARCHAR(50)' }, { name: 'number', type: 'VARCHAR(50)' },
{ name: 'total', type: 'DECIMAL(10,2)' }, { name: 'total', type: 'DECIMAL(10,2)' },
{ name: 'status', type: 'VARCHAR(50)' }, { name: 'status', type: 'VARCHAR(50)' },
{ name: 'date', type: 'DATE' } { name: 'date', type: 'DATE' },
], ],
cliente: [ cliente: [
{ name: 'nombre', type: 'VARCHAR(100)' }, { name: 'nombre', type: 'VARCHAR(100)' },
{ name: 'email', type: 'VARCHAR(255)' }, { name: 'email', type: 'VARCHAR(255)' },
{ name: 'direccion', type: 'TEXT' }, { name: 'direccion', type: 'TEXT' },
{ name: 'telefono', type: 'VARCHAR(20)' } { name: 'telefono', type: 'VARCHAR(20)' },
], ],
customer: [ customer: [
{ name: 'name', type: 'VARCHAR(100)' }, { name: 'name', type: 'VARCHAR(100)' },
{ name: 'email', type: 'VARCHAR(255)' }, { name: 'email', type: 'VARCHAR(255)' },
{ name: 'address', type: 'TEXT' }, { name: 'address', type: 'TEXT' },
{ name: 'phone', type: 'VARCHAR(20)' } { name: 'phone', type: 'VARCHAR(20)' },
] ],
}; };
return attributeMap[entityName] || [ return attributeMap[entityName] || [
{ name: 'name', type: 'VARCHAR(100)' }, { name: 'name', type: 'VARCHAR(100)' },
{ name: 'description', type: 'TEXT' } { name: 'description', type: 'TEXT' },
]; ];
}; };
@ -213,14 +212,14 @@ const getContextualAttributes = (entityName: string): Array<{ name: string; type
*/ */
const extractAttributesFromDescription = ( const extractAttributesFromDescription = (
description: string, description: string,
entityName: string entityName: string,
): Array<{ name: string; type: string }> => { ): Array<{ name: string; type: string }> => {
const attributes: Array<{ name: string; type: string }> = []; const attributes: Array<{ name: string; type: string }> = [];
// Look for attribute patterns // Look for attribute patterns
const attributePatterns = [ const attributePatterns = [
/(?:campo|field|atributo|attribute|columna|column)\s*:?\s*([^,.\n]+)/gi, /(?:campo|field|atributo|attribute|columna|column)\s*:?\s*([^,.\n]+)/gi,
new RegExp(`${entityName}\\s+(?:tiene|has|incluye|includes?)\\s+([^,.\n]+)`, 'gi') new RegExp(`${entityName}\\s+(?:tiene|has|incluye|includes?)\\s+([^,.\n]+)`, 'gi'),
]; ];
for (const pattern of attributePatterns) { for (const pattern of attributePatterns) {
@ -271,7 +270,7 @@ const inferAttributeType = (attributeName: string): string => {
const extractDatabaseRelationships = ( const extractDatabaseRelationships = (
relationships: Array<{ from: string; to: string; type: string; label?: string }>, relationships: Array<{ from: string; to: string; type: string; label?: string }>,
description: string, description: string,
entities: Array<{ name: string; attributes: any[] }> entities: Array<{ name: string; attributes: any[] }>,
): Array<{ from: string; to: string; type: string; label?: string; cardinality?: string }> => { ): Array<{ from: string; to: string; type: string; label?: string; cardinality?: string }> => {
const dbRelationships: Array<{ from: string; to: string; type: string; label?: string; cardinality?: string }> = []; const dbRelationships: Array<{ from: string; to: string; type: string; label?: string; cardinality?: string }> = [];
@ -280,7 +279,7 @@ const extractDatabaseRelationships = (
const cardinality = inferCardinality(rel.from, rel.to, description); const cardinality = inferCardinality(rel.from, rel.to, description);
dbRelationships.push({ dbRelationships.push({
...rel, ...rel,
cardinality cardinality,
}); });
}); });
@ -293,7 +292,7 @@ const extractDatabaseRelationships = (
// Check if relationship already exists // Check if relationship already exists
const existingRel = dbRelationships.find( const existingRel = dbRelationships.find(
rel => (rel.from === entity1.name && rel.to === entity2.name) || rel => (rel.from === entity1.name && rel.to === entity2.name) ||
(rel.from === entity2.name && rel.to === entity1.name) (rel.from === entity2.name && rel.to === entity1.name),
); );
if (!existingRel) { if (!existingRel) {
@ -331,13 +330,13 @@ const inferCardinality = (entity1: string, entity2: string, description: string)
const commonOneToMany = [ const commonOneToMany = [
['usuario', 'pedido'], ['user', 'order'], ['usuario', 'pedido'], ['user', 'order'],
['cliente', 'pedido'], ['customer', 'order'], ['cliente', 'pedido'], ['customer', 'order'],
['categoria', 'producto'], ['category', 'product'] ['categoria', 'producto'], ['category', 'product'],
]; ];
const commonManyToMany = [ const commonManyToMany = [
['producto', 'categoria'], ['product', 'category'], ['producto', 'categoria'], ['product', 'category'],
['usuario', 'rol'], ['user', 'role'], ['usuario', 'rol'], ['user', 'role'],
['estudiante', 'curso'], ['student', 'course'] ['estudiante', 'curso'], ['student', 'course'],
]; ];
for (const [first, second] of commonOneToMany) { for (const [first, second] of commonOneToMany) {
@ -363,7 +362,7 @@ const inferCardinality = (entity1: string, entity2: string, description: string)
const inferImplicitRelationship = ( const inferImplicitRelationship = (
entity1: string, entity1: string,
entity2: string, entity2: string,
description: string _description: string,
): { from: string; to: string; type: string; label?: string; cardinality?: string } | null => { ): { from: string; to: string; type: string; label?: string; cardinality?: string } | null => {
const e1 = entity1.toLowerCase(); const e1 = entity1.toLowerCase();
const e2 = entity2.toLowerCase(); const e2 = entity2.toLowerCase();
@ -375,7 +374,7 @@ const inferImplicitRelationship = (
{ entities: ['cliente', 'pedido'], label: 'hace', cardinality: '1:N' }, { entities: ['cliente', 'pedido'], label: 'hace', cardinality: '1:N' },
{ entities: ['customer', 'order'], label: 'makes', cardinality: '1:N' }, { entities: ['customer', 'order'], label: 'makes', cardinality: '1:N' },
{ entities: ['producto', 'categoria'], label: 'pertenece a', cardinality: 'N:M' }, { entities: ['producto', 'categoria'], label: 'pertenece a', cardinality: 'N:M' },
{ entities: ['product', 'category'], label: 'belongs to', cardinality: 'N:M' } { entities: ['product', 'category'], label: 'belongs to', cardinality: 'N:M' },
]; ];
for (const pattern of relationshipPatterns) { for (const pattern of relationshipPatterns) {
@ -387,7 +386,7 @@ const inferImplicitRelationship = (
to: entity2, to: entity2,
type: 'relationship', type: 'relationship',
label: pattern.label, label: pattern.label,
cardinality: pattern.cardinality cardinality: pattern.cardinality,
}; };
} }
} }
@ -425,7 +424,7 @@ const createEntity = (
entity: { name: string; attributes: Array<{ name: string; type: string; isPrimaryKey?: boolean; isForeignKey?: boolean }> }, entity: { name: string; attributes: Array<{ name: string; type: string; isPrimaryKey?: boolean; isForeignKey?: boolean }> },
x: number, x: number,
y: number, y: number,
complexity: 'simple' | 'detailed' complexity: 'simple' | 'detailed',
): DiagramElement => { ): DiagramElement => {
const width = complexity === 'detailed' ? 200 : 150; const width = complexity === 'detailed' ? 200 : 150;
const attributeHeight = complexity === 'detailed' ? 20 : 16; const attributeHeight = complexity === 'detailed' ? 20 : 16;
@ -457,7 +456,7 @@ const createEntity = (
label, label,
geometry: { x, y, width, height }, geometry: { x, y, width, height },
style: 'swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;fillColor=#dae8fc;strokeColor=#6c8ebf;', style: 'swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { entityType: 'table', entityName: entity.name } properties: { entityType: 'table', entityName: entity.name },
}; };
}; };
@ -468,7 +467,7 @@ const createRelationship = (
sourceId: string, sourceId: string,
targetId: string, targetId: string,
relationshipType: string, relationshipType: string,
label?: string label?: string,
): DiagramConnection => { ): DiagramConnection => {
return { return {
id: `rel-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, id: `rel-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
@ -476,6 +475,6 @@ const createRelationship = (
target: targetId, target: targetId,
label: label || '', label: label || '',
style: 'edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;startArrow=ERmandOne;', style: 'edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;startArrow=ERmandOne;',
properties: { relationshipType, cardinality: '1:N' } properties: { relationshipType, cardinality: '1:N' },
}; };
}; };

View File

@ -11,7 +11,6 @@ import {
ListToolsRequestSchema, ListToolsRequestSchema,
McpError, McpError,
ReadResourceRequestSchema, ReadResourceRequestSchema,
InitializeRequestSchema,
} from '@modelcontextprotocol/sdk/types.js'; } from '@modelcontextprotocol/sdk/types.js';
// Import tools and utilities // Import tools and utilities
@ -64,103 +63,103 @@ const createToolDefinitions = () => [
properties: { properties: {
name: { name: {
type: 'string', type: 'string',
description: 'Name of the diagram file (without extension)' description: 'Name of the diagram file (without extension)',
}, },
type: { type: {
type: 'string', type: 'string',
enum: Object.values(DiagramType), enum: Object.values(DiagramType),
description: 'Type of diagram to create' description: 'Type of diagram to create',
}, },
format: { format: {
type: 'string', type: 'string',
enum: Object.values(DiagramFormat), enum: Object.values(DiagramFormat),
default: 'drawio', default: 'drawio',
description: 'File format for the diagram' description: 'File format for the diagram',
}, },
description: { description: {
type: 'string', type: 'string',
description: 'Natural language description of the diagram to generate using AI' description: 'Natural language description of the diagram to generate using AI',
}, },
outputPath: { outputPath: {
type: 'string', type: 'string',
description: 'Output directory path (relative to workspace)' description: 'Output directory path (relative to workspace)',
}, },
complexity: { complexity: {
type: 'string', type: 'string',
enum: ['simple', 'detailed'], enum: ['simple', 'detailed'],
default: 'detailed', default: 'detailed',
description: 'Complexity level of the generated diagram' description: 'Complexity level of the generated diagram',
}, },
language: { language: {
type: 'string', type: 'string',
default: 'es', default: 'es',
description: 'Language for diagram labels and text' description: 'Language for diagram labels and text',
}, },
// Legacy parameters for backward compatibility // Legacy parameters for backward compatibility
processName: { processName: {
type: 'string', type: 'string',
description: 'Name of the BPMN process (legacy)' description: 'Name of the BPMN process (legacy)',
}, },
tasks: { tasks: {
type: 'array', type: 'array',
items: { type: 'string' }, items: { type: 'string' },
description: 'List of tasks for BPMN process (legacy)' description: 'List of tasks for BPMN process (legacy)',
}, },
gatewayType: { gatewayType: {
type: 'string', type: 'string',
enum: ['exclusive', 'parallel'], enum: ['exclusive', 'parallel'],
description: 'Type of gateway for BPMN process (legacy)' description: 'Type of gateway for BPMN process (legacy)',
}, },
branches: { branches: {
type: 'array', type: 'array',
items: { items: {
type: 'array', type: 'array',
items: { type: 'string' } items: { type: 'string' },
}, },
description: 'Branches for BPMN gateway (legacy)' description: 'Branches for BPMN gateway (legacy)',
}, },
beforeGateway: { beforeGateway: {
type: 'array', type: 'array',
items: { type: 'string' }, items: { type: 'string' },
description: 'Tasks before gateway (legacy)' description: 'Tasks before gateway (legacy)',
}, },
afterGateway: { afterGateway: {
type: 'array', type: 'array',
items: { type: 'string' }, items: { type: 'string' },
description: 'Tasks after gateway (legacy)' description: 'Tasks after gateway (legacy)',
}, },
classes: { classes: {
type: 'array', type: 'array',
items: { type: 'string' }, items: { type: 'string' },
description: 'List of classes for UML class diagram (legacy)' description: 'List of classes for UML class diagram (legacy)',
}, },
entities: { entities: {
type: 'array', type: 'array',
items: { type: 'string' }, items: { type: 'string' },
description: 'List of entities for ER diagram (legacy)' description: 'List of entities for ER diagram (legacy)',
}, },
components: { components: {
type: 'array', type: 'array',
items: { type: 'string' }, items: { type: 'string' },
description: 'List of components for network or architecture diagram (legacy)' description: 'List of components for network or architecture diagram (legacy)',
}, },
processes: { processes: {
type: 'array', type: 'array',
items: { type: 'string' }, items: { type: 'string' },
description: 'List of processes for flowchart (legacy)' description: 'List of processes for flowchart (legacy)',
} },
}, },
required: ['name', 'type'] required: ['name', 'type'],
} },
}, },
{ {
name: 'get_diagram_types', name: 'get_diagram_types',
description: 'Get list of supported diagram types with descriptions', description: 'Get list of supported diagram types with descriptions',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: {} properties: {},
} },
} },
]; ];
// Pure function to create resource definitions // Pure function to create resource definitions
@ -169,8 +168,8 @@ const createResourceDefinitions = () => [
uri: 'diagrams://types/supported', uri: 'diagrams://types/supported',
name: 'Supported Diagram Types', name: 'Supported Diagram Types',
mimeType: 'application/json', mimeType: 'application/json',
description: 'List of supported diagram types and their descriptions' description: 'List of supported diagram types and their descriptions',
} },
]; ];
// Pure function to create tool handlers // Pure function to create tool handlers
@ -179,22 +178,22 @@ const createToolHandlers = (config: ServerConfig): ToolHandlers => ({
if (!validateCreateDiagramInput(args)) { if (!validateCreateDiagramInput(args)) {
throw new McpError( throw new McpError(
ErrorCode.InvalidParams, ErrorCode.InvalidParams,
'Invalid create_diagram arguments' 'Invalid create_diagram arguments',
); );
} }
const result = await createDiagram({ const result = await createDiagram({
...args, ...args,
workspaceRoot: args.workspaceRoot || config.workspaceRoot workspaceRoot: args.workspaceRoot || config.workspaceRoot,
}); });
return { return {
content: [ content: [
{ {
type: 'text', type: 'text',
text: JSON.stringify(result, null, 2) text: JSON.stringify(result, null, 2),
} },
] ],
}; };
}, },
@ -202,7 +201,7 @@ const createToolHandlers = (config: ServerConfig): ToolHandlers => ({
const types = getSupportedDiagramTypes(); const types = getSupportedDiagramTypes();
const typesWithDescriptions = types.map(type => ({ const typesWithDescriptions = types.map(type => ({
type, type,
description: getDiagramTypeDescription(type) description: getDiagramTypeDescription(type),
})); }));
return { return {
@ -211,21 +210,21 @@ const createToolHandlers = (config: ServerConfig): ToolHandlers => ({
type: 'text', type: 'text',
text: JSON.stringify({ text: JSON.stringify({
success: true, success: true,
supportedTypes: typesWithDescriptions supportedTypes: typesWithDescriptions,
}, null, 2) }, null, 2),
} },
] ],
}; };
} },
}); });
// Pure function to create resource handlers // Pure function to create resource handlers
const createResourceHandlers = (config: ServerConfig): ResourceHandlers => ({ const createResourceHandlers = (_config: ServerConfig): ResourceHandlers => ({
'diagrams://types/supported': async () => { 'diagrams://types/supported': async () => {
const types = getSupportedDiagramTypes(); const types = getSupportedDiagramTypes();
const typesWithDescriptions = types.map(type => ({ const typesWithDescriptions = types.map(type => ({
type, type,
description: getDiagramTypeDescription(type) description: getDiagramTypeDescription(type),
})); }));
return { return {
@ -233,18 +232,18 @@ const createResourceHandlers = (config: ServerConfig): ResourceHandlers => ({
{ {
uri: 'diagrams://types/supported', uri: 'diagrams://types/supported',
mimeType: 'application/json', mimeType: 'application/json',
text: JSON.stringify(typesWithDescriptions, null, 2) text: JSON.stringify(typesWithDescriptions, null, 2),
} },
] ],
}; };
} },
}); });
// Pure function to setup tool request handlers // Pure function to setup tool request handlers
const setupToolRequestHandlers = (server: Server, toolHandlers: ToolHandlers) => { const setupToolRequestHandlers = (server: Server, toolHandlers: ToolHandlers) => {
// List available tools // List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({ server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: createToolDefinitions() tools: createToolDefinitions(),
})); }));
// Handle tool calls // Handle tool calls
@ -254,7 +253,7 @@ const setupToolRequestHandlers = (server: Server, toolHandlers: ToolHandlers) =>
if (!handler) { if (!handler) {
throw new McpError( throw new McpError(
ErrorCode.MethodNotFound, ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}` `Unknown tool: ${request.params.name}`,
); );
} }
@ -280,7 +279,7 @@ const setupToolRequestHandlers = (server: Server, toolHandlers: ToolHandlers) =>
const setupResourceRequestHandlers = (server: Server, resourceHandlers: ResourceHandlers) => { const setupResourceRequestHandlers = (server: Server, resourceHandlers: ResourceHandlers) => {
// List available resources // List available resources
server.setRequestHandler(ListResourcesRequestSchema, async () => ({ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: createResourceDefinitions() resources: createResourceDefinitions(),
})); }));
// Handle resource requests // Handle resource requests
@ -292,7 +291,7 @@ const setupResourceRequestHandlers = (server: Server, resourceHandlers: Resource
if (!handler) { if (!handler) {
throw new McpError( throw new McpError(
ErrorCode.InvalidRequest, ErrorCode.InvalidRequest,
`Unknown resource URI: ${uri}` `Unknown resource URI: ${uri}`,
); );
} }
@ -300,7 +299,7 @@ const setupResourceRequestHandlers = (server: Server, resourceHandlers: Resource
} catch (error) { } catch (error) {
throw new McpError( throw new McpError(
ErrorCode.InternalError, ErrorCode.InternalError,
`Failed to read resource: ${error}` `Failed to read resource: ${error}`,
); );
} }
}); });
@ -323,7 +322,7 @@ const createMCPServer = (config: ServerConfig): Server => {
}, },
{ {
capabilities: config.capabilities, capabilities: config.capabilities,
} },
); );
const toolHandlers = createToolHandlers(config); const toolHandlers = createToolHandlers(config);
@ -368,7 +367,7 @@ const isInitializeRequest = (body: any): boolean => {
}; };
// Pure function to create session transport // Pure function to create session transport
const createSessionTransport = (config: ServerConfig): StreamableHTTPServerTransport => { const createSessionTransport = (_config: ServerConfig): StreamableHTTPServerTransport => {
return new StreamableHTTPServerTransport({ return new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(), sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sessionId) => { onsessioninitialized: (sessionId) => {
@ -484,8 +483,8 @@ const setupAPIRoutes = (app: express.Application, config: ServerConfig) => {
services: { services: {
mcp: 'operational', mcp: 'operational',
diagramGeneration: 'operational', diagramGeneration: 'operational',
sessions: Object.keys(transports).length sessions: Object.keys(transports).length,
} },
}); });
}); });

View File

@ -1,221 +0,0 @@
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
// Mock Express and MCP modules
jest.mock('express');
jest.mock('@modelcontextprotocol/sdk/server/index.js');
jest.mock('@modelcontextprotocol/sdk/server/streamableHttp.js');
describe('MCP Server Tests', () => {
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
jest.resetAllMocks();
});
describe('Server Configuration', () => {
it('should create server configuration with default values', () => {
// Test basic server configuration creation
const config = {
name: 'drawio-mcp-server',
version: '0.1.0',
workspaceRoot: process.cwd(),
httpPort: 3000,
capabilities: {
resources: {},
tools: {}
}
};
expect(config.name).toBe('drawio-mcp-server');
expect(config.version).toBe('0.1.0');
expect(config.httpPort).toBe(3000);
expect(typeof config.workspaceRoot).toBe('string');
});
it('should create server configuration with custom values', () => {
const customConfig = {
name: 'drawio-mcp-server',
version: '0.1.0',
workspaceRoot: '/custom/workspace',
httpPort: 8080,
capabilities: {
resources: {},
tools: {}
}
};
expect(customConfig.workspaceRoot).toBe('/custom/workspace');
expect(customConfig.httpPort).toBe(8080);
});
});
describe('Tool Definitions', () => {
it('should create correct tool definitions', () => {
const toolDefinitions = [
{
name: 'create_diagram',
description: 'Create a new diagram of specified type (BPMN, UML, ER, Network, Architecture, etc.) with AI-powered generation',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the diagram file (without extension)'
},
type: {
type: 'string',
description: 'Type of diagram to create'
}
},
required: ['name', 'type']
}
},
{
name: 'get_diagram_types',
description: 'Get list of supported diagram types with descriptions',
inputSchema: {
type: 'object',
properties: {}
}
}
];
expect(toolDefinitions).toHaveLength(2);
expect(toolDefinitions[0].name).toBe('create_diagram');
expect(toolDefinitions[1].name).toBe('get_diagram_types');
// Check required fields
expect(toolDefinitions[0].inputSchema.required).toContain('name');
expect(toolDefinitions[0].inputSchema.required).toContain('type');
});
});
describe('Resource Definitions', () => {
it('should create correct resource definitions', () => {
const resourceDefinitions = [
{
uri: 'diagrams://types/supported',
name: 'Supported Diagram Types',
mimeType: 'application/json',
description: 'List of supported diagram types and their descriptions'
}
];
expect(resourceDefinitions).toHaveLength(1);
expect(resourceDefinitions[0].uri).toBe('diagrams://types/supported');
expect(resourceDefinitions[0].mimeType).toBe('application/json');
});
});
describe('HTTP Endpoints', () => {
it('should define correct HTTP endpoints', () => {
const endpoints = [
{ method: 'POST', path: '/mcp', description: 'Client-to-server MCP communication' },
{ method: 'GET', path: '/mcp', description: 'Server-to-client notifications via SSE' },
{ method: 'DELETE', path: '/mcp', description: 'Session termination' },
{ method: 'GET', path: '/health', description: 'Health check' },
{ method: 'GET', path: '/', description: 'API documentation' }
];
expect(endpoints).toHaveLength(5);
const postEndpoint = endpoints.find(e => e.method === 'POST' && e.path === '/mcp');
expect(postEndpoint).toBeDefined();
const healthEndpoint = endpoints.find(e => e.method === 'GET' && e.path === '/health');
expect(healthEndpoint).toBeDefined();
});
});
describe('Session Management', () => {
it('should handle session creation', () => {
const sessionId = 'test-session-123';
const sessions: Record<string, any> = {};
// Simulate session creation
sessions[sessionId] = {
id: sessionId,
created: new Date().toISOString(),
active: true
};
expect(sessions[sessionId]).toBeDefined();
expect(sessions[sessionId].id).toBe(sessionId);
expect(sessions[sessionId].active).toBe(true);
});
it('should handle session cleanup', () => {
const sessionId = 'test-session-123';
const sessions: Record<string, any> = {
[sessionId]: {
id: sessionId,
created: new Date().toISOString(),
active: true
}
};
// Simulate session cleanup
delete sessions[sessionId];
expect(sessions[sessionId]).toBeUndefined();
});
});
describe('Error Handling', () => {
it('should handle invalid MCP requests', () => {
const invalidRequest = {
jsonrpc: '2.0',
method: 'invalid_method',
id: 1
};
const errorResponse = {
jsonrpc: '2.0',
error: {
code: -32601,
message: 'Method not found'
},
id: 1
};
expect(errorResponse.error.code).toBe(-32601);
expect(errorResponse.error.message).toBe('Method not found');
});
it('should handle missing session ID', () => {
const errorResponse = {
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided'
},
id: null
};
expect(errorResponse.error.code).toBe(-32000);
expect(errorResponse.error.message).toContain('session ID');
});
});
describe('Health Check', () => {
it('should return correct health status', () => {
const healthResponse = {
status: 'healthy',
timestamp: new Date().toISOString(),
version: '0.1.0',
services: {
mcp: 'operational',
diagramGeneration: 'operational',
sessions: 0
}
};
expect(healthResponse.status).toBe('healthy');
expect(healthResponse.services.mcp).toBe('operational');
expect(healthResponse.services.diagramGeneration).toBe('operational');
expect(typeof healthResponse.services.sessions).toBe('number');
});
});
});

View File

@ -1,44 +1,6 @@
// Test setup file for Jest // Test setup file for Jest
import { beforeEach, afterEach } from '@jest/globals'; import { beforeEach, afterEach } from '@jest/globals';
// Type declarations for global test utilities
declare global {
var createMockDiagramData: () => any;
var createMockFileConfig: () => any;
var createMockVSCodeConfig: () => any;
}
// Global test utilities
(global as any).createMockDiagramData = () => ({
elements: [
{
id: 'element-1',
type: 'rectangle',
label: 'Test Element',
geometry: { x: 100, y: 100, width: 200, height: 100 },
style: 'rounded=0;whiteSpace=wrap;html=1;',
properties: {}
}
],
connections: [],
metadata: {
type: 'flowchart',
format: 'drawio',
created: '2025-01-01T00:00:00.000Z',
modified: '2025-01-01T00:00:00.000Z',
version: '1.0'
}
});
(global as any).createMockFileConfig = () => ({
workspaceRoot: '/test/workspace'
});
(global as any).createMockVSCodeConfig = () => ({
workspaceRoot: '/test/workspace',
extensionId: 'hediet.vscode-drawio'
});
// Setup test environment // Setup test environment
beforeEach(() => { beforeEach(() => {
// Clear any previous test state // Clear any previous test state

View File

@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals
import { createDiagram, validateCreateDiagramInput, getSupportedDiagramTypes, getDiagramTypeDescription } from '../../tools/create-diagram.js'; import { createDiagram, validateCreateDiagramInput, getSupportedDiagramTypes, getDiagramTypeDescription } from '../../tools/create-diagram.js';
import { DiagramType, DiagramFormat } from '../../types/diagram-types.js'; import { DiagramType, DiagramFormat } from '../../types/diagram-types.js';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path';
// Mock file system operations // Mock file system operations
jest.mock('fs', () => ({ jest.mock('fs', () => ({

View File

@ -1,5 +1,5 @@
import { DiagramType, DiagramFormat, DiagramData } from '../types/diagram-types.js'; import { DiagramType, DiagramFormat, DiagramData } from '../types/diagram-types.js';
import { convertToDrawioXML, createDiagramFile, getDefaultStyle, generateId } from '../utils/drawio-converter.js'; import { createDiagramFile } from '../utils/drawio-converter.js';
import { analyzeDiagramDescription } from '../ai/diagram-analyzer.js'; import { analyzeDiagramDescription } from '../ai/diagram-analyzer.js';
import { generateSmartBPMNDiagram } from '../generators/smart-bpmn-generator.js'; import { generateSmartBPMNDiagram } from '../generators/smart-bpmn-generator.js';
import { generateSmartERDiagram } from '../generators/smart-er-generator.js'; import { generateSmartERDiagram } from '../generators/smart-er-generator.js';
@ -45,26 +45,26 @@ const createSuccessResult = (
content: string, content: string,
diagramType: DiagramType, diagramType: DiagramType,
format: DiagramFormat, format: DiagramFormat,
name: string name: string,
): CreateDiagramResult => ({ ): CreateDiagramResult => ({
success: true, success: true,
filePath, filePath,
content, content,
message: `Successfully created ${diagramType} diagram: ${name}`, message: `Successfully created ${diagramType} diagram: ${name}`,
diagramType, diagramType,
format format,
}); });
// Pure function to create error result // Pure function to create error result
const createErrorResult = ( const createErrorResult = (
error: unknown, error: unknown,
diagramType: DiagramType, diagramType: DiagramType,
format: DiagramFormat format: DiagramFormat,
): CreateDiagramResult => ({ ): CreateDiagramResult => ({
success: false, success: false,
message: `Failed to create diagram: ${error instanceof Error ? error.message : String(error)}`, message: `Failed to create diagram: ${error instanceof Error ? error.message : String(error)}`,
diagramType, diagramType,
format format,
}); });
// Pure function to ensure directory exists // Pure function to ensure directory exists
@ -93,33 +93,33 @@ const generateAIDiagram = async (input: CreateDiagramInput): Promise<DiagramData
// Generate diagram based on type // Generate diagram based on type
const preferences = { const preferences = {
complexity: input.complexity || 'detailed', complexity: input.complexity || 'detailed',
language: input.language || 'es' language: input.language || 'es',
}; };
switch (input.type) { switch (input.type) {
case DiagramType.BPMN_PROCESS: case DiagramType.BPMN_PROCESS:
case DiagramType.BPMN_COLLABORATION: case DiagramType.BPMN_COLLABORATION:
case DiagramType.BPMN_CHOREOGRAPHY: case DiagramType.BPMN_CHOREOGRAPHY:
return generateSmartBPMNDiagram(input.description, analysis, preferences); return generateSmartBPMNDiagram(input.description, analysis, preferences);
case DiagramType.ER_DIAGRAM: case DiagramType.ER_DIAGRAM:
case DiagramType.DATABASE_SCHEMA: case DiagramType.DATABASE_SCHEMA:
case DiagramType.CONCEPTUAL_MODEL: case DiagramType.CONCEPTUAL_MODEL:
return generateSmartERDiagram(input.description, analysis, preferences); return generateSmartERDiagram(input.description, analysis, preferences);
case DiagramType.SYSTEM_ARCHITECTURE: case DiagramType.SYSTEM_ARCHITECTURE:
case DiagramType.MICROSERVICES: case DiagramType.MICROSERVICES:
case DiagramType.LAYERED_ARCHITECTURE: case DiagramType.LAYERED_ARCHITECTURE:
case DiagramType.C4_CONTEXT: case DiagramType.C4_CONTEXT:
case DiagramType.C4_CONTAINER: case DiagramType.C4_CONTAINER:
case DiagramType.C4_COMPONENT: case DiagramType.C4_COMPONENT:
case DiagramType.CLOUD_ARCHITECTURE: case DiagramType.CLOUD_ARCHITECTURE:
case DiagramType.INFRASTRUCTURE: case DiagramType.INFRASTRUCTURE:
return generateSmartArchitectureDiagram(input.description, analysis, preferences); return generateSmartArchitectureDiagram(input.description, analysis, preferences);
default: default:
// Fallback to basic diagram generation // Fallback to basic diagram generation
return generateBasicDiagram(input); return generateBasicDiagram(input);
} }
}; };
@ -134,10 +134,10 @@ const generateBasicDiagram = (input: CreateDiagramInput): DiagramData => {
x: 100, x: 100,
y: 100, y: 100,
width: 200, width: 200,
height: 100 height: 100,
}, },
style: 'rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;', style: 'rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: {} properties: {},
}], }],
connections: [], connections: [],
metadata: { metadata: {
@ -145,8 +145,8 @@ const generateBasicDiagram = (input: CreateDiagramInput): DiagramData => {
format: input.format || DiagramFormat.DRAWIO, format: input.format || DiagramFormat.DRAWIO,
created: new Date().toISOString(), created: new Date().toISOString(),
modified: new Date().toISOString(), modified: new Date().toISOString(),
version: '1.0' version: '1.0',
} },
}; };
}; };
@ -167,10 +167,10 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
x: 100, x: 100,
y: 100 + (index * 120), y: 100 + (index * 120),
width: 120, width: 120,
height: 80 height: 80,
}, },
style: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;', style: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { isTask: true } properties: { isTask: true },
}); });
if (index > 0) { if (index > 0) {
@ -180,7 +180,7 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
target: `task-${index}`, target: `task-${index}`,
label: '', label: '',
style: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;', style: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;',
properties: {} properties: {},
}); });
} }
}); });
@ -195,10 +195,10 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
x: 100 + (index * 200), x: 100 + (index * 200),
y: 100, y: 100,
width: 120, width: 120,
height: 80 height: 80,
}, },
style: 'whiteSpace=wrap;html=1;align=center;fillColor=#e1d5e7;strokeColor=#9673a6;', style: 'whiteSpace=wrap;html=1;align=center;fillColor=#e1d5e7;strokeColor=#9673a6;',
properties: { isEntity: true } properties: { isEntity: true },
}); });
}); });
} else if (input.classes && input.classes.length > 0) { } else if (input.classes && input.classes.length > 0) {
@ -212,10 +212,10 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
x: 100 + (index * 200), x: 100 + (index * 200),
y: 100, y: 100,
width: 160, width: 160,
height: 120 height: 120,
}, },
style: 'swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=#dae8fc;strokeColor=#6c8ebf;', style: 'swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { isClass: true } properties: { isClass: true },
}); });
}); });
} else if (input.components && input.components.length > 0) { } else if (input.components && input.components.length > 0) {
@ -229,10 +229,10 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
x: 100 + (index % 2) * 300, x: 100 + (index % 2) * 300,
y: 100 + Math.floor(index / 2) * 150, y: 100 + Math.floor(index / 2) * 150,
width: 200, width: 200,
height: 100 height: 100,
}, },
style: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;', style: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { isComponent: true } properties: { isComponent: true },
}); });
}); });
} else { } else {
@ -248,8 +248,8 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
format: input.format || DiagramFormat.DRAWIO, format: input.format || DiagramFormat.DRAWIO,
created: new Date().toISOString(), created: new Date().toISOString(),
modified: new Date().toISOString(), modified: new Date().toISOString(),
version: '1.0' version: '1.0',
} },
}; };
}; };
@ -333,7 +333,7 @@ const getDiagramTypeDescriptions = (): Readonly<Record<DiagramType, string>> =>
[DiagramType.ORGCHART]: 'Organizational chart showing hierarchy', [DiagramType.ORGCHART]: 'Organizational chart showing hierarchy',
[DiagramType.MINDMAP]: 'Mind map diagram for brainstorming and organizing ideas', [DiagramType.MINDMAP]: 'Mind map diagram for brainstorming and organizing ideas',
[DiagramType.WIREFRAME]: 'Wireframe diagram for UI/UX design', [DiagramType.WIREFRAME]: 'Wireframe diagram for UI/UX design',
[DiagramType.GANTT]: 'Gantt chart for project management and scheduling' [DiagramType.GANTT]: 'Gantt chart for project management and scheduling',
}); });
// Pure function to get description for a specific diagram type // Pure function to get description for a specific diagram type

View File

@ -13,13 +13,13 @@ export const convertToDrawioXML = (diagramData: DiagramData): string => {
agent: 'Cline MCP Server', agent: 'Cline MCP Server',
version: '24.7.17', version: '24.7.17',
etag: generateEtag(), etag: generateEtag(),
type: 'device' type: 'device',
}; };
// Create diagram structure // Create diagram structure
const diagram = { const diagram = {
id: generateId(), id: generateId(),
name: `${metadata.type}-diagram` name: `${metadata.type}-diagram`,
}; };
// Create mxGraphModel // Create mxGraphModel
@ -38,7 +38,7 @@ export const convertToDrawioXML = (diagramData: DiagramData): string => {
pageWidth: '827', pageWidth: '827',
pageHeight: '1169', pageHeight: '1169',
math: '0', math: '0',
shadow: '0' shadow: '0',
}; };
// Create root cell // Create root cell
@ -68,7 +68,7 @@ export const convertToDrawioXML = (diagramData: DiagramData): string => {
* Convert diagram element to mxCell * Convert diagram element to mxCell
*/ */
const convertElementToMxCell = (element: DiagramElement, id: number): any => { const convertElementToMxCell = (element: DiagramElement, id: number): any => {
const { geometry, style, label, properties } = element; const { geometry, style, label } = element;
return { return {
id: id.toString(), id: id.toString(),
@ -81,8 +81,8 @@ const convertElementToMxCell = (element: DiagramElement, id: number): any => {
y: geometry?.y?.toString() || '0', y: geometry?.y?.toString() || '0',
width: geometry?.width?.toString() || '120', width: geometry?.width?.toString() || '120',
height: geometry?.height?.toString() || '80', height: geometry?.height?.toString() || '80',
as: 'geometry' as: 'geometry',
} },
}; };
}; };
@ -90,7 +90,7 @@ const convertElementToMxCell = (element: DiagramElement, id: number): any => {
* Convert diagram connection to mxCell * Convert diagram connection to mxCell
*/ */
const convertConnectionToMxCell = (connection: DiagramConnection, id: number): any => { const convertConnectionToMxCell = (connection: DiagramConnection, id: number): any => {
const { style, label, source, target, properties } = connection; const { style, label, source, target } = connection;
return { return {
id: id.toString(), id: id.toString(),
@ -102,8 +102,8 @@ const convertConnectionToMxCell = (connection: DiagramConnection, id: number): a
target: findElementIdByName(target), target: findElementIdByName(target),
geometry: { geometry: {
relative: '1', relative: '1',
as: 'geometry' as: 'geometry',
} },
}; };
}; };
@ -123,7 +123,7 @@ const buildDrawioXML = (
mxfile: any, mxfile: any,
diagram: any, diagram: any,
mxGraphModel: any, mxGraphModel: any,
mxCells: any[] mxCells: any[],
): string => { ): string => {
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n'; let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
xml += `<mxfile host="${mxfile.host}" modified="${mxfile.modified}" agent="${mxfile.agent}" version="${mxfile.version}" etag="${mxfile.etag}" type="${mxfile.type}">\n`; xml += `<mxfile host="${mxfile.host}" modified="${mxfile.modified}" agent="${mxfile.agent}" version="${mxfile.version}" etag="${mxfile.etag}" type="${mxfile.type}">\n`;
@ -181,21 +181,21 @@ const buildMxCellXML = (cell: any): string => {
*/ */
export const convertDiagramToFormat = ( export const convertDiagramToFormat = (
diagramData: DiagramData, diagramData: DiagramData,
format: DiagramFormat format: DiagramFormat,
): string => { ): string => {
switch (format) { switch (format) {
case DiagramFormat.DRAWIO: case DiagramFormat.DRAWIO:
case DiagramFormat.XML: case DiagramFormat.XML:
return convertToDrawioXML(diagramData); return convertToDrawioXML(diagramData);
case DiagramFormat.DRAWIO_SVG: case DiagramFormat.DRAWIO_SVG:
return convertToDrawioSVG(diagramData); return convertToDrawioSVG(diagramData);
case DiagramFormat.DRAWIO_PNG: case DiagramFormat.DRAWIO_PNG:
return convertToDrawioPNG(diagramData); return convertToDrawioPNG(diagramData);
default: default:
return convertToDrawioXML(diagramData); return convertToDrawioXML(diagramData);
} }
}; };
@ -209,8 +209,8 @@ const convertToDrawioSVG = (diagramData: DiagramData): string => {
// Calculate approximate SVG dimensions based on elements // Calculate approximate SVG dimensions based on elements
const { width, height } = calculateDiagramDimensions(diagramData.elements); const { width, height } = calculateDiagramDimensions(diagramData.elements);
let svg = `<?xml version="1.0" encoding="UTF-8"?>\n`; let svg = '<?xml version="1.0" encoding="UTF-8"?>\n';
svg += `<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n`; svg += '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n';
svg += `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="${width}px" height="${height}px" viewBox="-0.5 -0.5 ${width} ${height}" content="${encodedXML}">\n`; svg += `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="${width}px" height="${height}px" viewBox="-0.5 -0.5 ${width} ${height}" content="${encodedXML}">\n`;
svg += ' <defs/>\n'; svg += ' <defs/>\n';
svg += ' <g>\n'; svg += ' <g>\n';
@ -261,7 +261,7 @@ const calculateDiagramDimensions = (elements: DiagramElement[]): { width: number
return { return {
width: Math.max(maxX + 50, 800), // Add padding and minimum width width: Math.max(maxX + 50, 800), // Add padding and minimum width
height: Math.max(maxY + 50, 600) // Add padding and minimum height height: Math.max(maxY + 50, 600), // Add padding and minimum height
}; };
}; };
@ -367,7 +367,7 @@ export const getDefaultStyle = (elementType: string): string => {
'rectangle': 'rounded=0;whiteSpace=wrap;html=1;', 'rectangle': 'rounded=0;whiteSpace=wrap;html=1;',
'ellipse': 'ellipse;whiteSpace=wrap;html=1;', 'ellipse': 'ellipse;whiteSpace=wrap;html=1;',
'diamond': 'rhombus;whiteSpace=wrap;html=1;', 'diamond': 'rhombus;whiteSpace=wrap;html=1;',
'triangle': 'triangle;whiteSpace=wrap;html=1;' 'triangle': 'triangle;whiteSpace=wrap;html=1;',
}; };
return styleMap[elementType] || styleMap['rectangle']; return styleMap[elementType] || styleMap['rectangle'];
@ -400,40 +400,40 @@ const extractStrokeColor = (style?: string): string | null => {
export const createDiagramFile = ( export const createDiagramFile = (
diagramData: DiagramData, diagramData: DiagramData,
format: DiagramFormat, format: DiagramFormat,
filename?: string filename?: string,
): { content: string; filename: string; mimeType: string } => { ): { content: string; filename: string; mimeType: string } => {
const content = convertDiagramToFormat(diagramData, format); const content = convertDiagramToFormat(diagramData, format);
const baseFilename = filename || `diagram-${Date.now()}`; const baseFilename = filename || `diagram-${Date.now()}`;
switch (format) { switch (format) {
case DiagramFormat.DRAWIO: case DiagramFormat.DRAWIO:
case DiagramFormat.XML: case DiagramFormat.XML:
return { return {
content, content,
filename: `${baseFilename}.drawio`, filename: `${baseFilename}.drawio`,
mimeType: 'application/xml' mimeType: 'application/xml',
}; };
case DiagramFormat.DRAWIO_SVG: case DiagramFormat.DRAWIO_SVG:
return { return {
content, content,
filename: `${baseFilename}.svg`, filename: `${baseFilename}.svg`,
mimeType: 'image/svg+xml' mimeType: 'image/svg+xml',
}; };
case DiagramFormat.DRAWIO_PNG: case DiagramFormat.DRAWIO_PNG:
return { return {
content, content,
filename: `${baseFilename}.png`, filename: `${baseFilename}.png`,
mimeType: 'image/png' mimeType: 'image/png',
}; };
default: default:
return { return {
content, content,
filename: `${baseFilename}.drawio`, filename: `${baseFilename}.drawio`,
mimeType: 'application/xml' mimeType: 'application/xml',
}; };
} }
}; };
@ -442,15 +442,15 @@ export const createDiagramFile = (
*/ */
export const streamDiagramContent = ( export const streamDiagramContent = (
diagramData: DiagramData, diagramData: DiagramData,
format: DiagramFormat = DiagramFormat.DRAWIO format: DiagramFormat = DiagramFormat.DRAWIO,
): ReadableStream<Uint8Array> => { ): ReadableStream<Uint8Array> => {
const { content, mimeType } = createDiagramFile(diagramData, format); const { content } = createDiagramFile(diagramData, format);
return new ReadableStream({ return new ReadableStream({
start(controller) { start(controller) {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
controller.enqueue(encoder.encode(content)); controller.enqueue(encoder.encode(content));
controller.close(); controller.close();
} },
}); });
}; };