@eventcatalog/core 2.24.2 → 2.24.3

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.
@@ -37,7 +37,7 @@ var import_axios = __toESM(require("axios"), 1);
37
37
  var import_os = __toESM(require("os"), 1);
38
38
 
39
39
  // package.json
40
- var version = "2.24.2";
40
+ var version = "2.24.3";
41
41
 
42
42
  // src/constants.ts
43
43
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-Q77NQ43U.js";
4
- import "../chunk-AMO6QDAS.js";
3
+ } from "../chunk-OZZMZUB3.js";
4
+ import "../chunk-3MDI2MXJ.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -106,7 +106,7 @@ var import_axios = __toESM(require("axios"), 1);
106
106
  var import_os = __toESM(require("os"), 1);
107
107
 
108
108
  // package.json
109
- var version = "2.24.2";
109
+ var version = "2.24.3";
110
110
 
111
111
  // src/constants.ts
112
112
  var VERSION = version;
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-VOOF34RB.js";
4
- import "../chunk-Q77NQ43U.js";
5
- import "../chunk-AMO6QDAS.js";
3
+ } from "../chunk-7K4ERS3X.js";
4
+ import "../chunk-OZZMZUB3.js";
5
+ import "../chunk-3MDI2MXJ.js";
6
6
  import "../chunk-E7TXTI7G.js";
7
7
  export {
8
8
  log_build_default as default
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "2.24.2";
2
+ var version = "2.24.3";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-Q77NQ43U.js";
3
+ } from "./chunk-OZZMZUB3.js";
4
4
  import {
5
5
  getEventCatalogConfigFile,
6
6
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-AMO6QDAS.js";
3
+ } from "./chunk-3MDI2MXJ.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import axios from "axios";
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "2.24.2";
28
+ var version = "2.24.3";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-AMO6QDAS.js";
3
+ } from "./chunk-3MDI2MXJ.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -161,7 +161,7 @@ var import_axios = __toESM(require("axios"), 1);
161
161
  var import_os = __toESM(require("os"), 1);
162
162
 
163
163
  // package.json
164
- var version = "2.24.2";
164
+ var version = "2.24.3";
165
165
 
166
166
  // src/constants.ts
167
167
  var VERSION = version;
@@ -58,6 +58,7 @@ interface Config {
58
58
  enabled: boolean;
59
59
  model?: string;
60
60
  max_tokens?: number;
61
+ similarityResults?: number;
61
62
  };
62
63
  }
63
64
 
@@ -58,6 +58,7 @@ interface Config {
58
58
  enabled: boolean;
59
59
  model?: string;
60
60
  max_tokens?: number;
61
+ similarityResults?: number;
61
62
  };
62
63
  }
63
64
 
@@ -6,14 +6,14 @@ import {
6
6
  } from "./chunk-OW2FQPYP.js";
7
7
  import {
8
8
  log_build_default
9
- } from "./chunk-VOOF34RB.js";
10
- import "./chunk-Q77NQ43U.js";
9
+ } from "./chunk-7K4ERS3X.js";
10
+ import "./chunk-OZZMZUB3.js";
11
11
  import {
12
12
  catalogToAstro
13
13
  } from "./chunk-CXKIF3EI.js";
14
14
  import {
15
15
  VERSION
16
- } from "./chunk-AMO6QDAS.js";
16
+ } from "./chunk-3MDI2MXJ.js";
17
17
  import {
18
18
  isBackstagePluginEnabled
19
19
  } from "./chunk-XMDPVKIJ.js";
@@ -4,15 +4,7 @@ import { useChat } from './hooks/ChatProvider';
4
4
  import * as Dialog from '@radix-ui/react-dialog';
5
5
 
6
6
  const Sidebar: React.FC<{}> = () => {
7
- const {
8
- sessions = [],
9
- currentSession,
10
- createSession,
11
- deleteSession,
12
- addMessageToSession,
13
- setCurrentSession,
14
- isStreaming,
15
- } = useChat();
7
+ const { sessions = [], currentSession, createSession, deleteSession, setCurrentSession, isStreaming } = useChat();
16
8
  const [showHelp, setShowHelp] = React.useState(false);
17
9
 
18
10
  useEffect(() => {
@@ -1,15 +1,112 @@
1
- import { useEffect, useState, useRef } from 'react';
1
+ import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
2
2
  import { BookOpen, Send } from 'lucide-react';
3
- import { Document } from 'langchain/document';
4
3
  import { CreateWebWorkerMLCEngine, type InitProgressReport } from '@mlc-ai/web-llm';
5
- import { useChat } from './hooks/ChatProvider';
4
+ import { useChat, type Message } from './hooks/ChatProvider';
6
5
  import config from '@config';
6
+ import React from 'react';
7
+
8
+ // Update Message type to include resources
9
+ interface Resource {
10
+ id: string;
11
+ type: string;
12
+ url: string;
13
+ title?: string;
14
+ }
15
+
16
+ // Move formatMessageContent outside component since it doesn't use any component state or props
17
+ const formatMessageContent = (content: string, resources?: Resource[]): string => {
18
+ // First handle any full resource tags by replacing them with just their ID/title
19
+ content = content.replace(/<resource[^>]*?id="([^"]*)"[^>]*?>/g, '$1');
20
+
21
+ // First escape <resource> tags
22
+ let formattedContent = content.replace(/<resource[^>]*>/g, (match) => {
23
+ return match.replace(/</g, '&lt;').replace(/>/g, '&gt;');
24
+ });
25
+
26
+ // If we have resources, convert matching IDs to links
27
+ if (resources?.length) {
28
+ // Create a regex pattern that matches any resource ID/title
29
+ const resourceMatches = resources.map((r) => ({
30
+ pattern: r.title || r.id,
31
+ url: r.url,
32
+ type: r.type,
33
+ }));
34
+
35
+ // Sort by length (longest first) to prevent partial matches
36
+ resourceMatches.sort((a, b) => b.pattern.length - a.pattern.length);
37
+
38
+ // Replace matches with links, but skip if already inside an HTML tag
39
+ for (const { pattern, url, type } of resourceMatches) {
40
+ // Updated regex to match whole words only using word boundaries \b
41
+ const regex = new RegExp(`(?<!<[^>]*)\\b(${pattern})\\b(?![^<]*>)`, 'g');
42
+ formattedContent = formattedContent.replace(
43
+ regex,
44
+ `<a href="${url}" class="text-purple-600 hover:text-purple-800" target="_blank" rel="noopener noreferrer">$1 (${type})</a>`
45
+ );
46
+ }
47
+ }
48
+
49
+ // Handle code blocks
50
+ formattedContent = formattedContent.replace(/```([\s\S]*?)```/g, (match, codeContent) => {
51
+ const escapedCode = codeContent.trim().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
52
+ return `<pre class="bg-gray-800 border border-gray-700 p-4 my-3 rounded-lg overflow-x-auto"><code class="text-sm font-mono text-gray-200">${escapedCode}</code></pre>`;
53
+ });
54
+
55
+ // Handle inline code
56
+ formattedContent = formattedContent.replace(/(?<!`)`([^`]+)`(?!`)/g, (match, code) => {
57
+ const escapedCode = code.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
58
+ return `<code class="bg-gray-500 border border-gray-700 px-2 py-0.5 rounded text-sm font-mono text-gray-200">${escapedCode}</code>`;
59
+ });
60
+
61
+ // Handle bold text with double asterisks
62
+ formattedContent = formattedContent.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
63
+
64
+ // Convert newlines to <br>
65
+ formattedContent = formattedContent.replace(/\n(?!<\/code>)/g, '<br>');
66
+
67
+ return formattedContent;
68
+ };
69
+
70
+ // Create a memoized Message component
71
+ const ChatMessage = React.memo(({ message }: { message: Message }) => (
72
+ <div className={`flex ${message.isUser ? 'justify-end' : 'justify-start'} mb-4`}>
73
+ <div
74
+ className={`max-w-[80%] rounded-lg p-3 ${
75
+ message.isUser ? 'bg-purple-600 text-white rounded-br-none' : 'bg-gray-100 text-gray-800 rounded-bl-none'
76
+ }`}
77
+ >
78
+ <div dangerouslySetInnerHTML={{ __html: formatMessageContent(message.content, message.resources) }} />
79
+ {!message.isUser && message.resources && message.resources.length > 0 && (
80
+ <div className="mt-3 pt-3 border-t border-gray-200">
81
+ <p className="text-xs text-gray-500 mb-1">Referenced Resources:</p>
82
+ <div className="text-[10px]">
83
+ {message.resources.map((resource, idx) => (
84
+ <span key={resource.id}>
85
+ <a
86
+ href={resource.url}
87
+ className="text-purple-600 hover:text-purple-800"
88
+ target="_blank"
89
+ rel="noopener noreferrer"
90
+ >
91
+ {resource.title || resource.id} ({resource.type})
92
+ </a>
93
+ {idx < (message.resources?.length || 0) - 1 ? ', ' : ''}
94
+ </span>
95
+ ))}
96
+ </div>
97
+ </div>
98
+ )}
99
+ </div>
100
+ </div>
101
+ ));
102
+
103
+ ChatMessage.displayName = 'ChatMessage';
7
104
 
8
105
  const ChatWindow = () => {
9
106
  const [loading, setLoading] = useState(true);
10
107
  const [loadingProgress, setLoadingProgress] = useState(0);
11
108
  const [engine, setEngine] = useState<any>(null);
12
- const [messages, setMessages] = useState<Array<{ content: string; isUser: boolean }>>([]);
109
+ const [messages, setMessages] = useState<Array<Message>>([]);
13
110
  const [inputValue, setInputValue] = useState('');
14
111
  const [showWelcome, setShowWelcome] = useState(true);
15
112
  const [vectorWorker, setVectorWorker] = useState<Worker | null>(null);
@@ -18,10 +115,11 @@ const ChatWindow = () => {
18
115
  const outputRef = useRef<HTMLDivElement>(null);
19
116
 
20
117
  // LLM configuration from eventcatalog.config.js file
21
- const model = config.chat?.model || 'Llama-2-7b-chat-hf-q4f16_1-MLC';
22
- const max_tokens = config.chat?.max_tokens || 8192;
118
+ const model = config.chat?.model || 'Hermes-3-Llama-3.2-3B-q4f16_1-MLC';
119
+ const max_tokens = config.chat?.max_tokens || 4096;
120
+ const similarityResults = config.chat?.similarityResults || 50;
23
121
 
24
- const { currentSession, addMessageToSession, updateSession, isStreaming, setIsStreaming } = useChat();
122
+ const { currentSession, storeMessagesToSession, updateSession, isStreaming, setIsStreaming } = useChat();
25
123
 
26
124
  // Load messages when session changes
27
125
  useEffect(() => {
@@ -35,8 +133,15 @@ const ChatWindow = () => {
35
133
  }
36
134
  }, [currentSession]);
37
135
 
136
+ // If the messages change add them to the session
137
+ useEffect(() => {
138
+ if (currentSession) {
139
+ storeMessagesToSession(currentSession.id, messages);
140
+ }
141
+ }, [messages]);
142
+
38
143
  // Helper function to stop the current completion
39
- const handleStop = async () => {
144
+ const handleStop = useCallback(async () => {
40
145
  if (completionRef.current) {
41
146
  try {
42
147
  await engine.interruptGenerate();
@@ -47,25 +152,20 @@ const ChatWindow = () => {
47
152
  console.error('Error stopping completion:', error);
48
153
  }
49
154
  }
50
- };
155
+ }, [engine]);
51
156
 
52
- const handleSubmit = async () => {
157
+ const handleSubmit = useCallback(async () => {
53
158
  if (!inputValue.trim() || !engine) return;
54
159
 
55
160
  // Add to messages
56
- setMessages((prev) => [...prev, { content: inputValue, isUser: true }]);
57
-
58
- // Add message to session store only
59
- if (currentSession) {
60
- addMessageToSession(currentSession.id, inputValue, true);
61
- }
161
+ setMessages((prev) => [...prev, { content: inputValue, isUser: true, timestamp: Date.now() }]);
62
162
 
63
163
  setIsThinking(true);
64
164
  setIsStreaming(true);
65
165
  setInputValue('');
66
166
 
67
167
  // if the first message, update the session title
68
- if (messages.length === 1 && currentSession) {
168
+ if (currentSession) {
69
169
  updateSession({
70
170
  ...currentSession,
71
171
  title: inputValue.length > 25 ? `${inputValue.substring(0, 22)}...` : inputValue,
@@ -73,44 +173,69 @@ const ChatWindow = () => {
73
173
  }
74
174
 
75
175
  // Add input to vector store
76
- vectorWorker?.postMessage({ input: inputValue });
176
+ vectorWorker?.postMessage({ input: inputValue, similarityResults });
77
177
 
78
178
  // @ts-ignore
79
179
  vectorWorker.onmessage = async (event) => {
80
180
  if (event.data.action === 'search-results') {
81
181
  console.log('Results', event?.data?.results);
82
182
 
83
- const qaPrompt = `\n".
183
+ // Extract resources from results and ensure uniqueness by ID
184
+ const resources = Array.from(
185
+ new Map(
186
+ event.data.results.map((result: any) => {
187
+ const metadata = result[0].metadata;
188
+ const resource: Resource = {
189
+ id: metadata.id,
190
+ type: metadata.type,
191
+ url: `/docs/${metadata.type}s/${metadata.id}`,
192
+ title: metadata.title || metadata.id,
193
+ };
194
+ return [metadata.id, resource]; // Use ID as key for Map
195
+ })
196
+ ).values()
197
+ );
84
198
 
85
- You are an expert in the domain of software architecture.
86
-
87
- You are given a question and a list of resources.
199
+ console.log('resources', resources);
88
200
 
89
- Resource types include events, commands, queries, services and domains, users and teams.
201
+ const qaPrompt = `\n".
90
202
 
91
- Never make up information, only use the information provided in the resources.
203
+ You are an expert in event-driven architecture and domain-driven design, specializing in documentation for EventCatalog.
92
204
 
93
- Your job is to answer the question based on the resources.
205
+ You assist developers, architects, and business stakeholders who need information about their event-driven system catalog. You help with questions about:
206
+ - Events (asynchronous messages that notify about something that has happened)
207
+ - Commands (requests to perform an action)
208
+ - Queries (requests for information)
209
+ - Services (bounded contexts or applications that produce/consume events)
210
+ - Domains (business capabilities or functional areas)
94
211
 
95
- This resources are all the resources that are relevant to the question.
212
+ IMPORTANT RULES:
213
+ 1. Resources will be provided to you in <resource> tags. ONLY use these resources to answer questions.
214
+ 2. NEVER include ANY <resource> tags in your responses. This is a strict requirement.
215
+ 3. ALWAYS refer to resources by their name/ID/title attributes only.
216
+ 4. If asked about specific resource types (e.g., "What domains do we have?"), simply list their names without elaboration.
217
+ 5. NEVER invent or make up resources that aren't provided to you.
96
218
 
97
- Use the resource url value to link to the resources.
219
+ RESPONSE FORMAT EXAMPLES:
220
+ ✓ CORRECT: "The SubscriptionService produces the UserSubscribed event."
221
+ ✗ INCORRECT: "<resource id="SubscriptionService">...</resource> produces events."
98
222
 
99
- If any fields are undefined or missing just say you don't know as they are missing in the documentation.
223
+ When responding:
224
+ 1. Use only information from the provided resources
225
+ 2. Explain connections between resources when relevant
226
+ 3. Use appropriate technical terminology
227
+ 4. Use clear formatting with headings and bullet points when helpful
228
+ 5. State clearly when information is missing rather than making assumptions
229
+ 6. Don't provide code examples unless specifically requested
100
230
 
101
- When you give the name of resources back to the user, you can also provide them with a link directly to the resource, use the url field for this.
231
+ Your primary goal is to help users understand their event-driven system through accurate documentation interpretation.
102
232
 
103
233
  ==========
104
- ${event.data.results
105
- .map((result: any) => {
106
- const metadata = result[0].metadata;
107
- return `<resource ${Object.entries(metadata)
234
+ ${(resources as Resource[])
235
+ .map((resource: Resource) => {
236
+ return `<resource ${Object.entries(resource)
108
237
  .filter(([key, value]) => key !== 'markdown' && key !== 'loc')
109
238
  .map(([key, value]) => {
110
- // Special handling for resourceType to construct url
111
- if (key === 'type') {
112
- return `url="/docs/${value}s/${metadata.id}" ${key}="${value}"`;
113
- }
114
239
  return `${key}="${value}"`;
115
240
  })
116
241
  .join(' ')} />`;
@@ -121,6 +246,7 @@ const ChatWindow = () => {
121
246
  ""
122
247
  `;
123
248
 
249
+ console.log('qaPrompt', qaPrompt);
124
250
  try {
125
251
  // Get completion
126
252
  const completion = await engine.chat.completions.create({
@@ -142,9 +268,9 @@ const ChatWindow = () => {
142
268
  stream: true,
143
269
  temperature: 0.1,
144
270
  max_tokens,
145
- top_p: 0.2,
146
- top_k: 5,
147
- frequency_penalty: 0,
271
+ top_p: 0.9,
272
+ top_k: 40,
273
+ frequency_penalty: 0.1,
148
274
  presence_penalty: 0,
149
275
  });
150
276
 
@@ -162,7 +288,14 @@ const ChatWindow = () => {
162
288
 
163
289
  if (isFirstChunk) {
164
290
  setIsThinking(false);
165
- setMessages((prev) => [...prev, { content: responseText, isUser: false }]);
291
+ setMessages((prev) => [
292
+ ...prev,
293
+ {
294
+ content: responseText,
295
+ isUser: false,
296
+ timestamp: Date.now(),
297
+ },
298
+ ]);
166
299
  isFirstChunk = false;
167
300
  } else {
168
301
  setMessages((prev) => {
@@ -174,16 +307,20 @@ const ChatWindow = () => {
174
307
  return newMessages;
175
308
  });
176
309
  }
177
- // Add scroll after each chunk
178
310
  scrollToBottom();
179
311
  }
180
312
  }
181
313
 
182
- // Only add to session store after complete response
183
- if (currentSession) {
184
- console.log('currentSession', currentSession);
185
- addMessageToSession(currentSession.id, responseText, false);
186
- }
314
+ // Add resources after streaming is complete
315
+ setMessages((prev) => {
316
+ const newMessages = [...prev];
317
+ newMessages[newMessages.length - 1] = {
318
+ ...newMessages[newMessages.length - 1],
319
+ content: responseText,
320
+ resources: resources as { id: string; type: string; url: string; title?: string }[],
321
+ };
322
+ return newMessages;
323
+ });
187
324
  } catch (error: any) {
188
325
  if (error.message?.includes('cancelled')) {
189
326
  console.log('Completion was stopped by the user');
@@ -197,18 +334,19 @@ const ChatWindow = () => {
197
334
  completionRef.current = null;
198
335
  } catch (error: any) {
199
336
  console.error('Error:', error);
200
- const errorMessage = { content: 'Sorry, there was an error processing your request.', isUser: false };
337
+ const errorMessage = {
338
+ content: 'Sorry, there was an error processing your request.',
339
+ isUser: false,
340
+ timestamp: Date.now(),
341
+ };
201
342
  setMessages((prev) => [...prev, errorMessage]);
202
- if (currentSession) {
203
- addMessageToSession(currentSession.id, errorMessage.content, false);
204
- }
205
343
  setIsThinking(false);
206
344
  setIsStreaming(false);
207
345
  completionRef.current = null;
208
346
  }
209
347
  }
210
348
  };
211
- };
349
+ }, [inputValue, engine, messages, currentSession, vectorWorker]);
212
350
 
213
351
  const initProgressCallback = (report: InitProgressReport) => {
214
352
  console.log('Loading LLM locally', report);
@@ -244,89 +382,73 @@ const ChatWindow = () => {
244
382
  initEngine();
245
383
  }, []);
246
384
 
247
- // Helper function to format message content
248
- const formatMessageContent = (content: string): string => {
249
- // First handle code blocks
250
- let formattedContent = content.replace(/```([\s\S]*?)```/g, (match, codeContent) => {
251
- const escapedCode = codeContent.trim().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
252
- return `<pre class="bg-gray-800 border border-gray-700 p-4 my-3 rounded-lg overflow-x-auto"><code class="text-sm font-mono text-gray-200">${escapedCode}</code></pre>`;
253
- });
254
-
255
- // Handle inline code
256
- formattedContent = formattedContent.replace(/(?<!`)`([^`]+)`(?!`)/g, (match, code) => {
257
- const escapedCode = code.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
258
- return `<code class="bg-gray-500 border border-gray-700 px-2 py-0.5 rounded text-sm font-mono text-gray-200">${escapedCode}</code>`;
259
- });
260
-
261
- // Handle links
262
- formattedContent = formattedContent.replace(
263
- /<a\s+href="([^"]+)">/g,
264
- '<a href="$1" class="text-purple-600 hover:underline" target="_blank" rel="noopener noreferrer">'
265
- );
266
-
267
- // Convert newlines to <br>
268
- formattedContent = formattedContent.replace(/\n(?!<\/code>)/g, '<br>');
269
-
270
- return formattedContent;
271
- };
272
-
273
385
  // Add new function to handle smooth scrolling
274
- const scrollToBottom = (smooth = true) => {
386
+ const scrollToBottom = useCallback((smooth = true) => {
275
387
  if (outputRef.current) {
276
388
  outputRef.current.scrollTo({
277
389
  top: outputRef.current.scrollHeight,
278
390
  behavior: smooth ? 'smooth' : 'auto',
279
391
  });
280
392
  }
281
- };
393
+ }, []);
282
394
 
283
395
  // Add effect to scroll when messages change
284
396
  useEffect(() => {
285
397
  scrollToBottom();
286
398
  }, [messages]);
287
399
 
288
- return (
289
- <div className="flex-1 flex flex-col overflow-hidden h-[calc(100vh-60px)] w-full">
290
- {/* Chat Messages */}
291
- <div id="output" ref={outputRef} className="flex-1 overflow-y-auto p-4 space-y-4 w-full mx-auto">
292
- {showWelcome || messages.length === 0 ? (
293
- <div id="welcomeMessage" className="flex justify-center items-center h-full">
294
- <div className="text-center space-y-6 max-w-2xl px-4">
295
- <div className="flex justify-center">
296
- <BookOpen size={48} strokeWidth={1.5} className="text-gray-400" />
297
- </div>
298
- <div className="space-y-4">
299
- <h1 className="text-3xl font-semibold text-gray-800">Ask questions about your architecture</h1>
300
- <p className="text-sm text-gray-500">AI Models are local and do not leave your device.</p>
301
- </div>
302
- </div>
400
+ // Memoize static JSX elements
401
+ const welcomeMessage = useMemo(
402
+ () => (
403
+ <div id="welcomeMessage" className="flex justify-center items-center h-full">
404
+ <div className="text-center space-y-6 max-w-2xl px-4">
405
+ <div className="flex justify-center">
406
+ <BookOpen size={48} strokeWidth={1.5} className="text-gray-400" />
303
407
  </div>
304
- ) : (
305
- <div className="space-y-4 max-w-[900px] mx-auto">
306
- {messages.map((message, index) => (
307
- <div key={index} className={`flex ${message.isUser ? 'justify-end' : 'justify-start'} mb-4`}>
308
- <div
309
- className={`max-w-[80%] rounded-lg p-3 ${
310
- message.isUser ? 'bg-purple-600 text-white rounded-br-none' : 'bg-gray-100 text-gray-800 rounded-bl-none'
311
- }`}
312
- dangerouslySetInnerHTML={{ __html: formatMessageContent(message.content) }}
313
- />
314
- </div>
315
- ))}
316
- {isThinking && (
317
- <div className="flex justify-start mb-4">
318
- <div className="flex items-center space-x-2 max-w-[80%] rounded-lg p-3 bg-gray-100 text-gray-800 rounded-bl-none">
319
- <div className="flex space-x-1">
320
- <div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '0ms' }}></div>
321
- <div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '150ms' }}></div>
322
- <div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '300ms' }}></div>
323
- </div>
324
- </div>
408
+ <div className="space-y-4">
409
+ <h1 className="text-3xl font-semibold text-gray-800">Ask questions about your architecture</h1>
410
+ <p className="text-sm text-gray-500">AI Models are local and do not leave your device.</p>
411
+ </div>
412
+ </div>
413
+ </div>
414
+ ),
415
+ []
416
+ );
417
+
418
+ // Memoize the messages list with the new ChatMessage component
419
+ const messagesList = useMemo(
420
+ () => (
421
+ <div className="space-y-4 max-w-[900px] mx-auto">
422
+ {messages.map((message, index) => (
423
+ <ChatMessage key={index} message={message} />
424
+ ))}
425
+ {isThinking && (
426
+ <div className="flex justify-start mb-4">
427
+ <div className="flex items-center space-x-2 max-w-[80%] rounded-lg p-3 bg-gray-100 text-gray-800 rounded-bl-none">
428
+ <div className="flex space-x-1">
429
+ <div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '0ms' }}></div>
430
+ <div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '150ms' }}></div>
431
+ <div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '300ms' }}></div>
325
432
  </div>
326
- )}
433
+ </div>
327
434
  </div>
328
435
  )}
329
436
  </div>
437
+ ),
438
+ [messages, isThinking]
439
+ );
440
+
441
+ // Memoize the input change handler
442
+ const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
443
+ setInputValue(e.target.value);
444
+ }, []);
445
+
446
+ return (
447
+ <div className="flex-1 flex flex-col overflow-hidden h-[calc(100vh-60px)] w-full">
448
+ {/* Chat Messages */}
449
+ <div id="output" ref={outputRef} className="flex-1 overflow-y-auto p-4 space-y-4 w-full mx-auto">
450
+ {showWelcome || messages.length === 0 ? welcomeMessage : messagesList}
451
+ </div>
330
452
 
331
453
  {/* Loading Status */}
332
454
  {loading && (
@@ -350,7 +472,7 @@ const ChatWindow = () => {
350
472
  <input
351
473
  type="text"
352
474
  value={inputValue}
353
- onChange={(e) => setInputValue(e.target.value)}
475
+ onChange={handleInputChange}
354
476
  onKeyPress={(e) => {
355
477
  if (e.key === 'Enter' && !e.shiftKey) {
356
478
  e.preventDefault();
@@ -1,10 +1,16 @@
1
1
  import React, { createContext, useContext, useEffect, useState } from 'react';
2
2
  import type { ReactNode } from 'react';
3
3
 
4
- interface Message {
4
+ export interface Message {
5
5
  content: string;
6
6
  isUser: boolean;
7
7
  timestamp: number;
8
+ resources?: Array<{
9
+ id: string;
10
+ type: string;
11
+ url: string;
12
+ title?: string;
13
+ }>;
8
14
  }
9
15
 
10
16
  export interface ChatSession {
@@ -21,10 +27,10 @@ interface ChatContextType {
21
27
  createSession: () => void;
22
28
  updateSession: (session: ChatSession) => void;
23
29
  deleteSession: (id: string) => void;
24
- addMessageToSession: (sessionId: string, content: string, isUser: boolean) => void;
25
30
  setCurrentSession: (session: ChatSession | null) => void;
26
31
  isStreaming: boolean;
27
32
  setIsStreaming: (isStreaming: boolean) => void;
33
+ storeMessagesToSession: (sessionId: string, messages: Message[]) => void;
28
34
  }
29
35
 
30
36
  const ChatContext = createContext<ChatContextType | undefined>(undefined);
@@ -85,21 +91,15 @@ export function ChatProvider({ children }: { children: ReactNode }) {
85
91
  }
86
92
  };
87
93
 
88
- const addMessageToSession = (sessionId: string, content: string, isUser: boolean) => {
94
+ // Set all messages to the session
95
+ const storeMessagesToSession = (sessionId: string, messages: Message[]) => {
89
96
  setSessions((prev) => {
90
97
  const index = prev.findIndex((s) => s.id === sessionId);
91
98
  if (index === -1) return prev;
92
99
 
93
100
  const updated = [...prev];
94
- const session = { ...updated[index] };
95
- session.messages.push({
96
- content,
97
- isUser,
98
- timestamp: Date.now(),
99
- });
100
- session.lastUpdated = Date.now();
101
- updated[index] = session;
102
-
101
+ updated[index].messages = messages;
102
+ updated[index].lastUpdated = Date.now();
103
103
  localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
104
104
  return updated;
105
105
  });
@@ -121,10 +121,10 @@ export function ChatProvider({ children }: { children: ReactNode }) {
121
121
  createSession,
122
122
  updateSession,
123
123
  deleteSession,
124
- addMessageToSession,
125
124
  setCurrentSession,
126
125
  isStreaming,
127
126
  setIsStreaming,
127
+ storeMessagesToSession,
128
128
  }}
129
129
  >
130
130
  {children}
@@ -27,8 +27,10 @@ self.onmessage = async (event) => {
27
27
  }
28
28
 
29
29
  // Get the results
30
- const results = await vectorStore.similaritySearchWithScore(event.data.input, 10);
31
- postMessage({ results: results, action: 'search-results' });
30
+ const results = await vectorStore.similaritySearchWithScore(event.data.input, event?.data?.similarityResults || 10);
31
+ // Filter out results that don't have a score less than 0.5
32
+ const filteredResults = results.filter((result) => result[1] > 0.1);
33
+ postMessage({ results: filteredResults, action: 'search-results' });
32
34
  } catch (error) {
33
35
  console.log(error);
34
36
  self.postMessage({ error: (error as Error).message });
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/event-catalog/eventcatalog.git"
7
7
  },
8
8
  "type": "module",
9
- "version": "2.24.2",
9
+ "version": "2.24.3",
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
@@ -29,7 +29,7 @@
29
29
  "@asyncapi/avro-schema-parser": "^3.0.24",
30
30
  "@asyncapi/parser": "^3.4.0",
31
31
  "@asyncapi/react-component": "^2.4.3",
32
- "@eventcatalog/generator-ai": "^0.1.2",
32
+ "@eventcatalog/generator-ai": "^0.1.5",
33
33
  "@headlessui/react": "^2.0.3",
34
34
  "@heroicons/react": "^2.1.3",
35
35
  "@huggingface/transformers": "^3.3.3",