@eventcatalog/core 3.8.2 → 3.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-275AT7XV.js → chunk-4LOZDOZG.js} +1 -1
  6. package/dist/{chunk-KBMXUUXX.js → chunk-5S7Y7EII.js} +1 -1
  7. package/dist/{chunk-OCD75GFW.js → chunk-F2A3CZVC.js} +1 -1
  8. package/dist/{chunk-I7HRERRK.js → chunk-RC4O6Q6B.js} +1 -1
  9. package/dist/{chunk-MOBOWLEW.js → chunk-VUNGSK7U.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/eventcatalog.cjs +1 -1
  13. package/dist/eventcatalog.config.d.cts +1 -0
  14. package/dist/eventcatalog.config.d.ts +1 -0
  15. package/dist/eventcatalog.js +5 -5
  16. package/dist/generate.cjs +1 -1
  17. package/dist/generate.js +3 -3
  18. package/dist/utils/cli-logger.cjs +1 -1
  19. package/dist/utils/cli-logger.js +2 -2
  20. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +50 -0
  21. package/eventcatalog/src/components/MDX/Design/Design.astro +4 -1
  22. package/eventcatalog/src/components/MDX/EntityMap/EntityMap.astro +4 -0
  23. package/eventcatalog/src/components/MDX/Flow/Flow.astro +4 -1
  24. package/eventcatalog/src/components/MDX/NodeGraph/MermaidView.tsx +242 -0
  25. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +5 -0
  26. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +322 -189
  27. package/eventcatalog/src/components/MDX/NodeGraph/VisualizerDropdownContent.tsx +224 -0
  28. package/eventcatalog/src/content.config.ts +1 -1
  29. package/eventcatalog/src/enterprise/ai/chat-api.ts +23 -0
  30. package/eventcatalog/src/enterprise/tools/catalog-tools.ts +96 -0
  31. package/eventcatalog/src/pages/diagrams/[id]/[version]/embed.astro +1 -0
  32. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version].mermaid.ts +128 -0
  33. package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +4 -0
  34. package/eventcatalog/src/utils/clipboard.ts +22 -0
  35. package/eventcatalog/src/utils/mermaid-zoom.ts +1 -0
  36. package/eventcatalog/src/utils/node-graphs/export-mermaid.ts +299 -0
  37. package/package.json +1 -1
@@ -0,0 +1,224 @@
1
+ import React, { type RefObject } from 'react';
2
+ import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
3
+ import { Code, Share2, Search, Grid3x3, Maximize2, Map, Sparkles, Zap, EyeOff, ExternalLink } from 'lucide-react';
4
+ import { DocumentArrowDownIcon, PresentationChartLineIcon } from '@heroicons/react/24/outline';
5
+ import type { VisualiserSearchRef } from './VisualiserSearch';
6
+
7
+ interface VisualizerDropdownContentProps {
8
+ isMermaidView: boolean;
9
+ setIsMermaidView: (value: boolean) => void;
10
+ animateMessages: boolean;
11
+ toggleAnimateMessages: () => void;
12
+ hideChannels: boolean;
13
+ toggleChannelsVisibility: () => void;
14
+ hasChannels: boolean;
15
+ showMinimap: boolean;
16
+ setShowMinimap: (value: boolean) => void;
17
+ handleFitView: () => void;
18
+ searchRef: RefObject<VisualiserSearchRef | null>;
19
+ isChatEnabled: boolean;
20
+ openChat: () => void;
21
+ handleCopyArchitectureCode: () => void;
22
+ handleExportVisual: () => void;
23
+ setIsShareModalOpen: (value: boolean) => void;
24
+ toggleFullScreen: () => void;
25
+ openStudioModal: () => void;
26
+ }
27
+
28
+ const VisualizerDropdownContent: React.FC<VisualizerDropdownContentProps> = ({
29
+ isMermaidView,
30
+ setIsMermaidView,
31
+ animateMessages,
32
+ toggleAnimateMessages,
33
+ hideChannels,
34
+ toggleChannelsVisibility,
35
+ hasChannels,
36
+ showMinimap,
37
+ setShowMinimap,
38
+ handleFitView,
39
+ searchRef,
40
+ isChatEnabled,
41
+ openChat,
42
+ handleCopyArchitectureCode,
43
+ handleExportVisual,
44
+ setIsShareModalOpen,
45
+ toggleFullScreen,
46
+ openStudioModal,
47
+ }) => {
48
+ return (
49
+ <>
50
+ {/* Canvas Settings Submenu */}
51
+ <DropdownMenu.Sub>
52
+ <DropdownMenu.SubTrigger className="flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2 outline-none">
53
+ <Grid3x3 className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
54
+ <span className="flex-1 font-normal">Canvas</span>
55
+ <svg className="w-3 h-3 text-[rgb(var(--ec-page-text-muted))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
56
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
57
+ </svg>
58
+ </DropdownMenu.SubTrigger>
59
+ <DropdownMenu.Portal>
60
+ <DropdownMenu.SubContent
61
+ className="min-w-[200px] bg-[rgb(var(--ec-card-bg))] rounded-lg shadow-xl border border-[rgb(var(--ec-page-border))] py-1.5 z-[60]"
62
+ sideOffset={8}
63
+ alignOffset={-8}
64
+ >
65
+ <DropdownMenu.CheckboxItem
66
+ checked={isMermaidView}
67
+ onCheckedChange={setIsMermaidView}
68
+ className="flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2"
69
+ >
70
+ <Code className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
71
+ <span className="flex-1 font-normal">Render as mermaid</span>
72
+ <div
73
+ className={`w-7 h-4 rounded-full transition-all duration-200 flex-shrink-0 relative ${isMermaidView ? 'bg-[rgb(var(--ec-accent))]' : 'bg-[rgb(var(--ec-page-border))]'}`}
74
+ >
75
+ <div
76
+ className={`absolute top-0.5 w-3 h-3 rounded-full bg-white shadow-sm transition-all duration-200 ${isMermaidView ? 'left-3.5' : 'left-0.5'}`}
77
+ />
78
+ </div>
79
+ </DropdownMenu.CheckboxItem>
80
+
81
+ <DropdownMenu.Separator className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
82
+
83
+ <DropdownMenu.CheckboxItem
84
+ checked={animateMessages}
85
+ onCheckedChange={toggleAnimateMessages}
86
+ className="flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2"
87
+ >
88
+ <Zap className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
89
+ <span className="flex-1 font-normal">Simulate Messages</span>
90
+ <div
91
+ className={`w-7 h-4 rounded-full transition-all duration-200 flex-shrink-0 relative ${animateMessages ? 'bg-[rgb(var(--ec-accent))]' : 'bg-[rgb(var(--ec-page-border))]'}`}
92
+ >
93
+ <div
94
+ className={`absolute top-0.5 w-3 h-3 rounded-full bg-white shadow-sm transition-all duration-200 ${animateMessages ? 'left-3.5' : 'left-0.5'}`}
95
+ />
96
+ </div>
97
+ </DropdownMenu.CheckboxItem>
98
+
99
+ {hasChannels && (
100
+ <DropdownMenu.CheckboxItem
101
+ checked={hideChannels}
102
+ onCheckedChange={toggleChannelsVisibility}
103
+ className="flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2"
104
+ >
105
+ <EyeOff className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
106
+ <span className="flex-1 font-normal">Hide channels</span>
107
+ <div
108
+ className={`w-7 h-4 rounded-full transition-all duration-200 flex-shrink-0 relative ${hideChannels ? 'bg-[rgb(var(--ec-accent))]' : 'bg-[rgb(var(--ec-page-border))]'}`}
109
+ >
110
+ <div
111
+ className={`absolute top-0.5 w-3 h-3 rounded-full bg-white shadow-sm transition-all duration-200 ${hideChannels ? 'left-3.5' : 'left-0.5'}`}
112
+ />
113
+ </div>
114
+ </DropdownMenu.CheckboxItem>
115
+ )}
116
+
117
+ <DropdownMenu.CheckboxItem
118
+ checked={showMinimap}
119
+ onCheckedChange={setShowMinimap}
120
+ className="flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2"
121
+ >
122
+ <Map className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
123
+ <span className="flex-1 font-normal">Show minimap</span>
124
+ <div
125
+ className={`w-7 h-4 rounded-full transition-all duration-200 flex-shrink-0 relative ${showMinimap ? 'bg-[rgb(var(--ec-accent))]' : 'bg-[rgb(var(--ec-page-border))]'}`}
126
+ >
127
+ <div
128
+ className={`absolute top-0.5 w-3 h-3 rounded-full bg-white shadow-sm transition-all duration-200 ${showMinimap ? 'left-3.5' : 'left-0.5'}`}
129
+ />
130
+ </div>
131
+ </DropdownMenu.CheckboxItem>
132
+
133
+ <DropdownMenu.Separator className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
134
+
135
+ <DropdownMenu.Item
136
+ onClick={handleFitView}
137
+ className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
138
+ >
139
+ <Maximize2 className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
140
+ <span className="flex-1 font-normal">Fit to view</span>
141
+ </DropdownMenu.Item>
142
+
143
+ <DropdownMenu.Item
144
+ onClick={() => {
145
+ searchRef.current?.hideSuggestions();
146
+ setTimeout(() => {
147
+ const searchInput = document.querySelector('input[placeholder="Search nodes..."]') as HTMLInputElement;
148
+ searchInput?.focus();
149
+ }, 50);
150
+ }}
151
+ className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
152
+ >
153
+ <Search className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
154
+ <span className="flex-1 font-normal">Find on canvas</span>
155
+ </DropdownMenu.Item>
156
+ </DropdownMenu.SubContent>
157
+ </DropdownMenu.Portal>
158
+ </DropdownMenu.Sub>
159
+
160
+ {/* Ask AI */}
161
+ {isChatEnabled && (
162
+ <>
163
+ <DropdownMenu.Separator className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
164
+ <DropdownMenu.Item
165
+ onClick={openChat}
166
+ className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
167
+ >
168
+ <Sparkles className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
169
+ <span className="flex-1 font-normal">Ask a question</span>
170
+ </DropdownMenu.Item>
171
+ </>
172
+ )}
173
+
174
+ {/* Export Items */}
175
+ <DropdownMenu.Separator className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
176
+ <DropdownMenu.Item
177
+ onClick={handleCopyArchitectureCode}
178
+ className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
179
+ >
180
+ <Code className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
181
+ <span className="flex-1 font-normal">Copy as mermaid</span>
182
+ </DropdownMenu.Item>
183
+
184
+ <DropdownMenu.Item
185
+ onClick={handleExportVisual}
186
+ className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
187
+ >
188
+ <DocumentArrowDownIcon className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
189
+ <span className="flex-1 font-normal">Export image</span>
190
+ </DropdownMenu.Item>
191
+
192
+ {/* Share Link */}
193
+ <DropdownMenu.Item
194
+ onClick={() => setIsShareModalOpen(true)}
195
+ className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
196
+ >
197
+ <Share2 className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
198
+ <span className="flex-1 font-normal">Share Link</span>
199
+ </DropdownMenu.Item>
200
+
201
+ {/* Start Presentation */}
202
+ <DropdownMenu.Separator className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
203
+ <DropdownMenu.Item
204
+ onClick={toggleFullScreen}
205
+ className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
206
+ >
207
+ <PresentationChartLineIcon className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
208
+ <span className="flex-1 font-normal">Start Presentation</span>
209
+ </DropdownMenu.Item>
210
+
211
+ {/* Open in EventCatalog Studio */}
212
+ <DropdownMenu.Separator className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
213
+ <DropdownMenu.Item
214
+ onClick={openStudioModal}
215
+ className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
216
+ >
217
+ <ExternalLink className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
218
+ <span className="flex-1 font-normal">Open in EventCatalog Studio</span>
219
+ </DropdownMenu.Item>
220
+ </>
221
+ );
222
+ };
223
+
224
+ export default VisualizerDropdownContent;
@@ -453,7 +453,7 @@ const dataClassificationEnum = z.enum(['public', 'internal', 'confidential', 're
453
453
 
454
454
  const containers = defineCollection({
455
455
  loader: glob({
456
- pattern: ['**/containers/*/index.(md|mdx)', '**/containers/*/versioned/*/index.(md|mdx)'],
456
+ pattern: ['**/containers/**/index.(md|mdx)', '**/containers/**/versioned/*/index.(md|mdx)'],
457
457
  base: projectDirBase,
458
458
  generateId: ({ data }) => {
459
459
  return `${data.id}-${data.version}`;
@@ -11,9 +11,11 @@ import {
11
11
  getSchemaForResource as getSchemaImpl,
12
12
  getDataProductInputs as getDataProductInputsImpl,
13
13
  getDataProductOutputs as getDataProductOutputsImpl,
14
+ getArchitectureDiagramAsMermaid as getArchitectureDiagramImpl,
14
15
  collectionSchema,
15
16
  resourceCollectionSchema,
16
17
  messageCollectionSchema,
18
+ visualiserCollectionSchema,
17
19
  toolDescriptions,
18
20
  } from '@enterprise/tools/catalog-tools';
19
21
 
@@ -61,6 +63,7 @@ const builtInToolsMetadata = [
61
63
  { name: 'getSchemaForResource', description: 'Get the schema or specifications (OpenAPI, AsyncAPI, GraphQL) for a resource' },
62
64
  { name: 'getDataProductInputs', description: 'Get the inputs (resources consumed) for a data product' },
63
65
  { name: 'getDataProductOutputs', description: 'Get the outputs (resources produced) for a data product with data contracts' },
66
+ { name: 'getArchitectureDiagramAsMermaid', description: 'Get the architecture diagram for a resource as Mermaid code' },
64
67
  ];
65
68
 
66
69
  // Get extended tools metadata from user configuration
@@ -112,6 +115,11 @@ You may be able to get the resource from the URL
112
115
 
113
116
  The referer URL is: ${referrer}
114
117
 
118
+ IMPORTANT: When the user is on a visualiser page (/visualiser/*) or asks about architecture, dependencies, data flow, or connections:
119
+ - Use the getArchitectureDiagramAsMermaid tool to fetch the architecture diagram
120
+ - The mermaid code shows all the connections between resources (services, events, channels, etc.)
121
+ - Use this to provide accurate, contextual answers about the architecture
122
+
115
123
  Sometimes the user will be on the specification page (openapi, asyncapi, graphql) for a resource too
116
124
  - /docs/services/OrdersService/0.0.3/asyncapi/order-service-asyncapi -> id: OrdersService, version: 0.0.3, collection: services, schema (specification): asyncapi
117
125
  - /docs/services/OrdersService/0.0.3/spec/openapi-v2 -> id: OrdersService, version: 0.0.3, collection: services, schema (specification): openapi
@@ -317,6 +325,21 @@ export const POST = async ({ request }: APIContext<{ question: string; messages:
317
325
  return await getDataProductOutputsImpl({ dataProductId, dataProductVersion });
318
326
  },
319
327
  }),
328
+ getArchitectureDiagramAsMermaid: tool({
329
+ description: toolDescriptions.getArchitectureDiagramAsMermaid,
330
+ inputSchema: z.object({
331
+ resourceId: z.string().describe('The id of the resource to get the architecture diagram for'),
332
+ resourceVersion: z.string().describe('The version of the resource to get the architecture diagram for'),
333
+ resourceCollection: visualiserCollectionSchema
334
+ .describe(
335
+ 'The collection of the resource (events, commands, queries, services, domains, flows, containers, data-products)'
336
+ )
337
+ .default('services'),
338
+ }),
339
+ execute: async ({ resourceId, resourceVersion, resourceCollection }) => {
340
+ return await getArchitectureDiagramImpl({ resourceId, resourceVersion, resourceCollection });
341
+ },
342
+ }),
320
343
  suggestFollowUpQuestions: tool({
321
344
  description:
322
345
  'Use this tool after answering a question to suggest 2-3 relevant follow-up questions the user might want to ask. These will be displayed as clickable suggestions.',
@@ -9,6 +9,18 @@ import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/
9
9
  import { getUbiquitousLanguageWithSubdomains } from '@utils/collections/domains';
10
10
  import { getAbsoluteFilePathForAstroFile } from '@utils/files';
11
11
  import fs from 'node:fs';
12
+ import { getNodesAndEdges as getNodesAndEdgesForService } from '@utils/node-graphs/services-node-graph';
13
+ import {
14
+ getNodesAndEdgesForCommands,
15
+ getNodesAndEdgesForEvents,
16
+ getNodesAndEdgesForQueries,
17
+ } from '@utils/node-graphs/message-node-graph';
18
+ import { getNodesAndEdges as getNodesAndEdgesForDomain } from '@utils/node-graphs/domains-node-graph';
19
+ import { getNodesAndEdges as getNodesAndEdgesForFlows } from '@utils/node-graphs/flows-node-graph';
20
+ import { getNodesAndEdges as getNodesAndEdgesForDataProduct } from '@utils/node-graphs/data-products-node-graph';
21
+ import { getNodesAndEdges as getNodesAndEdgesForContainer } from '@utils/node-graphs/container-node-graph';
22
+ import { convertToMermaid } from '@utils/node-graphs/export-mermaid';
23
+ import config from '@config';
12
24
 
13
25
  // ============================================
14
26
  // Pagination utilities
@@ -110,6 +122,17 @@ export const resourceCollectionSchema = z.enum([
110
122
  'data-products',
111
123
  ]);
112
124
 
125
+ export const visualiserCollectionSchema = z.enum([
126
+ 'events',
127
+ 'commands',
128
+ 'queries',
129
+ 'services',
130
+ 'domains',
131
+ 'flows',
132
+ 'containers',
133
+ 'data-products',
134
+ ]);
135
+
113
136
  // ============================================
114
137
  // Tool implementations (core logic)
115
138
  // ============================================
@@ -821,6 +844,77 @@ export async function getDataProductOutputs(params: { dataProductId: string; dat
821
844
  };
822
845
  }
823
846
 
847
+ // ============================================
848
+ // Architecture Diagram (Mermaid) tools
849
+ // ============================================
850
+
851
+ const getNodesAndEdgesFunctions = {
852
+ services: getNodesAndEdgesForService,
853
+ events: getNodesAndEdgesForEvents,
854
+ commands: getNodesAndEdgesForCommands,
855
+ queries: getNodesAndEdgesForQueries,
856
+ domains: getNodesAndEdgesForDomain,
857
+ flows: getNodesAndEdgesForFlows,
858
+ containers: getNodesAndEdgesForContainer,
859
+ 'data-products': getNodesAndEdgesForDataProduct,
860
+ };
861
+
862
+ /**
863
+ * Get the architecture diagram for a resource as Mermaid code
864
+ * Returns flowchart syntax that can be rendered or used for understanding architecture
865
+ */
866
+ export async function getArchitectureDiagramAsMermaid(params: {
867
+ resourceId: string;
868
+ resourceVersion: string;
869
+ resourceCollection: string;
870
+ }) {
871
+ const { resourceId, resourceVersion, resourceCollection } = params;
872
+
873
+ // Validate the collection is supported for visualisation
874
+ if (!(resourceCollection in getNodesAndEdgesFunctions)) {
875
+ return {
876
+ error: `Collection '${resourceCollection}' does not support architecture diagrams. Supported collections: ${Object.keys(getNodesAndEdgesFunctions).join(', ')}`,
877
+ };
878
+ }
879
+
880
+ try {
881
+ // Get nodes and edges for this resource
882
+ const { nodes, edges } = await getNodesAndEdgesFunctions[resourceCollection as keyof typeof getNodesAndEdgesFunctions]({
883
+ id: resourceId,
884
+ version: resourceVersion,
885
+ mode: 'full',
886
+ channelRenderMode: config.visualiser?.channels?.renderMode === 'single' ? 'single' : 'flat',
887
+ });
888
+
889
+ if (!nodes || nodes.length === 0) {
890
+ return {
891
+ error: `No diagram data available for ${resourceCollection}/${resourceId} (v${resourceVersion})`,
892
+ };
893
+ }
894
+
895
+ // Convert to mermaid
896
+ const mermaidCode = convertToMermaid(nodes, edges, {
897
+ includeStyles: true,
898
+ direction: 'LR',
899
+ });
900
+
901
+ return {
902
+ resourceId,
903
+ resourceVersion,
904
+ resourceCollection,
905
+ mermaidCode,
906
+ nodeCount: nodes.length,
907
+ edgeCount: edges.length,
908
+ visualiserUrl: `/visualiser/${resourceCollection}/${resourceId}/${resourceVersion}`,
909
+ };
910
+ } catch (error) {
911
+ console.error('Error generating mermaid diagram:', error);
912
+ return {
913
+ error: `Failed to generate diagram for ${resourceCollection}/${resourceId} (v${resourceVersion})`,
914
+ };
915
+ }
916
+ }
917
+
824
918
  // ============================================
825
919
  // Tool metadata (descriptions)
826
920
  // ============================================
@@ -854,4 +948,6 @@ export const toolDescriptions = {
854
948
  'Use this tool to get the inputs (resources consumed) for a data product. Returns fully hydrated input resources with their id, version, name, summary, and collection type.',
855
949
  getDataProductOutputs:
856
950
  'Use this tool to get the outputs (resources produced) for a data product. Returns fully hydrated output resources with their id, version, name, summary, collection type, and data contracts (if defined). Data contracts include the contract name, path, format, type, and content.',
951
+ getArchitectureDiagramAsMermaid:
952
+ 'Use this tool to get the architecture diagram for a resource as Mermaid flowchart code. This shows how the resource connects to other resources (services, events, channels, etc.) in the architecture. The mermaid code can be rendered to visualize the architecture or used to understand relationships. Supported collections: events, commands, queries, services, domains, flows, containers, data-products.',
857
953
  };
@@ -570,6 +570,7 @@ const diagramId = props.data.id;
570
570
  const isDarkMode = document.documentElement.getAttribute('data-theme') === 'dark';
571
571
 
572
572
  mermaid.initialize({
573
+ maxTextSize: window.eventcatalog?.mermaid?.maxTextSize || 100000,
573
574
  startOnLoad: false,
574
575
  theme: isDarkMode ? 'dark' : 'default',
575
576
  fontFamily: 'system-ui, -apple-system, sans-serif',
@@ -0,0 +1,128 @@
1
+ /**
2
+ * API endpoint to return mermaid diagram code for a visualiser page
3
+ * URL: /visualiser/{type}/{id}/{version}.mermaid
4
+ *
5
+ * Returns plain text mermaid flowchart syntax that can be used by LLMs,
6
+ * documentation tools, or pasted into mermaid-compatible renderers.
7
+ */
8
+ import type { APIRoute, GetStaticPaths } from 'astro';
9
+ import { isAuthEnabled, isVisualiserEnabled } from '@utils/feature';
10
+ import { pageDataLoader } from '@utils/page-loaders/page-data-loader';
11
+ import { getNodesAndEdges as getNodesAndEdgesForService } from '@utils/node-graphs/services-node-graph';
12
+ import {
13
+ getNodesAndEdgesForCommands,
14
+ getNodesAndEdgesForEvents,
15
+ getNodesAndEdgesForQueries,
16
+ } from '@utils/node-graphs/message-node-graph';
17
+ import { getNodesAndEdges as getNodesAndEdgesForDomain } from '@utils/node-graphs/domains-node-graph';
18
+ import { getNodesAndEdges as getNodesAndEdgesForFlows } from '@utils/node-graphs/flows-node-graph';
19
+ import { getNodesAndEdges as getNodesAndEdgesForDataProduct } from '@utils/node-graphs/data-products-node-graph';
20
+ import { getNodesAndEdges as getNodesAndEdgesForContainer } from '@utils/node-graphs/container-node-graph';
21
+ import { convertToMermaid } from '@utils/node-graphs/export-mermaid';
22
+ import config from '@config';
23
+ import type { PageTypes } from '@types';
24
+
25
+ type PageTypesWithFlows = PageTypes | 'flows';
26
+
27
+ // Prerender static pages when auth is disabled, use SSR when auth is enabled
28
+ export const prerender = !isAuthEnabled();
29
+
30
+ const getNodesAndEdgesFunctions = {
31
+ services: getNodesAndEdgesForService,
32
+ events: getNodesAndEdgesForEvents,
33
+ commands: getNodesAndEdgesForCommands,
34
+ queries: getNodesAndEdgesForQueries,
35
+ domains: getNodesAndEdgesForDomain,
36
+ flows: getNodesAndEdgesForFlows,
37
+ containers: getNodesAndEdgesForContainer,
38
+ 'data-products': getNodesAndEdgesForDataProduct,
39
+ };
40
+
41
+ export const getStaticPaths: GetStaticPaths = async () => {
42
+ if (isAuthEnabled() || !isVisualiserEnabled()) {
43
+ return [];
44
+ }
45
+
46
+ const { getFlows } = await import('@utils/collections/flows');
47
+
48
+ const loaders = {
49
+ ...pageDataLoader,
50
+ flows: getFlows,
51
+ };
52
+
53
+ const itemTypes: PageTypesWithFlows[] = [
54
+ 'events',
55
+ 'commands',
56
+ 'queries',
57
+ 'services',
58
+ 'domains',
59
+ 'flows',
60
+ 'containers',
61
+ 'data-products',
62
+ ];
63
+
64
+ const allItems = await Promise.all(itemTypes.map((type) => loaders[type]()));
65
+
66
+ return allItems.flatMap((items, index) =>
67
+ items
68
+ .filter((item) => item.data.visualiser !== false)
69
+ .map((item) => ({
70
+ params: {
71
+ type: itemTypes[index],
72
+ id: item.data.id,
73
+ version: item.data.version,
74
+ },
75
+ }))
76
+ );
77
+ };
78
+
79
+ export const GET: APIRoute = async ({ params }) => {
80
+ const { type, id, version } = params;
81
+
82
+ if (!type || !id || !version || !isVisualiserEnabled()) {
83
+ return new Response('Not found', { status: 404 });
84
+ }
85
+
86
+ // Validate the type is supported
87
+ if (!(type in getNodesAndEdgesFunctions)) {
88
+ return new Response(`Unsupported type: ${type}`, { status: 400 });
89
+ }
90
+
91
+ try {
92
+ // Get nodes and edges for this resource
93
+ const { nodes, edges } = await getNodesAndEdgesFunctions[type as keyof typeof getNodesAndEdgesFunctions]({
94
+ id,
95
+ version,
96
+ mode: 'full',
97
+ channelRenderMode: config.visualiser?.channels?.renderMode === 'single' ? 'single' : 'flat',
98
+ });
99
+
100
+ if (!nodes || nodes.length === 0) {
101
+ return new Response('No diagram data available', { status: 404 });
102
+ }
103
+
104
+ // Convert to mermaid
105
+ const mermaidCode = convertToMermaid(nodes, edges, {
106
+ includeStyles: true,
107
+ direction: 'LR',
108
+ });
109
+
110
+ // Add header comment with metadata
111
+ const header = `%% EventCatalog Mermaid Diagram
112
+ %% Resource: ${type}/${id} (v${version})
113
+ %% Generated: ${new Date().toISOString()}
114
+
115
+ `;
116
+
117
+ return new Response(header + mermaidCode, {
118
+ status: 200,
119
+ headers: {
120
+ 'Content-Type': 'text/plain; charset=utf-8',
121
+ 'Cache-Control': 'public, max-age=3600',
122
+ },
123
+ });
124
+ } catch (error) {
125
+ console.error('Error generating mermaid diagram:', error);
126
+ return new Response('Failed to generate diagram', { status: 500 });
127
+ }
128
+ };
@@ -2,9 +2,12 @@
2
2
  import NodeGraph from '@components/MDX/NodeGraph/NodeGraph';
3
3
  import { ClientRouter } from 'astro:transitions';
4
4
  import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
5
+ import { isEventCatalogChatEnabled } from '@utils/feature';
5
6
 
6
7
  import { Page } from './_index.data';
7
8
 
9
+ const isChatEnabled = isEventCatalogChatEnabled();
10
+
8
11
  export const prerender = Page.prerender;
9
12
  export const getStaticPaths = Page.getStaticPaths;
10
13
 
@@ -25,6 +28,7 @@ const { data } = props;
25
28
  client:only="react"
26
29
  mode="full"
27
30
  linkTo="visualiser"
31
+ isChatEnabled={isChatEnabled}
28
32
  />
29
33
  </div>
30
34
  <ClientRouter />
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Copy text to clipboard with fallback for older browsers
3
+ * @param text - The text to copy to clipboard
4
+ * @returns Promise that resolves to true if successful
5
+ */
6
+ export async function copyToClipboard(text: string): Promise<boolean> {
7
+ try {
8
+ await navigator.clipboard.writeText(text);
9
+ return true;
10
+ } catch {
11
+ // Fallback for older browsers
12
+ const textarea = document.createElement('textarea');
13
+ textarea.value = text;
14
+ textarea.style.position = 'fixed';
15
+ textarea.style.opacity = '0';
16
+ document.body.appendChild(textarea);
17
+ textarea.select();
18
+ document.execCommand('copy');
19
+ document.body.removeChild(textarea);
20
+ return true;
21
+ }
22
+ }
@@ -600,6 +600,7 @@ export async function renderMermaidWithZoom(graphs: HTMLCollectionOf<Element>, m
600
600
  };
601
601
 
602
602
  mermaid.initialize({
603
+ maxTextSize: mermaidConfig?.maxTextSize || 100000,
603
604
  flowchart: {
604
605
  curve: 'linear',
605
606
  rankSpacing: 0,