@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.
- package/README.md +10 -0
- package/dist/__mocks__/astro-content.cjs +32 -0
- package/dist/__mocks__/astro-content.d.cts +13 -0
- package/dist/__mocks__/astro-content.d.ts +13 -0
- package/dist/__mocks__/astro-content.js +7 -0
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-JSONCD7V.js → chunk-2FUEBPD3.js} +1 -1
- package/dist/{chunk-3W6JYTHP.js → chunk-HABY2LVH.js} +6 -2
- package/dist/{chunk-H4QHE5YZ.js → chunk-KQAMO3R4.js} +1 -1
- package/dist/chunk-Q6KRYWPV.js +44 -0
- package/dist/{chunk-PQL6O5YA.js → chunk-RRP2B7BL.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +84 -65
- package/dist/eventcatalog.config.d.cts +4 -0
- package/dist/eventcatalog.config.d.ts +4 -0
- package/dist/eventcatalog.js +45 -57
- package/dist/generate.cjs +48 -2
- package/dist/generate.js +3 -1
- package/dist/utils/cli-logger.cjs +82 -0
- package/dist/utils/cli-logger.d.cts +10 -0
- package/dist/utils/cli-logger.d.ts +10 -0
- package/dist/utils/cli-logger.js +7 -0
- package/eventcatalog/astro.config.mjs +4 -1
- package/eventcatalog/integrations/ecstudio-watcher.mjs +1 -1
- package/eventcatalog/integrations/eventcatalog-features.ts +69 -0
- package/eventcatalog/public/icons/asyncapi-black.svg +2 -0
- package/eventcatalog/public/icons/graphql-black.svg +1 -0
- package/eventcatalog/public/icons/openapi-black.svg +1 -0
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +821 -0
- package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +24 -0
- package/eventcatalog/src/components/Grids/DomainGrid.tsx +1 -3
- package/eventcatalog/src/components/Grids/MessageGrid.tsx +8 -8
- package/eventcatalog/src/components/Header.astro +25 -5
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +14 -3
- package/eventcatalog/src/components/Search/Search.astro +2 -2
- package/eventcatalog/src/components/Search/SearchModal.tsx +16 -7
- package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +9 -2
- package/eventcatalog/src/components/SideNav/NestedSideBar/builders/domain.ts +7 -6
- package/eventcatalog/src/components/SideNav/NestedSideBar/builders/service.ts +6 -3
- package/eventcatalog/src/components/SideNav/NestedSideBar/builders/shared.ts +1 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +23 -8
- package/eventcatalog/src/components/SideNav/NestedSideBar/sidebar-builder.ts +57 -11
- package/eventcatalog/src/content.config.ts +1 -10
- package/eventcatalog/src/enterprise/ai/chat-api.ts +262 -0
- package/eventcatalog/src/enterprise/auth/[...auth].ts +3 -0
- package/eventcatalog/src/enterprise/auth/login.astro +420 -0
- package/eventcatalog/src/enterprise/collections/index.ts +0 -1
- package/eventcatalog/src/layouts/Footer.astro +8 -5
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +30 -19
- package/eventcatalog/src/pages/_index.astro +8 -9
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +19 -3
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +7 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +5 -5
- package/eventcatalog/src/pages/docs/teams/[id].mdx.ts +36 -0
- package/eventcatalog/src/pages/docs/users/[id].mdx.ts +36 -0
- package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +178 -0
- package/eventcatalog/src/pages/schemas/explorer/index.astro +5 -155
- package/eventcatalog/src/remark-plugins/directives.ts +30 -9
- package/eventcatalog/src/utils/collections/schemas.ts +31 -7
- package/eventcatalog/src/utils/feature.ts +8 -4
- package/eventcatalog/src/utils/resource-files.ts +86 -0
- package/package.json +12 -15
- package/default-files-for-collections/changelogs.md +0 -5
- package/default-files-for-collections/channels.md +0 -8
- package/default-files-for-collections/commands.md +0 -8
- package/default-files-for-collections/domains.md +0 -8
- package/default-files-for-collections/events.md +0 -8
- package/default-files-for-collections/flows.md +0 -11
- package/default-files-for-collections/queries.md +0 -8
- package/default-files-for-collections/services.md +0 -8
- package/default-files-for-collections/ubiquitousLanguages.md +0 -7
- package/eventcatalog/src/enterprise/collections/chat-prompts.ts +0 -32
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +0 -60
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +0 -414
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatSidebar.tsx +0 -169
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/InputModal.tsx +0 -244
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/MentionInput.tsx +0 -211
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/WelcomePromptArea.tsx +0 -176
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/default-prompts.ts +0 -93
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/hooks/ChatProvider.tsx +0 -143
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +0 -387
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/chat.ts +0 -59
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +0 -104
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +0 -140
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/anthropic.ts +0 -28
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/google.ts +0 -41
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/index.ts +0 -26
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/openai.ts +0 -61
- package/eventcatalog/src/enterprise/eventcatalog-chat/utils/chat-prompts.ts +0 -50
- package/eventcatalog/src/pages/auth/login.astro +0 -280
- package/eventcatalog/src/pages/chat/feature.astro +0 -179
- package/eventcatalog/src/pages/chat/index.astro +0 -10
- package/eventcatalog/src/pages/nav-index.json.ts +0 -30
- /package/eventcatalog/src/{pages → enterprise}/auth/error.astro +0 -0
- /package/eventcatalog/src/{middleware-auth.ts → enterprise/auth/middleware/middleware-auth.ts} +0 -0
- /package/eventcatalog/src/{middleware.ts → enterprise/auth/middleware/middleware.ts} +0 -0
- /package/eventcatalog/src/{pages/unauthorized/index.astro → enterprise/auth/unauthorized.astro} +0 -0
- /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;
|