@eventcatalog/core 2.60.0 → 2.61.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.
- 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-WASYSRBV.js → chunk-3CAAQQUB.js} +1 -1
- package/dist/{chunk-QMWI6M2I.js → chunk-I2B6B3XZ.js} +1 -1
- package/dist/{chunk-6FRDYEMA.js → chunk-OEWPHKD6.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +2 -2
- package/dist/eventcatalog.js +4 -4
- package/eventcatalog/astro.config.mjs +1 -0
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +19 -9
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +227 -69
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/hooks/ChatProvider.tsx +4 -3
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +46 -176
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/chat.ts +59 -0
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +0 -37
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +83 -4
- package/eventcatalog/src/middleware-auth.ts +13 -2
- package/eventcatalog/src/pages/chat/feature.astro +24 -16
- package/eventcatalog/src/pages/docs/[type]/[id]/[version].mdx.ts +22 -11
- package/eventcatalog/src/pages/docs/llm/llms-full.txt.ts +3 -3
- package/eventcatalog/src/utils/feature.ts +2 -1
- package/eventcatalog/src/utils/url-builder.ts +9 -0
- package/package.json +7 -10
- package/eventcatalog/src/enterprise/eventcatalog-chat/EventCatalogVectorStore.ts +0 -66
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.client.tsx +0 -540
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/workers/document-importer.ts +0 -38
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/workers/engine.ts +0 -7
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/ai/chat.ts +0 -54
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/ai/resources.ts +0 -42
- package/eventcatalog/src/enterprise/eventcatalog-chat/utils/ai.ts +0 -112
package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx
CHANGED
|
@@ -5,9 +5,9 @@ import React from 'react';
|
|
|
5
5
|
import MentionInput from '../MentionInput';
|
|
6
6
|
import InputModal from '../InputModal';
|
|
7
7
|
import type { ChatPromptCategoryGroup, ChatPrompt } from '@enterprise/eventcatalog-chat/utils/chat-prompts';
|
|
8
|
-
import { useMutation } from '@tanstack/react-query';
|
|
9
8
|
import WelcomePromptArea from '../WelcomePromptArea';
|
|
10
9
|
import ChatMessage from '../ChatMessage'; // Import the new component
|
|
10
|
+
import { useChat as useAiChat, type UIMessage } from '@ai-sdk/react';
|
|
11
11
|
|
|
12
12
|
// Update Message type to include resources
|
|
13
13
|
interface Resource {
|
|
@@ -37,21 +37,22 @@ const ChatWindow = ({
|
|
|
37
37
|
resources: mentionInputResources = [],
|
|
38
38
|
chatPrompts,
|
|
39
39
|
}: ChatWindowProps) => {
|
|
40
|
-
const [messages
|
|
40
|
+
// const [messages] = useState<Array<Message>>([]);
|
|
41
41
|
const [inputValue, setInputValue] = useState('');
|
|
42
|
-
const [showWelcome, setShowWelcome] = useState(true);
|
|
43
|
-
const [isThinking, setIsThinking] = useState(false);
|
|
44
|
-
const completionRef = useRef<any>(null);
|
|
45
42
|
const outputRef = useRef<HTMLDivElement>(null);
|
|
46
43
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
47
44
|
const [activeCategory, setActiveCategory] = useState<string>(chatPrompts?.[0]?.label || '');
|
|
48
45
|
|
|
46
|
+
const { messages, sendMessage, stop, status, setMessages: setAiMessages } = useAiChat();
|
|
47
|
+
const isStreaming = status === 'streaming' && messages.length > 0;
|
|
48
|
+
const isThinking = status === 'submitted' || (status === 'streaming' && messages.length === 0);
|
|
49
|
+
|
|
49
50
|
// --- New state for input modal ---
|
|
50
51
|
const [promptForInput, setPromptForInput] = useState<ChatPrompt | null>(null);
|
|
51
52
|
const [isInputModalOpen, setIsInputModalOpen] = useState(false);
|
|
52
53
|
// --- End new state ---
|
|
53
54
|
|
|
54
|
-
const { currentSession, storeMessagesToSession, updateSession,
|
|
55
|
+
const { currentSession, storeMessagesToSession, updateSession, createSession } = useChat();
|
|
55
56
|
|
|
56
57
|
// If the messages change add them to the session
|
|
57
58
|
useEffect(() => {
|
|
@@ -60,134 +61,10 @@ const ChatWindow = ({
|
|
|
60
61
|
}
|
|
61
62
|
}, [messages]);
|
|
62
63
|
|
|
63
|
-
const mutation = useMutation({
|
|
64
|
-
mutationFn: async (input: { question: string; additionalContext?: string }) => {
|
|
65
|
-
const history = messages.map((message) => ({
|
|
66
|
-
createdAt: new Date(message.timestamp),
|
|
67
|
-
content: message.content,
|
|
68
|
-
role: message.isUser ? 'user' : 'assistant', // Correct role mapping
|
|
69
|
-
}));
|
|
70
|
-
|
|
71
|
-
const chatPromise = fetch('/api/server/ai/chat', {
|
|
72
|
-
method: 'POST',
|
|
73
|
-
headers: { 'Content-Type': 'application/json' },
|
|
74
|
-
body: JSON.stringify({ question: input.question, messages: history, additionalContext: input.additionalContext }),
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const resourcesPromise = fetch('/api/server/ai/resources', {
|
|
78
|
-
method: 'POST',
|
|
79
|
-
headers: { 'Content-Type': 'application/json' },
|
|
80
|
-
body: JSON.stringify({ question: input.question }),
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const [chatResponse, resourcesResponse] = await Promise.all([chatPromise, resourcesPromise]);
|
|
84
|
-
const { resources } = await resourcesResponse.json();
|
|
85
|
-
|
|
86
|
-
if (!chatResponse.ok) {
|
|
87
|
-
const chatResponseJson = await chatResponse.json();
|
|
88
|
-
if (chatResponseJson?.error) {
|
|
89
|
-
throw new Error(`Chat API request failed with status ${chatResponse.status}: ${chatResponseJson.error}`);
|
|
90
|
-
} else {
|
|
91
|
-
throw new Error(`Chat API request failed with status ${chatResponse.status}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (!chatResponse.body) {
|
|
95
|
-
throw new Error('No response body from chat API');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const reader = chatResponse.body.getReader();
|
|
99
|
-
const decoder = new TextDecoder();
|
|
100
|
-
let responseText = '';
|
|
101
|
-
let isFirstChunk = true;
|
|
102
|
-
|
|
103
|
-
// Start processing the stream
|
|
104
|
-
// eslint-disable-next-line no-constant-condition
|
|
105
|
-
while (true) {
|
|
106
|
-
try {
|
|
107
|
-
const { done, value } = await reader.read();
|
|
108
|
-
if (done) break;
|
|
109
|
-
|
|
110
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
111
|
-
responseText += chunk;
|
|
112
|
-
|
|
113
|
-
if (isFirstChunk) {
|
|
114
|
-
setIsThinking(false);
|
|
115
|
-
setMessages((prev) => [...prev, { content: responseText, isUser: false, timestamp: Date.now() }]);
|
|
116
|
-
isFirstChunk = false;
|
|
117
|
-
} else {
|
|
118
|
-
setMessages((prev) => {
|
|
119
|
-
const newMessages = [...prev];
|
|
120
|
-
const lastMessageIndex = newMessages.length - 1;
|
|
121
|
-
if (lastMessageIndex >= 0 && !newMessages[lastMessageIndex].isUser) {
|
|
122
|
-
newMessages[lastMessageIndex] = {
|
|
123
|
-
...newMessages[lastMessageIndex],
|
|
124
|
-
content: responseText,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
return newMessages;
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
// Defer scroll until after state update seems complete
|
|
131
|
-
requestAnimationFrame(() => scrollToBottom(false)); // Use non-smooth scroll during stream
|
|
132
|
-
} catch (error) {
|
|
133
|
-
console.error('Error reading stream:', error);
|
|
134
|
-
setIsThinking(false);
|
|
135
|
-
setIsStreaming(false);
|
|
136
|
-
// Potentially set an error message state here
|
|
137
|
-
throw error; // Re-throw to allow mutation's onError to catch it
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Final state update including resources
|
|
142
|
-
let finalMessages: Message[] = [];
|
|
143
|
-
setMessages((prev) => {
|
|
144
|
-
const newMessages = [...prev];
|
|
145
|
-
const lastMessageIndex = newMessages.length - 1;
|
|
146
|
-
if (lastMessageIndex >= 0 && !newMessages[lastMessageIndex].isUser) {
|
|
147
|
-
newMessages[lastMessageIndex] = {
|
|
148
|
-
...newMessages[lastMessageIndex],
|
|
149
|
-
content: responseText, // Ensure final content is set
|
|
150
|
-
resources: resources,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
finalMessages = newMessages; // Capture the final state
|
|
154
|
-
return newMessages;
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// Store messages to session AFTER streaming is complete and state is updated
|
|
158
|
-
if (currentSession) {
|
|
159
|
-
storeMessagesToSession(currentSession.id, finalMessages);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Reset flags and scroll smoothly to the end
|
|
163
|
-
setIsThinking(false);
|
|
164
|
-
setIsStreaming(false);
|
|
165
|
-
completionRef.current = null; // Clear ref if needed
|
|
166
|
-
scrollToBottom(); // Smooth scroll after completion
|
|
167
|
-
|
|
168
|
-
return responseText; // Return the complete text
|
|
169
|
-
},
|
|
170
|
-
onError: (error) => {
|
|
171
|
-
console.error('Chat mutation error:', error);
|
|
172
|
-
// Handle error state in UI, e.g., show an error message to the user
|
|
173
|
-
setIsThinking(false);
|
|
174
|
-
setIsStreaming(false);
|
|
175
|
-
// Maybe add an error message to the chat
|
|
176
|
-
setMessages((prev) => [
|
|
177
|
-
...prev,
|
|
178
|
-
{ content: `Sorry, an error occurred: ${error.message}`, isUser: false, timestamp: Date.now() },
|
|
179
|
-
]);
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
|
|
183
64
|
// Load messages when session changes
|
|
184
65
|
useEffect(() => {
|
|
185
66
|
if (currentSession) {
|
|
186
|
-
|
|
187
|
-
setShowWelcome(false);
|
|
188
|
-
} else {
|
|
189
|
-
setMessages([]);
|
|
190
|
-
setShowWelcome(true);
|
|
67
|
+
setAiMessages(currentSession.messages as unknown as UIMessage[]);
|
|
191
68
|
}
|
|
192
69
|
}, [currentSession]);
|
|
193
70
|
|
|
@@ -198,18 +75,6 @@ const ChatWindow = ({
|
|
|
198
75
|
}
|
|
199
76
|
}, [isStreaming]);
|
|
200
77
|
|
|
201
|
-
// Helper function to stop the current completion
|
|
202
|
-
const handleStop = useCallback(async () => {
|
|
203
|
-
if (completionRef.current) {
|
|
204
|
-
try {
|
|
205
|
-
setIsStreaming(false);
|
|
206
|
-
setIsThinking(false);
|
|
207
|
-
} catch (error) {
|
|
208
|
-
console.error('Error stopping completion:', error);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}, []);
|
|
212
|
-
|
|
213
78
|
// New function to handle submitting a question (user input or predefined)
|
|
214
79
|
const submitQuestion = useCallback(
|
|
215
80
|
async (question: string, additionalContext?: string) => {
|
|
@@ -218,10 +83,8 @@ const ChatWindow = ({
|
|
|
218
83
|
const userMessage: Message = { content: question, isUser: true, timestamp: Date.now(), additionalContext };
|
|
219
84
|
const isFirstMessage = messages.length === 0;
|
|
220
85
|
|
|
221
|
-
setMessages((prev) => [...prev, userMessage]);
|
|
222
|
-
setShowWelcome(false);
|
|
223
|
-
setIsThinking(true);
|
|
224
|
-
setIsStreaming(true);
|
|
86
|
+
// setMessages((prev) => [...prev, userMessage]);
|
|
87
|
+
// setShowWelcome(false);
|
|
225
88
|
setInputValue('');
|
|
226
89
|
|
|
227
90
|
// Scroll to bottom immediately after adding user message and setting thinking state
|
|
@@ -235,15 +98,14 @@ const ChatWindow = ({
|
|
|
235
98
|
});
|
|
236
99
|
}
|
|
237
100
|
|
|
238
|
-
|
|
101
|
+
sendMessage({ text: additionalContext || question });
|
|
102
|
+
|
|
103
|
+
// mutation.mutate({ question, additionalContext });
|
|
239
104
|
},
|
|
240
105
|
[
|
|
241
106
|
currentSession,
|
|
242
|
-
mutation,
|
|
243
107
|
updateSession,
|
|
244
|
-
|
|
245
|
-
setIsThinking,
|
|
246
|
-
setMessages,
|
|
108
|
+
// setMessages,
|
|
247
109
|
setInputValue,
|
|
248
110
|
messages.length,
|
|
249
111
|
isStreaming,
|
|
@@ -303,9 +165,10 @@ const ChatWindow = ({
|
|
|
303
165
|
const handleSubmit = useCallback(
|
|
304
166
|
(e?: React.FormEvent) => {
|
|
305
167
|
e?.preventDefault();
|
|
168
|
+
// sendMessage({ text: inputValue });
|
|
306
169
|
submitQuestion(inputValue); // Use standard input value, no additional context here
|
|
307
170
|
},
|
|
308
|
-
[inputValue,
|
|
171
|
+
[inputValue, sendMessage]
|
|
309
172
|
);
|
|
310
173
|
|
|
311
174
|
// Add new function to handle smooth scrolling
|
|
@@ -327,9 +190,16 @@ const ChatWindow = ({
|
|
|
327
190
|
// Memoize the messages list with the new ChatMessage component
|
|
328
191
|
const messagesList = useMemo(
|
|
329
192
|
() => (
|
|
330
|
-
<div className="space-y-4 max-w-[900px] mx-auto">
|
|
193
|
+
<div className="space-y-4 max-w-[900px] mx-auto pb-6">
|
|
331
194
|
{messages.map((message, index) => (
|
|
332
|
-
<ChatMessage
|
|
195
|
+
<ChatMessage
|
|
196
|
+
key={message.id}
|
|
197
|
+
message={{
|
|
198
|
+
isUser: message.role === 'user',
|
|
199
|
+
content: message.parts.map((part) => (part.type === 'text' ? part.text : '')).join(''),
|
|
200
|
+
timestamp: Date.now(),
|
|
201
|
+
}}
|
|
202
|
+
/>
|
|
333
203
|
))}
|
|
334
204
|
{isThinking && (
|
|
335
205
|
<div className="flex justify-start mb-4">
|
|
@@ -431,26 +301,26 @@ const ChatWindow = ({
|
|
|
431
301
|
|
|
432
302
|
return (
|
|
433
303
|
<div className="flex-1 flex flex-col overflow-hidden h-[calc(100vh-60px)] w-full bg-white">
|
|
434
|
-
{
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
304
|
+
{messages.length === 0 && (
|
|
305
|
+
<WelcomePromptArea
|
|
306
|
+
chatPrompts={chatPrompts}
|
|
307
|
+
activeCategory={activeCategory}
|
|
308
|
+
setActiveCategory={setActiveCategory}
|
|
309
|
+
onPromptClick={handlePredefinedQuestionClick}
|
|
310
|
+
isProcessing={isStreaming || isThinking}
|
|
311
|
+
/>
|
|
312
|
+
)}
|
|
313
|
+
|
|
314
|
+
{messages.length > 0 && (
|
|
315
|
+
<div ref={outputRef} className="flex-1 overflow-y-auto">
|
|
316
|
+
{messages.length > 0 && (
|
|
317
|
+
<div id="output" className="p-4 space-y-4 w-full max-w-[900px] mx-auto h-full pb-16">
|
|
318
|
+
{messagesList}
|
|
319
|
+
</div>
|
|
320
|
+
)}
|
|
321
|
+
;
|
|
322
|
+
</div>
|
|
323
|
+
)}
|
|
454
324
|
|
|
455
325
|
{/* Input Area (remains at the bottom) */}
|
|
456
326
|
<div className="border-t border-gray-200 p-4 bg-white">
|
|
@@ -474,7 +344,7 @@ const ChatWindow = ({
|
|
|
474
344
|
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
|
475
345
|
{isStreaming ? (
|
|
476
346
|
<button
|
|
477
|
-
onClick={
|
|
347
|
+
onClick={() => stop()}
|
|
478
348
|
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 text-sm font-medium"
|
|
479
349
|
>
|
|
480
350
|
Stop
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { APIContext } from 'astro';
|
|
2
|
+
import { type UIMessage } from 'ai';
|
|
3
|
+
import config from '@config';
|
|
4
|
+
import { getProvider } from '@enterprise/eventcatalog-chat/providers';
|
|
5
|
+
|
|
6
|
+
// Map the Keys to use in the SDK, astro exports as import.meta.env
|
|
7
|
+
process.env.OPENAI_API_KEY = import.meta.env.OPENAI_API_KEY || '';
|
|
8
|
+
|
|
9
|
+
const baseSystemPrompt = `You are an expert in event-driven architecture and domain-driven design, specializing in documentation for EventCatalog.
|
|
10
|
+
|
|
11
|
+
You assist developers, architects, and business stakeholders who need information about their event-driven system catalog. You help with questions about:
|
|
12
|
+
- Events (asynchronous messages that notify about something that has happened)
|
|
13
|
+
- Commands (requests to perform an action)
|
|
14
|
+
- Queries (requests for information)
|
|
15
|
+
- Services (bounded contexts or applications that produce/consume events)
|
|
16
|
+
- Domains (business capabilities or functional areas)
|
|
17
|
+
|
|
18
|
+
Your primary goal is to help users understand their event-driven system through accurate documentation interpretation.
|
|
19
|
+
|
|
20
|
+
When responding:
|
|
21
|
+
2. Explain connections between resources when relevant.
|
|
22
|
+
3. Use appropriate technical terminology.
|
|
23
|
+
4. Use clear formatting with headings and bullet points when helpful.
|
|
24
|
+
5. State clearly when information is missing rather than making assumptions.
|
|
25
|
+
6. Don't provide code examples unless specifically requested.
|
|
26
|
+
|
|
27
|
+
Your primary goal is to help users understand their event-driven system through accurate documentation interpretation.
|
|
28
|
+
|
|
29
|
+
If you have additional context, use it to answer the question.`;
|
|
30
|
+
|
|
31
|
+
interface Message {
|
|
32
|
+
content: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const GET = async ({ request }: APIContext<{ question: string; messages: Message[]; additionalContext?: string }>) => {
|
|
36
|
+
// return 404
|
|
37
|
+
return new Response(JSON.stringify({ error: 'Not found' }), {
|
|
38
|
+
status: 404,
|
|
39
|
+
headers: { 'Content-Type': 'application/json' },
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const POST = async ({ request }: APIContext<{ question: string; messages: Message[]; additionalContext?: string }>) => {
|
|
44
|
+
const { messages }: { messages: UIMessage[] } = await request.json();
|
|
45
|
+
|
|
46
|
+
const provider = getProvider(config?.chat?.provider || 'openai', {
|
|
47
|
+
modelId: config?.chat?.model || 'gpt-3.5-turbo-0125',
|
|
48
|
+
temperature: config?.chat?.temperature,
|
|
49
|
+
topP: config?.chat?.topP,
|
|
50
|
+
topK: config?.chat?.topK,
|
|
51
|
+
frequencyPenalty: config?.chat?.frequencyPenalty,
|
|
52
|
+
presencePenalty: config?.chat?.presencePenalty,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const result = await provider.streamText(baseSystemPrompt, messages);
|
|
56
|
+
return result.toUIMessageStreamResponse();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const prerender = false;
|
|
@@ -61,19 +61,6 @@ const generatorConfig = `
|
|
|
61
61
|
isEnabled ? (
|
|
62
62
|
directoryExists ? (
|
|
63
63
|
<>
|
|
64
|
-
<div id="browserWarning" class="flex items-center justify-center w-full p-4 sm:p-8" style="display: none;">
|
|
65
|
-
<div class="max-w-2xl text-center bg-yellow-100 text-yellow-800 px-8 py-6 rounded-lg shadow-md">
|
|
66
|
-
<h2 class="text-2xl font-bold mb-4">Unsupported Browser</h2>
|
|
67
|
-
<p>
|
|
68
|
-
EventCatalog AI Assistant uses LLM models in the browser which is{' '}
|
|
69
|
-
<a href="https://developer.mozilla.org/en-US/docs/Web/API/GPU" class="text-blue-500 hover:underline">
|
|
70
|
-
only supported in Chrome and Edge browsers
|
|
71
|
-
</a>
|
|
72
|
-
. <br />
|
|
73
|
-
Please switch to a supported browser for the best experience.
|
|
74
|
-
</p>
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
64
|
<div id="chatContainer" class="w-full">
|
|
78
65
|
<Chat
|
|
79
66
|
client:only="react"
|
|
@@ -163,30 +150,6 @@ const generatorConfig = `
|
|
|
163
150
|
</div>
|
|
164
151
|
</VerticalSideBarLayout>
|
|
165
152
|
|
|
166
|
-
<script>
|
|
167
|
-
function checkBrowser() {
|
|
168
|
-
const userAgent = navigator.userAgent.toLowerCase();
|
|
169
|
-
const isChrome = /chrome/.test(userAgent) && !/edg/.test(userAgent);
|
|
170
|
-
const isEdge = /edg/.test(userAgent);
|
|
171
|
-
|
|
172
|
-
const warningElement = document.getElementById('browserWarning') as HTMLElement;
|
|
173
|
-
const chatContainer = document.getElementById('chatContainer') as HTMLElement;
|
|
174
|
-
|
|
175
|
-
if (warningElement && chatContainer) {
|
|
176
|
-
if (!isChrome && !isEdge) {
|
|
177
|
-
warningElement.style.display = 'flex';
|
|
178
|
-
chatContainer.style.display = 'none';
|
|
179
|
-
} else {
|
|
180
|
-
warningElement.style.display = 'none';
|
|
181
|
-
chatContainer.style.display = 'block';
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Run check when DOM is loaded
|
|
187
|
-
document.addEventListener('DOMContentLoaded', checkBrowser);
|
|
188
|
-
</script>
|
|
189
|
-
|
|
190
153
|
<style>
|
|
191
154
|
.loading-status.ready {
|
|
192
155
|
background-color: rgb(240 253 244); /* light green bg */
|
|
@@ -1,4 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getBaseURL } from '@utils/url-builder';
|
|
2
|
+
import { streamText, tool, type LanguageModel, type UIMessage, stepCountIs, convertToModelMessages } from 'ai';
|
|
3
|
+
import { z } from 'astro/zod';
|
|
4
|
+
|
|
5
|
+
export const getEventCatalogResources = async () => {
|
|
6
|
+
const baseUrl = process.env.EVENTCATALOG_URL || getBaseURL();
|
|
7
|
+
const url = new URL('/docs/llm/llms.txt', baseUrl);
|
|
8
|
+
const response = await fetch(url.toString());
|
|
9
|
+
const text = await response.text();
|
|
10
|
+
console.log('URL', url.toString());
|
|
11
|
+
// console.log('TEXT', text);
|
|
12
|
+
return text;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const getResourceInformation = async (type: string, id: string, version: string) => {
|
|
16
|
+
const baseUrl = process.env.EVENTCATALOG_URL || getBaseURL();
|
|
17
|
+
const url = new URL(`/docs/${type}/${id}/${version}.mdx`, baseUrl);
|
|
18
|
+
const response = await fetch(url.toString());
|
|
19
|
+
const text = await response.text();
|
|
20
|
+
return text;
|
|
21
|
+
};
|
|
2
22
|
|
|
3
23
|
// base class for AI providers
|
|
4
24
|
export interface AIProviderOptions {
|
|
@@ -12,7 +32,7 @@ export interface AIProviderOptions {
|
|
|
12
32
|
}
|
|
13
33
|
|
|
14
34
|
export class AIProvider {
|
|
15
|
-
private model?: LanguageModel;
|
|
35
|
+
private model?: string | LanguageModel;
|
|
16
36
|
private modelId: string;
|
|
17
37
|
private temperature: number;
|
|
18
38
|
private topP: number | undefined;
|
|
@@ -40,19 +60,78 @@ export class AIProvider {
|
|
|
40
60
|
return { isValidModel, listOfModels: this.models };
|
|
41
61
|
}
|
|
42
62
|
|
|
43
|
-
async streamText(messages: Array<
|
|
63
|
+
async streamText(system: string, messages: Array<UIMessage> | Array<Omit<UIMessage, 'id'>>) {
|
|
44
64
|
if (!this.model) {
|
|
45
65
|
throw new Error('Model not set');
|
|
46
66
|
}
|
|
47
67
|
|
|
48
68
|
return await streamText({
|
|
49
69
|
model: this.model,
|
|
50
|
-
|
|
70
|
+
system: system ?? '',
|
|
71
|
+
messages: convertToModelMessages(messages),
|
|
72
|
+
stopWhen: stepCountIs(5),
|
|
51
73
|
temperature: this.temperature,
|
|
52
74
|
...(this.topP && { topP: this.topP }),
|
|
53
75
|
...(this.topK && { topK: this.topK }),
|
|
54
76
|
...(this.frequencyPenalty && { frequencyPenalty: this.frequencyPenalty }),
|
|
55
77
|
...(this.presencePenalty && { presencePenalty: this.presencePenalty }),
|
|
78
|
+
tools: {
|
|
79
|
+
findResources: tool({
|
|
80
|
+
description: [
|
|
81
|
+
'Find resources that are available in EventCatalog',
|
|
82
|
+
'',
|
|
83
|
+
'Use this tool when you need to:',
|
|
84
|
+
'- Get a list of resources in EventCatalog including services, domains, events, commands, queries and flows, ubiquitous language terms and entities',
|
|
85
|
+
"- Find a resource's id and version to aid other tool requests",
|
|
86
|
+
'- Just return the list of matched resources in EventCatalog with a short description of each resource',
|
|
87
|
+
"- Don't return bullet points, just return the list of resources in a readable format",
|
|
88
|
+
'- Include the resource name, description, and a link to the resource',
|
|
89
|
+
'- When you return a link, remove the .mdx from the end of the url',
|
|
90
|
+
'- Return a list of messages the resource produces and consumes, these are marked as sends and receives',
|
|
91
|
+
'- If the resource has a domain, include it in the response',
|
|
92
|
+
'- Ask the user if they would like more information about a specific resource',
|
|
93
|
+
'- When you return a message, in brackets let me know if its a query, command or event',
|
|
94
|
+
`- The host URL is ${process.env.EVENTCATALOG_URL}`,
|
|
95
|
+
].join('\n'),
|
|
96
|
+
inputSchema: z.object({}),
|
|
97
|
+
execute: async () => {
|
|
98
|
+
console.log('TOOL HAS BEEN CALLED');
|
|
99
|
+
const text = await getEventCatalogResources();
|
|
100
|
+
return text;
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
findResource: tool({
|
|
104
|
+
description: [
|
|
105
|
+
'Get more information about a service, domain, event, command, query or flow in EventCatalog using its id and version',
|
|
106
|
+
'Use this tool when you need to:',
|
|
107
|
+
'- Get more details/information about a service, domain, event, command, query, flow or entity in EventCatalog',
|
|
108
|
+
'- Use the id to find more information about a service, domain, event, command, query, flow or entity',
|
|
109
|
+
'- Return everything you know about this resource',
|
|
110
|
+
'- If the resource has a specification return links to the specification file',
|
|
111
|
+
'- When you find owners the url would look something like /docs/users/{id} if its a user or /docs/teams/{id} if its a team',
|
|
112
|
+
'- When you return the producers and consumers (the messages the service produces and consumes) make sure they include the url to the documentation, the url would look something like /docs/{type}/{id}, e.g /docs/events/MyEvent/1.0.0 or /docs/commands/MyCommand/1.0.0',
|
|
113
|
+
'- When you return owners make sure they include the url to the documentation',
|
|
114
|
+
'- If the resource has a domain, include it in the response',
|
|
115
|
+
'- Ask the user if they would like more information about a specific resource',
|
|
116
|
+
'- When you return a message, in brackets let me know if its a query, command or event',
|
|
117
|
+
`- If you are returning a flow (state machine) try and return the result in mermaid to the user, visualizing how the business logic flows`,
|
|
118
|
+
`- If you return any URLS make sure to include the host URL ${process.env.EVENTCATALOG_URL}`,
|
|
119
|
+
'- You will ALWAYS return the visualizer URL in an IFRAME in the given response - example <iframe src="http://localhost:3000/visualiser/{type}/{id}/{version}?embed=true"></iframe>',
|
|
120
|
+
].join('\n'),
|
|
121
|
+
inputSchema: z.object({
|
|
122
|
+
id: z.string().describe('The id of the resource to find'),
|
|
123
|
+
version: z.string().describe('The version of the resource to find'),
|
|
124
|
+
type: z
|
|
125
|
+
.enum(['services', 'domains', 'events', 'commands', 'queries', 'flows', 'entities'])
|
|
126
|
+
.describe('The type of resource to find'),
|
|
127
|
+
}),
|
|
128
|
+
execute: async ({ id, version, type }) => {
|
|
129
|
+
console.log('TOOL HAS BEEN CALLED TO GET DETAILED INFORMATION ABOUT A RESOURCE');
|
|
130
|
+
const text = await getResourceInformation(type, id, version);
|
|
131
|
+
return text;
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
},
|
|
56
135
|
});
|
|
57
136
|
}
|
|
58
137
|
}
|
|
@@ -3,6 +3,9 @@ import type { MiddlewareHandler } from 'astro';
|
|
|
3
3
|
import { getSession } from 'auth-astro/server';
|
|
4
4
|
import { isAuthEnabled } from '@utils/feature';
|
|
5
5
|
import jwt from 'jsonwebtoken';
|
|
6
|
+
import { isLLMSTxtEnabled } from '@utils/feature';
|
|
7
|
+
|
|
8
|
+
const isLLMSTextEnabled = isLLMSTxtEnabled();
|
|
6
9
|
|
|
7
10
|
// Define the types in this file
|
|
8
11
|
export interface NormalizedUser {
|
|
@@ -89,13 +92,21 @@ export const authMiddleware: MiddlewareHandler = async (context, next) => {
|
|
|
89
92
|
|
|
90
93
|
// Skip system/browser requests
|
|
91
94
|
const systemRoutes = ['/.well-known/', '/favicon.ico', '/robots.txt', '/sitemap.xml', '/_astro/', '/__astro'];
|
|
92
|
-
|
|
95
|
+
let publicRoutes = ['/auth/login', '/auth/signout', '/auth/error', '/api/auth'];
|
|
96
|
+
|
|
97
|
+
const llmsRoutes = ['/docs/llm/llms.txt', '/docs/llm/llms-services.txt', '/docs/llm/llms-full.txt'];
|
|
98
|
+
|
|
99
|
+
if (isLLMSTextEnabled) {
|
|
100
|
+
publicRoutes = [...publicRoutes, ...llmsRoutes];
|
|
101
|
+
}
|
|
93
102
|
|
|
94
103
|
if (
|
|
95
104
|
pathname.startsWith('/_') ||
|
|
96
105
|
systemRoutes.some((route) => pathname.startsWith(route)) ||
|
|
97
106
|
pathname.startsWith('/.well-known/') ||
|
|
98
|
-
publicRoutes.some((route) => pathname.startsWith(route))
|
|
107
|
+
publicRoutes.some((route) => pathname.startsWith(route)) ||
|
|
108
|
+
(pathname.endsWith('.mdx') && isLLMSTextEnabled) ||
|
|
109
|
+
(pathname.endsWith('.md') && isLLMSTextEnabled)
|
|
99
110
|
) {
|
|
100
111
|
return next();
|
|
101
112
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
|
|
3
|
-
import { isEventCatalogChatEnabled as hasEventCatlaogChatLicense } from '@utils/feature';
|
|
3
|
+
import { isEventCatalogChatEnabled as hasEventCatlaogChatLicense, isSSR } from '@utils/feature';
|
|
4
4
|
import { BotMessageSquare } from 'lucide-react';
|
|
5
5
|
const hasChatLicense = hasEventCatlaogChatLicense();
|
|
6
6
|
|
|
@@ -27,20 +27,19 @@ if (hasChatLicense) {
|
|
|
27
27
|
<div>
|
|
28
28
|
<div class="inline-flex items-center px-4 py-2 rounded-full bg-purple-100 text-purple-700 font-medium text-sm mb-6">
|
|
29
29
|
<BotMessageSquare className="w-4 h-4 mr-2" />
|
|
30
|
-
|
|
30
|
+
EventCatalog: Agent
|
|
31
31
|
</div>
|
|
32
32
|
<h1 class="text-4xl font-bold text-gray-900 tracking-tight mb-4">Ask. Understand. Ship faster.</h1>
|
|
33
33
|
<p class="text-xl text-gray-600 mb-8">
|
|
34
|
-
Get answers about your
|
|
35
|
-
leaves your machine.
|
|
34
|
+
Get answers about your architecture — instantly. Connect to your own AI models and data.
|
|
36
35
|
</p>
|
|
37
|
-
<div class="flex flex-col sm:flex-row gap-4 mb-
|
|
36
|
+
<div class="flex flex-col sm:flex-row gap-4 mb-2">
|
|
38
37
|
<a
|
|
39
|
-
href="https://
|
|
38
|
+
href="https://www.eventcatalog.dev/docs/development/guides/eventcatlaog-chat/what-is-eventcatalog-chat"
|
|
40
39
|
target="_blank"
|
|
41
40
|
class="inline-flex items-center juNot ready for AI chat? You castify-center px-6 py-3 border border-transparent text-base font-medium rounded-lg text-white bg-purple-600 hover:bg-purple-700 transition-colors duration-150"
|
|
42
41
|
>
|
|
43
|
-
|
|
42
|
+
Get Started
|
|
44
43
|
<svg class="ml-2 w-4 h-4" viewBox="0 0 20 20" fill="currentColor">
|
|
45
44
|
<path
|
|
46
45
|
fill-rule="evenodd"
|
|
@@ -56,12 +55,19 @@ if (hasChatLicense) {
|
|
|
56
55
|
Try for free
|
|
57
56
|
</a>
|
|
58
57
|
</div>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
|
|
59
|
+
<p class="text-sm text-gray-500 italic mb-6">Available with EventCatalog Starter or Scale plans</p>
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
!isSSR() && (
|
|
63
|
+
<p class="text-sm text-gray-500 my-4 bg-yellow-50 p-4 rounded-lg">
|
|
64
|
+
<span class="font-bold">This feature is only available on server side.</span> You can switch to server side by
|
|
65
|
+
setting the <code class="font-mono bg-gray-100 p-0.5 rounded">output</code> property to{' '}
|
|
66
|
+
<code class="font-mono bg-gray-100 p-0.5 rounded">server</code> in your{' '}
|
|
67
|
+
<code class="font-mono bg-gray-100 p-0.5 rounded">eventcatalog.config.js</code> file.
|
|
68
|
+
</p>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
65
71
|
</div>
|
|
66
72
|
|
|
67
73
|
<div class="relative">
|
|
@@ -117,9 +123,9 @@ if (hasChatLicense) {
|
|
|
117
123
|
></path>
|
|
118
124
|
</svg>
|
|
119
125
|
</div>
|
|
120
|
-
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
|
126
|
+
<h3 class="text-lg font-semibold text-gray-900 mb-2">Direct Answers</h3>
|
|
121
127
|
<p class="text-gray-600">
|
|
122
|
-
|
|
128
|
+
Ask questions about your catalog and get direct answers, using your own models and API keys.
|
|
123
129
|
</p>
|
|
124
130
|
</div>
|
|
125
131
|
|
|
@@ -144,7 +150,9 @@ if (hasChatLicense) {
|
|
|
144
150
|
</svg>
|
|
145
151
|
</div>
|
|
146
152
|
<h3 class="text-lg font-semibold text-gray-900 mb-2">Privacy First</h3>
|
|
147
|
-
<p class="text-gray-600">
|
|
153
|
+
<p class="text-gray-600">
|
|
154
|
+
Runs on your own infrastructure, and your own models. Provide your own API keys to get started.
|
|
155
|
+
</p>
|
|
148
156
|
</div>
|
|
149
157
|
</div>
|
|
150
158
|
|