@eventcatalogtest/studio
Version:
A drag and drop UI for distributed systems that keeps your diagrams where they belong – in your repo
185 lines (159 loc) • 6.36 kB
text/typescript
import { ReactFlowJsonObject, Node, Edge } from '@xyflow/react';
export interface GeneratePromptOptions {
flowData: ReactFlowJsonObject;
designName: string;
}
interface NodeData extends Record<string, unknown> {
message?: { name?: string; summary?: string; version?: string; collection?: string };
service?: { name?: string; summary?: string; version?: string };
query?: { name?: string; summary?: string; version?: string };
command?: { name?: string; summary?: string; version?: string };
data?: { name?: string; summary?: string; version?: string };
name?: string;
summary?: string;
version?: string;
}
interface EdgeData extends Record<string, unknown> {
message?: { collection?: string };
label?: string;
}
export const generateMermaidDiagram = (flowData: ReactFlowJsonObject): string => {
if (!flowData?.nodes || flowData.nodes.length === 0) {
return 'flowchart TD\n A[No nodes available]';
}
const { nodes, edges } = flowData;
// Helper function to get node display name
const getNodeName = (node: Node<NodeData>) => {
return node.data?.message?.name || node.data?.service?.name || node.data?.query?.name ||
node.data?.command?.name || node.data?.data?.name || node.data?.name ||
`${node.type || 'unknown'}_${node.id.slice(-4)}`;
};
// Helper function to get mermaid node shape based on type
const getMermaidNodeShape = (type: string, name: string) => {
switch (type) {
case 'service':
return `${name}[${name}]`;
case 'event':
return `${name}((${name}))`;
case 'command':
return `${name}>${name}]`;
case 'query':
return `${name}{${name}}`;
case 'domain':
return `${name}[/${name}/]`;
case 'external-system':
return `${name}[[${name}]]`;
case 'data':
return `${name}[(${name})]`;
case 'channel':
return `${name}[${name}]`;
case 'view':
return `${name}[${name}]`;
case 'actor':
return `${name}[${name}]`;
default:
return `${name}[${name}]`;
}
};
// Build Mermaid diagram
let mermaidDiagram = 'flowchart TD\n';
// Add all nodes first
const nodeNameMap: { [key: string]: string } = {};
(nodes as Node<NodeData>[]).forEach(node => {
const displayName = getNodeName(node);
const safeName = displayName.replace(/[^a-zA-Z0-9]/g, '_');
nodeNameMap[node.id] = safeName;
mermaidDiagram += ` ${getMermaidNodeShape(node.type || 'unknown', safeName)}\n`;
});
// Add edges/connections
(edges as Edge<EdgeData>[]).forEach(edge => {
const sourceName = nodeNameMap[edge.source];
const targetName = nodeNameMap[edge.target];
const edgeData = edge.data as EdgeData | undefined;
const label = edgeData?.message?.collection || edge.label || '';
if (sourceName && targetName) {
if (label) {
mermaidDiagram += ` ${sourceName} -->|${label}| ${targetName}\n`;
} else {
mermaidDiagram += ` ${sourceName} --> ${targetName}\n`;
}
}
});
// Add styling for different node types
mermaidDiagram += '\n %% Styling\n';
mermaidDiagram += ' classDef service fill:#ff9999\n';
mermaidDiagram += ' classDef event fill:#99ccff\n';
mermaidDiagram += ' classDef command fill:#99ff99\n';
mermaidDiagram += ' classDef query fill:#ffcc99\n';
mermaidDiagram += ' classDef domain fill:#e6ccff\n';
mermaidDiagram += ' classDef external fill:#ffff99\n';
mermaidDiagram += ' classDef data fill:#ccffcc\n';
// Apply classes to nodes
(nodes as Node<NodeData>[]).forEach(node => {
const safeName = nodeNameMap[node.id];
if (safeName) {
switch (node.type) {
case 'service':
mermaidDiagram += ` class ${safeName} service\n`;
break;
case 'event':
mermaidDiagram += ` class ${safeName} event\n`;
break;
case 'command':
mermaidDiagram += ` class ${safeName} command\n`;
break;
case 'query':
mermaidDiagram += ` class ${safeName} query\n`;
break;
case 'domain':
mermaidDiagram += ` class ${safeName} domain\n`;
break;
case 'external-system':
mermaidDiagram += ` class ${safeName} external\n`;
break;
case 'data':
mermaidDiagram += ` class ${safeName} data\n`;
break;
}
}
});
return mermaidDiagram;
};
export const generateAIPrompt = ({ flowData, designName }: GeneratePromptOptions): string => {
if (!flowData?.nodes || flowData.nodes.length === 0) {
throw new Error('No design data available. Please add some nodes to your canvas first.');
}
const { nodes } = flowData;
// Helper function to get node display name
const getNodeName = (node: Node<NodeData>) => {
return node.data?.message?.name || node.data?.service?.name || node.data?.query?.name ||
node.data?.command?.name || node.data?.data?.name || node.data?.name ||
`${node.type || 'unknown'}_${node.id.slice(-4)}`;
};
// Generate mermaid diagram using the separate function
const mermaidDiagram = generateMermaidDiagram(flowData);
// Build component details
let componentDetails = '\n## Components\n\n';
(nodes as Node<NodeData>[]).forEach(node => {
const name = getNodeName(node);
const nodeData = node.data as NodeData | undefined;
const summary = nodeData?.message?.summary || nodeData?.service?.summary ||
nodeData?.query?.summary || nodeData?.command?.summary ||
nodeData?.data?.summary || nodeData?.summary || '';
const version = nodeData?.message?.version || nodeData?.service?.version ||
nodeData?.query?.version || nodeData?.command?.version ||
nodeData?.version || '';
componentDetails += `### ${name} (${node.type || 'unknown'})\n`;
if (version) componentDetails += `**Version:** ${version}\n\n`;
if (summary) componentDetails += `**Description:** ${summary}\n\n`;
componentDetails += '---\n\n';
});
const aiPrompt = `# Architecture Design: ${designName}
This is a design created using EventCatalog Studio.
## Architecture Diagram
\`\`\`mermaid
${mermaidDiagram}
\`\`\`
${componentDetails}`;
return aiPrompt;
};