@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.
Files changed (33) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-WASYSRBV.js → chunk-3CAAQQUB.js} +1 -1
  6. package/dist/{chunk-QMWI6M2I.js → chunk-I2B6B3XZ.js} +1 -1
  7. package/dist/{chunk-6FRDYEMA.js → chunk-OEWPHKD6.js} +1 -1
  8. package/dist/constants.cjs +1 -1
  9. package/dist/constants.js +1 -1
  10. package/dist/eventcatalog.cjs +2 -2
  11. package/dist/eventcatalog.js +4 -4
  12. package/eventcatalog/astro.config.mjs +1 -0
  13. package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +19 -9
  14. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +227 -69
  15. package/eventcatalog/src/enterprise/eventcatalog-chat/components/hooks/ChatProvider.tsx +4 -3
  16. package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +46 -176
  17. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/chat.ts +59 -0
  18. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +0 -37
  19. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +83 -4
  20. package/eventcatalog/src/middleware-auth.ts +13 -2
  21. package/eventcatalog/src/pages/chat/feature.astro +24 -16
  22. package/eventcatalog/src/pages/docs/[type]/[id]/[version].mdx.ts +22 -11
  23. package/eventcatalog/src/pages/docs/llm/llms-full.txt.ts +3 -3
  24. package/eventcatalog/src/utils/feature.ts +2 -1
  25. package/eventcatalog/src/utils/url-builder.ts +9 -0
  26. package/package.json +7 -10
  27. package/eventcatalog/src/enterprise/eventcatalog-chat/EventCatalogVectorStore.ts +0 -66
  28. package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.client.tsx +0 -540
  29. package/eventcatalog/src/enterprise/eventcatalog-chat/components/workers/document-importer.ts +0 -38
  30. package/eventcatalog/src/enterprise/eventcatalog-chat/components/workers/engine.ts +0 -7
  31. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/ai/chat.ts +0 -54
  32. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/ai/resources.ts +0 -42
  33. package/eventcatalog/src/enterprise/eventcatalog-chat/utils/ai.ts +0 -112
@@ -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, setMessages] = useState<Array<Message>>([]);
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, isStreaming, setIsStreaming, createSession } = useChat();
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
- setMessages(currentSession.messages);
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
- mutation.mutate({ question, additionalContext });
101
+ sendMessage({ text: additionalContext || question });
102
+
103
+ // mutation.mutate({ question, additionalContext });
239
104
  },
240
105
  [
241
106
  currentSession,
242
- mutation,
243
107
  updateSession,
244
- setIsStreaming,
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, submitQuestion]
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 key={message.timestamp} message={message} />
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
- {/* Main content area - renders messages or predefined questions */}
435
- <div ref={outputRef} className="flex-1 overflow-y-auto">
436
- {' '}
437
- {/* Outer container handles scroll OR centering */}
438
- {messages.length > 0 ? (
439
- // Render messages when they exist
440
- <div id="output" className="p-4 space-y-4 w-full max-w-[900px] mx-auto h-full pb-10">
441
- {messagesList}
442
- </div>
443
- ) : (
444
- // Render centered predefined questions when chat is empty
445
- <WelcomePromptArea
446
- chatPrompts={chatPrompts}
447
- activeCategory={activeCategory}
448
- setActiveCategory={setActiveCategory}
449
- onPromptClick={handlePredefinedQuestionClick} // Pass the existing handler
450
- isProcessing={isThinking || isStreaming} // Combine thinking/streaming state
451
- />
452
- )}
453
- </div>
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={handleStop}
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 { streamText, type CoreMessage, type LanguageModel, type Message } from 'ai';
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<CoreMessage> | Array<Omit<Message, 'id'>>) {
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
- messages: messages,
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
- const publicRoutes = ['/auth/login', '/auth/signout', '/auth/error', '/api/auth'];
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
- New: Ask your catalog anything
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 events, services, and domains — instantly. Runs entirely in your browser your data never
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-8">
36
+ <div class="flex flex-col sm:flex-row gap-4 mb-2">
38
37
  <a
39
- href="https://demo.eventcatalog.dev/chat"
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
- Try the demo
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
- <p class="text-sm text-gray-500">
60
- Available with EventCatalog Starter or Scale plans
61
- <a href="https://www.eventcatalog.dev/pricing" class="text-purple-600 font-medium block"
62
- >Try free for 14 days, no credit card required</a
63
- >
64
- </p>
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">Instant Search</h3>
126
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">Direct Answers</h3>
121
127
  <p class="text-gray-600">
122
- Search and understand complex service relationships in seconds with natural language queries.
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">All processing happens locally in your browser. Your data never leaves your machine.</p>
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