@eventcatalog/core 2.60.0 → 2.61.1

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-CMVDQ42N.js} +1 -1
  6. package/dist/{chunk-6FRDYEMA.js → chunk-MDWG55PH.js} +1 -1
  7. package/dist/{chunk-QMWI6M2I.js → chunk-RPWUOJ3F.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 +12 -112
  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 +12 -15
  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;
@@ -3,32 +3,17 @@ import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
3
3
  import Chat from '@enterprise/eventcatalog-chat/components/Chat';
4
4
  import { getChatPromptsGroupedByCategory } from '@enterprise/eventcatalog-chat/utils/chat-prompts';
5
5
  import config from '@config';
6
- import path from 'node:path';
7
- import fs from 'node:fs';
8
6
  import { Code } from 'astro-expressive-code/components';
9
7
  import { getDomains } from '@utils/collections/domains';
10
8
  import { getEvents } from '@utils/events';
11
9
  import { getCommands } from '@utils/commands';
12
10
  import { getServices } from '@utils/collections/services';
13
11
  import { getQueries } from '@utils/queries';
14
- import { getConfigurationForGivenGenerator } from '@utils/generators';
15
12
 
16
13
  const isEnabled = config.chat?.enabled || false;
17
14
  const chatConfig = config.chat || {};
18
15
  const output = config.output || 'static';
19
16
 
20
- const aiGeneratorConfig = getConfigurationForGivenGenerator('@eventcatalog/generator-ai');
21
- let embeddingModel = aiGeneratorConfig?.embedding?.model || 'all-MiniLM-L6-v2';
22
-
23
- if (aiGeneratorConfig?.embedding?.provider === 'openai') {
24
- embeddingModel = aiGeneratorConfig?.embedding?.model || 'text-embedding-3-large';
25
- }
26
-
27
- const PROJECT_DIR = path.resolve(process.env.PROJECT_DIR || process.cwd());
28
- const GENERATED_AI_DIR = path.resolve(PROJECT_DIR, 'public/ai');
29
-
30
- const directoryExists = fs.existsSync(GENERATED_AI_DIR);
31
-
32
17
  // Get all information for the mention input
33
18
  const [events, commands, queries, services, domains] = await Promise.all([
34
19
  getEvents({ getAllVersions: false }),
@@ -42,86 +27,25 @@ const allItems = [...events, ...commands, ...queries, ...services, ...domains];
42
27
  const chatPrompts = await getChatPromptsGroupedByCategory();
43
28
 
44
29
  const resources = allItems.map((item) => ({ id: item.data.id, type: item.collection, name: item.data.name }));
45
-
46
- const generatorConfig = `
47
- generators: [
48
- [
49
- "@eventcatalog/generator-ai", {
50
- // If you want to chunk files into smaller chunks, set this to true
51
- splitMarkdownFiles: true
52
- }
53
- ],
54
- ],
55
- `;
56
30
  ---
57
31
 
58
32
  <VerticalSideBarLayout title="AI Chat">
59
33
  <div class="flex h-[calc(100vh-60px)] bg-white">
60
34
  {
61
35
  isEnabled ? (
62
- directoryExists ? (
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
- <div id="chatContainer" class="w-full">
78
- <Chat
79
- client:only="react"
80
- chatConfig={{
81
- ...chatConfig,
82
- embeddingModel,
83
- }}
84
- resources={resources}
85
- chatPrompts={chatPrompts}
86
- output={output as 'static' | 'server'}
87
- />
88
- </div>
89
- </>
90
- ) : (
91
- <div class="flex items-center justify-center w-full p-4 sm:p-8">
92
- <div class="max-w-2xl text-center">
93
- <h2 class="text-2xl font-bold text-gray-900 mb-4">EventCatalog AI Assistant Setup Required</h2>
94
- <p class="text-gray-600 mb-6">
95
- To use the AI Assistant, you need to generate the AI data first.
96
- <br />
97
- Please install the plugin and run the generator command to generate the AI data.
98
- </p>
99
-
100
- <div class="text-left space-y-4">
101
- <div class="space-y-2">
102
- <h2 class="text-lg font-semibold text-left">1. Install the generator</h2>
103
- <Code code={`npm i @eventcatalog/generator-ai`} lang="bash" frame="terminal" />
104
- </div>
105
-
106
- <div class="space-y-2">
107
- <h2 class="text-lg font-semibold text-left">2. Configure the generator</h2>
108
- <Code code={generatorConfig} lang="javascript" frame="terminal" title="eventcatalog.config.js" />
109
- </div>
110
-
111
- <div class="space-y-2">
112
- <h2 class="text-lg font-semibold text-left">3. Generate the AI data</h2>
113
- <Code code={`npm run generate`} lang="bash" frame="none" />
114
- </div>
115
- <p class="text-gray-600 text-sm mt-2">
116
- After running the generator, a new folder called <code class="bg-gray-100 p-1 rounded-md">generated-ai</code>{' '}
117
- will be created in your project.
118
- </p>
119
-
120
- <p class="text-gray-600 text-sm mt-2">Once done, refresh this page.</p>
121
- </div>
122
- </div>
36
+ <>
37
+ <div id="chatContainer" class="w-full">
38
+ <Chat
39
+ client:only="react"
40
+ chatConfig={{
41
+ ...chatConfig,
42
+ }}
43
+ resources={resources}
44
+ chatPrompts={chatPrompts}
45
+ output={output as 'static' | 'server'}
46
+ />
123
47
  </div>
124
- )
48
+ </>
125
49
  ) : (
126
50
  <div class="flex items-center justify-center w-full p-4 sm:p-8">
127
51
  <div class="max-w-6xl flex flex-col md:flex-row gap-8 md:gap-12 items-center">
@@ -163,30 +87,6 @@ const generatorConfig = `
163
87
  </div>
164
88
  </VerticalSideBarLayout>
165
89
 
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
90
  <style>
191
91
  .loading-status.ready {
192
92
  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
  }