@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.
- 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-KATU7H36.js → chunk-5XXPX5HQ.js} +1 -1
- package/dist/{chunk-PTBY5DEP.js → chunk-7LCJC7ER.js} +1 -1
- package/dist/{chunk-ZIZUBYNO.js → chunk-HDG7YSFG.js} +13 -3
- package/dist/{chunk-OSGQFZTR.js → chunk-TVAQVUFO.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +24 -4
- package/dist/eventcatalog.config.d.cts +1 -0
- package/dist/eventcatalog.config.d.ts +1 -0
- package/dist/eventcatalog.js +21 -5
- package/dist/features.cjs +48 -5
- package/dist/features.d.cts +2 -1
- package/dist/features.d.ts +2 -1
- package/dist/features.js +6 -3
- package/eventcatalog/astro.config.mjs +8 -2
- package/eventcatalog/src/components/Tables/columns/DomainTableColumns.tsx +1 -2
- package/eventcatalog/src/content.config.ts +13 -2
- package/eventcatalog/src/enterprise/collections/chat-prompts.ts +32 -0
- package/eventcatalog/src/enterprise/collections/custom-pages.ts +12 -15
- package/eventcatalog/src/enterprise/collections/index.ts +2 -0
- package/eventcatalog/src/enterprise/custom-documentation/collection.ts +1 -1
- package/eventcatalog/src/enterprise/eventcatalog-chat/EventCatalogVectorStore.ts +50 -0
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +50 -0
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +231 -0
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/InputModal.tsx +233 -0
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/MentionInput.tsx +211 -0
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/WelcomePromptArea.tsx +88 -0
- package/eventcatalog/src/enterprise/{ai-assistant/components/ChatWindow.tsx → eventcatalog-chat/components/windows/ChatWindow.client.tsx} +3 -5
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +499 -0
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/ai/chat.ts +56 -0
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/ai/resources.ts +42 -0
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +189 -0
- package/eventcatalog/src/enterprise/eventcatalog-chat/utils/ai.ts +151 -0
- package/eventcatalog/src/enterprise/eventcatalog-chat/utils/chat-prompts.ts +50 -0
- package/eventcatalog/src/pages/chat/index.astro +2 -168
- package/eventcatalog/src/types/react-syntax-highlighter.d.ts +1 -0
- package/package.json +8 -1
- package/eventcatalog/src/enterprise/ai-assistant/components/Chat.tsx +0 -16
- /package/eventcatalog/src/{shared-collections.ts → content.config-shared-collections.ts} +0 -0
- /package/eventcatalog/src/enterprise/{ai-assistant → eventcatalog-chat}/components/ChatSidebar.tsx +0 -0
- /package/eventcatalog/src/enterprise/{ai-assistant → eventcatalog-chat}/components/hooks/ChatProvider.tsx +0 -0
- /package/eventcatalog/src/enterprise/{ai-assistant → eventcatalog-chat}/components/workers/document-importer.ts +0 -0
- /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 '
|
|
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('
|
|
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('
|
|
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
|
};
|