@eventcatalog/core 3.0.0-beta.2 → 3.0.0-beta.21

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 (103) hide show
  1. package/README.md +10 -0
  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/{chunk-JSONCD7V.js → chunk-2FUEBPD3.js} +1 -1
  11. package/dist/{chunk-3W6JYTHP.js → chunk-HABY2LVH.js} +6 -2
  12. package/dist/{chunk-H4QHE5YZ.js → chunk-KQAMO3R4.js} +1 -1
  13. package/dist/chunk-Q6KRYWPV.js +44 -0
  14. package/dist/{chunk-PQL6O5YA.js → chunk-RRP2B7BL.js} +1 -1
  15. package/dist/constants.cjs +1 -1
  16. package/dist/constants.js +1 -1
  17. package/dist/eventcatalog.cjs +84 -65
  18. package/dist/eventcatalog.config.d.cts +4 -0
  19. package/dist/eventcatalog.config.d.ts +4 -0
  20. package/dist/eventcatalog.js +45 -57
  21. package/dist/generate.cjs +48 -2
  22. package/dist/generate.js +3 -1
  23. package/dist/utils/cli-logger.cjs +82 -0
  24. package/dist/utils/cli-logger.d.cts +10 -0
  25. package/dist/utils/cli-logger.d.ts +10 -0
  26. package/dist/utils/cli-logger.js +7 -0
  27. package/eventcatalog/astro.config.mjs +4 -1
  28. package/eventcatalog/integrations/ecstudio-watcher.mjs +1 -1
  29. package/eventcatalog/integrations/eventcatalog-features.ts +69 -0
  30. package/eventcatalog/public/icons/asyncapi-black.svg +2 -0
  31. package/eventcatalog/public/icons/graphql-black.svg +1 -0
  32. package/eventcatalog/public/icons/openapi-black.svg +1 -0
  33. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +821 -0
  34. package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +24 -0
  35. package/eventcatalog/src/components/Grids/DomainGrid.tsx +1 -3
  36. package/eventcatalog/src/components/Grids/MessageGrid.tsx +8 -8
  37. package/eventcatalog/src/components/Header.astro +25 -5
  38. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +14 -3
  39. package/eventcatalog/src/components/Search/Search.astro +2 -2
  40. package/eventcatalog/src/components/Search/SearchModal.tsx +16 -7
  41. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +9 -2
  42. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/domain.ts +7 -6
  43. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/service.ts +6 -3
  44. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/shared.ts +1 -0
  45. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +23 -8
  46. package/eventcatalog/src/components/SideNav/NestedSideBar/sidebar-builder.ts +57 -11
  47. package/eventcatalog/src/content.config.ts +1 -10
  48. package/eventcatalog/src/enterprise/ai/chat-api.ts +262 -0
  49. package/eventcatalog/src/enterprise/auth/[...auth].ts +3 -0
  50. package/eventcatalog/src/enterprise/auth/login.astro +420 -0
  51. package/eventcatalog/src/enterprise/collections/index.ts +0 -1
  52. package/eventcatalog/src/layouts/Footer.astro +8 -5
  53. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +30 -19
  54. package/eventcatalog/src/pages/_index.astro +8 -9
  55. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +19 -3
  56. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +7 -7
  57. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
  58. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +5 -5
  59. package/eventcatalog/src/pages/docs/teams/[id].mdx.ts +36 -0
  60. package/eventcatalog/src/pages/docs/users/[id].mdx.ts +36 -0
  61. package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +178 -0
  62. package/eventcatalog/src/pages/schemas/explorer/index.astro +5 -155
  63. package/eventcatalog/src/remark-plugins/directives.ts +30 -9
  64. package/eventcatalog/src/utils/collections/schemas.ts +31 -7
  65. package/eventcatalog/src/utils/feature.ts +8 -4
  66. package/eventcatalog/src/utils/resource-files.ts +86 -0
  67. package/package.json +12 -15
  68. package/default-files-for-collections/changelogs.md +0 -5
  69. package/default-files-for-collections/channels.md +0 -8
  70. package/default-files-for-collections/commands.md +0 -8
  71. package/default-files-for-collections/domains.md +0 -8
  72. package/default-files-for-collections/events.md +0 -8
  73. package/default-files-for-collections/flows.md +0 -11
  74. package/default-files-for-collections/queries.md +0 -8
  75. package/default-files-for-collections/services.md +0 -8
  76. package/default-files-for-collections/ubiquitousLanguages.md +0 -7
  77. package/eventcatalog/src/enterprise/collections/chat-prompts.ts +0 -32
  78. package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +0 -60
  79. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +0 -414
  80. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatSidebar.tsx +0 -169
  81. package/eventcatalog/src/enterprise/eventcatalog-chat/components/InputModal.tsx +0 -244
  82. package/eventcatalog/src/enterprise/eventcatalog-chat/components/MentionInput.tsx +0 -211
  83. package/eventcatalog/src/enterprise/eventcatalog-chat/components/WelcomePromptArea.tsx +0 -176
  84. package/eventcatalog/src/enterprise/eventcatalog-chat/components/default-prompts.ts +0 -93
  85. package/eventcatalog/src/enterprise/eventcatalog-chat/components/hooks/ChatProvider.tsx +0 -143
  86. package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +0 -387
  87. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/chat.ts +0 -59
  88. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +0 -104
  89. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +0 -140
  90. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/anthropic.ts +0 -28
  91. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/google.ts +0 -41
  92. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/index.ts +0 -26
  93. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/openai.ts +0 -61
  94. package/eventcatalog/src/enterprise/eventcatalog-chat/utils/chat-prompts.ts +0 -50
  95. package/eventcatalog/src/pages/auth/login.astro +0 -280
  96. package/eventcatalog/src/pages/chat/feature.astro +0 -179
  97. package/eventcatalog/src/pages/chat/index.astro +0 -10
  98. package/eventcatalog/src/pages/nav-index.json.ts +0 -30
  99. /package/eventcatalog/src/{pages → enterprise}/auth/error.astro +0 -0
  100. /package/eventcatalog/src/{middleware-auth.ts → enterprise/auth/middleware/middleware-auth.ts} +0 -0
  101. /package/eventcatalog/src/{middleware.ts → enterprise/auth/middleware/middleware.ts} +0 -0
  102. /package/eventcatalog/src/{pages/unauthorized/index.astro → enterprise/auth/unauthorized.astro} +0 -0
  103. /package/eventcatalog/src/{pages → enterprise}/plans/index.astro +0 -0
@@ -1,414 +0,0 @@
1
- import React, { useState } from 'react';
2
- import ReactMarkdown, { type ExtraProps } from 'react-markdown';
3
- import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
4
- import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
5
- import type { Message } from '@enterprise/eventcatalog-chat/components/hooks/ChatProvider';
6
- import * as Dialog from '@radix-ui/react-dialog';
7
- import { Fullscreen, X, Clipboard, Check, ChevronDown, ChevronRight } from 'lucide-react'; // Add Clipboard, Check, ChevronDown, ChevronRight icons
8
-
9
- // Define Resource type locally
10
- interface Resource {
11
- id: string;
12
- type: string;
13
- url: string;
14
- title?: string;
15
- name?: string | null; // Allow null for name
16
- }
17
-
18
- interface ChatMessageProps {
19
- message: Message;
20
- }
21
-
22
- // Function to escape special characters for regex
23
- function escapeRegex(string: string): string {
24
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
25
- }
26
-
27
- // Function to parse and render content with iframes
28
- function parseContentWithIframes(content: string): React.ReactNode[] {
29
- const iframeRegex = /<iframe[^>]*>.*?<\/iframe>/gi;
30
- const parts: React.ReactNode[] = [];
31
- let lastIndex = 0;
32
- let match;
33
-
34
- while ((match = iframeRegex.exec(content)) !== null) {
35
- // Add text before iframe as markdown
36
- if (match.index > lastIndex) {
37
- const textBefore = content.slice(lastIndex, match.index);
38
- if (textBefore.trim()) {
39
- parts.push(
40
- <ReactMarkdown
41
- key={`text-${lastIndex}`}
42
- components={{
43
- code({ node, className, children, ...props }: CodeComponentProps) {
44
- const inline = props.inline;
45
- const match = /language-(\w+)/.exec(className || '');
46
- const codeString = String(children);
47
- const language = match ? match[1] : 'text';
48
- const codeBlockId = `code-${React.useId()}`;
49
- const isCopied = false; // We'll handle this in the parent component
50
-
51
- const treatAsInline = inline || !codeString.includes('\n');
52
-
53
- return !treatAsInline ? (
54
- <div className="code-block bg-[#1e1e1e] rounded-md overflow-hidden my-4 relative group">
55
- <div className="flex justify-between items-center px-4 py-1.5 bg-gray-700 text-gray-300 text-xs">
56
- <span>{language}</span>
57
- </div>
58
- <SyntaxHighlighter
59
- style={vscDarkPlus as any}
60
- language={language}
61
- PreTag="div"
62
- showLineNumbers
63
- wrapLines={true}
64
- customStyle={{ margin: 0, borderRadius: '0 0 0.375rem 0.375rem', padding: '1rem' }}
65
- >
66
- {codeString.replace(/\n$/, '')}
67
- </SyntaxHighlighter>
68
- </div>
69
- ) : (
70
- <code
71
- className={`px-1 py-0.5 rounded text-xs font-mono bg-gray-300/70 text-gray-900 ${className || ''}`}
72
- {...props}
73
- >
74
- {codeString.trim()}
75
- </code>
76
- );
77
- },
78
- a: ({ node, ...props }) => (
79
- <a {...props} target="_blank" rel="noopener noreferrer" className="text-purple-600 hover:text-purple-800" />
80
- ),
81
- p: ({ node, ...props }) => <p {...props} className="mb-2 last:mb-0" />,
82
- }}
83
- >
84
- {textBefore}
85
- </ReactMarkdown>
86
- );
87
- }
88
- }
89
-
90
- // Parse iframe attributes
91
- const iframeHtml = match[0];
92
- const srcMatch = iframeHtml.match(/src=["']([^"']+)["']/i);
93
- const widthMatch = iframeHtml.match(/width=["']([^"']+)["']/i);
94
- const heightMatch = iframeHtml.match(/height=["']([^"']+)["']/i);
95
- const titleMatch = iframeHtml.match(/title=["']([^"']+)["']/i);
96
-
97
- if (srcMatch) {
98
- parts.push(
99
- <iframe
100
- key={`iframe-${match.index}`}
101
- src={srcMatch[1]}
102
- width={widthMatch ? widthMatch[1] : '100%'}
103
- height={heightMatch ? heightMatch[1] : '400'}
104
- title={titleMatch ? titleMatch[1] : 'Embedded content'}
105
- className="w-full h-96 border rounded-lg my-4"
106
- sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
107
- loading="lazy"
108
- />
109
- );
110
- }
111
-
112
- lastIndex = match.index + match[0].length;
113
- }
114
-
115
- // Add remaining text after last iframe
116
- if (lastIndex < content.length) {
117
- const textAfter = content.slice(lastIndex);
118
- if (textAfter.trim()) {
119
- parts.push(
120
- <ReactMarkdown
121
- key={`text-${lastIndex}`}
122
- components={{
123
- code({ node, className, children, ...props }: CodeComponentProps) {
124
- const inline = props.inline;
125
- const match = /language-(\w+)/.exec(className || '');
126
- const codeString = String(children);
127
- const language = match ? match[1] : 'text';
128
- const codeBlockId = `code-${React.useId()}`;
129
- const isCopied = false;
130
-
131
- const treatAsInline = inline || !codeString.includes('\n');
132
-
133
- return !treatAsInline ? (
134
- <div className="code-block bg-[#1e1e1e] rounded-md overflow-hidden my-4 relative group">
135
- <div className="flex justify-between items-center px-4 py-1.5 bg-gray-700 text-gray-300 text-xs">
136
- <span>{language}</span>
137
- </div>
138
- <SyntaxHighlighter
139
- style={vscDarkPlus as any}
140
- language={language}
141
- PreTag="div"
142
- showLineNumbers
143
- wrapLines={true}
144
- customStyle={{ margin: 0, borderRadius: '0 0 0.375rem 0.375rem', padding: '1rem' }}
145
- >
146
- {codeString.replace(/\n$/, '')}
147
- </SyntaxHighlighter>
148
- </div>
149
- ) : (
150
- <code
151
- className={`px-1 py-0.5 rounded text-xs font-mono bg-gray-300/70 text-gray-900 ${className || ''}`}
152
- {...props}
153
- >
154
- {codeString.trim()}
155
- </code>
156
- );
157
- },
158
- a: ({ node, ...props }) => (
159
- <a {...props} target="_blank" rel="noopener noreferrer" className="text-purple-600 hover:text-purple-800" />
160
- ),
161
- p: ({ node, ...props }) => <p {...props} className="mb-2 last:mb-0" />,
162
- }}
163
- >
164
- {textAfter}
165
- </ReactMarkdown>
166
- );
167
- }
168
- }
169
-
170
- return parts;
171
- }
172
-
173
- // Define props for the code component explicitly
174
- interface CodeComponentProps extends React.HTMLAttributes<HTMLElement>, ExtraProps {
175
- inline?: boolean;
176
- }
177
-
178
- const ChatMessage = React.memo(({ message }: ChatMessageProps) => {
179
- const [isModalOpen, setIsModalOpen] = useState(false);
180
- const [modalContent, setModalContent] = useState<{ language: string; code: string } | null>(null);
181
- const [copiedStates, setCopiedStates] = useState<Record<string, boolean>>({}); // State for copy feedback
182
- const [isResourcesCollapsed, setIsResourcesCollapsed] = useState(true); // State for resource section collapse
183
- const [isContextCollapsed, setIsContextCollapsed] = useState(true); // State for additional context collapse
184
-
185
- // Helper to get display name for resource, ensuring a fallback
186
- const getResourceDisplayName = (resource: Resource): string => {
187
- return resource.title || resource.name || resource.id || 'Resource'; // Added fallback
188
- };
189
-
190
- const handleCopy = (codeToCopy: string, id: string) => {
191
- navigator.clipboard
192
- .writeText(codeToCopy)
193
- .then(() => {
194
- setCopiedStates((prev) => ({ ...prev, [id]: true }));
195
- setTimeout(() => {
196
- setCopiedStates((prev) => ({ ...prev, [id]: false }));
197
- }, 1500); // Reset after 1.5 seconds
198
- })
199
- .catch((err) => {
200
- console.error('Failed to copy code: ', err);
201
- });
202
- };
203
-
204
- return (
205
- <div className={`flex ${message.isUser ? 'justify-end' : 'justify-start'} mb-4`}>
206
- <div
207
- className={`max-w-[80%] rounded-lg p-3 ${message.isUser ? 'bg-purple-600 text-white rounded-br-none' : 'bg-gray-100 text-gray-800 rounded-bl-none'}`}
208
- >
209
- {/* Apply prose styles, including prose-invert for user messages for better text contrast */}
210
- <div className={`prose prose-sm max-w-none ${message.isUser ? 'prose-invert' : ''}`}>
211
- {(() => {
212
- const content = message.content || '';
213
- const iframeRegex = /<iframe[^>]*>.*?<\/iframe>/gi;
214
-
215
- // If content contains iframes, use custom parser
216
- if (iframeRegex.test(content)) {
217
- return parseContentWithIframes(content);
218
- }
219
-
220
- // Otherwise, use regular ReactMarkdown
221
- return (
222
- <ReactMarkdown
223
- components={{
224
- code({ node, className, children, ...props }: CodeComponentProps) {
225
- const inline = props.inline;
226
- const match = /language-(\w+)/.exec(className || '');
227
- const codeString = String(children);
228
- const language = match ? match[1] : 'text';
229
- const codeBlockId = `code-${React.useId()}`;
230
- const isCopied = copiedStates[codeBlockId];
231
-
232
- const handleOpenModal = () => {
233
- setModalContent({ language, code: codeString.replace(/\n$/, '') });
234
- setIsModalOpen(true);
235
- };
236
-
237
- // Heuristic: Treat as inline if it doesn't contain newlines OR if explicitly inline.
238
- // This handles parser quirks with single-line snippets in lists, etc.
239
- const treatAsInline = inline || !codeString.includes('\n');
240
-
241
- return !treatAsInline ? (
242
- <div className="code-block bg-[#1e1e1e] rounded-md overflow-hidden my-4 relative group">
243
- <div className="flex justify-between items-center px-4 py-1.5 bg-gray-700 text-gray-300 text-xs">
244
- <span>{language}</span>
245
- <div className="flex items-center gap-2">
246
- <button
247
- onClick={() => handleCopy(codeString.replace(/\n$/, ''), codeBlockId)}
248
- className="text-gray-300 hover:text-white flex items-center"
249
- aria-label={isCopied ? 'Copied' : 'Copy code'}
250
- >
251
- {isCopied ? <Check size={14} className="text-green-400" /> : <Clipboard size={14} />}
252
- </button>
253
- <button
254
- onClick={handleOpenModal}
255
- className="text-gray-300 hover:text-white"
256
- aria-label="View code fullscreen"
257
- >
258
- <Fullscreen size={14} />
259
- </button>
260
- </div>
261
- </div>
262
- <SyntaxHighlighter
263
- style={vscDarkPlus as any}
264
- language={language}
265
- PreTag="div"
266
- showLineNumbers
267
- wrapLines={true}
268
- customStyle={{ margin: 0, borderRadius: '0 0 0.375rem 0.375rem', padding: '1rem' }}
269
- >
270
- {codeString.replace(/\n$/, '')}
271
- </SyntaxHighlighter>
272
- </div>
273
- ) : (
274
- <code
275
- className={`px-1 py-0.5 rounded text-xs font-mono ${
276
- message.isUser
277
- ? 'bg-purple-800/70 text-purple-100' // Darker purple bg, light text for user
278
- : 'bg-gray-300/70 text-gray-900' // Darker gray bg, dark text for assistant
279
- } ${className || ''}`}
280
- {...props}
281
- >
282
- {/* Render trimmed version for inline code */}
283
- {codeString.trim()}
284
- </code>
285
- );
286
- },
287
- a: ({ node, ...props }) => (
288
- <a {...props} target="_blank" rel="noopener noreferrer" className="text-purple-600 hover:text-purple-800" />
289
- ),
290
- p: ({ node, ...props }) => <p {...props} className="mb-2 last:mb-0" />,
291
- }}
292
- >
293
- {content}
294
- </ReactMarkdown>
295
- );
296
- })()}
297
- </div>
298
-
299
- {/* Additional Context section (for user messages) */}
300
- {message.isUser && message.additionalContext && (
301
- <div className="mt-3 pt-3 border-t border-purple-700/50">
302
- {' '}
303
- {/* Adjusted border color for subtlety */}
304
- <button
305
- className="flex items-center text-xs text-purple-300 mb-1 w-full text-left focus:outline-none" // Adjusted text color for subtlety
306
- onClick={() => setIsContextCollapsed(!isContextCollapsed)}
307
- aria-expanded={!isContextCollapsed}
308
- aria-controls="additional-context-content"
309
- >
310
- {isContextCollapsed ? <ChevronRight size={14} className="mr-1" /> : <ChevronDown size={14} className="mr-1" />}
311
- Prompt used:
312
- </button>
313
- {!isContextCollapsed && (
314
- <div className="text-[10px] mt-1 pl-5 prose prose-sm prose-invert" id="additional-context-content">
315
- {' '}
316
- {/* Removed max-w-none */}
317
- <pre className="whitespace-pre-wrap break-words">{message.additionalContext}</pre> {/* Use pre for formatting */}
318
- </div>
319
- )}
320
- </div>
321
- )}
322
-
323
- {/* Resource section */}
324
- {!message.isUser && message.resources && message.resources.length > 0 && (
325
- <div className="mt-3 pt-3 border-t border-gray-200">
326
- {/* Make the title clickable to toggle collapse */}
327
- <button
328
- className="flex items-center text-xs text-gray-500 mb-1 w-full text-left focus:outline-none"
329
- onClick={() => setIsResourcesCollapsed(!isResourcesCollapsed)}
330
- aria-expanded={!isResourcesCollapsed}
331
- aria-controls="resource-list"
332
- >
333
- {isResourcesCollapsed ? <ChevronRight size={14} className="mr-1" /> : <ChevronDown size={14} className="mr-1" />}
334
- Referenced Resources:
335
- </button>
336
- {/* Conditionally render the list based on the collapsed state */}
337
- {!isResourcesCollapsed && (
338
- <div className="text-[10px] mt-1 pl-5" id="resource-list">
339
- {' '}
340
- {/* Added pl-5 for indentation */}
341
- {(message.resources as Resource[]).map((resource: Resource, idx: number) => (
342
- <span key={resource.id || `res-${idx}`}>
343
- <a
344
- href={resource.url}
345
- className="text-purple-600 hover:text-purple-800"
346
- target="_blank"
347
- rel="noopener noreferrer"
348
- >
349
- {/* Use helper function for clarity and add parentheses for operator precedence */}
350
- {getResourceDisplayName(resource)} ({resource.type})
351
- </a>
352
- {idx < (message.resources?.length || 0) - 1 ? ', ' : ''}
353
- </span>
354
- ))}
355
- </div>
356
- )}
357
- </div>
358
- )}
359
- </div>
360
-
361
- {/* Fullscreen Code Modal */}
362
- <Dialog.Root open={isModalOpen} onOpenChange={setIsModalOpen}>
363
- <Dialog.Portal>
364
- {/* Add z-index and animations to Overlay */}
365
- {/* NOTE: Define overlayShow/overlayHide keyframes in CSS/Tailwind config */}
366
- <Dialog.Overlay className="fixed inset-0 bg-black/60 z-50 data-[state=open]:animate-overlayShow data-[state=closed]:animate-overlayHide" />
367
- {/* Add z-index and animations to Content */}
368
- {/* NOTE: Define contentShow/contentHide keyframes in CSS/Tailwind config */}
369
- <Dialog.Content className="fixed left-1/2 top-1/2 z-[100] w-[90vw] max-w-4xl max-h-[85vh] -translate-x-1/2 -translate-y-1/2 rounded-lg bg-[#1e1e1e] p-0 shadow-lg focus:outline-none data-[state=open]:animate-contentShow data-[state=closed]:animate-contentHide overflow-hidden flex flex-col">
370
- {/* Modal Header with Copy Button */}
371
- <div className="flex justify-between items-center px-4 py-2.5 bg-gray-700 text-gray-300 text-sm flex-shrink-0 border-b border-gray-600">
372
- <Dialog.Title className="font-medium">{modalContent?.language}</Dialog.Title>
373
- <div className="flex items-center gap-3">
374
- {modalContent && (
375
- <button
376
- onClick={() => handleCopy(modalContent.code, 'modal-copy')}
377
- className="text-gray-300 hover:text-white flex items-center gap-1 text-xs"
378
- aria-label={copiedStates['modal-copy'] ? 'Copied' : 'Copy code'}
379
- >
380
- {copiedStates['modal-copy'] ? <Check size={14} className="text-green-400" /> : <Clipboard size={14} />}
381
- {copiedStates['modal-copy'] ? 'Copied!' : 'Copy'}
382
- </button>
383
- )}
384
- <Dialog.Close asChild>
385
- <button className="text-gray-300 hover:text-white" aria-label="Close">
386
- <X size={18} />
387
- </button>
388
- </Dialog.Close>
389
- </div>
390
- </div>
391
- <div className="flex-grow overflow-auto">
392
- {modalContent && (
393
- <SyntaxHighlighter
394
- style={vscDarkPlus as any}
395
- language={modalContent.language}
396
- PreTag="div"
397
- showLineNumbers
398
- wrapLines={true}
399
- customStyle={{ margin: 0, height: '100%', padding: '1rem' }} // Ensure it fills height and has padding
400
- >
401
- {modalContent.code}
402
- </SyntaxHighlighter>
403
- )}
404
- </div>
405
- </Dialog.Content>
406
- </Dialog.Portal>
407
- </Dialog.Root>
408
- </div>
409
- );
410
- });
411
-
412
- ChatMessage.displayName = 'ChatMessage';
413
-
414
- export default ChatMessage;
@@ -1,169 +0,0 @@
1
- import React, { useEffect } from 'react';
2
- import { format } from 'date-fns';
3
- import { useChat } from './hooks/ChatProvider';
4
- import * as Dialog from '@radix-ui/react-dialog';
5
-
6
- const Sidebar: React.FC<{}> = () => {
7
- const { sessions = [], currentSession, createSession, deleteSession, setCurrentSession, isStreaming } = useChat();
8
- const [showHelp, setShowHelp] = React.useState(false);
9
-
10
- useEffect(() => {
11
- // Check if this is the first visit after component mounts
12
- const hasVisited = localStorage.getItem('eventCatalogAIVisited');
13
- if (!hasVisited || hasVisited === 'false') {
14
- localStorage.setItem('eventCatalogAIVisited', 'true');
15
- setShowHelp(true);
16
- }
17
- }, []);
18
-
19
- useEffect(() => {
20
- if (sessions.length === 0) {
21
- createSession();
22
- }
23
- }, [sessions]);
24
-
25
- return (
26
- <div className="h-[calc(100vh-64px)] bg-gray-50 flex-shrink-0 border-r border-gray-200">
27
- <div className="w-64 bg-gray-50 border-r border-gray-200 flex flex-col h-full">
28
- {/* New Chat Button */}
29
- <div className="p-4 border-b border-gray-200">
30
- <div className="flex items-center justify-between gap-2">
31
- <button
32
- onClick={createSession}
33
- disabled={isStreaming}
34
- className={`flex-1 flex items-center justify-between px-3 py-2 text-sm bg-white hover:bg-gray-100 rounded-lg border border-gray-200 text-gray-700 ${isStreaming ? 'opacity-50 cursor-not-allowed' : ''}`}
35
- >
36
- <span>New chat</span>
37
- <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
38
- <path
39
- fillRule="evenodd"
40
- d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
41
- clipRule="evenodd"
42
- />
43
- </svg>
44
- </button>
45
- </div>
46
- </div>
47
-
48
- {/* Chat History */}
49
- <div className="flex-1 overflow-y-auto">
50
- <div className="p-2 space-y-2">
51
- <div className="text-xs text-gray-500 px-2 py-1">Your chats</div>
52
- <div className="space-y-1">
53
- {sessions.map((session) => (
54
- <div
55
- key={session.id}
56
- className={`group relative flex flex-col ${
57
- currentSession?.id === session.id ? 'bg-gray-100' : 'hover:bg-gray-50'
58
- } rounded-lg`}
59
- >
60
- <button
61
- onClick={(e) => deleteSession(session.id)}
62
- disabled={isStreaming}
63
- className={`absolute right-1 top-1 p-1.5 opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-gray-400 hover:text-gray-600 ${currentSession?.id === session.id ? 'opacity-100' : ''} ${isStreaming ? 'opacity-50 cursor-not-allowed' : ''}`}
64
- title="Delete chat"
65
- >
66
- <svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">
67
- <path
68
- fillRule="evenodd"
69
- d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
70
- clipRule="evenodd"
71
- />
72
- </svg>
73
- </button>
74
- <button
75
- onClick={() => setCurrentSession(session)}
76
- disabled={isStreaming}
77
- className={`flex-1 text-left px-3 py-2 text-sm ${isStreaming ? 'opacity-50 cursor-not-allowed' : ''}`}
78
- >
79
- <div className="font-medium text-gray-700 pr-7">{session.title}</div>
80
- <div className="flex justify-between items-center mt-1 text-xs text-gray-400">
81
- <span>{session.messages.length} messages</span>
82
- <span>{format(session.lastUpdated, 'dd/MM/yyyy, HH:mm:ss')}</span>
83
- </div>
84
- </button>
85
- </div>
86
- ))}
87
- </div>
88
- </div>
89
- </div>
90
-
91
- {/* Beta Message */}
92
- <div className="p-4 text-xs text-gray-500 border-t border-gray-200">
93
- <div>
94
- Have issues or ideas? Let us know on{' '}
95
- <a
96
- href="https://discord.com/channels/918092420338569216/1342473496957288581"
97
- className="text-blue-600 hover:text-blue-800 underline"
98
- target="_blank"
99
- rel="noopener noreferrer"
100
- >
101
- Discord
102
- </a>
103
- <div className="inline-block">
104
- <Dialog.Root open={showHelp} onOpenChange={setShowHelp}>
105
- <Dialog.Trigger asChild>
106
- <button title="Help" className="p-1.5 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg">
107
- <svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
108
- <path
109
- fillRule="evenodd"
110
- d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z"
111
- clipRule="evenodd"
112
- />
113
- </svg>
114
- </button>
115
- </Dialog.Trigger>
116
- <Dialog.Portal>
117
- <Dialog.Overlay className="fixed inset-0 bg-black/50 z-40" />
118
- <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg z-50 p-6 w-[600px] max-h-[85vh] overflow-y-auto">
119
- <Dialog.Title className="text-lg font-semibold text-gray-900 mb-4">
120
- Welcome to the EventCatalog AI Assistant
121
- </Dialog.Title>
122
- <div className="text-sm text-gray-600 space-y-4">
123
- <p>
124
- This is your private AI assistant for EventCatalog. All conversations are stored locally and your data
125
- remains completely private.
126
- </p>
127
- <p>Key features:</p>
128
- <ul className="list-disc pl-4 space-y-2">
129
- <li>Local-first: All conversations are stored on your device only</li>
130
- <li>Privacy-focused: Your data is never shared with external servers</li>
131
- <li>Persistent chat history: Access your previous conversations anytime</li>
132
- <li>Multiple sessions: Organize different topics in separate chats</li>
133
- </ul>
134
-
135
- <div className="mt-6">
136
- <p className="font-medium mb-2">Example prompts you can try:</p>
137
- <ul className="list-disc pl-4 space-y-2 bg-gray-50 p-4 rounded-lg">
138
- <li>"I want to create a new feature, what events do we have related to Payments?"</li>
139
- <li>"What domains do we have and who owns them?"</li>
140
- <li>"What are the events for the X domain?"</li>
141
- <li>"Create me a code snippet for this event"</li>
142
- </ul>
143
- </div>
144
-
145
- <p className="mt-4">For additional support or feature requests, please join our Discord community.</p>
146
- </div>
147
- <Dialog.Close asChild>
148
- <button className="absolute top-4 right-4 text-gray-400 hover:text-gray-600" aria-label="Close">
149
- <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
150
- <path
151
- fillRule="evenodd"
152
- d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
153
- clipRule="evenodd"
154
- />
155
- </svg>
156
- </button>
157
- </Dialog.Close>
158
- </Dialog.Content>
159
- </Dialog.Portal>
160
- </Dialog.Root>
161
- </div>
162
- </div>
163
- </div>
164
- </div>
165
- </div>
166
- );
167
- };
168
-
169
- export default Sidebar;