@eventcatalog/core 2.24.1 → 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.
- package/README.md +1 -1
- 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-XBPWZJUU.js → chunk-3MDI2MXJ.js} +1 -1
- package/dist/{chunk-TGASC7WJ.js → chunk-7K4ERS3X.js} +1 -1
- package/dist/{chunk-XDRFRTPG.js → chunk-OZZMZUB3.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.config.d.cts +1 -0
- package/dist/eventcatalog.config.d.ts +1 -0
- package/dist/eventcatalog.js +3 -3
- package/eventcatalog/src/enterprise/ai-assistant/components/ChatSidebar.tsx +1 -9
- package/eventcatalog/src/enterprise/ai-assistant/components/ChatWindow.tsx +240 -118
- package/eventcatalog/src/enterprise/ai-assistant/components/hooks/ChatProvider.tsx +13 -13
- package/eventcatalog/src/enterprise/ai-assistant/components/workers/document-importer.ts +6 -4
- package/eventcatalog/src/pages/chat/index.astro +42 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log_build_default
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
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
|
package/dist/constants.cjs
CHANGED
package/dist/constants.js
CHANGED
package/dist/eventcatalog.cjs
CHANGED
package/dist/eventcatalog.js
CHANGED
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
} from "./chunk-OW2FQPYP.js";
|
|
7
7
|
import {
|
|
8
8
|
log_build_default
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
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-
|
|
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, '<').replace(/>/g, '>');
|
|
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, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
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, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
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<
|
|
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-
|
|
22
|
-
const max_tokens = config.chat?.max_tokens ||
|
|
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,
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
You are given a question and a list of resources.
|
|
199
|
+
console.log('resources', resources);
|
|
88
200
|
|
|
89
|
-
|
|
201
|
+
const qaPrompt = `\n".
|
|
90
202
|
|
|
91
|
-
|
|
203
|
+
You are an expert in event-driven architecture and domain-driven design, specializing in documentation for EventCatalog.
|
|
92
204
|
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
+
RESPONSE FORMAT EXAMPLES:
|
|
220
|
+
✓ CORRECT: "The SubscriptionService produces the UserSubscribed event."
|
|
221
|
+
✗ INCORRECT: "<resource id="SubscriptionService">...</resource> produces events."
|
|
98
222
|
|
|
99
|
-
|
|
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
|
-
|
|
231
|
+
Your primary goal is to help users understand their event-driven system through accurate documentation interpretation.
|
|
102
232
|
|
|
103
233
|
==========
|
|
104
|
-
${
|
|
105
|
-
.map((
|
|
106
|
-
|
|
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.
|
|
146
|
-
top_k:
|
|
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) => [
|
|
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
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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 = {
|
|
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, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
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, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
<div id="
|
|
292
|
-
|
|
293
|
-
<div
|
|
294
|
-
<
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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={
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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}
|
|
@@ -13,8 +13,8 @@ self.onmessage = async (event) => {
|
|
|
13
13
|
try {
|
|
14
14
|
// Initialize the vector store
|
|
15
15
|
if (event?.data?.init && !documents && !embeddings) {
|
|
16
|
-
const documentsImport = await fetch(`/
|
|
17
|
-
const embeddingsImport = await fetch(`/
|
|
16
|
+
const documentsImport = await fetch(`/ai/documents.json`);
|
|
17
|
+
const embeddingsImport = await fetch(`/ai/embeddings.json`);
|
|
18
18
|
|
|
19
19
|
documents = await documentsImport.json();
|
|
20
20
|
embeddings = await embeddingsImport.json();
|
|
@@ -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
|
-
|
|
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 });
|
|
@@ -31,7 +31,24 @@ const generatorConfig = `
|
|
|
31
31
|
{
|
|
32
32
|
isEnabled ? (
|
|
33
33
|
directoryExists ? (
|
|
34
|
-
|
|
34
|
+
<>
|
|
35
|
+
<div id="browserWarning" class="flex items-center justify-center w-full p-4 sm:p-8" style="display: none;">
|
|
36
|
+
<div class="max-w-2xl text-center bg-yellow-100 text-yellow-800 px-8 py-6 rounded-lg shadow-md">
|
|
37
|
+
<h2 class="text-2xl font-bold mb-4">Unsupported Browser</h2>
|
|
38
|
+
<p>
|
|
39
|
+
EventCatalog AI Assistant uses LLM models in the browser which is{' '}
|
|
40
|
+
<a href="https://developer.mozilla.org/en-US/docs/Web/API/GPU" class="text-blue-500 hover:underline">
|
|
41
|
+
only supported in Chrome and Edge browsers
|
|
42
|
+
</a>
|
|
43
|
+
. <br />
|
|
44
|
+
Please switch to a supported browser for the best experience.
|
|
45
|
+
</p>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div id="chatContainer" class="w-full">
|
|
49
|
+
<Chat client:only="react" />
|
|
50
|
+
</div>
|
|
51
|
+
</>
|
|
35
52
|
) : (
|
|
36
53
|
<div class="flex items-center justify-center w-full p-4 sm:p-8">
|
|
37
54
|
<div class="max-w-2xl text-center">
|
|
@@ -105,6 +122,30 @@ const generatorConfig = `
|
|
|
105
122
|
</div>
|
|
106
123
|
</VerticalSideBarLayout>
|
|
107
124
|
|
|
125
|
+
<script>
|
|
126
|
+
function checkBrowser() {
|
|
127
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
128
|
+
const isChrome = /chrome/.test(userAgent) && !/edg/.test(userAgent);
|
|
129
|
+
const isEdge = /edg/.test(userAgent);
|
|
130
|
+
|
|
131
|
+
const warningElement = document.getElementById('browserWarning') as HTMLElement;
|
|
132
|
+
const chatContainer = document.getElementById('chatContainer') as HTMLElement;
|
|
133
|
+
|
|
134
|
+
if (warningElement && chatContainer) {
|
|
135
|
+
if (!isChrome && !isEdge) {
|
|
136
|
+
warningElement.style.display = 'flex';
|
|
137
|
+
chatContainer.style.display = 'none';
|
|
138
|
+
} else {
|
|
139
|
+
warningElement.style.display = 'none';
|
|
140
|
+
chatContainer.style.display = 'block';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Run check when DOM is loaded
|
|
146
|
+
document.addEventListener('DOMContentLoaded', checkBrowser);
|
|
147
|
+
</script>
|
|
148
|
+
|
|
108
149
|
<style>
|
|
109
150
|
.loading-status.ready {
|
|
110
151
|
background-color: rgb(240 253 244); /* light green bg */
|
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.
|
|
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.
|
|
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",
|