@eventcatalog/core 2.23.3 → 2.24.1
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/LICENSE.md +10 -1
- 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-4Z6CL5T6.js → chunk-TGASC7WJ.js} +1 -1
- package/dist/{chunk-UQNBP7A5.js → chunk-XBPWZJUU.js} +1 -1
- package/dist/{chunk-APTSNZPW.js → chunk-XDRFRTPG.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.config.d.cts +5 -0
- package/dist/eventcatalog.config.d.ts +5 -0
- package/dist/eventcatalog.js +3 -3
- package/eventcatalog/astro.config.mjs +6 -0
- package/eventcatalog/public/images/ai-placeholder.png +0 -0
- package/eventcatalog/src/enterprise/LICENSE.txt +4 -0
- package/eventcatalog/src/enterprise/README.md +5 -0
- package/eventcatalog/src/enterprise/ai-assistant/components/Chat.tsx +16 -0
- package/eventcatalog/src/enterprise/ai-assistant/components/ChatSidebar.tsx +180 -0
- package/eventcatalog/src/enterprise/ai-assistant/components/ChatWindow.tsx +395 -0
- package/eventcatalog/src/enterprise/ai-assistant/components/hooks/ChatProvider.tsx +141 -0
- package/eventcatalog/src/enterprise/ai-assistant/components/workers/document-importer.ts +36 -0
- package/eventcatalog/src/enterprise/ai-assistant/components/workers/engine.ts +7 -0
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +8 -1
- package/eventcatalog/src/pages/chat/index.astro +122 -0
- package/eventcatalog/src/pages/docs/teams/[id].md.ts +0 -4
- package/eventcatalog/tsconfig.json +3 -1
- package/package.json +10 -1
package/LICENSE.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
Source code in this repository is split into two parts:
|
|
2
|
+
|
|
3
|
+
* Outside the `/src/enterprise` directory, source code in a given file is licensed under MIT (see below).
|
|
4
|
+
* Inside the `/src/enterprise` directory, source code in a given file is licensed under the EventCatalog Plugin/Commercial License, unless otherwise noted.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
All code outside of the `/src/enterprise` directory is licensed under the MIT license which is below.
|
|
9
|
+
|
|
1
10
|
MIT License
|
|
2
11
|
|
|
3
|
-
Copyright (c) 2022-
|
|
12
|
+
Copyright (c) 2022-2025 boyney123
|
|
4
13
|
|
|
5
14
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
15
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log_build_default
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-TGASC7WJ.js";
|
|
4
|
+
import "../chunk-XDRFRTPG.js";
|
|
5
|
+
import "../chunk-XBPWZJUU.js";
|
|
6
6
|
import "../chunk-E7TXTI7G.js";
|
|
7
7
|
export {
|
|
8
8
|
log_build_default as default
|
package/dist/constants.cjs
CHANGED
package/dist/constants.js
CHANGED
package/dist/eventcatalog.cjs
CHANGED
package/dist/eventcatalog.js
CHANGED
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
} from "./chunk-OW2FQPYP.js";
|
|
7
7
|
import {
|
|
8
8
|
log_build_default
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-TGASC7WJ.js";
|
|
10
|
+
import "./chunk-XDRFRTPG.js";
|
|
11
11
|
import {
|
|
12
12
|
catalogToAstro
|
|
13
13
|
} from "./chunk-CXKIF3EI.js";
|
|
14
14
|
import {
|
|
15
15
|
VERSION
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-XBPWZJUU.js";
|
|
17
17
|
import {
|
|
18
18
|
isBackstagePluginEnabled
|
|
19
19
|
} from "./chunk-XMDPVKIJ.js";
|
|
@@ -22,6 +22,9 @@ export default defineConfig({
|
|
|
22
22
|
base,
|
|
23
23
|
server: { port: config.port || 3000 },
|
|
24
24
|
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
25
28
|
outDir: config.outDir ? join(projectDirectory, config.outDir) : join(projectDirectory, 'dist'),
|
|
26
29
|
|
|
27
30
|
// https://docs.astro.build/en/reference/configuration-reference/#site
|
|
@@ -61,6 +64,9 @@ export default defineConfig({
|
|
|
61
64
|
*/
|
|
62
65
|
'__EC_TRAILING_SLASH__': config.trailingSlash || false,
|
|
63
66
|
},
|
|
67
|
+
worker: {
|
|
68
|
+
format: 'es',
|
|
69
|
+
},
|
|
64
70
|
build: {
|
|
65
71
|
commonjsOptions: {
|
|
66
72
|
transformMixedEsModules: true,
|
|
Binary file
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
Usage of files in this directory and its subdirectories, and of EventCatalog Plugin / Enterprise features, is subject to
|
|
2
|
+
the EventCatalog Plugin Licenses (https://www.eventcatalog.dev), and conditional on having a
|
|
3
|
+
valid plugin license from EventCatalog. Access to files in this directory and its subdirectories does not constitute
|
|
4
|
+
permission to use this code or EventCatalog Plugin / Enterprise features.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
## EventCatalog License
|
|
2
|
+
|
|
3
|
+
Usage of files in this directory and its subdirectories, and of EventCatalog Plugin / Enterprise features, is subject to the EventCatalog Commercial License, and conditional on having the right license from EventCatalog. Access to files in this directory and its subdirectories does not constitute permission to use this code or EventCatalog Enterprise Edition features.
|
|
4
|
+
|
|
5
|
+
Unless otherwise noted, all files Copyright © 2025 EventCatalog Ltd.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Sidebar from './ChatSidebar';
|
|
2
|
+
import { ChatProvider } from './hooks/ChatProvider';
|
|
3
|
+
import ChatWindow from './ChatWindow';
|
|
4
|
+
|
|
5
|
+
const Chat = () => {
|
|
6
|
+
return (
|
|
7
|
+
<ChatProvider>
|
|
8
|
+
<div className="flex overflow-hidden w-full">
|
|
9
|
+
<Sidebar />
|
|
10
|
+
<ChatWindow />
|
|
11
|
+
</div>
|
|
12
|
+
</ChatProvider>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default Chat;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { format } from 'date-fns';
|
|
3
|
+
import { useChat } from './hooks/ChatProvider';
|
|
4
|
+
import * as Dialog from '@radix-ui/react-dialog';
|
|
5
|
+
|
|
6
|
+
const Sidebar: React.FC<{}> = () => {
|
|
7
|
+
const {
|
|
8
|
+
sessions = [],
|
|
9
|
+
currentSession,
|
|
10
|
+
createSession,
|
|
11
|
+
deleteSession,
|
|
12
|
+
addMessageToSession,
|
|
13
|
+
setCurrentSession,
|
|
14
|
+
isStreaming,
|
|
15
|
+
} = useChat();
|
|
16
|
+
const [showHelp, setShowHelp] = React.useState(false);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
// Check if this is the first visit after component mounts
|
|
20
|
+
const hasVisited = localStorage.getItem('eventCatalogAIVisited');
|
|
21
|
+
if (!hasVisited || hasVisited === 'false') {
|
|
22
|
+
console.log('setting showHelp to true');
|
|
23
|
+
localStorage.setItem('eventCatalogAIVisited', 'true');
|
|
24
|
+
setShowHelp(true);
|
|
25
|
+
}
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (sessions.length === 0) {
|
|
30
|
+
createSession();
|
|
31
|
+
}
|
|
32
|
+
}, [sessions]);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="h-[calc(100vh-64px)] bg-gray-50 flex-shrink-0 border-r border-gray-200">
|
|
36
|
+
<div className="w-64 bg-gray-50 border-r border-gray-200 flex flex-col h-full">
|
|
37
|
+
{/* New Chat Button */}
|
|
38
|
+
<div className="p-4 border-b border-gray-200">
|
|
39
|
+
<div className="flex items-center justify-between gap-2">
|
|
40
|
+
<button
|
|
41
|
+
onClick={createSession}
|
|
42
|
+
disabled={isStreaming}
|
|
43
|
+
className={`flex-1 flex items-center justify-between px-3 py-2 text-sm bg-white hover:bg-gray-100 rounded-lg border border-gray-200 text-gray-700 ${isStreaming ? 'opacity-50 cursor-not-allowed' : ''}`}
|
|
44
|
+
>
|
|
45
|
+
<span>New chat</span>
|
|
46
|
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
|
47
|
+
<path
|
|
48
|
+
fillRule="evenodd"
|
|
49
|
+
d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
|
|
50
|
+
clipRule="evenodd"
|
|
51
|
+
/>
|
|
52
|
+
</svg>
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
{/* Chat History */}
|
|
58
|
+
<div className="flex-1 overflow-y-auto">
|
|
59
|
+
<div className="p-2 space-y-2">
|
|
60
|
+
<div className="text-xs text-gray-500 px-2 py-1">Your chats</div>
|
|
61
|
+
<div className="space-y-1">
|
|
62
|
+
{sessions.map((session) => (
|
|
63
|
+
<div
|
|
64
|
+
key={session.id}
|
|
65
|
+
className={`group relative flex flex-col ${
|
|
66
|
+
currentSession?.id === session.id ? 'bg-gray-100' : 'hover:bg-gray-50'
|
|
67
|
+
} rounded-lg`}
|
|
68
|
+
>
|
|
69
|
+
<button
|
|
70
|
+
onClick={(e) => deleteSession(session.id)}
|
|
71
|
+
disabled={isStreaming}
|
|
72
|
+
className={`absolute right-1 top-1 p-1.5 opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-gray-400 hover:text-gray-600 ${currentSession?.id === session.id ? 'opacity-100' : ''} ${isStreaming ? 'opacity-50 cursor-not-allowed' : ''}`}
|
|
73
|
+
title="Delete chat"
|
|
74
|
+
>
|
|
75
|
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">
|
|
76
|
+
<path
|
|
77
|
+
fillRule="evenodd"
|
|
78
|
+
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
|
79
|
+
clipRule="evenodd"
|
|
80
|
+
/>
|
|
81
|
+
</svg>
|
|
82
|
+
</button>
|
|
83
|
+
<button
|
|
84
|
+
onClick={() => setCurrentSession(session)}
|
|
85
|
+
disabled={isStreaming}
|
|
86
|
+
className={`flex-1 text-left px-3 py-2 text-sm ${isStreaming ? 'opacity-50 cursor-not-allowed' : ''}`}
|
|
87
|
+
>
|
|
88
|
+
<div className="font-medium text-gray-700 pr-7">{session.title}</div>
|
|
89
|
+
<div className="flex justify-between items-center mt-1 text-xs text-gray-400">
|
|
90
|
+
<span>{session.messages.length} messages</span>
|
|
91
|
+
<span>{format(session.lastUpdated, 'dd/MM/yyyy, HH:mm:ss')}</span>
|
|
92
|
+
</div>
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{/* Beta Message */}
|
|
101
|
+
<div className="p-4 text-xs text-gray-500 border-t border-gray-200">
|
|
102
|
+
<div className="flex items-center justify-between mb-2">
|
|
103
|
+
<p className="font-bold">This feature is currently in beta.</p>
|
|
104
|
+
<Dialog.Root open={showHelp} onOpenChange={setShowHelp}>
|
|
105
|
+
<Dialog.Trigger asChild>
|
|
106
|
+
<button title="Help" className="p-1.5 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg">
|
|
107
|
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
|
108
|
+
<path
|
|
109
|
+
fillRule="evenodd"
|
|
110
|
+
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z"
|
|
111
|
+
clipRule="evenodd"
|
|
112
|
+
/>
|
|
113
|
+
</svg>
|
|
114
|
+
</button>
|
|
115
|
+
</Dialog.Trigger>
|
|
116
|
+
<Dialog.Portal>
|
|
117
|
+
<Dialog.Overlay className="fixed inset-0 bg-black/50 z-40" />
|
|
118
|
+
<Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg z-50 p-6 w-[600px] max-h-[85vh] overflow-y-auto">
|
|
119
|
+
<Dialog.Title className="text-lg font-semibold text-gray-900 mb-4">
|
|
120
|
+
Welcome to the EventCatalog AI Assistant
|
|
121
|
+
</Dialog.Title>
|
|
122
|
+
<div className="text-sm text-gray-600 space-y-4">
|
|
123
|
+
<p>
|
|
124
|
+
This is your private AI assistant for EventCatalog. All conversations are stored locally and your data
|
|
125
|
+
remains completely private.
|
|
126
|
+
</p>
|
|
127
|
+
<p>Key features:</p>
|
|
128
|
+
<ul className="list-disc pl-4 space-y-2">
|
|
129
|
+
<li>Local-first: All conversations are stored on your device only</li>
|
|
130
|
+
<li>Privacy-focused: Your data is never shared with external servers</li>
|
|
131
|
+
<li>Persistent chat history: Access your previous conversations anytime</li>
|
|
132
|
+
<li>Multiple sessions: Organize different topics in separate chats</li>
|
|
133
|
+
</ul>
|
|
134
|
+
|
|
135
|
+
<div className="mt-6">
|
|
136
|
+
<p className="font-medium mb-2">Example prompts you can try:</p>
|
|
137
|
+
<ul className="list-disc pl-4 space-y-2 bg-gray-50 p-4 rounded-lg">
|
|
138
|
+
<li>"I want to create a new feature, what events do we have related to Payments?"</li>
|
|
139
|
+
<li>"What domains do we have and who owns them?"</li>
|
|
140
|
+
<li>"What are the events for the X domain?"</li>
|
|
141
|
+
<li>"Create me a code snippet for this event"</li>
|
|
142
|
+
</ul>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<p className="mt-4">For additional support or feature requests, please join our Discord community.</p>
|
|
146
|
+
</div>
|
|
147
|
+
<Dialog.Close asChild>
|
|
148
|
+
<button className="absolute top-4 right-4 text-gray-400 hover:text-gray-600" aria-label="Close">
|
|
149
|
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
150
|
+
<path
|
|
151
|
+
fillRule="evenodd"
|
|
152
|
+
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
|
153
|
+
clipRule="evenodd"
|
|
154
|
+
/>
|
|
155
|
+
</svg>
|
|
156
|
+
</button>
|
|
157
|
+
</Dialog.Close>
|
|
158
|
+
</Dialog.Content>
|
|
159
|
+
</Dialog.Portal>
|
|
160
|
+
</Dialog.Root>
|
|
161
|
+
</div>
|
|
162
|
+
<p>
|
|
163
|
+
Have issues or ideas? Let us know on{' '}
|
|
164
|
+
<a
|
|
165
|
+
href="https://discord.com/channels/918092420338569216/1342473496957288581"
|
|
166
|
+
className="text-blue-600 hover:text-blue-800 underline"
|
|
167
|
+
target="_blank"
|
|
168
|
+
rel="noopener noreferrer"
|
|
169
|
+
>
|
|
170
|
+
Discord
|
|
171
|
+
</a>
|
|
172
|
+
.
|
|
173
|
+
</p>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export default Sidebar;
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { useEffect, useState, useRef } from 'react';
|
|
2
|
+
import { BookOpen, Send } from 'lucide-react';
|
|
3
|
+
import { Document } from 'langchain/document';
|
|
4
|
+
import { CreateWebWorkerMLCEngine, type InitProgressReport } from '@mlc-ai/web-llm';
|
|
5
|
+
import { useChat } from './hooks/ChatProvider';
|
|
6
|
+
import config from '@config';
|
|
7
|
+
|
|
8
|
+
const ChatWindow = () => {
|
|
9
|
+
const [loading, setLoading] = useState(true);
|
|
10
|
+
const [loadingProgress, setLoadingProgress] = useState(0);
|
|
11
|
+
const [engine, setEngine] = useState<any>(null);
|
|
12
|
+
const [messages, setMessages] = useState<Array<{ content: string; isUser: boolean }>>([]);
|
|
13
|
+
const [inputValue, setInputValue] = useState('');
|
|
14
|
+
const [showWelcome, setShowWelcome] = useState(true);
|
|
15
|
+
const [vectorWorker, setVectorWorker] = useState<Worker | null>(null);
|
|
16
|
+
const [isThinking, setIsThinking] = useState(false);
|
|
17
|
+
const completionRef = useRef<any>(null);
|
|
18
|
+
const outputRef = useRef<HTMLDivElement>(null);
|
|
19
|
+
|
|
20
|
+
// LLM configuration from eventcatalog.config.js file
|
|
21
|
+
const model = config.chat?.model || 'Llama-2-7b-chat-hf-q4f16_1-MLC';
|
|
22
|
+
const max_tokens = config.chat?.max_tokens || 8192;
|
|
23
|
+
|
|
24
|
+
const { currentSession, addMessageToSession, updateSession, isStreaming, setIsStreaming } = useChat();
|
|
25
|
+
|
|
26
|
+
// Load messages when session changes
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (currentSession) {
|
|
29
|
+
console.log('currentSession', currentSession.messages);
|
|
30
|
+
setMessages(currentSession.messages);
|
|
31
|
+
setShowWelcome(false);
|
|
32
|
+
} else {
|
|
33
|
+
setMessages([]);
|
|
34
|
+
setShowWelcome(true);
|
|
35
|
+
}
|
|
36
|
+
}, [currentSession]);
|
|
37
|
+
|
|
38
|
+
// Helper function to stop the current completion
|
|
39
|
+
const handleStop = async () => {
|
|
40
|
+
if (completionRef.current) {
|
|
41
|
+
try {
|
|
42
|
+
await engine.interruptGenerate();
|
|
43
|
+
completionRef.current = null;
|
|
44
|
+
setIsStreaming(false);
|
|
45
|
+
setIsThinking(false);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Error stopping completion:', error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handleSubmit = async () => {
|
|
53
|
+
if (!inputValue.trim() || !engine) return;
|
|
54
|
+
|
|
55
|
+
// Add to messages
|
|
56
|
+
setMessages((prev) => [...prev, { content: inputValue, isUser: true }]);
|
|
57
|
+
|
|
58
|
+
// Add message to session store only
|
|
59
|
+
if (currentSession) {
|
|
60
|
+
addMessageToSession(currentSession.id, inputValue, true);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setIsThinking(true);
|
|
64
|
+
setIsStreaming(true);
|
|
65
|
+
setInputValue('');
|
|
66
|
+
|
|
67
|
+
// if the first message, update the session title
|
|
68
|
+
if (messages.length === 1 && currentSession) {
|
|
69
|
+
updateSession({
|
|
70
|
+
...currentSession,
|
|
71
|
+
title: inputValue.length > 25 ? `${inputValue.substring(0, 22)}...` : inputValue,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Add input to vector store
|
|
76
|
+
vectorWorker?.postMessage({ input: inputValue });
|
|
77
|
+
|
|
78
|
+
// @ts-ignore
|
|
79
|
+
vectorWorker.onmessage = async (event) => {
|
|
80
|
+
if (event.data.action === 'search-results') {
|
|
81
|
+
console.log('Results', event?.data?.results);
|
|
82
|
+
|
|
83
|
+
const qaPrompt = `\n".
|
|
84
|
+
|
|
85
|
+
You are an expert in the domain of software architecture.
|
|
86
|
+
|
|
87
|
+
You are given a question and a list of resources.
|
|
88
|
+
|
|
89
|
+
Resource types include events, commands, queries, services and domains, users and teams.
|
|
90
|
+
|
|
91
|
+
Never make up information, only use the information provided in the resources.
|
|
92
|
+
|
|
93
|
+
Your job is to answer the question based on the resources.
|
|
94
|
+
|
|
95
|
+
This resources are all the resources that are relevant to the question.
|
|
96
|
+
|
|
97
|
+
Use the resource url value to link to the resources.
|
|
98
|
+
|
|
99
|
+
If any fields are undefined or missing just say you don't know as they are missing in the documentation.
|
|
100
|
+
|
|
101
|
+
When you give the name of resources back to the user, you can also provide them with a link directly to the resource, use the url field for this.
|
|
102
|
+
|
|
103
|
+
==========
|
|
104
|
+
${event.data.results
|
|
105
|
+
.map((result: any) => {
|
|
106
|
+
const metadata = result[0].metadata;
|
|
107
|
+
return `<resource ${Object.entries(metadata)
|
|
108
|
+
.filter(([key, value]) => key !== 'markdown' && key !== 'loc')
|
|
109
|
+
.map(([key, value]) => {
|
|
110
|
+
// Special handling for resourceType to construct url
|
|
111
|
+
if (key === 'type') {
|
|
112
|
+
return `url="/docs/${value}s/${metadata.id}" ${key}="${value}"`;
|
|
113
|
+
}
|
|
114
|
+
return `${key}="${value}"`;
|
|
115
|
+
})
|
|
116
|
+
.join(' ')} />`;
|
|
117
|
+
})
|
|
118
|
+
.join('\n')}\n
|
|
119
|
+
==========
|
|
120
|
+
|
|
121
|
+
""
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// Get completion
|
|
126
|
+
const completion = await engine.chat.completions.create({
|
|
127
|
+
messages: [
|
|
128
|
+
{
|
|
129
|
+
role: 'system',
|
|
130
|
+
content: qaPrompt,
|
|
131
|
+
},
|
|
132
|
+
// previous messages
|
|
133
|
+
...messages.map((msg) => ({
|
|
134
|
+
role: msg.isUser ? 'user' : 'assistant',
|
|
135
|
+
content: msg.content,
|
|
136
|
+
})),
|
|
137
|
+
{
|
|
138
|
+
role: 'user',
|
|
139
|
+
content: inputValue,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
stream: true,
|
|
143
|
+
temperature: 0.1,
|
|
144
|
+
max_tokens,
|
|
145
|
+
top_p: 0.2,
|
|
146
|
+
top_k: 5,
|
|
147
|
+
frequency_penalty: 0,
|
|
148
|
+
presence_penalty: 0,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Store completion reference for potential cancellation
|
|
152
|
+
completionRef.current = completion;
|
|
153
|
+
|
|
154
|
+
let isFirstChunk = true;
|
|
155
|
+
let responseText = '';
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
for await (const chunk of completion) {
|
|
159
|
+
const content = chunk.choices[0]?.delta?.content || '';
|
|
160
|
+
if (content) {
|
|
161
|
+
responseText += content;
|
|
162
|
+
|
|
163
|
+
if (isFirstChunk) {
|
|
164
|
+
setIsThinking(false);
|
|
165
|
+
setMessages((prev) => [...prev, { content: responseText, isUser: false }]);
|
|
166
|
+
isFirstChunk = false;
|
|
167
|
+
} else {
|
|
168
|
+
setMessages((prev) => {
|
|
169
|
+
const newMessages = [...prev];
|
|
170
|
+
newMessages[newMessages.length - 1] = {
|
|
171
|
+
...newMessages[newMessages.length - 1],
|
|
172
|
+
content: responseText,
|
|
173
|
+
};
|
|
174
|
+
return newMessages;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
// Add scroll after each chunk
|
|
178
|
+
scrollToBottom();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Only add to session store after complete response
|
|
183
|
+
if (currentSession) {
|
|
184
|
+
console.log('currentSession', currentSession);
|
|
185
|
+
addMessageToSession(currentSession.id, responseText, false);
|
|
186
|
+
}
|
|
187
|
+
} catch (error: any) {
|
|
188
|
+
if (error.message?.includes('cancelled')) {
|
|
189
|
+
console.log('Completion was stopped by the user');
|
|
190
|
+
} else {
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
setIsThinking(false);
|
|
196
|
+
setIsStreaming(false);
|
|
197
|
+
completionRef.current = null;
|
|
198
|
+
} catch (error: any) {
|
|
199
|
+
console.error('Error:', error);
|
|
200
|
+
const errorMessage = { content: 'Sorry, there was an error processing your request.', isUser: false };
|
|
201
|
+
setMessages((prev) => [...prev, errorMessage]);
|
|
202
|
+
if (currentSession) {
|
|
203
|
+
addMessageToSession(currentSession.id, errorMessage.content, false);
|
|
204
|
+
}
|
|
205
|
+
setIsThinking(false);
|
|
206
|
+
setIsStreaming(false);
|
|
207
|
+
completionRef.current = null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const initProgressCallback = (report: InitProgressReport) => {
|
|
214
|
+
console.log('Loading LLM locally', report);
|
|
215
|
+
setLoadingProgress(Math.round(report.progress * 100));
|
|
216
|
+
if (report.progress === 1) {
|
|
217
|
+
setLoading(false);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
const initEngine = async () => {
|
|
223
|
+
try {
|
|
224
|
+
// Cache the LLMs text file
|
|
225
|
+
const engineCreator = CreateWebWorkerMLCEngine;
|
|
226
|
+
const newEngine = await engineCreator(
|
|
227
|
+
new Worker(new URL('./workers/engine.ts', import.meta.url), { type: 'module' }),
|
|
228
|
+
model,
|
|
229
|
+
{ initProgressCallback }
|
|
230
|
+
);
|
|
231
|
+
setEngine(newEngine);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error('Failed to initialize:', error);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const importDocuments = async () => {
|
|
238
|
+
const worker = new Worker(new URL('./workers/document-importer.ts', import.meta.url), { type: 'module' });
|
|
239
|
+
worker.postMessage({ init: true });
|
|
240
|
+
setVectorWorker(worker);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
importDocuments();
|
|
244
|
+
initEngine();
|
|
245
|
+
}, []);
|
|
246
|
+
|
|
247
|
+
// Helper function to format message content
|
|
248
|
+
const formatMessageContent = (content: string): string => {
|
|
249
|
+
// First handle code blocks
|
|
250
|
+
let formattedContent = content.replace(/```([\s\S]*?)```/g, (match, codeContent) => {
|
|
251
|
+
const escapedCode = codeContent.trim().replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
252
|
+
return `<pre class="bg-gray-800 border border-gray-700 p-4 my-3 rounded-lg overflow-x-auto"><code class="text-sm font-mono text-gray-200">${escapedCode}</code></pre>`;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Handle inline code
|
|
256
|
+
formattedContent = formattedContent.replace(/(?<!`)`([^`]+)`(?!`)/g, (match, code) => {
|
|
257
|
+
const escapedCode = code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
258
|
+
return `<code class="bg-gray-500 border border-gray-700 px-2 py-0.5 rounded text-sm font-mono text-gray-200">${escapedCode}</code>`;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Handle links
|
|
262
|
+
formattedContent = formattedContent.replace(
|
|
263
|
+
/<a\s+href="([^"]+)">/g,
|
|
264
|
+
'<a href="$1" class="text-purple-600 hover:underline" target="_blank" rel="noopener noreferrer">'
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
// Convert newlines to <br>
|
|
268
|
+
formattedContent = formattedContent.replace(/\n(?!<\/code>)/g, '<br>');
|
|
269
|
+
|
|
270
|
+
return formattedContent;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Add new function to handle smooth scrolling
|
|
274
|
+
const scrollToBottom = (smooth = true) => {
|
|
275
|
+
if (outputRef.current) {
|
|
276
|
+
outputRef.current.scrollTo({
|
|
277
|
+
top: outputRef.current.scrollHeight,
|
|
278
|
+
behavior: smooth ? 'smooth' : 'auto',
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Add effect to scroll when messages change
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
scrollToBottom();
|
|
286
|
+
}, [messages]);
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<div className="flex-1 flex flex-col overflow-hidden h-[calc(100vh-60px)] w-full">
|
|
290
|
+
{/* Chat Messages */}
|
|
291
|
+
<div id="output" ref={outputRef} className="flex-1 overflow-y-auto p-4 space-y-4 w-full mx-auto">
|
|
292
|
+
{showWelcome || messages.length === 0 ? (
|
|
293
|
+
<div id="welcomeMessage" className="flex justify-center items-center h-full">
|
|
294
|
+
<div className="text-center space-y-6 max-w-2xl px-4">
|
|
295
|
+
<div className="flex justify-center">
|
|
296
|
+
<BookOpen size={48} strokeWidth={1.5} className="text-gray-400" />
|
|
297
|
+
</div>
|
|
298
|
+
<div className="space-y-4">
|
|
299
|
+
<h1 className="text-3xl font-semibold text-gray-800">Ask questions about your architecture</h1>
|
|
300
|
+
<p className="text-sm text-gray-500">AI Models are local and do not leave your device.</p>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
) : (
|
|
305
|
+
<div className="space-y-4 max-w-[900px] mx-auto">
|
|
306
|
+
{messages.map((message, index) => (
|
|
307
|
+
<div key={index} className={`flex ${message.isUser ? 'justify-end' : 'justify-start'} mb-4`}>
|
|
308
|
+
<div
|
|
309
|
+
className={`max-w-[80%] rounded-lg p-3 ${
|
|
310
|
+
message.isUser ? 'bg-purple-600 text-white rounded-br-none' : 'bg-gray-100 text-gray-800 rounded-bl-none'
|
|
311
|
+
}`}
|
|
312
|
+
dangerouslySetInnerHTML={{ __html: formatMessageContent(message.content) }}
|
|
313
|
+
/>
|
|
314
|
+
</div>
|
|
315
|
+
))}
|
|
316
|
+
{isThinking && (
|
|
317
|
+
<div className="flex justify-start mb-4">
|
|
318
|
+
<div className="flex items-center space-x-2 max-w-[80%] rounded-lg p-3 bg-gray-100 text-gray-800 rounded-bl-none">
|
|
319
|
+
<div className="flex space-x-1">
|
|
320
|
+
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '0ms' }}></div>
|
|
321
|
+
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '150ms' }}></div>
|
|
322
|
+
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '300ms' }}></div>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
)}
|
|
327
|
+
</div>
|
|
328
|
+
)}
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
{/* Loading Status */}
|
|
332
|
+
{loading && (
|
|
333
|
+
<div className="max-w-[900px] mx-auto w-full px-4">
|
|
334
|
+
<div id="loadingStatus" className="mb-2 py-2 px-4 bg-gray-100 text-gray-700 rounded-lg text-center loading-status">
|
|
335
|
+
<span className="block">
|
|
336
|
+
Initializing AI model...
|
|
337
|
+
{loadingProgress > 0 && `(${loadingProgress}%)`}
|
|
338
|
+
</span>
|
|
339
|
+
<span className="block text-xs text-gray-500">
|
|
340
|
+
Loading model into your browser, this may take a minute or two. The first time it will take longer then the model is
|
|
341
|
+
cached.
|
|
342
|
+
</span>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
)}
|
|
346
|
+
|
|
347
|
+
{/* Input Area */}
|
|
348
|
+
<div className="border-t border-gray-200 p-4 bg-white">
|
|
349
|
+
<div className="max-w-[900px] mx-auto relative">
|
|
350
|
+
<input
|
|
351
|
+
type="text"
|
|
352
|
+
value={inputValue}
|
|
353
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
354
|
+
onKeyPress={(e) => {
|
|
355
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
356
|
+
e.preventDefault();
|
|
357
|
+
handleSubmit();
|
|
358
|
+
}
|
|
359
|
+
}}
|
|
360
|
+
placeholder="What events do we have?"
|
|
361
|
+
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"
|
|
362
|
+
disabled={loading || isStreaming}
|
|
363
|
+
/>
|
|
364
|
+
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
|
365
|
+
{isStreaming ? (
|
|
366
|
+
<button
|
|
367
|
+
onClick={handleStop}
|
|
368
|
+
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 text-sm font-medium"
|
|
369
|
+
>
|
|
370
|
+
Stop
|
|
371
|
+
</button>
|
|
372
|
+
) : (
|
|
373
|
+
<button
|
|
374
|
+
onClick={handleSubmit}
|
|
375
|
+
disabled={loading || isStreaming}
|
|
376
|
+
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"
|
|
377
|
+
>
|
|
378
|
+
{/* Add icon */}
|
|
379
|
+
<Send size={16} strokeWidth={1.5} className="mr-2" />
|
|
380
|
+
Send
|
|
381
|
+
</button>
|
|
382
|
+
)}
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
<div className="max-w-[900px] mx-auto flex justify-between">
|
|
386
|
+
{/* show what model is loaded */}
|
|
387
|
+
<p className="text-xs text-gray-400 mt-2">Model: {model}</p>
|
|
388
|
+
<p className="text-xs text-gray-500 mt-2">EventCatalog AI can make mistakes. Check important info.</p>
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
);
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
export default ChatWindow;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface Message {
|
|
5
|
+
content: string;
|
|
6
|
+
isUser: boolean;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ChatSession {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
messages: Message[];
|
|
14
|
+
lastUpdated: number;
|
|
15
|
+
createdAt: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ChatContextType {
|
|
19
|
+
sessions: ChatSession[];
|
|
20
|
+
currentSession: ChatSession | null;
|
|
21
|
+
createSession: () => void;
|
|
22
|
+
updateSession: (session: ChatSession) => void;
|
|
23
|
+
deleteSession: (id: string) => void;
|
|
24
|
+
addMessageToSession: (sessionId: string, content: string, isUser: boolean) => void;
|
|
25
|
+
setCurrentSession: (session: ChatSession | null) => void;
|
|
26
|
+
isStreaming: boolean;
|
|
27
|
+
setIsStreaming: (isStreaming: boolean) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ChatContext = createContext<ChatContextType | undefined>(undefined);
|
|
31
|
+
|
|
32
|
+
const STORAGE_KEY = 'chat_sessions';
|
|
33
|
+
|
|
34
|
+
export function ChatProvider({ children }: { children: ReactNode }) {
|
|
35
|
+
const [sessions, setSessions] = useState<ChatSession[]>(() => {
|
|
36
|
+
if (typeof window !== 'undefined') {
|
|
37
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
38
|
+
return stored ? JSON.parse(stored) : [];
|
|
39
|
+
}
|
|
40
|
+
return [];
|
|
41
|
+
});
|
|
42
|
+
const [currentSession, setCurrentSession] = useState<ChatSession | null>(null);
|
|
43
|
+
const [isStreaming, setIsStreaming] = useState(false);
|
|
44
|
+
|
|
45
|
+
const createSession = () => {
|
|
46
|
+
const newSession: ChatSession = {
|
|
47
|
+
id: crypto.randomUUID(),
|
|
48
|
+
title: `Chat ${sessions.length + 1}`,
|
|
49
|
+
messages: [],
|
|
50
|
+
lastUpdated: Date.now(),
|
|
51
|
+
createdAt: Date.now(),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
setSessions((prev) => {
|
|
55
|
+
const updated = [newSession, ...prev];
|
|
56
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
|
|
57
|
+
return updated;
|
|
58
|
+
});
|
|
59
|
+
setCurrentSession(newSession);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const updateSession = (session: ChatSession) => {
|
|
63
|
+
setSessions((prev) => {
|
|
64
|
+
const index = prev.findIndex((s) => s.id === session.id);
|
|
65
|
+
if (index === -1) return prev;
|
|
66
|
+
|
|
67
|
+
const updated = [...prev];
|
|
68
|
+
updated[index] = {
|
|
69
|
+
...session,
|
|
70
|
+
lastUpdated: Date.now(),
|
|
71
|
+
};
|
|
72
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
|
|
73
|
+
return updated;
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const deleteSession = (id: string) => {
|
|
78
|
+
setSessions((prev) => {
|
|
79
|
+
const updated = prev.filter((session) => session.id !== id);
|
|
80
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
|
|
81
|
+
return updated;
|
|
82
|
+
});
|
|
83
|
+
if (currentSession?.id === id) {
|
|
84
|
+
setCurrentSession(null);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const addMessageToSession = (sessionId: string, content: string, isUser: boolean) => {
|
|
89
|
+
setSessions((prev) => {
|
|
90
|
+
const index = prev.findIndex((s) => s.id === sessionId);
|
|
91
|
+
if (index === -1) return prev;
|
|
92
|
+
|
|
93
|
+
const updated = [...prev];
|
|
94
|
+
const session = { ...updated[index] };
|
|
95
|
+
session.messages.push({
|
|
96
|
+
content,
|
|
97
|
+
isUser,
|
|
98
|
+
timestamp: Date.now(),
|
|
99
|
+
});
|
|
100
|
+
session.lastUpdated = Date.now();
|
|
101
|
+
updated[index] = session;
|
|
102
|
+
|
|
103
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
|
|
104
|
+
return updated;
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (sessions.length > 0) {
|
|
110
|
+
setCurrentSession(sessions[0]);
|
|
111
|
+
} else {
|
|
112
|
+
createSession();
|
|
113
|
+
}
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<ChatContext.Provider
|
|
118
|
+
value={{
|
|
119
|
+
sessions,
|
|
120
|
+
currentSession,
|
|
121
|
+
createSession,
|
|
122
|
+
updateSession,
|
|
123
|
+
deleteSession,
|
|
124
|
+
addMessageToSession,
|
|
125
|
+
setCurrentSession,
|
|
126
|
+
isStreaming,
|
|
127
|
+
setIsStreaming,
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
{children}
|
|
131
|
+
</ChatContext.Provider>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function useChat() {
|
|
136
|
+
const context = useContext(ChatContext);
|
|
137
|
+
if (context === undefined) {
|
|
138
|
+
throw new Error('useChat must be used within a ChatProvider');
|
|
139
|
+
}
|
|
140
|
+
return context;
|
|
141
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { HuggingFaceTransformersEmbeddings } from '@langchain/community/embeddings/huggingface_transformers';
|
|
2
|
+
import { MemoryVectorStore } from 'langchain/vectorstores/memory';
|
|
3
|
+
|
|
4
|
+
const embeddingsInstance = new HuggingFaceTransformersEmbeddings({ model: 'Xenova/all-MiniLM-L6-v2' });
|
|
5
|
+
|
|
6
|
+
// Create the vector store
|
|
7
|
+
const vectorStore = new MemoryVectorStore(embeddingsInstance);
|
|
8
|
+
|
|
9
|
+
let documents: any;
|
|
10
|
+
let embeddings: any;
|
|
11
|
+
|
|
12
|
+
self.onmessage = async (event) => {
|
|
13
|
+
try {
|
|
14
|
+
// Initialize the vector store
|
|
15
|
+
if (event?.data?.init && !documents && !embeddings) {
|
|
16
|
+
const documentsImport = await fetch(`/generated/ai/documents.json`);
|
|
17
|
+
const embeddingsImport = await fetch(`/generated/ai/embeddings.json`);
|
|
18
|
+
|
|
19
|
+
documents = await documentsImport.json();
|
|
20
|
+
embeddings = await embeddingsImport.json();
|
|
21
|
+
|
|
22
|
+
await vectorStore.addVectors(embeddings, documents);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!event?.data?.input) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Get the results
|
|
30
|
+
const results = await vectorStore.similaritySearchWithScore(event.data.input, 10);
|
|
31
|
+
postMessage({ results: results, action: 'search-results' });
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.log(error);
|
|
34
|
+
self.postMessage({ error: (error as Error).message });
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -5,7 +5,7 @@ interface Props {
|
|
|
5
5
|
description?: string;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
import { BookOpenText, Workflow, TableProperties, House, BookUser } from 'lucide-react';
|
|
8
|
+
import { BookOpenText, Workflow, TableProperties, House, BookUser, MessageSquare, BotMessageSquare } from 'lucide-react';
|
|
9
9
|
import Header from '../components/Header.astro';
|
|
10
10
|
import SEO from '../components/Seo.astro';
|
|
11
11
|
import SideNav from '../components/SideNav/SideNav.astro';
|
|
@@ -92,6 +92,13 @@ const navigationItems = [
|
|
|
92
92
|
current: currentPath.includes('/directory'),
|
|
93
93
|
sidebar: false,
|
|
94
94
|
},
|
|
95
|
+
{
|
|
96
|
+
id: '/chat',
|
|
97
|
+
label: 'AI Assistant',
|
|
98
|
+
icon: BotMessageSquare,
|
|
99
|
+
href: buildUrl('/chat'),
|
|
100
|
+
current: currentPath.includes('/chat'),
|
|
101
|
+
},
|
|
95
102
|
];
|
|
96
103
|
|
|
97
104
|
const currentNavigationItem = navigationItems.find((item) => item.current);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
|
|
3
|
+
import Chat from '@enterprise/ai-assistant/components/Chat';
|
|
4
|
+
import config from '@config';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
|
|
8
|
+
import { Code } from 'astro-expressive-code/components';
|
|
9
|
+
|
|
10
|
+
const isEnabled = config.chat?.enabled || false;
|
|
11
|
+
|
|
12
|
+
const PROJECT_DIR = path.resolve(process.env.PROJECT_DIR || process.cwd());
|
|
13
|
+
const GENERATED_AI_DIR = path.resolve(PROJECT_DIR, 'public/ai');
|
|
14
|
+
|
|
15
|
+
const directoryExists = fs.existsSync(GENERATED_AI_DIR);
|
|
16
|
+
|
|
17
|
+
const generatorConfig = `
|
|
18
|
+
generators: [
|
|
19
|
+
[
|
|
20
|
+
"@eventcatalog/generator-ai", {
|
|
21
|
+
// If you want to chunk files into smaller chunks, set this to true
|
|
22
|
+
splitMarkdownFiles: true
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
],
|
|
26
|
+
`;
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
<VerticalSideBarLayout title="AI Chat">
|
|
30
|
+
<div class="flex h-[calc(100vh-60px)] bg-white">
|
|
31
|
+
{
|
|
32
|
+
isEnabled ? (
|
|
33
|
+
directoryExists ? (
|
|
34
|
+
<Chat client:only="react" />
|
|
35
|
+
) : (
|
|
36
|
+
<div class="flex items-center justify-center w-full p-4 sm:p-8">
|
|
37
|
+
<div class="max-w-2xl text-center">
|
|
38
|
+
<h2 class="text-2xl font-bold text-gray-900 mb-4">EventCatalog AI Assistant Setup Required</h2>
|
|
39
|
+
<p class="text-gray-600 mb-6">
|
|
40
|
+
To use the AI Assistant, you need to generate the AI data first.
|
|
41
|
+
<br />
|
|
42
|
+
Please install the plugin and run the generator command to generate the AI data.
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
<div class="text-left space-y-4">
|
|
46
|
+
<div class="space-y-2">
|
|
47
|
+
<h2 class="text-lg font-semibold text-left">1. Install the generator</h2>
|
|
48
|
+
<Code code={`npm i @eventcatalog/generator-ai`} lang="bash" frame="terminal" />
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="space-y-2">
|
|
52
|
+
<h2 class="text-lg font-semibold text-left">2. Configure the generator</h2>
|
|
53
|
+
<Code code={generatorConfig} lang="javascript" frame="terminal" title="eventcatalog.config.js" />
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="space-y-2">
|
|
57
|
+
<h2 class="text-lg font-semibold text-left">3. Generate the AI data</h2>
|
|
58
|
+
<Code code={`npm run generate`} lang="bash" frame="none" />
|
|
59
|
+
</div>
|
|
60
|
+
<p class="text-gray-600 text-sm mt-2">
|
|
61
|
+
After running the generator, a new folder called <code class="bg-gray-100 p-1 rounded-md">generated-ai</code>{' '}
|
|
62
|
+
will be created in your project.
|
|
63
|
+
</p>
|
|
64
|
+
|
|
65
|
+
<p class="text-gray-600 text-sm mt-2">Once done, refresh this page.</p>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
) : (
|
|
71
|
+
<div class="flex items-center justify-center w-full p-4 sm:p-8">
|
|
72
|
+
<div class="max-w-6xl flex flex-col md:flex-row gap-8 md:gap-12 items-center">
|
|
73
|
+
{/* Left side - Illustration */}
|
|
74
|
+
<div class="flex-1">
|
|
75
|
+
<img
|
|
76
|
+
src="/images/ai-placeholder.png"
|
|
77
|
+
alt="AI Assistant Illustration"
|
|
78
|
+
class="w-full max-w-sm md:max-w-md mx-auto rounded-lg shadow-lg"
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{/* Right side - Content */}
|
|
83
|
+
<div class="flex-1">
|
|
84
|
+
<h2 class="text-2xl md:text-3xl font-bold text-gray-900 mb-4">EventCatalog AI Assistant</h2>
|
|
85
|
+
<p class="text-gray-600 mb-6 text-base md:text-lg">
|
|
86
|
+
Chat with your catalog to quickly find information, understand relationships, and get instant answers about your
|
|
87
|
+
architecture. Our AI-powered assistant makes exploring your event catalog faster and more intuitive than ever.
|
|
88
|
+
</p>
|
|
89
|
+
|
|
90
|
+
<div class="bg-gray-700 rounded-lg p-4 md:p-6 mb-4">
|
|
91
|
+
<h3 class="text-lg text-white font-semibold mb-3">Quick Setup</h3>
|
|
92
|
+
<Code code={`npm install @eventcatalog/generator-ai`} lang="bash" frame="none" />
|
|
93
|
+
<a href="https://www.eventcatalog.dev/ai-assistant" class="inline-flex items-center text-sm text-white mt-4">
|
|
94
|
+
Learn more about setup →
|
|
95
|
+
</a>
|
|
96
|
+
</div>
|
|
97
|
+
<p class="text-gray-600 mb-6 text-sm">
|
|
98
|
+
Your data stays private and secure - all processing happens locally on your machine.
|
|
99
|
+
</p>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
</div>
|
|
106
|
+
</VerticalSideBarLayout>
|
|
107
|
+
|
|
108
|
+
<style>
|
|
109
|
+
.loading-status.ready {
|
|
110
|
+
background-color: rgb(240 253 244); /* light green bg */
|
|
111
|
+
color: rgb(22 163 74); /* green text */
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.loading-status.hidden {
|
|
115
|
+
display: none;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Add smooth scrolling behavior */
|
|
119
|
+
.scroll-smooth {
|
|
120
|
+
scroll-behavior: smooth;
|
|
121
|
+
}
|
|
122
|
+
</style>
|
|
@@ -15,8 +15,6 @@ export async function getStaticPaths() {
|
|
|
15
15
|
return [];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
console.log(teams);
|
|
19
|
-
|
|
20
18
|
return teams.map((team) => ({
|
|
21
19
|
params: { type: 'teams', id: team.data.id },
|
|
22
20
|
props: { content: team },
|
|
@@ -29,8 +27,6 @@ export const GET: APIRoute = async ({ params, props }) => {
|
|
|
29
27
|
return new Response('llms.txt is not enabled for this Catalog.', { status: 404 });
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
console.log(props.context);
|
|
33
|
-
|
|
34
30
|
if (props?.content?.data?.pathToFile) {
|
|
35
31
|
const file = fs.readFileSync(props.content.data.pathToFile, 'utf8');
|
|
36
32
|
return new Response(file, { status: 200 });
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"@catalog/components/*": ["src/custom-defined-components/*"],
|
|
12
12
|
"@types": ["src/types/index.ts"],
|
|
13
13
|
"@utils/*": ["src/utils/*"],
|
|
14
|
-
"@layouts/*": ["src/layouts/*"]
|
|
14
|
+
"@layouts/*": ["src/layouts/*"],
|
|
15
|
+
"@enterprise/*": ["src/enterprise/*"],
|
|
16
|
+
"@ai/*": ["src/generated-ai/*"]
|
|
15
17
|
},
|
|
16
18
|
"jsx": "react-jsx",
|
|
17
19
|
"jsxImportSource": "react",
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"url": "https://github.com/event-catalog/eventcatalog.git"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.24.1",
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
@@ -29,10 +29,15 @@
|
|
|
29
29
|
"@asyncapi/avro-schema-parser": "^3.0.24",
|
|
30
30
|
"@asyncapi/parser": "^3.4.0",
|
|
31
31
|
"@asyncapi/react-component": "^2.4.3",
|
|
32
|
+
"@eventcatalog/generator-ai": "^0.1.1",
|
|
32
33
|
"@headlessui/react": "^2.0.3",
|
|
33
34
|
"@heroicons/react": "^2.1.3",
|
|
35
|
+
"@huggingface/transformers": "^3.3.3",
|
|
34
36
|
"@iconify-json/logos": "^1.2.4",
|
|
37
|
+
"@langchain/community": "^0.3.32",
|
|
38
|
+
"@mlc-ai/web-llm": "^0.2.78",
|
|
35
39
|
"@parcel/watcher": "^2.4.1",
|
|
40
|
+
"@radix-ui/react-dialog": "^1.1.6",
|
|
36
41
|
"@stoplight/json-schema-viewer": "^4.16.4",
|
|
37
42
|
"@tailwindcss/typography": "^0.5.13",
|
|
38
43
|
"@tanstack/react-table": "^8.17.3",
|
|
@@ -47,12 +52,14 @@
|
|
|
47
52
|
"concurrently": "^8.2.2",
|
|
48
53
|
"cross-env": "^7.0.3",
|
|
49
54
|
"dagre": "^0.8.5",
|
|
55
|
+
"date-fns": "^4.1.0",
|
|
50
56
|
"diff": "^7.0.0",
|
|
51
57
|
"diff2html": "^3.4.48",
|
|
52
58
|
"glob": "^10.4.1",
|
|
53
59
|
"gray-matter": "^4.0.3",
|
|
54
60
|
"html-to-image": "^1.11.11",
|
|
55
61
|
"js-yaml": "^4.1.0",
|
|
62
|
+
"langchain": "^0.3.19",
|
|
56
63
|
"lodash.debounce": "^4.0.8",
|
|
57
64
|
"lodash.merge": "4.6.2",
|
|
58
65
|
"lucide-react": "^0.453.0",
|
|
@@ -108,6 +115,8 @@
|
|
|
108
115
|
"preview": "astro preview",
|
|
109
116
|
"astro": "astro",
|
|
110
117
|
"start:catalog": "node scripts/start-catalog-locally.js",
|
|
118
|
+
"preview:catalog": "node scripts/preview-catalog-locally.js",
|
|
119
|
+
"generate:catalog": "node scripts/generate-catalog-locally.js",
|
|
111
120
|
"verify-build:catalog": "rimraf dist && pnpm run build:cd",
|
|
112
121
|
"changeset": "changeset",
|
|
113
122
|
"generate": "pnpm run build:bin && npx . generate",
|