@eventcatalog/core 3.0.0-beta.9 → 3.0.0

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 (148) hide show
  1. package/README.md +41 -98
  2. package/dist/__mocks__/astro-content.cjs +32 -0
  3. package/dist/__mocks__/astro-content.d.cts +13 -0
  4. package/dist/__mocks__/astro-content.d.ts +13 -0
  5. package/dist/__mocks__/astro-content.js +7 -0
  6. package/dist/analytics/analytics.cjs +1 -1
  7. package/dist/analytics/analytics.js +2 -2
  8. package/dist/analytics/log-build.cjs +1 -1
  9. package/dist/analytics/log-build.js +3 -3
  10. package/dist/catalog-to-astro-content-directory.cjs +2 -19
  11. package/dist/catalog-to-astro-content-directory.d.cts +1 -2
  12. package/dist/catalog-to-astro-content-directory.d.ts +1 -2
  13. package/dist/catalog-to-astro-content-directory.js +3 -5
  14. package/dist/{chunk-R2BJ7MJG.js → chunk-6Z6ARMQS.js} +1 -17
  15. package/dist/{chunk-A4MGWK5T.js → chunk-BYP43AAT.js} +1 -1
  16. package/dist/{chunk-RAJ7TGWN.js → chunk-E5Q7TZYT.js} +1 -1
  17. package/dist/{chunk-TT4LZO2Q.js → chunk-EKGR533N.js} +1 -1
  18. package/dist/{chunk-2VPX4WIJ.js → chunk-KF5PARQK.js} +1 -1
  19. package/dist/{chunk-TC3R47V6.js → chunk-VO5WYA44.js} +1 -1
  20. package/dist/constants.cjs +1 -1
  21. package/dist/constants.js +1 -1
  22. package/dist/eventcatalog.cjs +20 -64
  23. package/dist/eventcatalog.config.d.cts +4 -0
  24. package/dist/eventcatalog.config.d.ts +4 -0
  25. package/dist/eventcatalog.js +26 -52
  26. package/dist/generate.cjs +1 -1
  27. package/dist/generate.js +3 -3
  28. package/dist/utils/cli-logger.cjs +1 -1
  29. package/dist/utils/cli-logger.js +2 -2
  30. package/eventcatalog/astro.config.mjs +4 -1
  31. package/eventcatalog/integrations/eventcatalog-features.ts +69 -0
  32. package/eventcatalog/public/icons/asyncapi-black.svg +2 -0
  33. package/eventcatalog/public/icons/graphql-black.svg +1 -0
  34. package/eventcatalog/public/icons/openapi-black.svg +1 -0
  35. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +994 -0
  36. package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +24 -0
  37. package/eventcatalog/src/components/Grids/DomainGrid.tsx +310 -173
  38. package/eventcatalog/src/components/Grids/MessageGrid.tsx +299 -180
  39. package/eventcatalog/src/components/Grids/specification-utils.ts +106 -0
  40. package/eventcatalog/src/components/Header.astro +25 -5
  41. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +14 -3
  42. package/eventcatalog/src/components/SchemaExplorer/ApiAccessSection.tsx +95 -90
  43. package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +144 -0
  44. package/eventcatalog/src/components/SchemaExplorer/Pagination.tsx +34 -8
  45. package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +2 -2
  46. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsHeader.tsx +140 -109
  47. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +5 -14
  48. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +247 -59
  49. package/eventcatalog/src/components/SchemaExplorer/SchemaFilters.tsx +64 -126
  50. package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +41 -43
  51. package/eventcatalog/src/components/Search/Search.astro +2 -2
  52. package/eventcatalog/src/components/Search/SearchDataLoader.astro +25 -0
  53. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +6 -3
  54. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +44 -16
  55. package/eventcatalog/src/components/SideNav/SideNav.astro +0 -15
  56. package/eventcatalog/src/components/Tables/Table.tsx +96 -77
  57. package/eventcatalog/src/components/Tables/columns/ContainersTableColumns.tsx +108 -74
  58. package/eventcatalog/src/components/Tables/columns/DomainTableColumns.tsx +74 -55
  59. package/eventcatalog/src/components/Tables/columns/FlowTableColumns.tsx +36 -36
  60. package/eventcatalog/src/components/Tables/columns/MessageTableColumns.tsx +110 -77
  61. package/eventcatalog/src/components/Tables/columns/ServiceTableColumns.tsx +105 -94
  62. package/eventcatalog/src/components/Tables/columns/SharedColumns.tsx +31 -26
  63. package/eventcatalog/src/components/Tables/columns/TeamsTableColumns.tsx +115 -215
  64. package/eventcatalog/src/components/Tables/columns/UserTableColumns.tsx +145 -243
  65. package/eventcatalog/src/content.config.ts +1 -13
  66. package/eventcatalog/src/enterprise/ai/chat-api.ts +360 -0
  67. package/eventcatalog/src/enterprise/auth/[...auth].ts +3 -0
  68. package/eventcatalog/src/enterprise/auth/login.astro +420 -0
  69. package/eventcatalog/src/enterprise/collections/index.ts +0 -1
  70. package/eventcatalog/src/layouts/Footer.astro +8 -5
  71. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +133 -117
  72. package/eventcatalog/src/pages/_index.astro +243 -559
  73. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +8 -2
  74. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +9 -5
  75. package/eventcatalog/src/pages/directory/[type]/index.astro +6 -0
  76. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +19 -3
  77. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +7 -7
  78. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
  79. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +10 -7
  80. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +194 -121
  81. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +94 -70
  82. package/eventcatalog/src/pages/docs/teams/[id].mdx.ts +36 -0
  83. package/eventcatalog/src/pages/docs/users/[id]/index.astro +56 -45
  84. package/eventcatalog/src/pages/docs/users/[id].mdx.ts +36 -0
  85. package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +178 -0
  86. package/eventcatalog/src/pages/schemas/explorer/index.astro +7 -157
  87. package/eventcatalog/src/pages/studio.astro +124 -72
  88. package/eventcatalog/src/remark-plugins/directives.ts +30 -9
  89. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/container.ts +10 -1
  90. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/domain.ts +17 -7
  91. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/message.ts +10 -1
  92. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/service.ts +11 -4
  93. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/shared.ts +14 -0
  94. package/eventcatalog/src/stores/{sidebar-store.ts → sidebar-store/index.ts} +1 -1
  95. package/eventcatalog/src/utils/collections/channels.ts +0 -2
  96. package/eventcatalog/src/utils/collections/commands.ts +0 -2
  97. package/eventcatalog/src/utils/collections/containers.ts +0 -2
  98. package/eventcatalog/src/utils/collections/domains.ts +0 -2
  99. package/eventcatalog/src/utils/collections/entities.ts +0 -2
  100. package/eventcatalog/src/utils/collections/events.ts +0 -2
  101. package/eventcatalog/src/utils/collections/flows.ts +0 -2
  102. package/eventcatalog/src/utils/collections/queries.ts +0 -2
  103. package/eventcatalog/src/utils/collections/schemas.ts +45 -7
  104. package/eventcatalog/src/utils/collections/services.ts +0 -2
  105. package/eventcatalog/src/utils/feature.ts +9 -5
  106. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +1 -1
  107. package/eventcatalog/src/utils/resource-files.ts +86 -0
  108. package/package.json +12 -15
  109. package/default-files-for-collections/changelogs.md +0 -5
  110. package/default-files-for-collections/channels.md +0 -8
  111. package/default-files-for-collections/commands.md +0 -8
  112. package/default-files-for-collections/domains.md +0 -8
  113. package/default-files-for-collections/events.md +0 -8
  114. package/default-files-for-collections/flows.md +0 -11
  115. package/default-files-for-collections/queries.md +0 -8
  116. package/default-files-for-collections/services.md +0 -8
  117. package/default-files-for-collections/ubiquitousLanguages.md +0 -7
  118. package/eventcatalog/src/enterprise/collections/chat-prompts.ts +0 -32
  119. package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +0 -60
  120. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +0 -414
  121. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatSidebar.tsx +0 -169
  122. package/eventcatalog/src/enterprise/eventcatalog-chat/components/InputModal.tsx +0 -244
  123. package/eventcatalog/src/enterprise/eventcatalog-chat/components/MentionInput.tsx +0 -211
  124. package/eventcatalog/src/enterprise/eventcatalog-chat/components/WelcomePromptArea.tsx +0 -176
  125. package/eventcatalog/src/enterprise/eventcatalog-chat/components/default-prompts.ts +0 -93
  126. package/eventcatalog/src/enterprise/eventcatalog-chat/components/hooks/ChatProvider.tsx +0 -143
  127. package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +0 -387
  128. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/chat.ts +0 -59
  129. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +0 -104
  130. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +0 -140
  131. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/anthropic.ts +0 -28
  132. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/google.ts +0 -41
  133. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/index.ts +0 -26
  134. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/openai.ts +0 -61
  135. package/eventcatalog/src/enterprise/eventcatalog-chat/utils/chat-prompts.ts +0 -50
  136. package/eventcatalog/src/pages/auth/login.astro +0 -280
  137. package/eventcatalog/src/pages/chat/feature.astro +0 -179
  138. package/eventcatalog/src/pages/chat/index.astro +0 -10
  139. package/eventcatalog/src/pages/docs/_default-docs.mdx +0 -25
  140. package/eventcatalog/src/pages/docs/index.astro +0 -33
  141. package/eventcatalog/src/pages/nav-index.json.ts +0 -30
  142. /package/eventcatalog/src/{pages → enterprise}/auth/error.astro +0 -0
  143. /package/eventcatalog/src/{middleware-auth.ts → enterprise/auth/middleware/middleware-auth.ts} +0 -0
  144. /package/eventcatalog/src/{middleware.ts → enterprise/auth/middleware/middleware.ts} +0 -0
  145. /package/eventcatalog/src/{pages/unauthorized/index.astro → enterprise/auth/unauthorized.astro} +0 -0
  146. /package/eventcatalog/src/{pages → enterprise}/plans/index.astro +0 -0
  147. /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/flow.ts +0 -0
  148. /package/eventcatalog/src/{components/SideNav/NestedSideBar/sidebar-builder.ts → stores/sidebar-store/state.ts} +0 -0
@@ -1,219 +1,338 @@
1
- import { ServerIcon, CircleStackIcon } from '@heroicons/react/24/outline';
1
+ import { memo, useState } from 'react';
2
+ import {
3
+ ServerIcon,
4
+ CircleStackIcon,
5
+ ArrowTopRightOnSquareIcon,
6
+ ArrowLongRightIcon,
7
+ BoltIcon,
8
+ ChatBubbleLeftIcon,
9
+ MagnifyingGlassIcon,
10
+ ChevronDownIcon,
11
+ ChevronUpIcon,
12
+ } from '@heroicons/react/24/outline';
2
13
  import { buildUrl } from '@utils/url-builder';
3
14
  import type { CollectionEntry } from 'astro:content';
4
- import { getCollectionStyles } from './utils';
15
+ import { getSpecUrl, getSpecIcon, getSpecLabel, getSpecColor, type Specification } from './specification-utils';
5
16
 
6
17
  interface MessageGridV2Props {
7
18
  service: CollectionEntry<'services'>;
8
19
  embeded?: boolean;
20
+ specifications?: Specification[];
9
21
  }
10
22
 
11
- export default function MessageGridV2({ service, embeded = false }: MessageGridV2Props) {
12
- const { sends = [], receives = [], writesTo = [], readsFrom = [] } = service.data;
23
+ // Helper to get message icon and color
24
+ const getMessageStyle = (collection: string) => {
25
+ switch (collection) {
26
+ case 'events':
27
+ return { Icon: BoltIcon, color: 'orange', bg: 'bg-orange-50', border: 'border-orange-200', text: 'text-orange-600' };
28
+ case 'commands':
29
+ return { Icon: ChatBubbleLeftIcon, color: 'blue', bg: 'bg-blue-50', border: 'border-blue-200', text: 'text-blue-600' };
30
+ case 'queries':
31
+ return {
32
+ Icon: MagnifyingGlassIcon,
33
+ color: 'emerald',
34
+ bg: 'bg-emerald-50',
35
+ border: 'border-emerald-200',
36
+ text: 'text-emerald-600',
37
+ };
38
+ default:
39
+ return { Icon: BoltIcon, color: 'gray', bg: 'bg-gray-50', border: 'border-gray-200', text: 'text-gray-600' };
40
+ }
41
+ };
42
+
43
+ // Message Card Component
44
+ const MessageCard = memo(({ message, compact = false }: { message: any; compact?: boolean }) => {
45
+ const { Icon, color } = getMessageStyle(message.collection);
13
46
 
14
- const renderMessageGrid = (messages: any[]) => (
15
- <div className="grid grid-cols-1 gap-6">
16
- {messages.map((message) => {
17
- const { color, Icon } = getCollectionStyles(message.collection);
18
- return (
19
- <a
20
- key={message.data.name}
21
- href={buildUrl(`/docs/${message.collection}/${message.data.id}/${message.data.version}`)}
22
- className={`group bg-white border hover:bg-${color}-100 rounded-lg shadow-sm hover:shadow-lg transition-all duration-200 overflow-hidden border-${color}-500`}
47
+ return (
48
+ <a
49
+ href={buildUrl(`/docs/${message.collection}/${message.data.id}/${message.data.version}`)}
50
+ className={`group block bg-white border border-${color}-200 rounded-lg shadow-sm hover:shadow-md hover:border-${color}-300 transition-all`}
51
+ >
52
+ <div className={compact ? 'p-3' : 'p-4'}>
53
+ <div className="flex items-center gap-2 mb-1">
54
+ <div className={`flex items-center justify-center w-7 h-7 bg-${color}-100 rounded-md`}>
55
+ <Icon className={`h-4 w-4 text-${color}-600`} />
56
+ </div>
57
+ <h3
58
+ className={`font-semibold text-gray-900 group-hover:text-${color}-600 transition-colors truncate ${compact ? 'text-sm' : 'text-base'}`}
23
59
  >
24
- <div className="p-4 py-2 flex-1">
25
- <div className="flex items-center justify-between mb-3">
26
- <div className="flex items-center gap-2">
27
- {!embeded && <Icon className={`h-5 w-5 text-${color}-500`} />}
28
- <h3
29
- className={`font-semibold text-gray-900 truncate group-hover:text-${color}-500 transition-colors duration-200 ${embeded ? 'text-sm' : 'text-md'}`}
30
- >
31
- {message.data.name} (v{message.data.version})
32
- </h3>
33
- </div>
34
- </div>
35
- {message.data.summary && <p className="text-gray-600 text-xs line-clamp-2 mb-4">{message.data.summary}</p>}
36
- </div>
37
- </a>
38
- );
39
- })}
40
- </div>
60
+ {message.data.name}
61
+ </h3>
62
+ <span className={`text-[10px] text-${color}-600 font-medium bg-${color}-50 px-1.5 py-0.5 rounded flex-shrink-0`}>
63
+ v{message.data.version}
64
+ </span>
65
+ </div>
66
+ {message.data.summary && (
67
+ <p className={`text-gray-600 line-clamp-2 ${compact ? 'text-xs mt-1' : 'text-sm mt-2'}`}>{message.data.summary}</p>
68
+ )}
69
+ </div>
70
+ </a>
41
71
  );
72
+ });
73
+
74
+ // Container Card Component
75
+ const ContainerCard = memo(({ container, type }: { container: any; type: 'reads' | 'writes' }) => {
76
+ const colorClass = type === 'reads' ? 'amber' : 'violet';
42
77
 
43
78
  return (
44
- <div className={`rounded-xl overflow-hidden bg-pink-50 p-8 border-2 border-dashed border-pink-300`}>
45
- {/* Service Title */}
46
- <div className="flex items-center gap-2 mb-8">
47
- <ServerIcon className="h-6 w-6 text-pink-500" />
48
- <h2 className="text-2xl font-semibold text-gray-900">{service.data.name}</h2>
49
- <div className="flex gap-2 ml-auto">
50
- <a
51
- href={buildUrl(`/visualiser/services/${service.data.id}`)}
52
- className="inline-flex items-center px-3 py-2 text-sm font-medium bg-white border border-gray-300 rounded-md transition-colors duration-200 hover:bg-gray-50"
53
- >
54
- View in visualizer
55
- </a>
56
- <a
57
- href={buildUrl(`/docs/services/${service.data.id}/${service.data.version}`)}
58
- className="inline-flex items-center px-3 py-2 text-sm font-medium text-black border border-gray-300 bg-white rounded-md transition-colors duration-200 hover:bg-gray-50"
59
- >
60
- Read documentation
61
- </a>
79
+ <a
80
+ href={buildUrl(`/docs/containers/${container.data.id}/${container.data.version}`)}
81
+ className={`group block bg-white border rounded-lg shadow-sm hover:shadow-md transition-all ${
82
+ type === 'reads' ? 'border-amber-200 hover:border-amber-300' : 'border-violet-200 hover:border-violet-300'
83
+ }`}
84
+ >
85
+ <div className="p-3">
86
+ <div className="flex items-center gap-2">
87
+ <div className={`flex items-center justify-center w-7 h-7 bg-${colorClass}-100 rounded-md`}>
88
+ <CircleStackIcon className={`h-4 w-4 text-${colorClass}-600`} />
89
+ </div>
90
+ <h3 className={`font-semibold text-gray-900 text-sm group-hover:text-${colorClass}-600 transition-colors truncate`}>
91
+ {container.data.name}
92
+ </h3>
62
93
  </div>
94
+ {container.data.summary && <p className="text-xs text-gray-600 mt-1.5 line-clamp-2">{container.data.summary}</p>}
63
95
  </div>
96
+ </a>
97
+ );
98
+ });
99
+
100
+ // Specification Card Component
101
+ const SpecificationCard = memo(
102
+ ({ spec, serviceId, serviceVersion }: { spec: any; serviceId: string; serviceVersion: string }) => {
103
+ const color = getSpecColor(spec.type);
104
+
105
+ return (
106
+ <a
107
+ href={getSpecUrl(spec, serviceId, serviceVersion)}
108
+ className={`group flex items-center gap-3 p-3 bg-white border border-${color}-200 rounded-lg shadow-sm hover:shadow-md hover:border-${color}-300 transition-all`}
109
+ >
110
+ <img src={buildUrl(`/icons/${getSpecIcon(spec.type)}.svg`, true)} alt={`${spec.type} icon`} className="h-6 w-6" />
111
+ <div className="flex-1 min-w-0">
112
+ <h3 className={`font-semibold text-gray-900 text-sm group-hover:text-${color}-600 transition-colors truncate`}>
113
+ {spec.name || spec.filename}
114
+ </h3>
115
+ <p className="text-xs text-gray-500">{getSpecLabel(spec.type)}</p>
116
+ </div>
117
+ </a>
118
+ );
119
+ }
120
+ );
64
121
 
65
- <div className="grid grid-cols-3 gap-8 relative">
66
- {/* Left Column - Receives Messages & Reads From Containers */}
67
- <div className="space-y-6">
68
- {/* Receives Messages Section */}
69
- <div className="bg-blue-50 bg-opacity-50 border border-blue-300 border-dashed rounded-lg p-4">
70
- <div className="mb-6">
71
- <h2 className={`font-semibold text-gray-900 flex items-center gap-2 ${embeded ? 'text-sm' : 'text-xl'}`}>
72
- <ServerIcon className="h-5 w-5 text-blue-500" />
73
- Receives ({receives.length})
74
- </h2>
122
+ // Collapsible Message Section Component
123
+ const CollapsibleMessageSection = memo(
124
+ ({
125
+ icon: Icon,
126
+ title,
127
+ messages,
128
+ color,
129
+ emptyText,
130
+ embeded = false,
131
+ }: {
132
+ icon: any;
133
+ title: string;
134
+ messages: any[];
135
+ color: string;
136
+ emptyText: string;
137
+ embeded?: boolean;
138
+ }) => {
139
+ const [isCollapsed, setIsCollapsed] = useState(false);
140
+ const hasContent = messages.length > 0;
141
+
142
+ return (
143
+ <div className="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
144
+ <div
145
+ onClick={() => setIsCollapsed(!isCollapsed)}
146
+ className={`flex items-center justify-between px-5 py-4 cursor-pointer hover:bg-gray-50 transition-colors ${!isCollapsed && hasContent ? 'border-b border-gray-200' : ''}`}
147
+ >
148
+ <div className="flex items-center gap-3">
149
+ <div className={`flex items-center justify-center w-8 h-8 bg-${color}-100 rounded-lg`}>
150
+ <Icon className={`h-4 w-4 text-${color}-600`} />
75
151
  </div>
76
- {receives.length > 0 ? (
77
- renderMessageGrid(receives)
78
- ) : (
79
- <div className="text-center py-12">
80
- <p className="text-gray-500 text-sm">No messages</p>
81
- </div>
82
- )}
152
+ <h2 className="text-base font-bold text-gray-900">{title}</h2>
153
+ <span className="text-sm text-gray-500 bg-gray-100 px-2 py-0.5 rounded-full font-medium">{messages.length}</span>
83
154
  </div>
84
-
85
- {/* Reads From Containers */}
86
- {readsFrom.length > 0 && (
87
- <div className="bg-orange-50 border border-orange-300 border-dashed rounded-lg p-4 relative">
88
- <div className="mb-6">
89
- <h2 className={`font-semibold text-gray-900 flex items-center gap-2 ${embeded ? 'text-sm' : 'text-xl'}`}>
90
- <CircleStackIcon className="h-5 w-5 text-orange-500" />
91
- Reads from ({readsFrom.length})
92
- </h2>
93
- </div>
155
+ <div className="text-gray-400">
156
+ {isCollapsed ? <ChevronDownIcon className="h-5 w-5" /> : <ChevronUpIcon className="h-5 w-5" />}
157
+ </div>
158
+ </div>
159
+ {!isCollapsed && (
160
+ <div className="p-5">
161
+ {hasContent ? (
94
162
  <div className="space-y-3">
95
- {readsFrom.map((container: any) => (
96
- <a
97
- key={container.data.id}
98
- href={buildUrl(`/docs/containers/${container.data.id}/${container.data.version}`)}
99
- className="group bg-white border border-orange-200 hover:bg-orange-100 rounded-lg p-3 block transition-all duration-200"
100
- >
101
- <div className="flex items-center gap-2">
102
- <CircleStackIcon className="h-4 w-4 text-orange-500" />
103
- <h3 className="font-semibold text-gray-900 text-sm group-hover:text-orange-700">{container.data.name}</h3>
104
- </div>
105
- {container.data.summary && (
106
- <p className="text-xs text-gray-600 mt-1 line-clamp-2">{container.data.summary}</p>
107
- )}
108
- </a>
163
+ {messages.map((message: any) => (
164
+ <MessageCard key={`${message.data.id}-${message.data.version}`} message={message} compact={embeded} />
109
165
  ))}
110
166
  </div>
111
- {/* Arrow from Reads From to Service */}
112
- <div className="absolute -right-8 top-1/2 -translate-y-1/2 flex items-center justify-center w-16 z-10">
113
- <div className="absolute left-0 w-4 h-4 border-b-[3px] border-l-[3px] border-orange-200 transform rotate-45 -translate-x-1 translate-y-[-1px] shadow-[-1px_1px_0_1px_rgba(0,0,0,0.1)]"></div>
114
- <div className="w-full h-[3px] bg-orange-200 shadow-[0_0_0_1px_rgba(0,0,0,0.1)]"></div>
167
+ ) : (
168
+ <div className="text-center py-4">
169
+ <p className="text-sm text-gray-400">{emptyText}</p>
115
170
  </div>
171
+ )}
172
+ </div>
173
+ )}
174
+ </div>
175
+ );
176
+ }
177
+ );
178
+
179
+ // Collapsible Container Section Component
180
+ const CollapsibleContainerSection = memo(
181
+ ({ title, containers, color, type }: { title: string; containers: any[]; color: string; type: 'reads' | 'writes' }) => {
182
+ const [isCollapsed, setIsCollapsed] = useState(false);
183
+
184
+ return (
185
+ <div className="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
186
+ <div
187
+ onClick={() => setIsCollapsed(!isCollapsed)}
188
+ className={`flex items-center justify-between px-5 py-4 cursor-pointer hover:bg-gray-50 transition-colors ${!isCollapsed ? 'border-b border-gray-200' : ''}`}
189
+ >
190
+ <div className="flex items-center gap-3">
191
+ <div className={`flex items-center justify-center w-8 h-8 bg-${color}-100 rounded-lg`}>
192
+ <CircleStackIcon className={`h-4 w-4 text-${color}-600`} />
116
193
  </div>
117
- )}
194
+ <h2 className="text-base font-bold text-gray-900">{title}</h2>
195
+ <span className="text-sm text-gray-500 bg-gray-100 px-2 py-0.5 rounded-full font-medium">{containers.length}</span>
196
+ </div>
197
+ <div className="text-gray-400">
198
+ {isCollapsed ? <ChevronDownIcon className="h-5 w-5" /> : <ChevronUpIcon className="h-5 w-5" />}
199
+ </div>
118
200
  </div>
201
+ {!isCollapsed && (
202
+ <div className="p-5">
203
+ <div className="space-y-3">
204
+ {containers.map((container: any) => (
205
+ <ContainerCard key={container.data.id} container={container} type={type} />
206
+ ))}
207
+ </div>
208
+ </div>
209
+ )}
210
+ </div>
211
+ );
212
+ }
213
+ );
119
214
 
120
- {/* Arrow from Receives to Service */}
121
- <div className="absolute left-[30%] top-[25%] -translate-y-1/2 flex items-center justify-center w-16">
122
- <div className="w-full h-[3px] bg-blue-200 shadow-[0_0_0_1px_rgba(0,0,0,0.1)]"></div>
123
- <div className="absolute right-0 w-4 h-4 border-t-[3px] border-r-[3px] border-blue-200 transform rotate-45 translate-x-1 translate-y-[-1px] shadow-[1px_-1px_0_1px_rgba(0,0,0,0.1)]"></div>
215
+ export default function MessageGridV2({ service, embeded = false, specifications = [] }: MessageGridV2Props) {
216
+ const { sends = [], receives = [], writesTo = [], readsFrom = [] } = service.data;
217
+ const hasContainers = readsFrom.length > 0 || writesTo.length > 0;
218
+ const hasMessages = receives.length > 0 || sends.length > 0;
219
+ const hasSpecs = specifications.length > 0;
220
+
221
+ return (
222
+ <div className="w-full">
223
+ {/* Service Header - Doc style */}
224
+ <div className="border-b border-gray-200 md:pb-4">
225
+ <div className="flex items-start justify-between">
226
+ <div>
227
+ <h2 className="text-2xl md:text-4xl font-bold text-black">{service.data.name}</h2>
228
+ {service.data.summary && <p className="text-lg pt-2 text-gray-500 font-light">{service.data.summary}</p>}
229
+ </div>
230
+ <div className="flex items-center gap-2 flex-shrink-0">
231
+ <a
232
+ href={buildUrl(`/docs/services/${service.data.id}/${service.data.version}`)}
233
+ className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 transition-all"
234
+ >
235
+ View docs
236
+ <ArrowTopRightOnSquareIcon className="h-4 w-4 text-gray-400" />
237
+ </a>
238
+ <a
239
+ href={buildUrl(`/visualiser/services/${service.data.id}/${service.data.version}`)}
240
+ className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-white bg-gray-800 rounded-lg hover:bg-gray-900 transition-all"
241
+ >
242
+ Visualizer
243
+ <ArrowTopRightOnSquareIcon className="h-4 w-4 text-gray-400" />
244
+ </a>
245
+ </div>
124
246
  </div>
247
+ </div>
125
248
 
126
- {/* Service Information (Center) */}
127
- <div className="bg-white border-2 border-pink-100 rounded-lg p-6 flex flex-col justify-center">
128
- <div className="flex flex-col items-center gap-4">
129
- <ServerIcon className="h-12 w-12 text-pink-500" />
130
- <p className="text-xl font-semibold text-gray-900 text-center">{service.data.name}</p>
131
-
132
- {/* Quick Stats Grid */}
133
- <div className="w-full grid grid-cols-2 gap-3 mt-2">
134
- <div className="text-center p-3 bg-blue-50 rounded-lg border border-blue-200">
135
- <div className="text-2xl font-bold text-blue-600">{receives.length}</div>
136
- <div className="text-xs text-gray-600 mt-1">Receives</div>
137
- </div>
138
- <div className="text-center p-3 bg-green-50 rounded-lg border border-green-200">
139
- <div className="text-2xl font-bold text-green-600">{sends.length}</div>
140
- <div className="text-xs text-gray-600 mt-1">Sends</div>
141
- </div>
249
+ {/* Service Content */}
250
+ <div className="py-4 space-y-8">
251
+ {/* Specifications */}
252
+ {hasSpecs && (
253
+ <div>
254
+ <div className="flex items-center gap-2 mb-4">
255
+ <h3 className="text-lg font-semibold text-gray-900">Specifications</h3>
256
+ <span className="text-sm text-gray-500 bg-gray-100 px-2.5 py-0.5 rounded-full font-medium">
257
+ {specifications.length}
258
+ </span>
259
+ </div>
260
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
261
+ {specifications.map((spec: any) => (
262
+ <SpecificationCard
263
+ key={`${spec.type}-${spec.filename}`}
264
+ spec={spec}
265
+ serviceId={service.data.id}
266
+ serviceVersion={service.data.version}
267
+ />
268
+ ))}
269
+ </div>
270
+ </div>
271
+ )}
272
+
273
+ {/* Message Flow - Two columns side by side */}
274
+ {hasMessages && (
275
+ <div>
276
+ <div className="flex items-center gap-2 mb-4">
277
+ <BoltIcon className="h-5 w-5 text-orange-500" />
278
+ <h3 className="text-lg font-semibold text-gray-900">Messages</h3>
279
+ <span className="text-sm text-gray-500 bg-gray-100 px-2.5 py-0.5 rounded-full font-medium">
280
+ {receives.length + sends.length}
281
+ </span>
282
+ </div>
283
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
284
+ <CollapsibleMessageSection
285
+ icon={ArrowLongRightIcon}
286
+ title="Receives"
287
+ messages={receives}
288
+ color="blue"
289
+ emptyText="No messages received"
290
+ embeded={embeded}
291
+ />
292
+ <CollapsibleMessageSection
293
+ icon={ArrowLongRightIcon}
294
+ title="Sends"
295
+ messages={sends}
296
+ color="emerald"
297
+ emptyText="No messages sent"
298
+ embeded={embeded}
299
+ />
300
+ </div>
301
+ </div>
302
+ )}
303
+
304
+ {/* Container Relationships */}
305
+ {hasContainers && (
306
+ <div>
307
+ <div className="flex items-center gap-2 mb-4">
308
+ <CircleStackIcon className="h-5 w-5 text-amber-500" />
309
+ <h3 className="text-lg font-semibold text-gray-900">Data Sources</h3>
310
+ <span className="text-sm text-gray-500 bg-gray-100 px-2.5 py-0.5 rounded-full font-medium">
311
+ {readsFrom.length + writesTo.length}
312
+ </span>
313
+ </div>
314
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
142
315
  {readsFrom.length > 0 && (
143
- <div className="text-center p-3 bg-orange-50 rounded-lg border border-orange-200">
144
- <div className="text-2xl font-bold text-orange-600">{readsFrom.length}</div>
145
- <div className="text-xs text-gray-600 mt-1">Reads from</div>
146
- </div>
316
+ <CollapsibleContainerSection title="Reads from" containers={readsFrom} color="amber" type="reads" />
147
317
  )}
148
318
  {writesTo.length > 0 && (
149
- <div className="text-center p-3 bg-purple-50 rounded-lg border border-purple-200">
150
- <div className="text-2xl font-bold text-purple-600">{writesTo.length}</div>
151
- <div className="text-xs text-gray-600 mt-1">Writes to</div>
152
- </div>
319
+ <CollapsibleContainerSection title="Writes to" containers={writesTo} color="violet" type="writes" />
153
320
  )}
154
321
  </div>
155
322
  </div>
156
- </div>
157
-
158
- {/* Arrow from Service to Sends */}
159
- <div className="absolute right-[30%] top-[25%] -translate-y-1/2 flex items-center justify-center w-16">
160
- <div className="w-full h-[3px] bg-green-200 shadow-[0_0_0_1px_rgba(0,0,0,0.1)]"></div>
161
- <div className="absolute right-0 w-4 h-4 border-t-[3px] border-r-[3px] border-green-200 transform rotate-45 translate-x-1 translate-y-[-1px] shadow-[1px_-1px_0_1px_rgba(0,0,0,0.1)]"></div>
162
- </div>
323
+ )}
163
324
 
164
- {/* Right Column - Sends Messages & Writes To Containers */}
165
- <div className="space-y-6">
166
- {/* Sends Messages Section */}
167
- <div className="bg-green-50 border border-green-300 border-dashed rounded-lg p-4">
168
- <div className="mb-6">
169
- <h2 className={`font-semibold text-gray-900 flex items-center gap-2 ${embeded ? 'text-sm' : 'text-xl'}`}>
170
- <ServerIcon className="h-5 w-5 text-emerald-500" />
171
- Sends ({sends.length})
172
- </h2>
325
+ {/* Empty State */}
326
+ {!hasMessages && !hasContainers && !hasSpecs && (
327
+ <div className="text-center py-12">
328
+ <div className="flex items-center justify-center w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-2xl">
329
+ <ServerIcon className="h-8 w-8 text-gray-400" />
173
330
  </div>
174
- {sends.length > 0 ? (
175
- renderMessageGrid(sends)
176
- ) : (
177
- <div className="text-center py-12">
178
- <p className="text-gray-500 text-sm">No messages</p>
179
- </div>
180
- )}
331
+ <p className="text-gray-500">
332
+ This service has no message flows, container relationships, or specifications defined.
333
+ </p>
181
334
  </div>
182
-
183
- {/* Writes To Containers */}
184
- {writesTo.length > 0 && (
185
- <div className="bg-purple-50 border border-purple-300 border-dashed rounded-lg p-4 relative">
186
- {/* Arrow from Service to Writes To */}
187
- <div className="absolute -left-8 top-1/2 -translate-y-1/2 flex items-center justify-center w-16 z-10">
188
- <div className="w-full h-[3px] bg-purple-200 shadow-[0_0_0_1px_rgba(0,0,0,0.1)]"></div>
189
- <div className="absolute right-0 w-4 h-4 border-t-[3px] border-r-[3px] border-purple-200 transform rotate-45 translate-x-1 translate-y-[-1px] shadow-[1px_-1px_0_1px_rgba(0,0,0,0.1)]"></div>
190
- </div>
191
- <div className="mb-6">
192
- <h2 className={`font-semibold text-gray-900 flex items-center gap-2 ${embeded ? 'text-sm' : 'text-xl'}`}>
193
- <CircleStackIcon className="h-5 w-5 text-purple-500" />
194
- Writes to ({writesTo.length})
195
- </h2>
196
- </div>
197
- <div className="space-y-3">
198
- {writesTo.map((container: any) => (
199
- <a
200
- key={container.data.id}
201
- href={buildUrl(`/docs/containers/${container.data.id}/${container.data.version}`)}
202
- className="group bg-white border border-purple-200 hover:bg-purple-100 rounded-lg p-3 block transition-all duration-200"
203
- >
204
- <div className="flex items-center gap-2">
205
- <CircleStackIcon className="h-4 w-4 text-purple-500" />
206
- <h3 className="font-semibold text-gray-900 text-sm group-hover:text-purple-700">{container.data.name}</h3>
207
- </div>
208
- {container.data.summary && (
209
- <p className="text-xs text-gray-600 mt-1 line-clamp-2">{container.data.summary}</p>
210
- )}
211
- </a>
212
- ))}
213
- </div>
214
- </div>
215
- )}
216
- </div>
335
+ )}
217
336
  </div>
218
337
  </div>
219
338
  );
@@ -0,0 +1,106 @@
1
+ import { buildUrl } from '@utils/url-builder';
2
+
3
+ export type SpecificationType = 'openapi' | 'asyncapi' | 'graphql';
4
+
5
+ export interface Specification {
6
+ type: SpecificationType;
7
+ path: string;
8
+ name?: string;
9
+ filename: string;
10
+ filenameWithoutExtension: string;
11
+ }
12
+
13
+ export const getSpecUrl = (spec: Specification, serviceId: string, serviceVersion: string): string => {
14
+ switch (spec.type) {
15
+ case 'openapi':
16
+ return buildUrl(`/docs/services/${serviceId}/${serviceVersion}/spec/${spec.filenameWithoutExtension}`);
17
+ case 'asyncapi':
18
+ return buildUrl(`/docs/services/${serviceId}/${serviceVersion}/asyncapi/${spec.filenameWithoutExtension}`);
19
+ case 'graphql':
20
+ return buildUrl(`/docs/services/${serviceId}/${serviceVersion}/graphql/${spec.filenameWithoutExtension}`);
21
+ default:
22
+ return '#';
23
+ }
24
+ };
25
+
26
+ export const getSpecIcon = (type: string): string => {
27
+ switch (type) {
28
+ case 'openapi':
29
+ return 'openapi';
30
+ case 'asyncapi':
31
+ return 'asyncapi';
32
+ case 'graphql':
33
+ return 'graphql';
34
+ default:
35
+ return 'json-schema';
36
+ }
37
+ };
38
+
39
+ export const getSpecLabel = (type: string): string => {
40
+ switch (type) {
41
+ case 'openapi':
42
+ return 'OpenAPI';
43
+ case 'asyncapi':
44
+ return 'AsyncAPI';
45
+ case 'graphql':
46
+ return 'GraphQL';
47
+ default:
48
+ return type;
49
+ }
50
+ };
51
+
52
+ export const getSpecColor = (type: string): string => {
53
+ switch (type) {
54
+ case 'openapi':
55
+ return 'green';
56
+ case 'asyncapi':
57
+ return 'purple';
58
+ case 'graphql':
59
+ return 'pink';
60
+ default:
61
+ return 'gray';
62
+ }
63
+ };
64
+
65
+ // Helper to normalize specifications from service data
66
+ export const getServiceSpecifications = (data: any): Specification[] => {
67
+ const specs = data?.specifications;
68
+ if (!specs) return [];
69
+
70
+ // Handle array format
71
+ if (Array.isArray(specs)) {
72
+ return specs.map((spec: any) => {
73
+ const filename = spec.path?.split('/').pop() || spec.path;
74
+ const filenameWithoutExtension = filename?.replace(/\.[^/.]+$/, '') || '';
75
+ return {
76
+ type: spec.type,
77
+ path: spec.path,
78
+ name: spec.name,
79
+ filename,
80
+ filenameWithoutExtension,
81
+ };
82
+ });
83
+ }
84
+
85
+ // Handle legacy object format
86
+ const result: Specification[] = [];
87
+ if (specs.asyncapiPath) {
88
+ const filename = specs.asyncapiPath.split('/').pop();
89
+ result.push({
90
+ type: 'asyncapi',
91
+ path: specs.asyncapiPath,
92
+ filename,
93
+ filenameWithoutExtension: filename?.replace(/\.[^/.]+$/, '') || '',
94
+ });
95
+ }
96
+ if (specs.openapiPath) {
97
+ const filename = specs.openapiPath.split('/').pop();
98
+ result.push({
99
+ type: 'openapi',
100
+ path: specs.openapiPath,
101
+ filename,
102
+ filenameWithoutExtension: filename?.replace(/\.[^/.]+$/, '') || '',
103
+ });
104
+ }
105
+ return result;
106
+ };