@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.
- package/README.md +10 -0
- package/dist/__mocks__/astro-content.cjs +32 -0
- package/dist/__mocks__/astro-content.d.cts +13 -0
- package/dist/__mocks__/astro-content.d.ts +13 -0
- package/dist/__mocks__/astro-content.js +7 -0
- 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-JSONCD7V.js → chunk-2FUEBPD3.js} +1 -1
- package/dist/{chunk-3W6JYTHP.js → chunk-HABY2LVH.js} +6 -2
- package/dist/{chunk-H4QHE5YZ.js → chunk-KQAMO3R4.js} +1 -1
- package/dist/chunk-Q6KRYWPV.js +44 -0
- package/dist/{chunk-PQL6O5YA.js → chunk-RRP2B7BL.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +84 -65
- package/dist/eventcatalog.config.d.cts +4 -0
- package/dist/eventcatalog.config.d.ts +4 -0
- package/dist/eventcatalog.js +45 -57
- package/dist/generate.cjs +48 -2
- package/dist/generate.js +3 -1
- package/dist/utils/cli-logger.cjs +82 -0
- package/dist/utils/cli-logger.d.cts +10 -0
- package/dist/utils/cli-logger.d.ts +10 -0
- package/dist/utils/cli-logger.js +7 -0
- package/eventcatalog/astro.config.mjs +4 -1
- package/eventcatalog/integrations/ecstudio-watcher.mjs +1 -1
- package/eventcatalog/integrations/eventcatalog-features.ts +69 -0
- package/eventcatalog/public/icons/asyncapi-black.svg +2 -0
- package/eventcatalog/public/icons/graphql-black.svg +1 -0
- package/eventcatalog/public/icons/openapi-black.svg +1 -0
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +821 -0
- package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +24 -0
- package/eventcatalog/src/components/Grids/DomainGrid.tsx +1 -3
- package/eventcatalog/src/components/Grids/MessageGrid.tsx +8 -8
- package/eventcatalog/src/components/Header.astro +25 -5
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +14 -3
- package/eventcatalog/src/components/Search/Search.astro +2 -2
- package/eventcatalog/src/components/Search/SearchModal.tsx +16 -7
- package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +9 -2
- package/eventcatalog/src/components/SideNav/NestedSideBar/builders/domain.ts +7 -6
- package/eventcatalog/src/components/SideNav/NestedSideBar/builders/service.ts +6 -3
- package/eventcatalog/src/components/SideNav/NestedSideBar/builders/shared.ts +1 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +23 -8
- package/eventcatalog/src/components/SideNav/NestedSideBar/sidebar-builder.ts +57 -11
- package/eventcatalog/src/content.config.ts +1 -10
- package/eventcatalog/src/enterprise/ai/chat-api.ts +262 -0
- package/eventcatalog/src/enterprise/auth/[...auth].ts +3 -0
- package/eventcatalog/src/enterprise/auth/login.astro +420 -0
- package/eventcatalog/src/enterprise/collections/index.ts +0 -1
- package/eventcatalog/src/layouts/Footer.astro +8 -5
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +30 -19
- package/eventcatalog/src/pages/_index.astro +8 -9
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +19 -3
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +7 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +5 -5
- package/eventcatalog/src/pages/docs/teams/[id].mdx.ts +36 -0
- package/eventcatalog/src/pages/docs/users/[id].mdx.ts +36 -0
- package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +178 -0
- package/eventcatalog/src/pages/schemas/explorer/index.astro +5 -155
- package/eventcatalog/src/remark-plugins/directives.ts +30 -9
- package/eventcatalog/src/utils/collections/schemas.ts +31 -7
- package/eventcatalog/src/utils/feature.ts +8 -4
- package/eventcatalog/src/utils/resource-files.ts +86 -0
- package/package.json +12 -15
- package/default-files-for-collections/changelogs.md +0 -5
- package/default-files-for-collections/channels.md +0 -8
- package/default-files-for-collections/commands.md +0 -8
- package/default-files-for-collections/domains.md +0 -8
- package/default-files-for-collections/events.md +0 -8
- package/default-files-for-collections/flows.md +0 -11
- package/default-files-for-collections/queries.md +0 -8
- package/default-files-for-collections/services.md +0 -8
- package/default-files-for-collections/ubiquitousLanguages.md +0 -7
- package/eventcatalog/src/enterprise/collections/chat-prompts.ts +0 -32
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +0 -60
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +0 -414
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatSidebar.tsx +0 -169
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/InputModal.tsx +0 -244
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/MentionInput.tsx +0 -211
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/WelcomePromptArea.tsx +0 -176
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/default-prompts.ts +0 -93
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/hooks/ChatProvider.tsx +0 -143
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +0 -387
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/chat.ts +0 -59
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +0 -104
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +0 -140
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/anthropic.ts +0 -28
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/google.ts +0 -41
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/index.ts +0 -26
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/openai.ts +0 -61
- package/eventcatalog/src/enterprise/eventcatalog-chat/utils/chat-prompts.ts +0 -50
- package/eventcatalog/src/pages/auth/login.astro +0 -280
- package/eventcatalog/src/pages/chat/feature.astro +0 -179
- package/eventcatalog/src/pages/chat/index.astro +0 -10
- package/eventcatalog/src/pages/nav-index.json.ts +0 -30
- /package/eventcatalog/src/{pages → enterprise}/auth/error.astro +0 -0
- /package/eventcatalog/src/{middleware-auth.ts → enterprise/auth/middleware/middleware-auth.ts} +0 -0
- /package/eventcatalog/src/{middleware.ts → enterprise/auth/middleware/middleware.ts} +0 -0
- /package/eventcatalog/src/{pages/unauthorized/index.astro → enterprise/auth/unauthorized.astro} +0 -0
- /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
|
-
}
|
package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx
DELETED
|
@@ -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;
|