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
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:
126
eslint.config.js
Normal file
126
eslint.config.js
Normal 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',
|
||||
],
|
||||
},
|
||||
];
|
2
src/@types/custom.d.ts
vendored
2
src/@types/custom.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
// index.d.ts
|
||||
declare module "*.gql" {
|
||||
declare module '*.gql' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
|
@ -6,98 +6,98 @@ const DIAGRAM_KEYWORDS: Record<DiagramType, string[]> = {
|
||||
'proceso', 'flujo', 'workflow', 'aprobación', 'tarea', 'decisión', 'gateway', 'evento',
|
||||
'actividad', 'secuencia', 'paralelo', 'exclusivo', 'inicio', 'fin', 'subprocess',
|
||||
'process', 'flow', 'approval', 'task', 'decision', 'activity', 'sequence', 'parallel',
|
||||
'exclusive', 'start', 'end', 'business process'
|
||||
'exclusive', 'start', 'end', 'business process',
|
||||
],
|
||||
[DiagramType.BPMN_COLLABORATION]: [
|
||||
'colaboración', 'participante', 'pool', 'lane', 'mensaje', 'collaboration',
|
||||
'participant', 'message', 'proceso', 'flujo', 'workflow'
|
||||
'participant', 'message', 'proceso', 'flujo', 'workflow',
|
||||
],
|
||||
[DiagramType.BPMN_CHOREOGRAPHY]: [
|
||||
'coreografía', 'intercambio', 'mensaje', 'choreography', 'exchange', 'message',
|
||||
'comunicación', 'communication', 'protocolo', 'protocol'
|
||||
'comunicación', 'communication', 'protocolo', 'protocol',
|
||||
],
|
||||
[DiagramType.ER_DIAGRAM]: [
|
||||
'base de datos', 'tabla', 'entidad', 'relación', 'campo', 'clave', 'foreign key',
|
||||
'primary key', 'atributo', 'cardinalidad', 'normalización', 'esquema',
|
||||
'database', 'table', 'entity', 'relationship', 'field', 'key', 'attribute',
|
||||
'cardinality', 'normalization', 'schema', 'sql', 'modelo de datos'
|
||||
'cardinality', 'normalization', 'schema', 'sql', 'modelo de datos',
|
||||
],
|
||||
[DiagramType.DATABASE_SCHEMA]: [
|
||||
'esquema', 'schema', 'base de datos', 'database', 'tabla', 'table', 'sql'
|
||||
'esquema', 'schema', 'base de datos', 'database', 'tabla', 'table', 'sql',
|
||||
],
|
||||
[DiagramType.CONCEPTUAL_MODEL]: [
|
||||
'modelo conceptual', 'conceptual model', 'concepto', 'concept', 'dominio', 'domain'
|
||||
'modelo conceptual', 'conceptual model', 'concepto', 'concept', 'dominio', 'domain',
|
||||
],
|
||||
[DiagramType.SYSTEM_ARCHITECTURE]: [
|
||||
'arquitectura', 'sistema', 'componente', 'servicio', 'api', 'microservicio', 'capa',
|
||||
'módulo', 'interfaz', 'dependencia', 'infraestructura', 'servidor',
|
||||
'architecture', 'system', 'component', 'service', 'microservice', 'layer',
|
||||
'module', 'interface', 'dependency', 'infrastructure', 'server', 'backend', 'frontend'
|
||||
'module', 'interface', 'dependency', 'infrastructure', 'server', 'backend', 'frontend',
|
||||
],
|
||||
[DiagramType.MICROSERVICES]: [
|
||||
'microservicio', 'microservice', 'servicio', 'service', 'api', 'contenedor', 'container'
|
||||
'microservicio', 'microservice', 'servicio', 'service', 'api', 'contenedor', 'container',
|
||||
],
|
||||
[DiagramType.LAYERED_ARCHITECTURE]: [
|
||||
'capas', 'layers', 'arquitectura por capas', 'layered architecture', 'nivel', 'tier'
|
||||
'capas', 'layers', 'arquitectura por capas', 'layered architecture', 'nivel', 'tier',
|
||||
],
|
||||
[DiagramType.C4_CONTEXT]: [
|
||||
'c4', 'contexto', 'context', 'sistema', 'system', 'usuario', 'user', 'actor'
|
||||
'c4', 'contexto', 'context', 'sistema', 'system', 'usuario', 'user', 'actor',
|
||||
],
|
||||
[DiagramType.C4_CONTAINER]: [
|
||||
'c4', 'contenedor', 'container', 'aplicación', 'application', 'servicio', 'service'
|
||||
'c4', 'contenedor', 'container', 'aplicación', 'application', 'servicio', 'service',
|
||||
],
|
||||
[DiagramType.C4_COMPONENT]: [
|
||||
'c4', 'componente', 'component', 'módulo', 'module', 'clase', 'class'
|
||||
'c4', 'componente', 'component', 'módulo', 'module', 'clase', 'class',
|
||||
],
|
||||
[DiagramType.UML_CLASS]: [
|
||||
'clase', 'objeto', 'herencia', 'polimorfismo', 'encapsulación', 'método', 'atributo',
|
||||
'class', 'object', 'inheritance', 'polymorphism', 'encapsulation', 'method',
|
||||
'uml', 'orientado a objetos', 'oop'
|
||||
'uml', 'orientado a objetos', 'oop',
|
||||
],
|
||||
[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]: [
|
||||
'caso de uso', 'use case', 'actor', 'funcionalidad', 'functionality', 'requisito', 'requirement'
|
||||
'caso de uso', 'use case', 'actor', 'funcionalidad', 'functionality', 'requisito', 'requirement',
|
||||
],
|
||||
[DiagramType.UML_ACTIVITY]: [
|
||||
'actividad', 'activity', 'flujo', 'flow', 'acción', 'action', 'proceso', 'process'
|
||||
'actividad', 'activity', 'flujo', 'flow', 'acción', 'action', 'proceso', 'process',
|
||||
],
|
||||
[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]: [
|
||||
'componente', 'component', 'interfaz', 'interface', 'dependencia', 'dependency'
|
||||
'componente', 'component', 'interfaz', 'interface', 'dependencia', 'dependency',
|
||||
],
|
||||
[DiagramType.UML_DEPLOYMENT]: [
|
||||
'despliegue', 'deployment', 'nodo', 'node', 'artefacto', 'artifact', 'hardware'
|
||||
'despliegue', 'deployment', 'nodo', 'node', 'artefacto', 'artifact', 'hardware',
|
||||
],
|
||||
[DiagramType.NETWORK_TOPOLOGY]: [
|
||||
'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]: [
|
||||
'infraestructura', 'infrastructure', 'servidor', 'server', 'hardware', 'datacenter'
|
||||
'infraestructura', 'infrastructure', 'servidor', 'server', 'hardware', 'datacenter',
|
||||
],
|
||||
[DiagramType.CLOUD_ARCHITECTURE]: [
|
||||
'nube', 'cloud', 'aws', 'azure', 'gcp', 'kubernetes', 'docker', 'contenedor'
|
||||
'nube', 'cloud', 'aws', 'azure', 'gcp', 'kubernetes', 'docker', 'contenedor',
|
||||
],
|
||||
[DiagramType.FLOWCHART]: [
|
||||
'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]: [
|
||||
'organigrama', 'orgchart', 'jerarquía', 'hierarchy', 'organización', 'organization'
|
||||
'organigrama', 'orgchart', 'jerarquía', 'hierarchy', 'organización', 'organization',
|
||||
],
|
||||
[DiagramType.MINDMAP]: [
|
||||
'mapa mental', 'mindmap', 'idea', 'concepto', 'brainstorming', 'lluvia de ideas'
|
||||
'mapa mental', 'mindmap', 'idea', 'concepto', 'brainstorming', 'lluvia de ideas',
|
||||
],
|
||||
[DiagramType.WIREFRAME]: [
|
||||
'wireframe', 'mockup', 'prototipo', 'prototype', 'interfaz', 'interface', 'ui', 'ux'
|
||||
'wireframe', 'mockup', 'prototipo', 'prototype', 'interfaz', 'interface', 'ui', 'ux',
|
||||
],
|
||||
[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
|
||||
@ -105,18 +105,18 @@ const ENTITY_PATTERNS: Partial<Record<DiagramType, Record<string, RegExp>>> = {
|
||||
[DiagramType.BPMN_PROCESS]: {
|
||||
tasks: /(?:tarea|task|actividad|activity|paso|step)\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]: {
|
||||
entities: /(?:tabla|table|entidad|entity)\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]: {
|
||||
components: /(?:componente|component|servicio|service|módulo|module)\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,
|
||||
relationships,
|
||||
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 };
|
||||
}
|
||||
|
||||
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 confidence = Math.min(maxScore / totalKeywords, 1);
|
||||
|
||||
@ -197,7 +197,7 @@ const extractEntities = (description: string, diagramType: DiagramType): string[
|
||||
}
|
||||
|
||||
// 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)];
|
||||
entities.push(...matches.map(match => match[1]?.trim()).filter(Boolean));
|
||||
}
|
||||
@ -221,7 +221,7 @@ const extractGenericEntities = (description: string): string[] => {
|
||||
|
||||
const entities = [
|
||||
...capitalizedWords,
|
||||
...quotedStrings.map(s => s.replace(/"/g, ''))
|
||||
...quotedStrings.map(s => s.replace(/"/g, '')),
|
||||
];
|
||||
|
||||
return [...new Set(entities)].slice(0, 10); // Limit to 10 entities
|
||||
@ -233,7 +233,7 @@ const extractGenericEntities = (description: string): string[] => {
|
||||
const extractRelationships = (
|
||||
description: string,
|
||||
entities: string[],
|
||||
diagramType: DiagramType
|
||||
diagramType: DiagramType,
|
||||
): 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 = [
|
||||
'conecta', 'connects', 'relaciona', 'relates', 'depende', 'depends',
|
||||
'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
|
||||
@ -254,7 +254,7 @@ const extractRelationships = (
|
||||
const pattern = new RegExp(
|
||||
`${entity1}.{0,50}(?:${relationshipKeywords.join('|')}).{0,50}${entity2}|` +
|
||||
`${entity2}.{0,50}(?:${relationshipKeywords.join('|')}).{0,50}${entity1}`,
|
||||
'i'
|
||||
'i',
|
||||
);
|
||||
|
||||
if (pattern.test(description)) {
|
||||
@ -262,7 +262,7 @@ const extractRelationships = (
|
||||
from: entity1,
|
||||
to: entity2,
|
||||
type: getRelationshipType(diagramType),
|
||||
label: extractRelationshipLabel(description, entity1, entity2)
|
||||
label: extractRelationshipLabel(description, entity1, entity2),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -276,16 +276,16 @@ const extractRelationships = (
|
||||
*/
|
||||
const getRelationshipType = (diagramType: DiagramType): string => {
|
||||
switch (diagramType) {
|
||||
case DiagramType.BPMN_PROCESS:
|
||||
return 'sequence';
|
||||
case DiagramType.ER_DIAGRAM:
|
||||
return 'relationship';
|
||||
case DiagramType.SYSTEM_ARCHITECTURE:
|
||||
return 'dependency';
|
||||
case DiagramType.UML_CLASS:
|
||||
return 'association';
|
||||
default:
|
||||
return 'connection';
|
||||
case DiagramType.BPMN_PROCESS:
|
||||
return 'sequence';
|
||||
case DiagramType.ER_DIAGRAM:
|
||||
return 'relationship';
|
||||
case DiagramType.SYSTEM_ARCHITECTURE:
|
||||
return 'dependency';
|
||||
case DiagramType.UML_CLASS:
|
||||
return 'association';
|
||||
default:
|
||||
return 'connection';
|
||||
}
|
||||
};
|
||||
|
||||
@ -313,7 +313,7 @@ const getMatchedKeywords = (text: string, diagramType: DiagramType): string[] =>
|
||||
const generateReasoning = (
|
||||
bestMatch: { type: DiagramType; confidence: number },
|
||||
keywords: string[],
|
||||
entities: string[]
|
||||
entities: string[],
|
||||
): string => {
|
||||
return `Detected ${bestMatch.type} with ${Math.round(bestMatch.confidence * 100)}% confidence. ` +
|
||||
`Found ${keywords.length} relevant keywords: ${keywords.slice(0, 5).join(', ')}. ` +
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { DiagramType, DiagramData, DiagramElement, DiagramConnection } from '../types/diagram-types.js';
|
||||
import { DiagramAnalysis } from '../types/diagram-types.js';
|
||||
import { DiagramType, DiagramData, DiagramElement, DiagramConnection, DiagramAnalysis } from '../types/diagram-types.js';
|
||||
|
||||
/**
|
||||
* Generate intelligent system architecture diagram based on analysis
|
||||
@ -7,7 +6,7 @@ import { DiagramAnalysis } from '../types/diagram-types.js';
|
||||
export const generateSmartArchitectureDiagram = (
|
||||
description: string,
|
||||
analysis: DiagramAnalysis,
|
||||
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {}
|
||||
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {},
|
||||
): DiagramData => {
|
||||
const { entities, relationships } = analysis;
|
||||
const complexity = preferences.complexity || 'detailed';
|
||||
@ -55,7 +54,7 @@ export const generateSmartArchitectureDiagram = (
|
||||
`component-${sourceComponent.name}`,
|
||||
`component-${targetComponent.name}`,
|
||||
connection.type,
|
||||
connection.label
|
||||
connection.label,
|
||||
);
|
||||
connectionElements.push(connectionElement);
|
||||
}
|
||||
@ -69,8 +68,8 @@ export const generateSmartArchitectureDiagram = (
|
||||
format: 'drawio' as any,
|
||||
created: 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'],
|
||||
backend: ['backend', 'servidor', 'server', 'node', 'express', 'spring'],
|
||||
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
|
||||
@ -110,7 +109,7 @@ const extractArchitectureComponents = (entities: string[], description: string):
|
||||
name: cleanComponentName(entity),
|
||||
type: componentType,
|
||||
description: extractComponentDescription(entity, description),
|
||||
technologies
|
||||
technologies,
|
||||
});
|
||||
});
|
||||
|
||||
@ -124,7 +123,7 @@ const extractArchitectureComponents = (entities: string[], description: string):
|
||||
{ name: 'Frontend', type: 'frontend', technologies: ['React'] },
|
||||
{ name: 'API Gateway', type: 'middleware', technologies: ['Express'] },
|
||||
{ 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 = (
|
||||
componentName: string,
|
||||
typeMap: Record<string, string[]>
|
||||
typeMap: Record<string, string[]>,
|
||||
): 'service' | 'database' | 'api' | 'frontend' | 'backend' | 'middleware' | 'external' => {
|
||||
const name = componentName.toLowerCase();
|
||||
|
||||
@ -157,7 +156,7 @@ const extractTechnologies = (componentName: string, description: string): string
|
||||
const techKeywords = [
|
||||
'react', 'vue', 'angular', 'node', 'express', 'spring', 'django',
|
||||
'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();
|
||||
@ -177,7 +176,7 @@ const extractComponentDescription = (componentName: string, description: string)
|
||||
// Look for descriptions near the component name
|
||||
const patterns = [
|
||||
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) {
|
||||
@ -195,7 +194,7 @@ const extractComponentDescription = (componentName: string, description: string)
|
||||
*/
|
||||
const extractComponentsFromDescription = (
|
||||
description: string,
|
||||
typeMap: Record<string, string[]>
|
||||
typeMap: Record<string, string[]>,
|
||||
): Array<{
|
||||
name: string;
|
||||
type: 'service' | 'database' | 'api' | 'frontend' | 'backend' | 'middleware' | 'external';
|
||||
@ -212,7 +211,7 @@ const extractComponentsFromDescription = (
|
||||
// Look for component patterns
|
||||
const componentPatterns = [
|
||||
/(?: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) {
|
||||
@ -226,7 +225,7 @@ const extractComponentsFromDescription = (
|
||||
components.push({
|
||||
name,
|
||||
type,
|
||||
technologies
|
||||
technologies,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -240,7 +239,7 @@ const extractComponentsFromDescription = (
|
||||
*/
|
||||
const extractArchitectureLayers = (
|
||||
description: string,
|
||||
components: Array<{ name: string; type: string }>
|
||||
components: Array<{ name: string; type: string }>,
|
||||
): Array<{
|
||||
name: string;
|
||||
type: 'presentation' | 'business' | 'data' | 'integration';
|
||||
@ -256,33 +255,33 @@ const extractArchitectureLayers = (
|
||||
const layerMappings = {
|
||||
presentation: {
|
||||
name: 'Capa de Presentación',
|
||||
types: ['frontend', 'ui', 'client']
|
||||
types: ['frontend', 'ui', 'client'],
|
||||
},
|
||||
business: {
|
||||
name: 'Capa de Negocio',
|
||||
types: ['service', 'backend', 'api']
|
||||
types: ['service', 'backend', 'api'],
|
||||
},
|
||||
data: {
|
||||
name: 'Capa de Datos',
|
||||
types: ['database', 'storage']
|
||||
types: ['database', 'storage'],
|
||||
},
|
||||
integration: {
|
||||
name: 'Capa de Integración',
|
||||
types: ['middleware', 'external', 'gateway']
|
||||
}
|
||||
types: ['middleware', 'external', 'gateway'],
|
||||
},
|
||||
};
|
||||
|
||||
// Organize components into layers
|
||||
Object.entries(layerMappings).forEach(([layerType, layerConfig]) => {
|
||||
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) {
|
||||
layers.push({
|
||||
name: layerConfig.name,
|
||||
type: layerType as any,
|
||||
components: layerComponents
|
||||
components: layerComponents,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -293,18 +292,18 @@ const extractArchitectureLayers = (
|
||||
{
|
||||
name: 'Capa de Presentación',
|
||||
type: 'presentation',
|
||||
components: components.filter(c => c.type === 'frontend')
|
||||
components: components.filter(c => c.type === 'frontend'),
|
||||
},
|
||||
{
|
||||
name: 'Capa de Negocio',
|
||||
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',
|
||||
type: 'data',
|
||||
components: components.filter(c => c.type === 'database')
|
||||
}
|
||||
components: components.filter(c => c.type === 'database'),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -317,7 +316,7 @@ const extractArchitectureLayers = (
|
||||
const extractArchitectureConnections = (
|
||||
relationships: Array<{ from: string; to: string; type: string; label?: string }>,
|
||||
description: string,
|
||||
components: Array<{ name: string; type: string }>
|
||||
components: Array<{ name: string; type: string }>,
|
||||
): 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 => {
|
||||
connections.push({
|
||||
...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
|
||||
const exists = connections.some(conn =>
|
||||
(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) {
|
||||
@ -359,7 +358,7 @@ const extractArchitectureConnections = (
|
||||
const inferConnectionType = (
|
||||
from: string,
|
||||
to: string,
|
||||
components: Array<{ name: string; type: string }>
|
||||
components: Array<{ name: string; type: string }>,
|
||||
): string => {
|
||||
const fromComp = components.find(c => c.name === from);
|
||||
const toComp = components.find(c => c.name === to);
|
||||
@ -381,7 +380,7 @@ const inferConnectionType = (
|
||||
const inferImplicitConnection = (
|
||||
comp1: { name: string; type: string },
|
||||
comp2: { name: string; type: string },
|
||||
description: string
|
||||
_description: string,
|
||||
): { from: string; to: string; type: string; label?: string } | null => {
|
||||
// Common architecture patterns
|
||||
const patterns = [
|
||||
@ -389,7 +388,7 @@ const inferImplicitConnection = (
|
||||
{ from: 'api', to: 'database', label: 'Query' },
|
||||
{ from: 'service', to: 'database', label: 'Data Access' },
|
||||
{ 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) {
|
||||
@ -399,7 +398,7 @@ const inferImplicitConnection = (
|
||||
from: comp1.name,
|
||||
to: comp2.name,
|
||||
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 = (
|
||||
layers: Array<{ components: Array<{ name: string; type: string }> }>,
|
||||
componentName: string
|
||||
componentName: string,
|
||||
): { name: string; type: string } | null => {
|
||||
for (const layer of layers) {
|
||||
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 }> },
|
||||
x: number,
|
||||
y: number,
|
||||
complexity: 'simple' | 'detailed'
|
||||
_complexity: 'simple' | 'detailed',
|
||||
): DiagramElement => {
|
||||
const width = 800;
|
||||
const height = 150;
|
||||
@ -449,7 +448,7 @@ const createLayer = (
|
||||
label: layer.name,
|
||||
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;',
|
||||
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[] },
|
||||
x: number,
|
||||
y: number,
|
||||
complexity: 'simple' | 'detailed'
|
||||
complexity: 'simple' | 'detailed',
|
||||
): DiagramElement => {
|
||||
const width = complexity === 'detailed' ? 180 : 120;
|
||||
const height = complexity === 'detailed' ? 100 : 80;
|
||||
@ -483,7 +482,7 @@ const createComponent = (
|
||||
api: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;',
|
||||
service: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;',
|
||||
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;
|
||||
@ -498,8 +497,8 @@ const createComponent = (
|
||||
componentType: component.type,
|
||||
componentName: component.name,
|
||||
technologies: component.technologies || [],
|
||||
description: component.description || ''
|
||||
}
|
||||
description: component.description || '',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -510,7 +509,7 @@ const createArchitectureConnection = (
|
||||
sourceId: string,
|
||||
targetId: string,
|
||||
connectionType: string,
|
||||
label?: string
|
||||
label?: string,
|
||||
): DiagramConnection => {
|
||||
// Choose style based on connection type
|
||||
const styleMap = {
|
||||
@ -518,7 +517,7 @@ const createArchitectureConnection = (
|
||||
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;',
|
||||
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;
|
||||
@ -529,6 +528,6 @@ const createArchitectureConnection = (
|
||||
target: targetId,
|
||||
label: label || '',
|
||||
style,
|
||||
properties: { connectionType }
|
||||
properties: { connectionType },
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { DiagramType, DiagramData, DiagramElement, DiagramConnection } from '../types/diagram-types.js';
|
||||
import { DiagramAnalysis } from '../types/diagram-types.js';
|
||||
import { DiagramType, DiagramData, DiagramElement, DiagramConnection, DiagramAnalysis } from '../types/diagram-types.js';
|
||||
|
||||
/**
|
||||
* Generate intelligent BPMN diagram based on analysis
|
||||
@ -7,9 +6,9 @@ import { DiagramAnalysis } from '../types/diagram-types.js';
|
||||
export const generateSmartBPMNDiagram = (
|
||||
description: string,
|
||||
analysis: DiagramAnalysis,
|
||||
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {}
|
||||
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {},
|
||||
): DiagramData => {
|
||||
const { entities, relationships } = analysis;
|
||||
const { entities } = analysis;
|
||||
const complexity = preferences.complexity || 'detailed';
|
||||
|
||||
// Identify different types of BPMN elements from entities
|
||||
@ -22,7 +21,7 @@ export const generateSmartBPMNDiagram = (
|
||||
const connections: DiagramConnection[] = [];
|
||||
|
||||
let currentX = 100;
|
||||
let currentY = 150;
|
||||
const currentY = 150;
|
||||
const spacing = 200;
|
||||
|
||||
// Add start event
|
||||
@ -39,17 +38,17 @@ export const generateSmartBPMNDiagram = (
|
||||
let element: DiagramElement;
|
||||
|
||||
switch (flowElement.type) {
|
||||
case 'task':
|
||||
element = createTask(flowElement.name, currentX, currentY, complexity);
|
||||
break;
|
||||
case 'gateway':
|
||||
element = createGateway(flowElement.name, currentX, currentY, flowElement.gatewayType || 'exclusive');
|
||||
break;
|
||||
case 'event':
|
||||
element = createIntermediateEvent(flowElement.name, currentX, currentY);
|
||||
break;
|
||||
default:
|
||||
element = createTask(flowElement.name, currentX, currentY, complexity);
|
||||
case 'task':
|
||||
element = createTask(flowElement.name, currentX, currentY, complexity);
|
||||
break;
|
||||
case 'gateway':
|
||||
element = createGateway(flowElement.name, currentX, currentY, flowElement.gatewayType || 'exclusive');
|
||||
break;
|
||||
case 'event':
|
||||
element = createIntermediateEvent(flowElement.name, currentX, currentY);
|
||||
break;
|
||||
default:
|
||||
element = createTask(flowElement.name, currentX, currentY, complexity);
|
||||
}
|
||||
|
||||
elements.push(element);
|
||||
@ -68,7 +67,7 @@ export const generateSmartBPMNDiagram = (
|
||||
currentX,
|
||||
currentY,
|
||||
spacing,
|
||||
complexity
|
||||
complexity,
|
||||
);
|
||||
|
||||
elements.push(...branchResults.elements);
|
||||
@ -93,8 +92,8 @@ export const generateSmartBPMNDiagram = (
|
||||
format: 'drawio' as any,
|
||||
created: 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
|
||||
*/
|
||||
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 events: string[] = [];
|
||||
|
||||
@ -175,7 +174,7 @@ const extractGateways = (entities: string[], description: string): Array<{ name:
|
||||
/si\s+(.+?)\s+entonces/gi,
|
||||
/if\s+(.+?)\s+then/gi,
|
||||
/cuando\s+(.+?)\s+[,.]?/gi,
|
||||
/en caso de\s+(.+?)\s+[,.]?/gi
|
||||
/en caso de\s+(.+?)\s+[,.]?/gi,
|
||||
];
|
||||
|
||||
for (const pattern of decisionPatterns) {
|
||||
@ -196,7 +195,7 @@ const extractGateways = (entities: string[], description: string): Array<{ name:
|
||||
const buildProcessFlow = (
|
||||
tasks: string[],
|
||||
gateways: Array<{ name: string; type: 'exclusive' | 'parallel' | 'inclusive' }>,
|
||||
events: string[]
|
||||
_events: string[],
|
||||
): 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',
|
||||
name: gateway.name,
|
||||
gatewayType: gateway.type,
|
||||
branches: branches
|
||||
branches: branches,
|
||||
});
|
||||
|
||||
gatewayIndex++;
|
||||
@ -265,7 +264,7 @@ const createStartEvent = (x: number, y: number): DiagramElement => ({
|
||||
label: 'Inicio',
|
||||
geometry: { x, y, width: 36, height: 36 },
|
||||
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 => ({
|
||||
@ -274,7 +273,7 @@ const createEndEvent = (x: number, y: number): DiagramElement => ({
|
||||
label: 'Fin',
|
||||
geometry: { x, y, width: 36, height: 36 },
|
||||
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 => {
|
||||
@ -287,7 +286,7 @@ const createTask = (name: string, x: number, y: number, complexity: 'simple' | '
|
||||
label: name,
|
||||
geometry: { x, y, width, height },
|
||||
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,
|
||||
geometry: { x, y, width: 50, height: 50 },
|
||||
style: 'rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;',
|
||||
properties: { gatewayType }
|
||||
properties: { gatewayType },
|
||||
});
|
||||
|
||||
const createIntermediateEvent = (name: string, x: number, y: number): DiagramElement => ({
|
||||
@ -306,7 +305,7 @@ const createIntermediateEvent = (name: string, x: number, y: number): DiagramEle
|
||||
label: name,
|
||||
geometry: { x, y, width: 36, height: 36 },
|
||||
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 => ({
|
||||
@ -315,7 +314,7 @@ const createSequenceFlow = (sourceId: string, targetId: string, label?: string):
|
||||
target: targetId,
|
||||
label: label || '',
|
||||
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,
|
||||
startY: number,
|
||||
spacing: number,
|
||||
complexity: 'simple' | 'detailed'
|
||||
complexity: 'simple' | 'detailed',
|
||||
): {
|
||||
elements: DiagramElement[];
|
||||
connections: DiagramConnection[];
|
||||
@ -350,7 +349,7 @@ const createGatewayBranchElements = (
|
||||
let branchX = startX;
|
||||
let previousId = gatewayId;
|
||||
|
||||
branch.forEach((taskName, taskIndex) => {
|
||||
branch.forEach((taskName, _taskIndex) => {
|
||||
branchX += spacing;
|
||||
const task = createTask(taskName, branchX, branchY, complexity);
|
||||
elements.push(task);
|
||||
@ -370,6 +369,6 @@ const createGatewayBranchElements = (
|
||||
elements,
|
||||
connections,
|
||||
nextX: maxX + spacing,
|
||||
convergenceGatewayId: convergenceGateway.id
|
||||
convergenceGatewayId: convergenceGateway.id,
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { DiagramType, DiagramData, DiagramElement, DiagramConnection } from '../types/diagram-types.js';
|
||||
import { DiagramAnalysis } from '../types/diagram-types.js';
|
||||
import { DiagramType, DiagramData, DiagramElement, DiagramConnection, DiagramAnalysis } from '../types/diagram-types.js';
|
||||
|
||||
/**
|
||||
* Generate intelligent ER diagram based on analysis
|
||||
@ -7,7 +6,7 @@ import { DiagramAnalysis } from '../types/diagram-types.js';
|
||||
export const generateSmartERDiagram = (
|
||||
description: string,
|
||||
analysis: DiagramAnalysis,
|
||||
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {}
|
||||
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {},
|
||||
): DiagramData => {
|
||||
const { entities, relationships } = analysis;
|
||||
const complexity = preferences.complexity || 'detailed';
|
||||
@ -47,7 +46,7 @@ export const generateSmartERDiagram = (
|
||||
`entity-${sourceEntity.name}`,
|
||||
`entity-${targetEntity.name}`,
|
||||
relationship.type,
|
||||
relationship.label
|
||||
relationship.label,
|
||||
);
|
||||
connections.push(connection);
|
||||
}
|
||||
@ -61,8 +60,8 @@ export const generateSmartERDiagram = (
|
||||
format: 'drawio' as any,
|
||||
created: 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);
|
||||
dbEntities.push({
|
||||
name: entityName,
|
||||
attributes
|
||||
attributes,
|
||||
});
|
||||
});
|
||||
|
||||
@ -119,7 +118,7 @@ const extractDatabaseEntities = (entities: string[], description: string): Array
|
||||
*/
|
||||
const generateEntityAttributes = (
|
||||
entityName: string,
|
||||
description: string
|
||||
description: string,
|
||||
): 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({
|
||||
name: 'id',
|
||||
type: 'INT',
|
||||
isPrimaryKey: true
|
||||
isPrimaryKey: true,
|
||||
});
|
||||
|
||||
// Generate context-specific attributes based on entity name
|
||||
@ -141,7 +140,7 @@ const generateEntityAttributes = (
|
||||
// Add common attributes
|
||||
attributes.push(
|
||||
{ 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
|
||||
@ -156,55 +155,55 @@ const getContextualAttributes = (entityName: string): Array<{ name: string; type
|
||||
{ name: 'nombre', type: 'VARCHAR(100)' },
|
||||
{ name: 'email', type: 'VARCHAR(255)' },
|
||||
{ name: 'password', type: 'VARCHAR(255)' },
|
||||
{ name: 'telefono', type: 'VARCHAR(20)' }
|
||||
{ name: 'telefono', type: 'VARCHAR(20)' },
|
||||
],
|
||||
user: [
|
||||
{ name: 'name', type: 'VARCHAR(100)' },
|
||||
{ name: 'email', type: 'VARCHAR(255)' },
|
||||
{ name: 'password', type: 'VARCHAR(255)' },
|
||||
{ name: 'phone', type: 'VARCHAR(20)' }
|
||||
{ name: 'phone', type: 'VARCHAR(20)' },
|
||||
],
|
||||
producto: [
|
||||
{ name: 'nombre', type: 'VARCHAR(200)' },
|
||||
{ name: 'descripcion', type: 'TEXT' },
|
||||
{ name: 'precio', type: 'DECIMAL(10,2)' },
|
||||
{ name: 'stock', type: 'INT' }
|
||||
{ name: 'stock', type: 'INT' },
|
||||
],
|
||||
product: [
|
||||
{ name: 'name', type: 'VARCHAR(200)' },
|
||||
{ name: 'description', type: 'TEXT' },
|
||||
{ name: 'price', type: 'DECIMAL(10,2)' },
|
||||
{ name: 'stock', type: 'INT' }
|
||||
{ name: 'stock', type: 'INT' },
|
||||
],
|
||||
pedido: [
|
||||
{ name: 'numero', type: 'VARCHAR(50)' },
|
||||
{ name: 'total', type: 'DECIMAL(10,2)' },
|
||||
{ name: 'estado', type: 'VARCHAR(50)' },
|
||||
{ name: 'fecha', type: 'DATE' }
|
||||
{ name: 'fecha', type: 'DATE' },
|
||||
],
|
||||
order: [
|
||||
{ name: 'number', type: 'VARCHAR(50)' },
|
||||
{ name: 'total', type: 'DECIMAL(10,2)' },
|
||||
{ name: 'status', type: 'VARCHAR(50)' },
|
||||
{ name: 'date', type: 'DATE' }
|
||||
{ name: 'date', type: 'DATE' },
|
||||
],
|
||||
cliente: [
|
||||
{ name: 'nombre', type: 'VARCHAR(100)' },
|
||||
{ name: 'email', type: 'VARCHAR(255)' },
|
||||
{ name: 'direccion', type: 'TEXT' },
|
||||
{ name: 'telefono', type: 'VARCHAR(20)' }
|
||||
{ name: 'telefono', type: 'VARCHAR(20)' },
|
||||
],
|
||||
customer: [
|
||||
{ name: 'name', type: 'VARCHAR(100)' },
|
||||
{ name: 'email', type: 'VARCHAR(255)' },
|
||||
{ name: 'address', type: 'TEXT' },
|
||||
{ name: 'phone', type: 'VARCHAR(20)' }
|
||||
]
|
||||
{ name: 'phone', type: 'VARCHAR(20)' },
|
||||
],
|
||||
};
|
||||
|
||||
return attributeMap[entityName] || [
|
||||
{ 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 = (
|
||||
description: string,
|
||||
entityName: string
|
||||
entityName: string,
|
||||
): Array<{ name: string; type: string }> => {
|
||||
const attributes: Array<{ name: string; type: string }> = [];
|
||||
|
||||
// Look for attribute patterns
|
||||
const attributePatterns = [
|
||||
/(?: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) {
|
||||
@ -271,7 +270,7 @@ const inferAttributeType = (attributeName: string): string => {
|
||||
const extractDatabaseRelationships = (
|
||||
relationships: Array<{ from: string; to: string; type: string; label?: 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 }> => {
|
||||
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);
|
||||
dbRelationships.push({
|
||||
...rel,
|
||||
cardinality
|
||||
cardinality,
|
||||
});
|
||||
});
|
||||
|
||||
@ -293,7 +292,7 @@ const extractDatabaseRelationships = (
|
||||
// Check if relationship already exists
|
||||
const existingRel = dbRelationships.find(
|
||||
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) {
|
||||
@ -331,13 +330,13 @@ const inferCardinality = (entity1: string, entity2: string, description: string)
|
||||
const commonOneToMany = [
|
||||
['usuario', 'pedido'], ['user', 'order'],
|
||||
['cliente', 'pedido'], ['customer', 'order'],
|
||||
['categoria', 'producto'], ['category', 'product']
|
||||
['categoria', 'producto'], ['category', 'product'],
|
||||
];
|
||||
|
||||
const commonManyToMany = [
|
||||
['producto', 'categoria'], ['product', 'category'],
|
||||
['usuario', 'rol'], ['user', 'role'],
|
||||
['estudiante', 'curso'], ['student', 'course']
|
||||
['estudiante', 'curso'], ['student', 'course'],
|
||||
];
|
||||
|
||||
for (const [first, second] of commonOneToMany) {
|
||||
@ -363,7 +362,7 @@ const inferCardinality = (entity1: string, entity2: string, description: string)
|
||||
const inferImplicitRelationship = (
|
||||
entity1: string,
|
||||
entity2: string,
|
||||
description: string
|
||||
_description: string,
|
||||
): { from: string; to: string; type: string; label?: string; cardinality?: string } | null => {
|
||||
const e1 = entity1.toLowerCase();
|
||||
const e2 = entity2.toLowerCase();
|
||||
@ -375,7 +374,7 @@ const inferImplicitRelationship = (
|
||||
{ entities: ['cliente', 'pedido'], label: 'hace', cardinality: '1:N' },
|
||||
{ entities: ['customer', 'order'], label: 'makes', cardinality: '1:N' },
|
||||
{ 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) {
|
||||
@ -387,7 +386,7 @@ const inferImplicitRelationship = (
|
||||
to: entity2,
|
||||
type: 'relationship',
|
||||
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 }> },
|
||||
x: number,
|
||||
y: number,
|
||||
complexity: 'simple' | 'detailed'
|
||||
complexity: 'simple' | 'detailed',
|
||||
): DiagramElement => {
|
||||
const width = complexity === 'detailed' ? 200 : 150;
|
||||
const attributeHeight = complexity === 'detailed' ? 20 : 16;
|
||||
@ -457,7 +456,7 @@ const createEntity = (
|
||||
label,
|
||||
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;',
|
||||
properties: { entityType: 'table', entityName: entity.name }
|
||||
properties: { entityType: 'table', entityName: entity.name },
|
||||
};
|
||||
};
|
||||
|
||||
@ -468,7 +467,7 @@ const createRelationship = (
|
||||
sourceId: string,
|
||||
targetId: string,
|
||||
relationshipType: string,
|
||||
label?: string
|
||||
label?: string,
|
||||
): DiagramConnection => {
|
||||
return {
|
||||
id: `rel-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
@ -476,6 +475,6 @@ const createRelationship = (
|
||||
target: targetId,
|
||||
label: label || '',
|
||||
style: 'edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;startArrow=ERmandOne;',
|
||||
properties: { relationshipType, cardinality: '1:N' }
|
||||
properties: { relationshipType, cardinality: '1:N' },
|
||||
};
|
||||
};
|
||||
|
105
src/index.ts
105
src/index.ts
@ -11,7 +11,6 @@ import {
|
||||
ListToolsRequestSchema,
|
||||
McpError,
|
||||
ReadResourceRequestSchema,
|
||||
InitializeRequestSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
// Import tools and utilities
|
||||
@ -64,103 +63,103 @@ const createToolDefinitions = () => [
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Name of the diagram file (without extension)'
|
||||
description: 'Name of the diagram file (without extension)',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: Object.values(DiagramType),
|
||||
description: 'Type of diagram to create'
|
||||
description: 'Type of diagram to create',
|
||||
},
|
||||
format: {
|
||||
type: 'string',
|
||||
enum: Object.values(DiagramFormat),
|
||||
default: 'drawio',
|
||||
description: 'File format for the diagram'
|
||||
description: 'File format for the diagram',
|
||||
},
|
||||
description: {
|
||||
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: {
|
||||
type: 'string',
|
||||
description: 'Output directory path (relative to workspace)'
|
||||
description: 'Output directory path (relative to workspace)',
|
||||
},
|
||||
complexity: {
|
||||
type: 'string',
|
||||
enum: ['simple', 'detailed'],
|
||||
default: 'detailed',
|
||||
description: 'Complexity level of the generated diagram'
|
||||
description: 'Complexity level of the generated diagram',
|
||||
},
|
||||
language: {
|
||||
type: 'string',
|
||||
default: 'es',
|
||||
description: 'Language for diagram labels and text'
|
||||
description: 'Language for diagram labels and text',
|
||||
},
|
||||
// Legacy parameters for backward compatibility
|
||||
processName: {
|
||||
type: 'string',
|
||||
description: 'Name of the BPMN process (legacy)'
|
||||
description: 'Name of the BPMN process (legacy)',
|
||||
},
|
||||
tasks: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'List of tasks for BPMN process (legacy)'
|
||||
description: 'List of tasks for BPMN process (legacy)',
|
||||
},
|
||||
gatewayType: {
|
||||
type: 'string',
|
||||
enum: ['exclusive', 'parallel'],
|
||||
description: 'Type of gateway for BPMN process (legacy)'
|
||||
description: 'Type of gateway for BPMN process (legacy)',
|
||||
},
|
||||
branches: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'array',
|
||||
items: { type: 'string' }
|
||||
items: { type: 'string' },
|
||||
},
|
||||
description: 'Branches for BPMN gateway (legacy)'
|
||||
description: 'Branches for BPMN gateway (legacy)',
|
||||
},
|
||||
beforeGateway: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Tasks before gateway (legacy)'
|
||||
description: 'Tasks before gateway (legacy)',
|
||||
},
|
||||
afterGateway: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Tasks after gateway (legacy)'
|
||||
description: 'Tasks after gateway (legacy)',
|
||||
},
|
||||
classes: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'List of classes for UML class diagram (legacy)'
|
||||
description: 'List of classes for UML class diagram (legacy)',
|
||||
},
|
||||
entities: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'List of entities for ER diagram (legacy)'
|
||||
description: 'List of entities for ER diagram (legacy)',
|
||||
},
|
||||
components: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'List of components for network or architecture diagram (legacy)'
|
||||
description: 'List of components for network or architecture diagram (legacy)',
|
||||
},
|
||||
processes: {
|
||||
type: 'array',
|
||||
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',
|
||||
description: 'Get list of supported diagram types with descriptions',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
}
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Pure function to create resource definitions
|
||||
@ -169,8 +168,8 @@ const createResourceDefinitions = () => [
|
||||
uri: 'diagrams://types/supported',
|
||||
name: 'Supported Diagram Types',
|
||||
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
|
||||
@ -179,22 +178,22 @@ const createToolHandlers = (config: ServerConfig): ToolHandlers => ({
|
||||
if (!validateCreateDiagramInput(args)) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
'Invalid create_diagram arguments'
|
||||
'Invalid create_diagram arguments',
|
||||
);
|
||||
}
|
||||
|
||||
const result = await createDiagram({
|
||||
...args,
|
||||
workspaceRoot: args.workspaceRoot || config.workspaceRoot
|
||||
workspaceRoot: args.workspaceRoot || config.workspaceRoot,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
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 typesWithDescriptions = types.map(type => ({
|
||||
type,
|
||||
description: getDiagramTypeDescription(type)
|
||||
description: getDiagramTypeDescription(type),
|
||||
}));
|
||||
|
||||
return {
|
||||
@ -211,21 +210,21 @@ const createToolHandlers = (config: ServerConfig): ToolHandlers => ({
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
supportedTypes: typesWithDescriptions
|
||||
}, null, 2)
|
||||
}
|
||||
]
|
||||
supportedTypes: typesWithDescriptions,
|
||||
}, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Pure function to create resource handlers
|
||||
const createResourceHandlers = (config: ServerConfig): ResourceHandlers => ({
|
||||
const createResourceHandlers = (_config: ServerConfig): ResourceHandlers => ({
|
||||
'diagrams://types/supported': async () => {
|
||||
const types = getSupportedDiagramTypes();
|
||||
const typesWithDescriptions = types.map(type => ({
|
||||
type,
|
||||
description: getDiagramTypeDescription(type)
|
||||
description: getDiagramTypeDescription(type),
|
||||
}));
|
||||
|
||||
return {
|
||||
@ -233,18 +232,18 @@ const createResourceHandlers = (config: ServerConfig): ResourceHandlers => ({
|
||||
{
|
||||
uri: 'diagrams://types/supported',
|
||||
mimeType: 'application/json',
|
||||
text: JSON.stringify(typesWithDescriptions, null, 2)
|
||||
}
|
||||
]
|
||||
text: JSON.stringify(typesWithDescriptions, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Pure function to setup tool request handlers
|
||||
const setupToolRequestHandlers = (server: Server, toolHandlers: ToolHandlers) => {
|
||||
// List available tools
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools: createToolDefinitions()
|
||||
tools: createToolDefinitions(),
|
||||
}));
|
||||
|
||||
// Handle tool calls
|
||||
@ -254,7 +253,7 @@ const setupToolRequestHandlers = (server: Server, toolHandlers: ToolHandlers) =>
|
||||
if (!handler) {
|
||||
throw new McpError(
|
||||
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) => {
|
||||
// List available resources
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
||||
resources: createResourceDefinitions()
|
||||
resources: createResourceDefinitions(),
|
||||
}));
|
||||
|
||||
// Handle resource requests
|
||||
@ -292,7 +291,7 @@ const setupResourceRequestHandlers = (server: Server, resourceHandlers: Resource
|
||||
if (!handler) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidRequest,
|
||||
`Unknown resource URI: ${uri}`
|
||||
`Unknown resource URI: ${uri}`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -300,7 +299,7 @@ const setupResourceRequestHandlers = (server: Server, resourceHandlers: Resource
|
||||
} catch (error) {
|
||||
throw new McpError(
|
||||
ErrorCode.InternalError,
|
||||
`Failed to read resource: ${error}`
|
||||
`Failed to read resource: ${error}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -323,7 +322,7 @@ const createMCPServer = (config: ServerConfig): Server => {
|
||||
},
|
||||
{
|
||||
capabilities: config.capabilities,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const toolHandlers = createToolHandlers(config);
|
||||
@ -368,7 +367,7 @@ const isInitializeRequest = (body: any): boolean => {
|
||||
};
|
||||
|
||||
// Pure function to create session transport
|
||||
const createSessionTransport = (config: ServerConfig): StreamableHTTPServerTransport => {
|
||||
const createSessionTransport = (_config: ServerConfig): StreamableHTTPServerTransport => {
|
||||
return new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => randomUUID(),
|
||||
onsessioninitialized: (sessionId) => {
|
||||
@ -484,8 +483,8 @@ const setupAPIRoutes = (app: express.Application, config: ServerConfig) => {
|
||||
services: {
|
||||
mcp: 'operational',
|
||||
diagramGeneration: 'operational',
|
||||
sessions: Object.keys(transports).length
|
||||
}
|
||||
sessions: Object.keys(transports).length,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
@ -1,44 +1,6 @@
|
||||
// Test setup file for Jest
|
||||
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
|
||||
beforeEach(() => {
|
||||
// Clear any previous test state
|
||||
|
@ -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 { DiagramType, DiagramFormat } from '../../types/diagram-types.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// Mock file system operations
|
||||
jest.mock('fs', () => ({
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { generateSmartBPMNDiagram } from '../generators/smart-bpmn-generator.js';
|
||||
import { generateSmartERDiagram } from '../generators/smart-er-generator.js';
|
||||
@ -45,26 +45,26 @@ const createSuccessResult = (
|
||||
content: string,
|
||||
diagramType: DiagramType,
|
||||
format: DiagramFormat,
|
||||
name: string
|
||||
name: string,
|
||||
): CreateDiagramResult => ({
|
||||
success: true,
|
||||
filePath,
|
||||
content,
|
||||
message: `Successfully created ${diagramType} diagram: ${name}`,
|
||||
diagramType,
|
||||
format
|
||||
format,
|
||||
});
|
||||
|
||||
// Pure function to create error result
|
||||
const createErrorResult = (
|
||||
error: unknown,
|
||||
diagramType: DiagramType,
|
||||
format: DiagramFormat
|
||||
format: DiagramFormat,
|
||||
): CreateDiagramResult => ({
|
||||
success: false,
|
||||
message: `Failed to create diagram: ${error instanceof Error ? error.message : String(error)}`,
|
||||
diagramType,
|
||||
format
|
||||
format,
|
||||
});
|
||||
|
||||
// Pure function to ensure directory exists
|
||||
@ -93,33 +93,33 @@ const generateAIDiagram = async (input: CreateDiagramInput): Promise<DiagramData
|
||||
// Generate diagram based on type
|
||||
const preferences = {
|
||||
complexity: input.complexity || 'detailed',
|
||||
language: input.language || 'es'
|
||||
language: input.language || 'es',
|
||||
};
|
||||
|
||||
switch (input.type) {
|
||||
case DiagramType.BPMN_PROCESS:
|
||||
case DiagramType.BPMN_COLLABORATION:
|
||||
case DiagramType.BPMN_CHOREOGRAPHY:
|
||||
return generateSmartBPMNDiagram(input.description, analysis, preferences);
|
||||
case DiagramType.BPMN_PROCESS:
|
||||
case DiagramType.BPMN_COLLABORATION:
|
||||
case DiagramType.BPMN_CHOREOGRAPHY:
|
||||
return generateSmartBPMNDiagram(input.description, analysis, preferences);
|
||||
|
||||
case DiagramType.ER_DIAGRAM:
|
||||
case DiagramType.DATABASE_SCHEMA:
|
||||
case DiagramType.CONCEPTUAL_MODEL:
|
||||
return generateSmartERDiagram(input.description, analysis, preferences);
|
||||
case DiagramType.ER_DIAGRAM:
|
||||
case DiagramType.DATABASE_SCHEMA:
|
||||
case DiagramType.CONCEPTUAL_MODEL:
|
||||
return generateSmartERDiagram(input.description, analysis, preferences);
|
||||
|
||||
case DiagramType.SYSTEM_ARCHITECTURE:
|
||||
case DiagramType.MICROSERVICES:
|
||||
case DiagramType.LAYERED_ARCHITECTURE:
|
||||
case DiagramType.C4_CONTEXT:
|
||||
case DiagramType.C4_CONTAINER:
|
||||
case DiagramType.C4_COMPONENT:
|
||||
case DiagramType.CLOUD_ARCHITECTURE:
|
||||
case DiagramType.INFRASTRUCTURE:
|
||||
return generateSmartArchitectureDiagram(input.description, analysis, preferences);
|
||||
case DiagramType.SYSTEM_ARCHITECTURE:
|
||||
case DiagramType.MICROSERVICES:
|
||||
case DiagramType.LAYERED_ARCHITECTURE:
|
||||
case DiagramType.C4_CONTEXT:
|
||||
case DiagramType.C4_CONTAINER:
|
||||
case DiagramType.C4_COMPONENT:
|
||||
case DiagramType.CLOUD_ARCHITECTURE:
|
||||
case DiagramType.INFRASTRUCTURE:
|
||||
return generateSmartArchitectureDiagram(input.description, analysis, preferences);
|
||||
|
||||
default:
|
||||
// Fallback to basic diagram generation
|
||||
return generateBasicDiagram(input);
|
||||
default:
|
||||
// Fallback to basic diagram generation
|
||||
return generateBasicDiagram(input);
|
||||
}
|
||||
};
|
||||
|
||||
@ -134,10 +134,10 @@ const generateBasicDiagram = (input: CreateDiagramInput): DiagramData => {
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 200,
|
||||
height: 100
|
||||
height: 100,
|
||||
},
|
||||
style: 'rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
|
||||
properties: {}
|
||||
properties: {},
|
||||
}],
|
||||
connections: [],
|
||||
metadata: {
|
||||
@ -145,8 +145,8 @@ const generateBasicDiagram = (input: CreateDiagramInput): DiagramData => {
|
||||
format: input.format || DiagramFormat.DRAWIO,
|
||||
created: new Date().toISOString(),
|
||||
modified: new Date().toISOString(),
|
||||
version: '1.0'
|
||||
}
|
||||
version: '1.0',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -167,10 +167,10 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
|
||||
x: 100,
|
||||
y: 100 + (index * 120),
|
||||
width: 120,
|
||||
height: 80
|
||||
height: 80,
|
||||
},
|
||||
style: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
|
||||
properties: { isTask: true }
|
||||
properties: { isTask: true },
|
||||
});
|
||||
|
||||
if (index > 0) {
|
||||
@ -180,7 +180,7 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
|
||||
target: `task-${index}`,
|
||||
label: '',
|
||||
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),
|
||||
y: 100,
|
||||
width: 120,
|
||||
height: 80
|
||||
height: 80,
|
||||
},
|
||||
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) {
|
||||
@ -212,10 +212,10 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
|
||||
x: 100 + (index * 200),
|
||||
y: 100,
|
||||
width: 160,
|
||||
height: 120
|
||||
height: 120,
|
||||
},
|
||||
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) {
|
||||
@ -229,10 +229,10 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
|
||||
x: 100 + (index % 2) * 300,
|
||||
y: 100 + Math.floor(index / 2) * 150,
|
||||
width: 200,
|
||||
height: 100
|
||||
height: 100,
|
||||
},
|
||||
style: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
|
||||
properties: { isComponent: true }
|
||||
properties: { isComponent: true },
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@ -248,8 +248,8 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
|
||||
format: input.format || DiagramFormat.DRAWIO,
|
||||
created: 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.MINDMAP]: 'Mind map diagram for brainstorming and organizing ideas',
|
||||
[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
|
||||
|
@ -13,13 +13,13 @@ export const convertToDrawioXML = (diagramData: DiagramData): string => {
|
||||
agent: 'Cline MCP Server',
|
||||
version: '24.7.17',
|
||||
etag: generateEtag(),
|
||||
type: 'device'
|
||||
type: 'device',
|
||||
};
|
||||
|
||||
// Create diagram structure
|
||||
const diagram = {
|
||||
id: generateId(),
|
||||
name: `${metadata.type}-diagram`
|
||||
name: `${metadata.type}-diagram`,
|
||||
};
|
||||
|
||||
// Create mxGraphModel
|
||||
@ -38,7 +38,7 @@ export const convertToDrawioXML = (diagramData: DiagramData): string => {
|
||||
pageWidth: '827',
|
||||
pageHeight: '1169',
|
||||
math: '0',
|
||||
shadow: '0'
|
||||
shadow: '0',
|
||||
};
|
||||
|
||||
// Create root cell
|
||||
@ -68,7 +68,7 @@ export const convertToDrawioXML = (diagramData: DiagramData): string => {
|
||||
* Convert diagram element to mxCell
|
||||
*/
|
||||
const convertElementToMxCell = (element: DiagramElement, id: number): any => {
|
||||
const { geometry, style, label, properties } = element;
|
||||
const { geometry, style, label } = element;
|
||||
|
||||
return {
|
||||
id: id.toString(),
|
||||
@ -81,8 +81,8 @@ const convertElementToMxCell = (element: DiagramElement, id: number): any => {
|
||||
y: geometry?.y?.toString() || '0',
|
||||
width: geometry?.width?.toString() || '120',
|
||||
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
|
||||
*/
|
||||
const convertConnectionToMxCell = (connection: DiagramConnection, id: number): any => {
|
||||
const { style, label, source, target, properties } = connection;
|
||||
const { style, label, source, target } = connection;
|
||||
|
||||
return {
|
||||
id: id.toString(),
|
||||
@ -102,8 +102,8 @@ const convertConnectionToMxCell = (connection: DiagramConnection, id: number): a
|
||||
target: findElementIdByName(target),
|
||||
geometry: {
|
||||
relative: '1',
|
||||
as: 'geometry'
|
||||
}
|
||||
as: 'geometry',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -123,7 +123,7 @@ const buildDrawioXML = (
|
||||
mxfile: any,
|
||||
diagram: any,
|
||||
mxGraphModel: any,
|
||||
mxCells: any[]
|
||||
mxCells: any[],
|
||||
): string => {
|
||||
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`;
|
||||
@ -181,21 +181,21 @@ const buildMxCellXML = (cell: any): string => {
|
||||
*/
|
||||
export const convertDiagramToFormat = (
|
||||
diagramData: DiagramData,
|
||||
format: DiagramFormat
|
||||
format: DiagramFormat,
|
||||
): string => {
|
||||
switch (format) {
|
||||
case DiagramFormat.DRAWIO:
|
||||
case DiagramFormat.XML:
|
||||
return convertToDrawioXML(diagramData);
|
||||
case DiagramFormat.DRAWIO:
|
||||
case DiagramFormat.XML:
|
||||
return convertToDrawioXML(diagramData);
|
||||
|
||||
case DiagramFormat.DRAWIO_SVG:
|
||||
return convertToDrawioSVG(diagramData);
|
||||
case DiagramFormat.DRAWIO_SVG:
|
||||
return convertToDrawioSVG(diagramData);
|
||||
|
||||
case DiagramFormat.DRAWIO_PNG:
|
||||
return convertToDrawioPNG(diagramData);
|
||||
case DiagramFormat.DRAWIO_PNG:
|
||||
return convertToDrawioPNG(diagramData);
|
||||
|
||||
default:
|
||||
return convertToDrawioXML(diagramData);
|
||||
default:
|
||||
return convertToDrawioXML(diagramData);
|
||||
}
|
||||
};
|
||||
|
||||
@ -209,8 +209,8 @@ const convertToDrawioSVG = (diagramData: DiagramData): string => {
|
||||
// Calculate approximate SVG dimensions based on elements
|
||||
const { width, height } = calculateDiagramDimensions(diagramData.elements);
|
||||
|
||||
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`;
|
||||
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 += `<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 += ' <g>\n';
|
||||
@ -261,7 +261,7 @@ const calculateDiagramDimensions = (elements: DiagramElement[]): { width: number
|
||||
|
||||
return {
|
||||
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;',
|
||||
'ellipse': 'ellipse;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'];
|
||||
@ -400,40 +400,40 @@ const extractStrokeColor = (style?: string): string | null => {
|
||||
export const createDiagramFile = (
|
||||
diagramData: DiagramData,
|
||||
format: DiagramFormat,
|
||||
filename?: string
|
||||
filename?: string,
|
||||
): { content: string; filename: string; mimeType: string } => {
|
||||
const content = convertDiagramToFormat(diagramData, format);
|
||||
const baseFilename = filename || `diagram-${Date.now()}`;
|
||||
|
||||
switch (format) {
|
||||
case DiagramFormat.DRAWIO:
|
||||
case DiagramFormat.XML:
|
||||
return {
|
||||
content,
|
||||
filename: `${baseFilename}.drawio`,
|
||||
mimeType: 'application/xml'
|
||||
};
|
||||
case DiagramFormat.DRAWIO:
|
||||
case DiagramFormat.XML:
|
||||
return {
|
||||
content,
|
||||
filename: `${baseFilename}.drawio`,
|
||||
mimeType: 'application/xml',
|
||||
};
|
||||
|
||||
case DiagramFormat.DRAWIO_SVG:
|
||||
return {
|
||||
content,
|
||||
filename: `${baseFilename}.svg`,
|
||||
mimeType: 'image/svg+xml'
|
||||
};
|
||||
case DiagramFormat.DRAWIO_SVG:
|
||||
return {
|
||||
content,
|
||||
filename: `${baseFilename}.svg`,
|
||||
mimeType: 'image/svg+xml',
|
||||
};
|
||||
|
||||
case DiagramFormat.DRAWIO_PNG:
|
||||
return {
|
||||
content,
|
||||
filename: `${baseFilename}.png`,
|
||||
mimeType: 'image/png'
|
||||
};
|
||||
case DiagramFormat.DRAWIO_PNG:
|
||||
return {
|
||||
content,
|
||||
filename: `${baseFilename}.png`,
|
||||
mimeType: 'image/png',
|
||||
};
|
||||
|
||||
default:
|
||||
return {
|
||||
content,
|
||||
filename: `${baseFilename}.drawio`,
|
||||
mimeType: 'application/xml'
|
||||
};
|
||||
default:
|
||||
return {
|
||||
content,
|
||||
filename: `${baseFilename}.drawio`,
|
||||
mimeType: 'application/xml',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@ -442,15 +442,15 @@ export const createDiagramFile = (
|
||||
*/
|
||||
export const streamDiagramContent = (
|
||||
diagramData: DiagramData,
|
||||
format: DiagramFormat = DiagramFormat.DRAWIO
|
||||
format: DiagramFormat = DiagramFormat.DRAWIO,
|
||||
): ReadableStream<Uint8Array> => {
|
||||
const { content, mimeType } = createDiagramFile(diagramData, format);
|
||||
const { content } = createDiagramFile(diagramData, format);
|
||||
|
||||
return new ReadableStream({
|
||||
start(controller) {
|
||||
const encoder = new TextEncoder();
|
||||
controller.enqueue(encoder.encode(content));
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
Reference in New Issue
Block a user