@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,93 +0,0 @@
1
- import type { ChatPrompt } from '@enterprise/eventcatalog-chat/utils/chat-prompts';
2
-
3
- export const defaultPrompts: ChatPrompt[] = [
4
- {
5
- id: 'default-events',
6
- collection: 'chatPrompts', // Required field
7
- body: 'List all events.', // The actual prompt text
8
- data: {
9
- title: 'What events do we have in our architecture?', // Text displayed on the button
10
- type: 'text', // Default type
11
- category: { id: 'general', label: 'General', icon: 'HelpCircle' },
12
- },
13
- },
14
- {
15
- id: 'default-events-by-domain-feature',
16
- collection: 'chatPrompts',
17
- body: 'What events are relevant to this feature {{feature-description}}?',
18
- data: {
19
- title: 'Im building a new feature, what events are relevant to this feature?',
20
- type: 'text',
21
- inputs: [
22
- {
23
- id: 'feature-description',
24
- label: 'Feature Description',
25
- type: 'text-area',
26
- },
27
- ],
28
- category: { id: 'general', label: 'General', icon: 'HelpCircle' },
29
- },
30
- },
31
- {
32
- id: 'default-events-by-domain-service',
33
- collection: 'chatPrompts',
34
- body: 'Review the given service {{service-name}}, and let me know how it works? You are an expert in the domain and architecture of the service.',
35
- data: {
36
- title: 'Review the given service, and let me know how it works?',
37
- type: 'text',
38
- inputs: [
39
- {
40
- id: 'service-name',
41
- label: 'Service Name',
42
- type: 'resource-list-services',
43
- },
44
- ],
45
- category: { id: 'general', label: 'General', icon: 'HelpCircle' },
46
- },
47
- },
48
- {
49
- id: 'default-services',
50
- collection: 'chatPrompts',
51
- body: 'List all services.',
52
- data: {
53
- title: 'What services do we have in our architecture?',
54
- type: 'text',
55
- category: { id: 'general', label: 'General', icon: 'HelpCircle' },
56
- },
57
- },
58
- // Example of another category
59
- {
60
- id: 'default-schema-json',
61
- collection: 'chatPrompts',
62
- body: 'Generate a JSON schema for {{event-name}}.',
63
- data: {
64
- title: 'Generate a JSON schema for the given event',
65
- type: 'text',
66
- inputs: [
67
- {
68
- id: 'event-name',
69
- label: 'Event Name',
70
- type: 'resource-list-events',
71
- },
72
- ],
73
- category: { id: 'code', label: 'Code Generation', icon: 'Code' },
74
- },
75
- },
76
- {
77
- id: 'default-schema-avro',
78
- collection: 'chatPrompts',
79
- body: 'Generate a Avro schema for {{event-name}}.',
80
- data: {
81
- title: 'Generate a Avro schema for the given event',
82
- type: 'text',
83
- inputs: [
84
- {
85
- id: 'event-name',
86
- label: 'Event Name',
87
- type: 'resource-list-events',
88
- },
89
- ],
90
- category: { id: 'code', label: 'Code Generation', icon: 'Code' },
91
- },
92
- },
93
- ];
@@ -1,143 +0,0 @@
1
- import type { UIMessage } from '@ai-sdk/react';
2
- import React, { createContext, useContext, useEffect, useState } from 'react';
3
- import type { ReactNode } from 'react';
4
-
5
- export interface Message {
6
- content: string;
7
- additionalContext?: string;
8
- isUser: boolean;
9
- timestamp: number;
10
- resources?: Array<{
11
- id: string;
12
- type: string;
13
- url: string;
14
- title?: string;
15
- }>;
16
- }
17
-
18
- export interface ChatSession {
19
- id: string;
20
- title: string;
21
- messages: Message[];
22
- lastUpdated: number;
23
- createdAt: number;
24
- }
25
-
26
- interface ChatContextType {
27
- sessions: ChatSession[];
28
- currentSession: ChatSession | null;
29
- createSession: () => void;
30
- updateSession: (session: ChatSession) => void;
31
- deleteSession: (id: string) => void;
32
- setCurrentSession: (session: ChatSession | null) => void;
33
- isStreaming: boolean;
34
- setIsStreaming: (isStreaming: boolean) => void;
35
- storeMessagesToSession: (sessionId: string, messages: UIMessage[]) => void;
36
- }
37
-
38
- const ChatContext = createContext<ChatContextType | undefined>(undefined);
39
-
40
- const STORAGE_KEY = 'chat_sessions';
41
-
42
- export function ChatProvider({ children }: { children: ReactNode }) {
43
- const [sessions, setSessions] = useState<ChatSession[]>(() => {
44
- if (typeof window !== 'undefined') {
45
- const stored = localStorage.getItem(STORAGE_KEY);
46
- return stored ? JSON.parse(stored) : [];
47
- }
48
- return [];
49
- });
50
- const [currentSession, setCurrentSession] = useState<ChatSession | null>(null);
51
- const [isStreaming, setIsStreaming] = useState(false);
52
-
53
- const createSession = () => {
54
- const newSession: ChatSession = {
55
- id: crypto.randomUUID(),
56
- title: `Chat ${sessions.length + 1}`,
57
- messages: [],
58
- lastUpdated: Date.now(),
59
- createdAt: Date.now(),
60
- };
61
-
62
- setSessions((prev) => {
63
- const updated = [newSession, ...prev];
64
- localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
65
- return updated;
66
- });
67
- setCurrentSession(newSession);
68
- };
69
-
70
- const updateSession = (session: ChatSession) => {
71
- setSessions((prev) => {
72
- const index = prev.findIndex((s) => s.id === session.id);
73
- if (index === -1) return prev;
74
-
75
- const updated = [...prev];
76
- updated[index] = {
77
- ...session,
78
- lastUpdated: Date.now(),
79
- };
80
- localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
81
- return updated;
82
- });
83
- };
84
-
85
- const deleteSession = (id: string) => {
86
- setSessions((prev) => {
87
- const updated = prev.filter((session) => session.id !== id);
88
- localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
89
- return updated;
90
- });
91
- if (currentSession?.id === id) {
92
- setCurrentSession(null);
93
- }
94
- };
95
-
96
- // Set all messages to the session
97
- const storeMessagesToSession = (sessionId: string, messages: UIMessage[]) => {
98
- setSessions((prev) => {
99
- const index = prev.findIndex((s) => s.id === sessionId);
100
- if (index === -1) return prev;
101
-
102
- const updated = [...prev];
103
- updated[index].messages = messages as unknown as Message[];
104
- updated[index].lastUpdated = Date.now();
105
- localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
106
- return updated;
107
- });
108
- };
109
-
110
- useEffect(() => {
111
- if (sessions.length > 0) {
112
- setCurrentSession(sessions[0]);
113
- } else {
114
- createSession();
115
- }
116
- }, []);
117
-
118
- return (
119
- <ChatContext.Provider
120
- value={{
121
- sessions,
122
- currentSession,
123
- createSession,
124
- updateSession,
125
- deleteSession,
126
- setCurrentSession,
127
- isStreaming,
128
- setIsStreaming,
129
- storeMessagesToSession,
130
- }}
131
- >
132
- {children}
133
- </ChatContext.Provider>
134
- );
135
- }
136
-
137
- export function useChat() {
138
- const context = useContext(ChatContext);
139
- if (context === undefined) {
140
- throw new Error('useChat must be used within a ChatProvider');
141
- }
142
- return context;
143
- }
@@ -1,387 +0,0 @@
1
- import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
2
- import { Send } from 'lucide-react';
3
- import { useChat, type Message } from '../hooks/ChatProvider';
4
- import React from 'react';
5
- import MentionInput from '../MentionInput';
6
- import InputModal from '../InputModal';
7
- import type { ChatPromptCategoryGroup, ChatPrompt } from '@enterprise/eventcatalog-chat/utils/chat-prompts';
8
- import WelcomePromptArea from '../WelcomePromptArea';
9
- import ChatMessage from '../ChatMessage'; // Import the new component
10
- import { useChat as useAiChat, type UIMessage } from '@ai-sdk/react';
11
-
12
- // Update Message type to include resources
13
- interface Resource {
14
- id: string;
15
- type: string;
16
- url: string;
17
- title?: string;
18
- name?: string;
19
- }
20
-
21
- interface ChatWindowProps {
22
- model?: string;
23
- provider?: string;
24
- embeddingModel?: string;
25
- max_tokens?: number;
26
- similarityResults?: number;
27
- resources: Resource[];
28
- chatPrompts: ChatPromptCategoryGroup[];
29
- }
30
-
31
- const ChatWindow = ({
32
- model = 'o4-mini',
33
- embeddingModel = 'text-embedding-3-large',
34
- provider = 'openai',
35
- max_tokens = 4096,
36
- similarityResults = 50,
37
- resources: mentionInputResources = [],
38
- chatPrompts,
39
- }: ChatWindowProps) => {
40
- // const [messages] = useState<Array<Message>>([]);
41
- const [inputValue, setInputValue] = useState('');
42
- const outputRef = useRef<HTMLDivElement>(null);
43
- const inputRef = useRef<HTMLInputElement>(null);
44
- const [activeCategory, setActiveCategory] = useState<string>(chatPrompts?.[0]?.label || '');
45
-
46
- const { messages, sendMessage, stop, status, setMessages: setAiMessages } = useAiChat();
47
- const isStreaming = status === 'streaming' && messages.length > 0;
48
- const isThinking = status === 'submitted' || (status === 'streaming' && messages.length === 0);
49
-
50
- // --- New state for input modal ---
51
- const [promptForInput, setPromptForInput] = useState<ChatPrompt | null>(null);
52
- const [isInputModalOpen, setIsInputModalOpen] = useState(false);
53
- // --- End new state ---
54
-
55
- const { currentSession, storeMessagesToSession, updateSession, createSession } = useChat();
56
-
57
- // If the messages change add them to the session
58
- useEffect(() => {
59
- if (currentSession) {
60
- storeMessagesToSession(currentSession.id, messages);
61
- }
62
- }, [messages]);
63
-
64
- // Load messages when session changes
65
- useEffect(() => {
66
- if (currentSession) {
67
- setAiMessages(currentSession.messages as unknown as UIMessage[]);
68
- }
69
- }, [currentSession]);
70
-
71
- // Add effect to focus input when streaming stops
72
- useEffect(() => {
73
- if (!isStreaming && inputRef.current) {
74
- inputRef.current.focus();
75
- }
76
- }, [isStreaming]);
77
-
78
- // New function to handle submitting a question (user input or predefined)
79
- const submitQuestion = useCallback(
80
- async (question: string, additionalContext?: string) => {
81
- if (!question.trim() || isStreaming || isThinking) return;
82
-
83
- const userMessage: Message = { content: question, isUser: true, timestamp: Date.now(), additionalContext };
84
- const isFirstMessage = messages.length === 0;
85
-
86
- // setMessages((prev) => [...prev, userMessage]);
87
- // setShowWelcome(false);
88
- setInputValue('');
89
-
90
- // Scroll to bottom immediately after adding user message and setting thinking state
91
- requestAnimationFrame(() => scrollToBottom(false));
92
-
93
- if (currentSession && isFirstMessage) {
94
- updateSession({
95
- ...currentSession,
96
- // Use the submitted question (potentially the prompt title) for the session title
97
- title: question.length > 25 ? `${question.substring(0, 22)}...` : question,
98
- });
99
- }
100
-
101
- sendMessage({ text: additionalContext || question });
102
-
103
- // mutation.mutate({ question, additionalContext });
104
- },
105
- [
106
- currentSession,
107
- updateSession,
108
- // setMessages,
109
- setInputValue,
110
- messages.length,
111
- isStreaming,
112
- isThinking,
113
- messages,
114
- ]
115
- );
116
-
117
- // --- New handler for submitting from the modal ---
118
- const handleSubmitWithInputs = useCallback(
119
- (prompt: ChatPrompt, inputValues: Record<string, string>) => {
120
- let finalBody = prompt.body || ''; // Start with the original body
121
- // Ensure prompt and prompt.data exist before accessing properties
122
- if (!prompt || !prompt.data) {
123
- console.error('handleSubmitWithInputs called without a valid prompt.');
124
- setIsInputModalOpen(false); // Close modal even on error
125
- setPromptForInput(null);
126
- return;
127
- }
128
-
129
- for (const [key, value] of Object.entries(inputValues)) {
130
- const placeholder = `{{${key}}}`;
131
- // Replace all occurrences of the placeholder in the body
132
- finalBody = finalBody.replaceAll(placeholder, `"${value}"`);
133
- }
134
-
135
- // Submit the processed title and the processed body as additional context
136
- submitQuestion(prompt.data.title, finalBody);
137
-
138
- setIsInputModalOpen(false); // Close modal
139
- setPromptForInput(null); // Clear stored prompt
140
- },
141
- [submitQuestion]
142
- );
143
-
144
- // --- Modified handler for clicking a predefined question ---
145
- const handlePredefinedQuestionClick = useCallback(
146
- (prompt: ChatPrompt) => {
147
- // Ensure prompt and prompt.data exist
148
- if (!prompt || !prompt.data) {
149
- console.error('handlePredefinedQuestionClick called with invalid prompt:', prompt);
150
- return;
151
- }
152
- // Check if prompt.data and prompt.data.inputs exist and have length > 0
153
- if (prompt.data?.inputs && prompt.data.inputs.length > 0) {
154
- setPromptForInput(prompt); // Store the prompt
155
- setIsInputModalOpen(true); // Open the modal
156
- } else {
157
- // No inputs needed, submit directly using title and body
158
- submitQuestion(prompt.data.title, prompt.body);
159
- }
160
- },
161
- [submitQuestion]
162
- );
163
-
164
- // Handler for standard input submission
165
- const handleSubmit = useCallback(
166
- (e?: React.FormEvent) => {
167
- e?.preventDefault();
168
- // sendMessage({ text: inputValue });
169
- submitQuestion(inputValue); // Use standard input value, no additional context here
170
- },
171
- [inputValue, sendMessage]
172
- );
173
-
174
- // Add new function to handle smooth scrolling
175
- const scrollToBottom = useCallback((smooth = true) => {
176
- if (outputRef.current) {
177
- outputRef.current.scrollTo({
178
- top: outputRef.current.scrollHeight,
179
- behavior: smooth ? 'smooth' : 'auto',
180
- });
181
- }
182
- }, []);
183
-
184
- // Add effect to scroll when messages change or thinking state changes
185
- useEffect(() => {
186
- // Scroll immediately for new messages or when thinking starts/stops
187
- requestAnimationFrame(() => scrollToBottom(messages.length > 0 && !isThinking));
188
- }, [messages, isThinking, scrollToBottom]);
189
-
190
- // Memoize the messages list with the new ChatMessage component
191
- const messagesList = useMemo(
192
- () => (
193
- <div className="space-y-4 max-w-[900px] mx-auto pb-6">
194
- {messages.map((message, index) => (
195
- <ChatMessage
196
- key={message.id}
197
- message={{
198
- isUser: message.role === 'user',
199
- content: message.parts.map((part) => (part.type === 'text' ? part.text : '')).join(''),
200
- timestamp: Date.now(),
201
- }}
202
- />
203
- ))}
204
- {isThinking && (
205
- <div className="flex justify-start mb-4">
206
- <div className="flex items-center space-x-2 max-w-[80%] rounded-lg p-3 bg-gray-100 text-gray-800 rounded-bl-none">
207
- <div className="flex space-x-1">
208
- <div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '0ms' }}></div>
209
- <div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '150ms' }}></div>
210
- <div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '300ms' }}></div>
211
- </div>
212
- </div>
213
- </div>
214
- )}
215
- </div>
216
- ),
217
- [messages, isThinking]
218
- );
219
-
220
- // Memoize the input change handler
221
- const handleInputChange = useCallback((newValue: string) => {
222
- setInputValue(newValue);
223
- }, []);
224
-
225
- // Memoize the key press handler for MentionInput
226
- const handleInputKeyPress = useCallback(
227
- (e: React.KeyboardEvent<HTMLInputElement>) => {
228
- // Submit only if Enter is pressed WITHOUT Shift and suggestions are NOT shown
229
- if (e.key === 'Enter' && !e.shiftKey) {
230
- // The MentionInput's internal onKeyDown will handle preventDefault
231
- // if suggestions are shown and Enter is pressed for selection.
232
- // We check here if it *wasn't* handled for selection, meaning we should submit.
233
- if (!e.defaultPrevented) {
234
- handleSubmit();
235
- }
236
- }
237
- },
238
- [handleSubmit]
239
- ); // Include handleSubmit in dependencies
240
-
241
- // Effect to update activeCategory if chatPrompts load after initial render
242
- useEffect(() => {
243
- if (!activeCategory && chatPrompts && chatPrompts.length > 0) {
244
- setActiveCategory(chatPrompts[0].label);
245
- }
246
- }, [chatPrompts, activeCategory]);
247
-
248
- // Add Effect for clipboard copy functionality
249
- useEffect(() => {
250
- const outputElement = outputRef.current;
251
- if (!outputElement) return;
252
-
253
- const handleClick = async (event: MouseEvent) => {
254
- const button = (event.target as Element).closest<HTMLButtonElement>('[data-copy-button="true"]');
255
- if (!button) return;
256
-
257
- const codeToCopy = button.dataset.code;
258
- if (!codeToCopy) return;
259
-
260
- try {
261
- await navigator.clipboard.writeText(codeToCopy);
262
- // Visual feedback: change icon to Check for a short time
263
- const originalIcon = button.innerHTML;
264
- button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
265
- button.disabled = true;
266
-
267
- setTimeout(() => {
268
- button.innerHTML = originalIcon;
269
- button.disabled = false;
270
- }, 1500); // Reset after 1.5 seconds
271
- } catch (err) {
272
- console.error('Failed to copy code: ', err);
273
- // Optional: Provide error feedback to the user
274
- const originalTitle = button.title;
275
- button.title = 'Failed to copy!';
276
- setTimeout(() => {
277
- button.title = originalTitle;
278
- }, 1500);
279
- }
280
- };
281
-
282
- outputElement.addEventListener('click', handleClick);
283
-
284
- // Cleanup listener on component unmount
285
- return () => {
286
- outputElement.removeEventListener('click', handleClick);
287
- };
288
- }, []); // Empty dependency array ensures this runs only once on mount
289
-
290
- // Handle the query from the url
291
- useEffect(() => {
292
- const urlParams = new URLSearchParams(window.location.search);
293
- const query = urlParams.get('query');
294
- if (query) {
295
- setTimeout(() => {
296
- createSession();
297
- setInputValue(query);
298
- }, 250);
299
- }
300
- }, []);
301
-
302
- return (
303
- <div className="flex-1 flex flex-col overflow-hidden h-[calc(100vh-60px)] w-full bg-white">
304
- {messages.length === 0 && (
305
- <WelcomePromptArea
306
- chatPrompts={chatPrompts}
307
- activeCategory={activeCategory}
308
- setActiveCategory={setActiveCategory}
309
- onPromptClick={handlePredefinedQuestionClick}
310
- isProcessing={isStreaming || isThinking}
311
- />
312
- )}
313
-
314
- {messages.length > 0 && (
315
- <div ref={outputRef} className="flex-1 overflow-y-auto">
316
- {messages.length > 0 && (
317
- <div id="output" className="p-4 space-y-4 w-full max-w-[900px] mx-auto h-full pb-16">
318
- {messagesList}
319
- </div>
320
- )}
321
- ;
322
- </div>
323
- )}
324
-
325
- {/* Input Area (remains at the bottom) */}
326
- <div className="border-t border-gray-200 p-4 bg-white">
327
- <div className="max-w-[900px] mx-auto relative">
328
- {/* Replace standard input with MentionInput */}
329
- <MentionInput
330
- suggestions={mentionInputResources.map((resource) => ({
331
- id: resource.id,
332
- name: resource.name || '',
333
- type: resource.type,
334
- }))}
335
- trigger="@"
336
- type="text"
337
- value={inputValue}
338
- onChange={handleInputChange}
339
- onKeyDown={handleInputKeyPress}
340
- placeholder="Type your message or '@' for events..."
341
- className="w-full px-4 py-3 bg-white text-gray-800 rounded-lg border border-gray-200 focus:outline-none focus:border-purple-500 focus:ring-1 focus:ring-purple-500 disabled:bg-gray-50 disabled:cursor-not-allowed pr-24"
342
- disabled={isStreaming || isThinking} // Disable input while streaming/thinking
343
- />
344
- <div className="absolute right-3 top-1/2 -translate-y-1/2">
345
- {isStreaming ? (
346
- <button
347
- onClick={() => stop()}
348
- className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 text-sm font-medium"
349
- >
350
- Stop
351
- </button>
352
- ) : (
353
- <button
354
- onClick={handleSubmit}
355
- disabled={!inputValue.trim() || isThinking} // Disable send if input empty or thinking
356
- className="px-4 py-2 flex items-center bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:bg-gray-200 disabled:cursor-not-allowed text-sm font-medium"
357
- >
358
- {/* Add icon */}
359
- <Send size={16} strokeWidth={1.5} className="mr-2" />
360
- Send
361
- </button>
362
- )}
363
- </div>
364
- </div>
365
- <div className="max-w-[900px] mx-auto flex justify-between">
366
- {/* show what model is loaded */}
367
- <p className="text-[10px] text-gray-400 mt-2">
368
- Provider: {provider.charAt(0).toUpperCase() + provider.slice(1)} | Model: {model}
369
- {/* Embedding Model: {embeddingModel} */}
370
- </p>
371
- <p className="text-xs text-gray-500 mt-2">EventCatalog Chat can make mistakes. Check important info.</p>
372
- </div>
373
- </div>
374
-
375
- {/* --- Render Input Modal --- */}
376
- <InputModal
377
- isOpen={isInputModalOpen}
378
- onClose={() => setIsInputModalOpen(false)} // Allow closing the modal
379
- prompt={promptForInput}
380
- onSubmit={handleSubmitWithInputs}
381
- resources={mentionInputResources} // Pass resources here
382
- />
383
- </div>
384
- );
385
- };
386
-
387
- export default ChatWindow;
@@ -1,59 +0,0 @@
1
- import type { APIContext } from 'astro';
2
- import { type UIMessage } from 'ai';
3
- import config from '@config';
4
- import { getProvider } from '@enterprise/eventcatalog-chat/providers';
5
-
6
- // Map the Keys to use in the SDK, astro exports as import.meta.env
7
- process.env.OPENAI_API_KEY = import.meta.env.OPENAI_API_KEY || '';
8
-
9
- const baseSystemPrompt = `You are an expert in event-driven architecture and domain-driven design, specializing in documentation for EventCatalog.
10
-
11
- You assist developers, architects, and business stakeholders who need information about their event-driven system catalog. You help with questions about:
12
- - Events (asynchronous messages that notify about something that has happened)
13
- - Commands (requests to perform an action)
14
- - Queries (requests for information)
15
- - Services (bounded contexts or applications that produce/consume events)
16
- - Domains (business capabilities or functional areas)
17
-
18
- Your primary goal is to help users understand their event-driven system through accurate documentation interpretation.
19
-
20
- When responding:
21
- 2. Explain connections between resources when relevant.
22
- 3. Use appropriate technical terminology.
23
- 4. Use clear formatting with headings and bullet points when helpful.
24
- 5. State clearly when information is missing rather than making assumptions.
25
- 6. Don't provide code examples unless specifically requested.
26
-
27
- Your primary goal is to help users understand their event-driven system through accurate documentation interpretation.
28
-
29
- If you have additional context, use it to answer the question.`;
30
-
31
- interface Message {
32
- content: string;
33
- }
34
-
35
- export const GET = async ({ request }: APIContext<{ question: string; messages: Message[]; additionalContext?: string }>) => {
36
- // return 404
37
- return new Response(JSON.stringify({ error: 'Not found' }), {
38
- status: 404,
39
- headers: { 'Content-Type': 'application/json' },
40
- });
41
- };
42
-
43
- export const POST = async ({ request }: APIContext<{ question: string; messages: Message[]; additionalContext?: string }>) => {
44
- const { messages }: { messages: UIMessage[] } = await request.json();
45
-
46
- const provider = getProvider(config?.chat?.provider || 'openai', {
47
- modelId: config?.chat?.model || 'gpt-3.5-turbo-0125',
48
- temperature: config?.chat?.temperature,
49
- topP: config?.chat?.topP,
50
- topK: config?.chat?.topK,
51
- frequencyPenalty: config?.chat?.frequencyPenalty,
52
- presencePenalty: config?.chat?.presencePenalty,
53
- });
54
-
55
- const result = await provider.streamText(baseSystemPrompt, messages);
56
- return result.toUIMessageStreamResponse();
57
- };
58
-
59
- export const prerender = false;