@eventcatalog/core 2.34.6 → 2.35.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 (47) 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-KATU7H36.js → chunk-5XXPX5HQ.js} +1 -1
  6. package/dist/{chunk-PTBY5DEP.js → chunk-7LCJC7ER.js} +1 -1
  7. package/dist/{chunk-ZIZUBYNO.js → chunk-HDG7YSFG.js} +13 -3
  8. package/dist/{chunk-OSGQFZTR.js → chunk-TVAQVUFO.js} +1 -1
  9. package/dist/constants.cjs +1 -1
  10. package/dist/constants.js +1 -1
  11. package/dist/eventcatalog.cjs +24 -4
  12. package/dist/eventcatalog.config.d.cts +1 -0
  13. package/dist/eventcatalog.config.d.ts +1 -0
  14. package/dist/eventcatalog.js +21 -5
  15. package/dist/features.cjs +48 -5
  16. package/dist/features.d.cts +2 -1
  17. package/dist/features.d.ts +2 -1
  18. package/dist/features.js +6 -3
  19. package/eventcatalog/astro.config.mjs +8 -2
  20. package/eventcatalog/src/components/Tables/columns/DomainTableColumns.tsx +1 -2
  21. package/eventcatalog/src/content.config.ts +13 -2
  22. package/eventcatalog/src/enterprise/collections/chat-prompts.ts +32 -0
  23. package/eventcatalog/src/enterprise/collections/custom-pages.ts +12 -15
  24. package/eventcatalog/src/enterprise/collections/index.ts +2 -0
  25. package/eventcatalog/src/enterprise/custom-documentation/collection.ts +1 -1
  26. package/eventcatalog/src/enterprise/eventcatalog-chat/EventCatalogVectorStore.ts +50 -0
  27. package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +50 -0
  28. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +231 -0
  29. package/eventcatalog/src/enterprise/eventcatalog-chat/components/InputModal.tsx +233 -0
  30. package/eventcatalog/src/enterprise/eventcatalog-chat/components/MentionInput.tsx +211 -0
  31. package/eventcatalog/src/enterprise/eventcatalog-chat/components/WelcomePromptArea.tsx +88 -0
  32. package/eventcatalog/src/enterprise/{ai-assistant/components/ChatWindow.tsx → eventcatalog-chat/components/windows/ChatWindow.client.tsx} +3 -5
  33. package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +499 -0
  34. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/ai/chat.ts +56 -0
  35. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/ai/resources.ts +42 -0
  36. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +189 -0
  37. package/eventcatalog/src/enterprise/eventcatalog-chat/utils/ai.ts +151 -0
  38. package/eventcatalog/src/enterprise/eventcatalog-chat/utils/chat-prompts.ts +50 -0
  39. package/eventcatalog/src/pages/chat/index.astro +2 -168
  40. package/eventcatalog/src/types/react-syntax-highlighter.d.ts +1 -0
  41. package/package.json +8 -1
  42. package/eventcatalog/src/enterprise/ai-assistant/components/Chat.tsx +0 -16
  43. /package/eventcatalog/src/{shared-collections.ts → content.config-shared-collections.ts} +0 -0
  44. /package/eventcatalog/src/enterprise/{ai-assistant → eventcatalog-chat}/components/ChatSidebar.tsx +0 -0
  45. /package/eventcatalog/src/enterprise/{ai-assistant → eventcatalog-chat}/components/hooks/ChatProvider.tsx +0 -0
  46. /package/eventcatalog/src/enterprise/{ai-assistant → eventcatalog-chat}/components/workers/document-importer.ts +0 -0
  47. /package/eventcatalog/src/enterprise/{ai-assistant → eventcatalog-chat}/components/workers/engine.ts +0 -0
@@ -0,0 +1,211 @@
1
+ import React, { useState, useRef, useEffect, useCallback } from 'react';
2
+
3
+ // Define the structure for suggestions with types
4
+ interface MentionSuggestion {
5
+ id: string; // Unique ID for the key prop
6
+ name: string; // The suggestion text (e.g., 'PaymentProcessed')
7
+ type: string; // The group type (e.g., 'event', 'service')
8
+ }
9
+
10
+ interface MentionInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
11
+ suggestions: MentionSuggestion[]; // Use the new type
12
+ trigger?: string;
13
+ onChange: (value: string) => void;
14
+ value: string;
15
+ }
16
+
17
+ const MentionInput: React.FC<MentionInputProps> = ({ suggestions, trigger = '@', onChange, value, ...inputProps }) => {
18
+ const [showSuggestions, setShowSuggestions] = useState(false);
19
+ const [filteredSuggestions, setFilteredSuggestions] = useState<MentionSuggestion[]>([]); // Use the new type
20
+ const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(0);
21
+ const [currentQuery, setCurrentQuery] = useState('');
22
+ const inputRef = useRef<HTMLInputElement>(null);
23
+ const suggestionsRef = useRef<HTMLUListElement>(null);
24
+
25
+ const updateSuggestions = useCallback(
26
+ (inputValue: string, cursorPosition: number | null) => {
27
+ if (cursorPosition === null) {
28
+ setShowSuggestions(false);
29
+ return;
30
+ }
31
+
32
+ // Find the start of the potential mention query
33
+ let queryStartIndex = -1;
34
+ for (let i = cursorPosition - 1; i >= 0; i--) {
35
+ const char = inputValue[i];
36
+ if (char === trigger) {
37
+ queryStartIndex = i;
38
+ break;
39
+ }
40
+ // Stop if we hit whitespace before the trigger
41
+ if (/\s/.test(char)) {
42
+ break;
43
+ }
44
+ }
45
+
46
+ if (queryStartIndex !== -1) {
47
+ const query = inputValue.substring(queryStartIndex + 1, cursorPosition).toLowerCase();
48
+ setCurrentQuery(query);
49
+
50
+ const filtered = suggestions.filter((s) => s.name.toLowerCase().includes(query));
51
+
52
+ // Update the filtered list and reset index
53
+ setFilteredSuggestions(filtered);
54
+ setActiveSuggestionIndex(0);
55
+ // Keep suggestions open as long as the trigger character is active
56
+ setShowSuggestions(true);
57
+ } else {
58
+ // Only hide suggestions if the trigger character sequence is broken
59
+ setShowSuggestions(false);
60
+ }
61
+ },
62
+ [suggestions, trigger]
63
+ );
64
+
65
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
66
+ const newValue = event.target.value;
67
+ onChange(newValue);
68
+ updateSuggestions(newValue, event.target.selectionStart);
69
+ };
70
+
71
+ // Modify handleSuggestionClick to accept the suggestion object
72
+ const handleSuggestionClick = (suggestion: MentionSuggestion) => {
73
+ if (inputRef.current) {
74
+ const cursorPosition = inputRef.current.selectionStart;
75
+ if (cursorPosition !== null) {
76
+ // Find the start of the @mention query relative to the cursor
77
+ let queryStartIndex = -1;
78
+ for (let i = cursorPosition - 1; i >= 0; i--) {
79
+ if (value[i] === trigger) {
80
+ queryStartIndex = i;
81
+ break;
82
+ }
83
+ if (/\s/.test(value[i])) {
84
+ break;
85
+ }
86
+ }
87
+
88
+ if (queryStartIndex !== -1) {
89
+ // Use suggestion.name for the inserted text
90
+ const newValue =
91
+ value.substring(0, queryStartIndex) +
92
+ suggestion.name +
93
+ ' ' + // Insert selected suggestion name and a space
94
+ value.substring(cursorPosition);
95
+
96
+ onChange(newValue);
97
+ setShowSuggestions(false);
98
+ setTimeout(() => {
99
+ if (inputRef.current) {
100
+ inputRef.current.focus();
101
+ const newCursorPos = queryStartIndex + suggestion.name.length + 1;
102
+ inputRef.current.setSelectionRange(newCursorPos, newCursorPos);
103
+ }
104
+ }, 0);
105
+ } else {
106
+ // Fallback: Use suggestion.name
107
+ onChange(value + suggestion.name + ' ');
108
+ setShowSuggestions(false);
109
+ inputRef.current.focus();
110
+ }
111
+ }
112
+ }
113
+ };
114
+
115
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
116
+ if (showSuggestions && filteredSuggestions.length > 0) {
117
+ if (event.key === 'ArrowDown') {
118
+ event.preventDefault();
119
+ setActiveSuggestionIndex((prevIndex) => (prevIndex + 1) % filteredSuggestions.length);
120
+ } else if (event.key === 'ArrowUp') {
121
+ event.preventDefault();
122
+ setActiveSuggestionIndex((prevIndex) => (prevIndex - 1 + filteredSuggestions.length) % filteredSuggestions.length);
123
+ } else if (event.key === 'Enter' || event.key === 'Tab') {
124
+ event.preventDefault();
125
+ // Pass the selected suggestion object
126
+ handleSuggestionClick(filteredSuggestions[activeSuggestionIndex]);
127
+ } else if (event.key === 'Escape') {
128
+ setShowSuggestions(false);
129
+ }
130
+ }
131
+
132
+ // Propagate other keydown events if needed (e.g., for parent's Enter key handling)
133
+ // Check if the event is Enter and if we are NOT showing suggestions before calling parent's submit
134
+ // This prevents submitting the form when selecting a suggestion with Enter
135
+ if (inputProps.onKeyDown && !(showSuggestions && event.key === 'Enter')) {
136
+ inputProps.onKeyDown(event);
137
+ }
138
+ };
139
+
140
+ // Scroll active suggestion into view
141
+ useEffect(() => {
142
+ if (showSuggestions && suggestionsRef.current) {
143
+ const activeItem = suggestionsRef.current.children[activeSuggestionIndex] as HTMLLIElement;
144
+ if (activeItem) {
145
+ activeItem.scrollIntoView({ block: 'nearest', inline: 'nearest' });
146
+ }
147
+ }
148
+ }, [activeSuggestionIndex, showSuggestions]);
149
+
150
+ // Handle clicks outside the input/suggestions list to close it
151
+ useEffect(() => {
152
+ const handleClickOutside = (event: MouseEvent) => {
153
+ if (
154
+ inputRef.current &&
155
+ !inputRef.current.contains(event.target as Node) &&
156
+ suggestionsRef.current &&
157
+ !suggestionsRef.current.contains(event.target as Node)
158
+ ) {
159
+ setShowSuggestions(false);
160
+ }
161
+ };
162
+
163
+ document.addEventListener('mousedown', handleClickOutside);
164
+ return () => {
165
+ document.removeEventListener('mousedown', handleClickOutside);
166
+ };
167
+ }, []);
168
+
169
+ return (
170
+ <div className="relative w-full">
171
+ <input
172
+ ref={inputRef}
173
+ {...inputProps}
174
+ value={value}
175
+ onChange={handleChange}
176
+ onKeyDown={handleKeyDown} // Use onKeyDown for better event control
177
+ onClick={(e) => updateSuggestions(value, e.currentTarget.selectionStart)} // Update suggestions on click too
178
+ />
179
+ {/* Keep the suggestions box open if showSuggestions is true */}
180
+ {showSuggestions && (
181
+ <ul
182
+ ref={suggestionsRef}
183
+ className="absolute bottom-full mb-2 left-0 right-0 bg-white border border-gray-300 rounded-md shadow-lg max-h-40 overflow-y-auto z-10"
184
+ style={{ minWidth: inputRef.current?.offsetWidth }}
185
+ >
186
+ {/* Conditionally render suggestions or 'No results' message */}
187
+ {filteredSuggestions.length > 0 ? (
188
+ filteredSuggestions.map((suggestion, index) => (
189
+ <li
190
+ key={suggestion.id + '-' + index}
191
+ className={`px-4 py-2 text-sm cursor-pointer flex justify-between items-center ${
192
+ index === activeSuggestionIndex ? 'bg-purple-100 text-purple-800' : 'text-gray-700 hover:bg-gray-100'
193
+ }`}
194
+ onClick={() => handleSuggestionClick(suggestion)}
195
+ onMouseEnter={() => setActiveSuggestionIndex(index)}
196
+ >
197
+ <span>{suggestion.name}</span>
198
+ <span className="text-xs text-gray-500 ml-2">({suggestion.type})</span>
199
+ </li>
200
+ ))
201
+ ) : (
202
+ /* Render this list item when no suggestions match */
203
+ <li className="px-4 py-2 text-sm text-gray-500 italic">No matching items found</li>
204
+ )}
205
+ </ul>
206
+ )}
207
+ </div>
208
+ );
209
+ };
210
+
211
+ export default MentionInput;
@@ -0,0 +1,88 @@
1
+ import React, { useMemo } from 'react';
2
+ import { HelpCircle } from 'lucide-react';
3
+ import * as icons from 'lucide-react'; // Import all icons
4
+ import type { ChatPromptCategoryGroup, ChatPrompt } from '@enterprise/eventcatalog-chat/utils/chat-prompts';
5
+
6
+ // Removed the static iconMap
7
+
8
+ const getCategoryIcon = (iconName?: string): React.ReactNode => {
9
+ // Default icon component
10
+ const DefaultIcon = icons.HelpCircle;
11
+ const IconComponent = iconName ? (icons as any)[iconName] : DefaultIcon;
12
+
13
+ // Render the found icon or the default one
14
+ return IconComponent ? (
15
+ <IconComponent size={16} className="mr-1 md:mr-2" />
16
+ ) : (
17
+ <DefaultIcon size={16} className="mr-1 md:mr-2" /> // Fallback just in case
18
+ );
19
+ };
20
+
21
+ interface WelcomePromptAreaProps {
22
+ chatPrompts: ChatPromptCategoryGroup[];
23
+ activeCategory: string;
24
+ setActiveCategory: (category: string) => void;
25
+ onPromptClick: (prompt: ChatPrompt) => void;
26
+ isProcessing: boolean; // Combined thinking/streaming state
27
+ }
28
+
29
+ const WelcomePromptArea: React.FC<WelcomePromptAreaProps> = ({
30
+ chatPrompts,
31
+ activeCategory,
32
+ setActiveCategory,
33
+ onPromptClick,
34
+ isProcessing,
35
+ }) => {
36
+ // Find the currently active category's questions from chatPrompts
37
+ const activeQuestions: ChatPrompt[] = useMemo(() => {
38
+ // Find the category group by label
39
+ const activeGroup = chatPrompts.find((group) => group.label === activeCategory);
40
+ // Return the items (questions) from that group, or an empty array if not found
41
+ return activeGroup?.items || [];
42
+ }, [activeCategory, chatPrompts]);
43
+
44
+ return (
45
+ <div className="h-full flex flex-col justify-center items-center px-4 pb-4 pt-0">
46
+ {' '}
47
+ {/* Use h-full and flex centering */}
48
+ <div className="max-w-2xl w-full text-left">
49
+ <h2 className="text-2xl font-semibold mb-6 text-gray-800">How can I help you?</h2>
50
+ {/* Category Tabs - Use chatPrompts */}
51
+ <div className="flex flex-wrap justify-left gap-2 mb-6 border-b border-gray-200 pb-4">
52
+ {chatPrompts.map((categoryGroup) => (
53
+ <button
54
+ key={categoryGroup.label} // Use label as key, assuming it's unique
55
+ onClick={() => setActiveCategory(categoryGroup.label)}
56
+ disabled={isProcessing}
57
+ className={`flex items-center px-3 py-2 rounded-md text-sm font-medium transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-purple-400 focus:ring-offset-2 focus:ring-offset-white disabled:opacity-50 ${
58
+ activeCategory === categoryGroup.label
59
+ ? 'bg-purple-600 text-white'
60
+ : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
61
+ }`}
62
+ >
63
+ {/* Use the icon mapping function */}
64
+ {getCategoryIcon(categoryGroup.icon)}
65
+ {categoryGroup.label}
66
+ </button>
67
+ ))}
68
+ </div>
69
+ {/* Questions List - Use activeQuestions derived from chatPrompts */}
70
+ <div className="space-y-2 text-left">
71
+ {activeQuestions.map((item, index) => (
72
+ <button
73
+ key={item.id || index} // Use item.id if available, otherwise index
74
+ onClick={() => onPromptClick(item)} // Use the passed handler
75
+ className="block w-full text-left px-3 py-2 text-gray-700 hover:bg-gray-100 rounded-md text-sm transition-colors duration-150 focus:outline-none focus:ring-1 focus:ring-purple-400 disabled:opacity-50"
76
+ disabled={isProcessing} // Disable while processing
77
+ >
78
+ {/* Display the question title */}
79
+ {item.data.title}
80
+ </button>
81
+ ))}
82
+ </div>
83
+ </div>
84
+ </div>
85
+ );
86
+ };
87
+
88
+ export default WelcomePromptArea;
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
2
2
  import { BookOpen, Send } from 'lucide-react';
3
3
  import { CreateWebWorkerMLCEngine, type InitProgressReport } from '@mlc-ai/web-llm';
4
- import { useChat, type Message } from './hooks/ChatProvider';
4
+ import { useChat, type Message } from '../hooks/ChatProvider';
5
5
  import React from 'react';
6
6
 
7
7
  // Update Message type to include resources
@@ -123,8 +123,6 @@ const ChatWindow = ({
123
123
  const completionRef = useRef<any>(null);
124
124
  const outputRef = useRef<HTMLDivElement>(null);
125
125
 
126
- console.log('model', model);
127
-
128
126
  const { currentSession, storeMessagesToSession, updateSession, isStreaming, setIsStreaming } = useChat();
129
127
 
130
128
  // Load messages when session changes
@@ -368,7 +366,7 @@ const ChatWindow = ({
368
366
  // Cache the LLMs text file
369
367
  const engineCreator = CreateWebWorkerMLCEngine;
370
368
  const newEngine = await engineCreator(
371
- new Worker(new URL('./workers/engine.ts', import.meta.url), { type: 'module' }),
369
+ new Worker(new URL('../workers/engine.ts', import.meta.url), { type: 'module' }),
372
370
  model,
373
371
  { initProgressCallback }
374
372
  );
@@ -379,7 +377,7 @@ const ChatWindow = ({
379
377
  };
380
378
 
381
379
  const importDocuments = async () => {
382
- const worker = new Worker(new URL('./workers/document-importer.ts', import.meta.url), { type: 'module' });
380
+ const worker = new Worker(new URL('../workers/document-importer.ts', import.meta.url), { type: 'module' });
383
381
  worker.postMessage({ init: true });
384
382
  setVectorWorker(worker);
385
383
  };