@eventcatalog/core 3.0.0-beta.2 → 3.0.0-beta.21

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 (103) hide show
  1. package/README.md +10 -0
  2. package/dist/__mocks__/astro-content.cjs +32 -0
  3. package/dist/__mocks__/astro-content.d.cts +13 -0
  4. package/dist/__mocks__/astro-content.d.ts +13 -0
  5. package/dist/__mocks__/astro-content.js +7 -0
  6. package/dist/analytics/analytics.cjs +1 -1
  7. package/dist/analytics/analytics.js +2 -2
  8. package/dist/analytics/log-build.cjs +1 -1
  9. package/dist/analytics/log-build.js +3 -3
  10. package/dist/{chunk-JSONCD7V.js → chunk-2FUEBPD3.js} +1 -1
  11. package/dist/{chunk-3W6JYTHP.js → chunk-HABY2LVH.js} +6 -2
  12. package/dist/{chunk-H4QHE5YZ.js → chunk-KQAMO3R4.js} +1 -1
  13. package/dist/chunk-Q6KRYWPV.js +44 -0
  14. package/dist/{chunk-PQL6O5YA.js → chunk-RRP2B7BL.js} +1 -1
  15. package/dist/constants.cjs +1 -1
  16. package/dist/constants.js +1 -1
  17. package/dist/eventcatalog.cjs +84 -65
  18. package/dist/eventcatalog.config.d.cts +4 -0
  19. package/dist/eventcatalog.config.d.ts +4 -0
  20. package/dist/eventcatalog.js +45 -57
  21. package/dist/generate.cjs +48 -2
  22. package/dist/generate.js +3 -1
  23. package/dist/utils/cli-logger.cjs +82 -0
  24. package/dist/utils/cli-logger.d.cts +10 -0
  25. package/dist/utils/cli-logger.d.ts +10 -0
  26. package/dist/utils/cli-logger.js +7 -0
  27. package/eventcatalog/astro.config.mjs +4 -1
  28. package/eventcatalog/integrations/ecstudio-watcher.mjs +1 -1
  29. package/eventcatalog/integrations/eventcatalog-features.ts +69 -0
  30. package/eventcatalog/public/icons/asyncapi-black.svg +2 -0
  31. package/eventcatalog/public/icons/graphql-black.svg +1 -0
  32. package/eventcatalog/public/icons/openapi-black.svg +1 -0
  33. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +821 -0
  34. package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +24 -0
  35. package/eventcatalog/src/components/Grids/DomainGrid.tsx +1 -3
  36. package/eventcatalog/src/components/Grids/MessageGrid.tsx +8 -8
  37. package/eventcatalog/src/components/Header.astro +25 -5
  38. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +14 -3
  39. package/eventcatalog/src/components/Search/Search.astro +2 -2
  40. package/eventcatalog/src/components/Search/SearchModal.tsx +16 -7
  41. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +9 -2
  42. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/domain.ts +7 -6
  43. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/service.ts +6 -3
  44. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/shared.ts +1 -0
  45. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +23 -8
  46. package/eventcatalog/src/components/SideNav/NestedSideBar/sidebar-builder.ts +57 -11
  47. package/eventcatalog/src/content.config.ts +1 -10
  48. package/eventcatalog/src/enterprise/ai/chat-api.ts +262 -0
  49. package/eventcatalog/src/enterprise/auth/[...auth].ts +3 -0
  50. package/eventcatalog/src/enterprise/auth/login.astro +420 -0
  51. package/eventcatalog/src/enterprise/collections/index.ts +0 -1
  52. package/eventcatalog/src/layouts/Footer.astro +8 -5
  53. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +30 -19
  54. package/eventcatalog/src/pages/_index.astro +8 -9
  55. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +19 -3
  56. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +7 -7
  57. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
  58. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +5 -5
  59. package/eventcatalog/src/pages/docs/teams/[id].mdx.ts +36 -0
  60. package/eventcatalog/src/pages/docs/users/[id].mdx.ts +36 -0
  61. package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +178 -0
  62. package/eventcatalog/src/pages/schemas/explorer/index.astro +5 -155
  63. package/eventcatalog/src/remark-plugins/directives.ts +30 -9
  64. package/eventcatalog/src/utils/collections/schemas.ts +31 -7
  65. package/eventcatalog/src/utils/feature.ts +8 -4
  66. package/eventcatalog/src/utils/resource-files.ts +86 -0
  67. package/package.json +12 -15
  68. package/default-files-for-collections/changelogs.md +0 -5
  69. package/default-files-for-collections/channels.md +0 -8
  70. package/default-files-for-collections/commands.md +0 -8
  71. package/default-files-for-collections/domains.md +0 -8
  72. package/default-files-for-collections/events.md +0 -8
  73. package/default-files-for-collections/flows.md +0 -11
  74. package/default-files-for-collections/queries.md +0 -8
  75. package/default-files-for-collections/services.md +0 -8
  76. package/default-files-for-collections/ubiquitousLanguages.md +0 -7
  77. package/eventcatalog/src/enterprise/collections/chat-prompts.ts +0 -32
  78. package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +0 -60
  79. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +0 -414
  80. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatSidebar.tsx +0 -169
  81. package/eventcatalog/src/enterprise/eventcatalog-chat/components/InputModal.tsx +0 -244
  82. package/eventcatalog/src/enterprise/eventcatalog-chat/components/MentionInput.tsx +0 -211
  83. package/eventcatalog/src/enterprise/eventcatalog-chat/components/WelcomePromptArea.tsx +0 -176
  84. package/eventcatalog/src/enterprise/eventcatalog-chat/components/default-prompts.ts +0 -93
  85. package/eventcatalog/src/enterprise/eventcatalog-chat/components/hooks/ChatProvider.tsx +0 -143
  86. package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +0 -387
  87. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/chat.ts +0 -59
  88. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +0 -104
  89. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +0 -140
  90. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/anthropic.ts +0 -28
  91. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/google.ts +0 -41
  92. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/index.ts +0 -26
  93. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/openai.ts +0 -61
  94. package/eventcatalog/src/enterprise/eventcatalog-chat/utils/chat-prompts.ts +0 -50
  95. package/eventcatalog/src/pages/auth/login.astro +0 -280
  96. package/eventcatalog/src/pages/chat/feature.astro +0 -179
  97. package/eventcatalog/src/pages/chat/index.astro +0 -10
  98. package/eventcatalog/src/pages/nav-index.json.ts +0 -30
  99. /package/eventcatalog/src/{pages → enterprise}/auth/error.astro +0 -0
  100. /package/eventcatalog/src/{middleware-auth.ts → enterprise/auth/middleware/middleware-auth.ts} +0 -0
  101. /package/eventcatalog/src/{middleware.ts → enterprise/auth/middleware/middleware.ts} +0 -0
  102. /package/eventcatalog/src/{pages/unauthorized/index.astro → enterprise/auth/unauthorized.astro} +0 -0
  103. /package/eventcatalog/src/{pages → enterprise}/plans/index.astro +0 -0
@@ -1,244 +0,0 @@
1
- import React, { useEffect, useState, useMemo } from 'react';
2
- import * as Dialog from '@radix-ui/react-dialog';
3
- import { Wand2 } from 'lucide-react';
4
- // TODO: Import ChatPrompt and ChatPromptInput from a central types location
5
- import type { ChatPrompt } from '@enterprise/eventcatalog-chat/utils/chat-prompts';
6
-
7
- // Define the possible resource list types
8
- type ResourceListType = 'resource-list-events' | 'resource-list-commands' | 'resource-list-services' | 'resource-list-queries';
9
-
10
- // Define ChatPromptInput type based on usage
11
- // NOTE: This should ideally match the schema defined for Astro content collections
12
- interface ChatPromptInput {
13
- id: string;
14
- label: string;
15
- type: 'text' | ResourceListType | 'select' | 'code' | 'text-area'; // Use the union type, add 'select', 'code', 'text-area'
16
- options?: string[]; // Add optional options for select type
17
- }
18
-
19
- // Define Resource type based on usage in ChatWindow
20
- interface Resource {
21
- id: string;
22
- type: string; // e.g., 'event', 'command', 'service', 'query'
23
- url: string;
24
- title?: string;
25
- name?: string;
26
- }
27
-
28
- // --- Input Modal Component ---
29
- interface InputModalProps {
30
- isOpen: boolean;
31
- onClose: () => void;
32
- prompt: ChatPrompt | null;
33
- onSubmit: (prompt: ChatPrompt, inputValues: Record<string, string>) => void;
34
- resources: Resource[]; // Add resources prop
35
- }
36
-
37
- // Helper to extract resource type from input type string
38
- const getResourceTypeFromInputType = (inputType: ResourceListType): string => {
39
- return inputType.replace('resource-list-', '');
40
- };
41
-
42
- const InputModal: React.FC<InputModalProps> = ({ isOpen, onClose, prompt, onSubmit, resources }) => {
43
- const [inputValues, setInputValues] = useState<Record<string, string>>({});
44
-
45
- // Memoize resource filtering based on prompt inputs
46
- const filteredResourcesByType = useMemo(() => {
47
- const resourceMap: Record<string, Resource[]> = {};
48
- prompt?.data?.inputs?.forEach((input) => {
49
- if (input.type.startsWith('resource-list-')) {
50
- const resourceType = getResourceTypeFromInputType(input.type as ResourceListType);
51
- const filtered = resources.filter((r) => r.type === resourceType);
52
- resourceMap[input.id] = filtered;
53
- }
54
- });
55
- return resourceMap;
56
- }, [prompt, resources]);
57
-
58
- // Reset input values when the prompt or resources change
59
- useEffect(() => {
60
- if (prompt?.data?.inputs) {
61
- const initialValues: Record<string, string> = {};
62
- prompt.data.inputs.forEach((input) => {
63
- const relevantResources = filteredResourcesByType[input.id];
64
- if (input.type.startsWith('resource-list-') && relevantResources?.length > 0) {
65
- initialValues[input.id] = relevantResources[0].id; // Pre-select the first available resource
66
- } else {
67
- initialValues[input.id] = ''; // Initialize others with empty strings
68
- }
69
- });
70
- setInputValues(initialValues);
71
- } else {
72
- setInputValues({}); // Clear if no inputs
73
- }
74
- }, [prompt, filteredResourcesByType]); // Depend on memoized filtered resources
75
-
76
- if (!prompt || !prompt.data?.inputs) {
77
- return null; // Don't render if no prompt or inputs
78
- }
79
-
80
- const handleInputChange = (id: string, value: string) => {
81
- setInputValues((prev) => ({ ...prev, [id]: value }));
82
- };
83
-
84
- const handleSubmit = (e: React.FormEvent) => {
85
- e.preventDefault();
86
- // Basic validation: Check if all required fields are filled
87
- const allFilled = prompt?.data?.inputs?.every((input) => {
88
- if (input.type.startsWith('resource-list-')) {
89
- return inputValues[input.id] && inputValues[input.id] !== '';
90
- }
91
- return inputValues[input.id]?.trim();
92
- });
93
-
94
- if (allFilled) {
95
- // Pass the selected resource NAME for resource lists, not the ID
96
- const processedValues = { ...inputValues };
97
- prompt.data.inputs?.forEach((input) => {
98
- if (input.type.startsWith('resource-list-')) {
99
- const relevantResources = filteredResourcesByType[input.id] || [];
100
- const selectedResource = relevantResources.find((r) => r.id === processedValues[input.id]);
101
- // Use resource name if available, otherwise fallback to the submitted ID (which might be name if no resource found)
102
- processedValues[input.id] = selectedResource?.name || processedValues[input.id];
103
- }
104
- });
105
- onSubmit(prompt, processedValues);
106
- } else {
107
- alert('Please fill in all required fields.');
108
- }
109
- };
110
-
111
- return (
112
- <Dialog.Root open={isOpen} onOpenChange={onClose}>
113
- <Dialog.Portal>
114
- <Dialog.Overlay className="fixed inset-0 bg-black/50 data-[state=open]:animate-overlayShow z-50" />
115
- <Dialog.Content className="fixed top-1/2 left-1/2 w-[90vw] max-w-lg -translate-x-1/2 -translate-y-1/2 rounded-lg bg-gray-50 p-8 shadow-xl focus:outline-none data-[state=open]:animate-contentShow z-[100]">
116
- <Dialog.Title className="text-lg font-semibold text-gray-800 mb-3">{prompt.data.title}</Dialog.Title>
117
- <Dialog.Description className="mt-1 mb-6 text-sm text-gray-600">
118
- Please provide the following details:
119
- </Dialog.Description>
120
- <form onSubmit={handleSubmit} className="space-y-4">
121
- {prompt.data.inputs.map((input: ChatPromptInput) => {
122
- const isResourceList = input.type.startsWith('resource-list-');
123
- const isSelect = input.type === 'select';
124
- const isCode = input.type === 'code';
125
- const isTextArea = input.type === 'text-area'; // Added for potential future use or consistency
126
- const resourceType = isResourceList ? getResourceTypeFromInputType(input.type as ResourceListType) : '';
127
- const relevantResources = filteredResourcesByType[input.id] || [];
128
-
129
- return (
130
- <div key={input.id}>
131
- <label htmlFor={input.id} className="block text-sm font-medium text-gray-700 mb-1.5">
132
- {input.label}
133
- </label>
134
- {isResourceList ? (
135
- <select
136
- id={input.id}
137
- name={input.id}
138
- value={inputValues[input.id] || ''}
139
- onChange={(e) => handleInputChange(input.id, e.target.value)}
140
- required
141
- className="w-full rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-300 focus:ring-offset-1 sm:text-sm transition duration-150 ease-in-out"
142
- >
143
- <option value="" disabled>
144
- Select a {resourceType}...
145
- </option>
146
- {relevantResources.map((resource) => (
147
- <option key={resource.id} value={resource.id}>
148
- {resource.name || resource.id} {/* Display name or ID as fallback */}
149
- </option>
150
- ))}
151
- {relevantResources.length === 0 && (
152
- <option value="" disabled>
153
- No {resourceType}s found
154
- </option>
155
- )}
156
- </select>
157
- ) : isSelect ? (
158
- <select
159
- id={input.id}
160
- name={input.id}
161
- value={inputValues[input.id] || ''}
162
- onChange={(e) => handleInputChange(input.id, e.target.value)}
163
- required
164
- className="w-full rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-300 focus:ring-offset-1 sm:text-sm transition duration-150 ease-in-out"
165
- >
166
- <option value="" disabled>
167
- Select an option...
168
- </option>
169
- {input.options?.map((option) => (
170
- <option key={option} value={option}>
171
- {option}
172
- </option>
173
- ))}
174
- {(!input.options || input.options.length === 0) && (
175
- <option value="" disabled>
176
- No options available
177
- </option>
178
- )}
179
- </select>
180
- ) : (
181
- <>
182
- {isCode || isTextArea ? (
183
- <textarea
184
- id={input.id}
185
- name={input.id}
186
- value={inputValues[input.id] || ''}
187
- onChange={(e) => handleInputChange(input.id, e.target.value)}
188
- onKeyDown={(e) => {
189
- // Prevent form submission on Enter key press unless Shift is held
190
- if (e.key === 'Enter' && !e.shiftKey) {
191
- e.preventDefault();
192
- // Optionally, you could trigger submission here if needed,
193
- // but the default behavior is just to prevent it.
194
- handleSubmit(e); // Example: trigger submit manually
195
- }
196
- }}
197
- required
198
- rows={isCode ? 6 : 3} // More rows for code input
199
- className={`w-full rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-300 focus:ring-offset-1 sm:text-sm transition duration-150 ease-in-out ${isCode ? 'font-mono text-sm' : ''}`}
200
- placeholder={isCode ? 'Paste your code here...' : ''}
201
- />
202
- ) : (
203
- <input
204
- type="text"
205
- id={input.id}
206
- name={input.id}
207
- value={inputValues[input.id] || ''}
208
- onChange={(e) => handleInputChange(input.id, e.target.value)}
209
- required
210
- className="w-full rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-300 focus:ring-offset-1 sm:text-sm transition duration-150 ease-in-out"
211
- />
212
- )}
213
- </>
214
- )}
215
- </div>
216
- );
217
- })}
218
- <div className="mt-6 flex justify-end space-x-2">
219
- <Dialog.Close asChild>
220
- <button
221
- type="button"
222
- className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
223
- onClick={onClose} // Ensure onClose is called when Cancel is clicked
224
- >
225
- Cancel
226
- </button>
227
- </Dialog.Close>
228
- <button
229
- type="submit"
230
- className="inline-flex items-center justify-center gap-1.5 rounded-md border border-transparent bg-purple-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
231
- >
232
- <Wand2 size={16} />
233
- Submit
234
- </button>
235
- </div>
236
- </form>
237
- </Dialog.Content>
238
- </Dialog.Portal>
239
- </Dialog.Root>
240
- );
241
- };
242
- // --- End Input Modal Component ---
243
-
244
- export default InputModal;
@@ -1,211 +0,0 @@
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;
@@ -1,176 +0,0 @@
1
- import React, { useState, useMemo } from 'react';
2
- import * as icons from 'lucide-react'; // Import all icons
3
- import type { ChatPromptCategoryGroup, ChatPrompt } from '@enterprise/eventcatalog-chat/utils/chat-prompts';
4
- import { defaultPrompts } from './default-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
- // Helper function to group default prompts by category label
30
- const groupDefaultPrompts = (prompts: ChatPrompt[]): ChatPromptCategoryGroup[] => {
31
- const groups: { [key: string]: { label: string; icon?: string; items: ChatPrompt[] } } = {};
32
-
33
- prompts.forEach((prompt) => {
34
- const categoryLabel = prompt.data.category.label;
35
- const categoryIcon = prompt.data.category.icon; // Get icon from prompt data
36
-
37
- if (!groups[categoryLabel]) {
38
- groups[categoryLabel] = { label: categoryLabel, icon: categoryIcon, items: [] };
39
- }
40
- groups[categoryLabel].items.push(prompt);
41
- });
42
-
43
- // Convert the groups object into an array matching ChatPromptCategoryGroup structure
44
- return Object.values(groups);
45
- };
46
-
47
- const WelcomePromptArea: React.FC<WelcomePromptAreaProps> = ({
48
- chatPrompts,
49
- activeCategory: activeCustomCategory, // Rename to avoid conflict
50
- setActiveCategory: setActiveCustomCategory, // Rename to avoid conflict
51
- onPromptClick,
52
- isProcessing,
53
- }) => {
54
- // Group default prompts and derive initial state only once
55
- const defaultPromptGroups = useMemo(() => groupDefaultPrompts(defaultPrompts), []);
56
- const [activeDefaultCategory, setActiveDefaultCategory] = useState<string>(() => defaultPromptGroups[0]?.label || '');
57
-
58
- // Find the currently active category's questions from chatPrompts
59
- const activeCustomQuestions: ChatPrompt[] = useMemo(() => {
60
- if (!chatPrompts || chatPrompts.length === 0) return [];
61
- const activeGroup = chatPrompts.find((group) => group.label === activeCustomCategory);
62
- return activeGroup?.items || (chatPrompts[0]?.items ?? []);
63
- }, [activeCustomCategory, chatPrompts]);
64
-
65
- // Find the currently active category's default questions
66
- const activeDefaultQuestions: ChatPrompt[] = useMemo(() => {
67
- if (!defaultPromptGroups || defaultPromptGroups.length === 0) return [];
68
- const activeGroup = defaultPromptGroups.find((group) => group.label === activeDefaultCategory);
69
- return activeGroup?.items || [];
70
- }, [activeDefaultCategory, defaultPromptGroups]);
71
-
72
- return (
73
- <div className="h-full flex flex-col justify-center items-center px-4 pb-4 pt-0">
74
- <div className="max-w-2xl w-full text-left">
75
- <h2 className="text-2xl font-semibold mb-6 text-gray-800">How can I help you?</h2>
76
-
77
- {/* Conditional Rendering: Custom Prompts OR Default Prompts */}
78
- {chatPrompts && chatPrompts.length > 0 ? (
79
- /* --- CUSTOM PROMPTS UI --- */
80
- <>
81
- {/* Category Tabs - Use chatPrompts */}
82
- <div className="flex flex-wrap justify-left gap-2 mb-6 border-b border-gray-200 pb-4">
83
- {chatPrompts.map((categoryGroup) => (
84
- <button
85
- key={categoryGroup.label}
86
- onClick={() => setActiveCustomCategory(categoryGroup.label)}
87
- disabled={isProcessing}
88
- 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 ${
89
- activeCustomCategory === categoryGroup.label
90
- ? 'bg-purple-600 text-white'
91
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
92
- }`}
93
- >
94
- {getCategoryIcon(categoryGroup.icon)}
95
- {categoryGroup.label}
96
- </button>
97
- ))}
98
- </div>
99
- {/* Questions List - Use activeCustomQuestions */}
100
- <div className="space-y-2 text-left">
101
- {activeCustomQuestions.map((item) => (
102
- <button
103
- key={item.id}
104
- onClick={() => onPromptClick(item)}
105
- 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"
106
- disabled={isProcessing}
107
- >
108
- {item.data.title}
109
- </button>
110
- ))}
111
- </div>
112
- </>
113
- ) : (
114
- /* --- DEFAULT PROMPTS UI (Grouped) --- */
115
- <>
116
- {/* Default Category Tabs - Use defaultPromptGroups */}
117
- <div className="flex flex-wrap justify-left gap-2 mb-6 border-b border-gray-200 pb-4">
118
- {defaultPromptGroups.map((categoryGroup) => (
119
- <button
120
- key={categoryGroup.label} // Use label as key
121
- onClick={() => setActiveDefaultCategory(categoryGroup.label)}
122
- disabled={isProcessing}
123
- 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 ${
124
- activeDefaultCategory === categoryGroup.label
125
- ? 'bg-purple-600 text-white'
126
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
127
- }`}
128
- >
129
- {/* Use the icon mapping function */}
130
- {getCategoryIcon(categoryGroup.icon)}
131
- {categoryGroup.label}
132
- </button>
133
- ))}
134
- </div>
135
- {/* Default Questions List - Use activeDefaultQuestions */}
136
- <div className="space-y-2 text-left">
137
- {activeDefaultQuestions.map((item) => (
138
- <button
139
- key={item.id}
140
- onClick={() => onPromptClick(item)}
141
- 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"
142
- disabled={isProcessing}
143
- >
144
- {item.data.title}
145
- </button>
146
- ))}
147
- </div>
148
-
149
- {/* Enhanced Bring Your Own Prompts section */}
150
- <div className="bg-purple-50 border border-purple-200 rounded-lg p-4 mt-6 text-gray-700 text-sm">
151
- <p className="flex items-center">
152
- <icons.Wand2 size={18} className="text-purple-600 mr-2 flex-shrink-0" /> {/* Added Icon */}
153
- <span>
154
- Want to tailor these suggestions?{' '}
155
- <a
156
- href="https://www.eventcatalog.dev/docs/development/guides/eventcatlaog-chat/what-is-eventcatalog-chat"
157
- target="_blank"
158
- rel="noopener noreferrer"
159
- className="text-purple-700 hover:underline font-semibold"
160
- >
161
- {' '}
162
- {/* Added target and rel */}
163
- Bring your own prompts!
164
- </a>{' '}
165
- Easily configure this list with relevant questions for your teams and organization.
166
- </span>
167
- </p>
168
- </div>
169
- </>
170
- )}
171
- </div>
172
- </div>
173
- );
174
- };
175
-
176
- export default WelcomePromptArea;