@eventcatalog/core 2.23.2 → 2.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/LICENSE.md +10 -1
  2. package/dist/analytics/analytics.cjs +1 -1
  3. package/dist/analytics/analytics.js +2 -2
  4. package/dist/analytics/log-build.cjs +1 -1
  5. package/dist/analytics/log-build.js +3 -3
  6. package/dist/{chunk-OXD723LJ.js → chunk-BV5L5YQN.js} +1 -1
  7. package/dist/{chunk-W3SPESBN.js → chunk-O4U4YIV4.js} +1 -1
  8. package/dist/{chunk-RMESKTDM.js → chunk-RX5LWHVR.js} +1 -1
  9. package/dist/constants.cjs +1 -1
  10. package/dist/constants.js +1 -1
  11. package/dist/eventcatalog.cjs +1 -1
  12. package/dist/eventcatalog.config.d.cts +5 -0
  13. package/dist/eventcatalog.config.d.ts +5 -0
  14. package/dist/eventcatalog.js +3 -3
  15. package/eventcatalog/astro.config.mjs +6 -0
  16. package/eventcatalog/public/images/ai-placeholder.png +0 -0
  17. package/eventcatalog/src/components/SideNav/TreeView/getTreeView.ts +2 -1
  18. package/eventcatalog/src/enterprise/LICENSE.txt +4 -0
  19. package/eventcatalog/src/enterprise/README.md +5 -0
  20. package/eventcatalog/src/enterprise/ai-assistant/components/Chat.tsx +16 -0
  21. package/eventcatalog/src/enterprise/ai-assistant/components/ChatSidebar.tsx +180 -0
  22. package/eventcatalog/src/enterprise/ai-assistant/components/ChatWindow.tsx +395 -0
  23. package/eventcatalog/src/enterprise/ai-assistant/components/hooks/ChatProvider.tsx +141 -0
  24. package/eventcatalog/src/enterprise/ai-assistant/components/workers/document-importer.ts +38 -0
  25. package/eventcatalog/src/enterprise/ai-assistant/components/workers/engine.ts +7 -0
  26. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +8 -1
  27. package/eventcatalog/src/pages/chat/index.astro +122 -0
  28. package/eventcatalog/src/pages/docs/teams/[id].md.ts +0 -4
  29. package/eventcatalog/tsconfig.json +3 -1
  30. package/package.json +9 -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-2024 boyney123
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
@@ -37,7 +37,7 @@ var import_axios = __toESM(require("axios"), 1);
37
37
  var import_os = __toESM(require("os"), 1);
38
38
 
39
39
  // package.json
40
- var version = "2.23.2";
40
+ var version = "2.24.0";
41
41
 
42
42
  // src/constants.ts
43
43
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-RMESKTDM.js";
4
- import "../chunk-OXD723LJ.js";
3
+ } from "../chunk-RX5LWHVR.js";
4
+ import "../chunk-BV5L5YQN.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -106,7 +106,7 @@ var import_axios = __toESM(require("axios"), 1);
106
106
  var import_os = __toESM(require("os"), 1);
107
107
 
108
108
  // package.json
109
- var version = "2.23.2";
109
+ var version = "2.24.0";
110
110
 
111
111
  // src/constants.ts
112
112
  var VERSION = version;
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-W3SPESBN.js";
4
- import "../chunk-RMESKTDM.js";
5
- import "../chunk-OXD723LJ.js";
3
+ } from "../chunk-O4U4YIV4.js";
4
+ import "../chunk-RX5LWHVR.js";
5
+ import "../chunk-BV5L5YQN.js";
6
6
  import "../chunk-E7TXTI7G.js";
7
7
  export {
8
8
  log_build_default as default
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "2.23.2";
2
+ var version = "2.24.0";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-RMESKTDM.js";
3
+ } from "./chunk-RX5LWHVR.js";
4
4
  import {
5
5
  getEventCatalogConfigFile,
6
6
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-OXD723LJ.js";
3
+ } from "./chunk-BV5L5YQN.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import axios from "axios";
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "2.23.2";
28
+ var version = "2.24.0";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-OXD723LJ.js";
3
+ } from "./chunk-BV5L5YQN.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -161,7 +161,7 @@ var import_axios = __toESM(require("axios"), 1);
161
161
  var import_os = __toESM(require("os"), 1);
162
162
 
163
163
  // package.json
164
- var version = "2.23.2";
164
+ var version = "2.24.0";
165
165
 
166
166
  // src/constants.ts
167
167
  var VERSION = version;
@@ -54,6 +54,11 @@ interface Config {
54
54
  mermaid?: {
55
55
  iconPacks?: string[];
56
56
  };
57
+ chat?: {
58
+ enabled: boolean;
59
+ model?: string;
60
+ max_tokens?: number;
61
+ };
57
62
  }
58
63
 
59
64
  export type { Config };
@@ -54,6 +54,11 @@ interface Config {
54
54
  mermaid?: {
55
55
  iconPacks?: string[];
56
56
  };
57
+ chat?: {
58
+ enabled: boolean;
59
+ model?: string;
60
+ max_tokens?: number;
61
+ };
57
62
  }
58
63
 
59
64
  export type { Config };
@@ -6,14 +6,14 @@ import {
6
6
  } from "./chunk-OW2FQPYP.js";
7
7
  import {
8
8
  log_build_default
9
- } from "./chunk-W3SPESBN.js";
10
- import "./chunk-RMESKTDM.js";
9
+ } from "./chunk-O4U4YIV4.js";
10
+ import "./chunk-RX5LWHVR.js";
11
11
  import {
12
12
  catalogToAstro
13
13
  } from "./chunk-CXKIF3EI.js";
14
14
  import {
15
15
  VERSION
16
- } from "./chunk-OXD723LJ.js";
16
+ } from "./chunk-BV5L5YQN.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,
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import os from 'node:os';
3
4
  import gm from 'gray-matter';
4
5
  import { globSync } from 'glob';
5
6
  import type { CollectionKey } from 'astro:content';
@@ -49,7 +50,7 @@ function buildTreeOfDir(directory: string, parentNode: TreeNode, options: { igno
49
50
 
50
51
  const resourceType = getResourceType(directory);
51
52
 
52
- const markdownFiles = globSync(path.join(directory, '/*.md'));
53
+ const markdownFiles = globSync(path.join(directory, '/*.md'), { windowsPathsNoEscape: os.platform() === 'win32' });
53
54
  const isResourceIgnored = options?.ignore && resourceType && options.ignore.includes(resourceType);
54
55
 
55
56
  if (markdownFiles.length > 0 && !isResourceIgnored) {
@@ -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 = ({ catalogPath }: { catalogPath: string }) => {
6
+ return (
7
+ <ChatProvider>
8
+ <div className="flex overflow-hidden w-full">
9
+ <Sidebar />
10
+ <ChatWindow catalogPath={catalogPath} />
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 = ({ catalogPath }: { catalogPath: string }) => {
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, catalogPath });
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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,38 @@
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 import(/* @vite-ignore */ `${event.data.catalogPath}/generated-ai/documents.json`);
17
+ const embeddingsImport = await import(/* @vite-ignore */ `${event.data.catalogPath}/generated-ai/embeddings.json`);
18
+
19
+ console.log('Loading documents and embeddings');
20
+ console.log(documentsImport);
21
+ console.log(embeddingsImport);
22
+
23
+ documents = documentsImport.default;
24
+ embeddings = embeddingsImport.default;
25
+ await vectorStore.addVectors(embeddings, documents);
26
+ }
27
+
28
+ if (!event?.data?.input) {
29
+ return;
30
+ }
31
+
32
+ // Get the results
33
+ const results = await vectorStore.similaritySearchWithScore(event.data.input, 10);
34
+ postMessage({ results: results, action: 'search-results' });
35
+ } catch (error) {
36
+ self.postMessage({ error: (error as Error).message });
37
+ }
38
+ };
@@ -0,0 +1,7 @@
1
+ import { WebWorkerMLCEngineHandler } from '@mlc-ai/web-llm';
2
+
3
+ // Hookup an Engine to a worker handler
4
+ const handler = new WebWorkerMLCEngineHandler();
5
+ self.onmessage = (msg: MessageEvent) => {
6
+ handler.onmessage(msg);
7
+ };
@@ -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, 'generated-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" catalogPath={PROJECT_DIR} />
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.23.2",
9
+ "version": "2.24.0",
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,7 @@
108
115
  "preview": "astro preview",
109
116
  "astro": "astro",
110
117
  "start:catalog": "node scripts/start-catalog-locally.js",
118
+ "generate:catalog": "node scripts/generate-catalog-locally.js",
111
119
  "verify-build:catalog": "rimraf dist && pnpm run build:cd",
112
120
  "changeset": "changeset",
113
121
  "generate": "pnpm run build:bin && npx . generate",