@eventcatalog/core 2.60.0 → 2.61.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 (33) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-WASYSRBV.js → chunk-3CAAQQUB.js} +1 -1
  6. package/dist/{chunk-QMWI6M2I.js → chunk-I2B6B3XZ.js} +1 -1
  7. package/dist/{chunk-6FRDYEMA.js → chunk-OEWPHKD6.js} +1 -1
  8. package/dist/constants.cjs +1 -1
  9. package/dist/constants.js +1 -1
  10. package/dist/eventcatalog.cjs +2 -2
  11. package/dist/eventcatalog.js +4 -4
  12. package/eventcatalog/astro.config.mjs +1 -0
  13. package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +19 -9
  14. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +227 -69
  15. package/eventcatalog/src/enterprise/eventcatalog-chat/components/hooks/ChatProvider.tsx +4 -3
  16. package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +46 -176
  17. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/chat.ts +59 -0
  18. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +0 -37
  19. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +83 -4
  20. package/eventcatalog/src/middleware-auth.ts +13 -2
  21. package/eventcatalog/src/pages/chat/feature.astro +24 -16
  22. package/eventcatalog/src/pages/docs/[type]/[id]/[version].mdx.ts +22 -11
  23. package/eventcatalog/src/pages/docs/llm/llms-full.txt.ts +3 -3
  24. package/eventcatalog/src/utils/feature.ts +2 -1
  25. package/eventcatalog/src/utils/url-builder.ts +9 -0
  26. package/package.json +7 -10
  27. package/eventcatalog/src/enterprise/eventcatalog-chat/EventCatalogVectorStore.ts +0 -66
  28. package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.client.tsx +0 -540
  29. package/eventcatalog/src/enterprise/eventcatalog-chat/components/workers/document-importer.ts +0 -38
  30. package/eventcatalog/src/enterprise/eventcatalog-chat/components/workers/engine.ts +0 -7
  31. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/ai/chat.ts +0 -54
  32. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/ai/resources.ts +0 -42
  33. package/eventcatalog/src/enterprise/eventcatalog-chat/utils/ai.ts +0 -112
@@ -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.60.0";
40
+ var version = "2.61.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-QMWI6M2I.js";
4
- import "../chunk-WASYSRBV.js";
3
+ } from "../chunk-I2B6B3XZ.js";
4
+ import "../chunk-3CAAQQUB.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.60.0";
109
+ var version = "2.61.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-6FRDYEMA.js";
4
- import "../chunk-QMWI6M2I.js";
5
- import "../chunk-WASYSRBV.js";
3
+ } from "../chunk-OEWPHKD6.js";
4
+ import "../chunk-I2B6B3XZ.js";
5
+ import "../chunk-3CAAQQUB.js";
6
6
  import "../chunk-UPONRQSN.js";
7
7
  export {
8
8
  log_build_default as default
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "2.60.0";
2
+ var version = "2.61.0";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-WASYSRBV.js";
3
+ } from "./chunk-3CAAQQUB.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import axios from "axios";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-QMWI6M2I.js";
3
+ } from "./chunk-I2B6B3XZ.js";
4
4
  import {
5
5
  getEventCatalogConfigFile,
6
6
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "2.60.0";
28
+ var version = "2.61.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-WASYSRBV.js";
3
+ } from "./chunk-3CAAQQUB.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -157,7 +157,7 @@ var import_axios = __toESM(require("axios"), 1);
157
157
  var import_os = __toESM(require("os"), 1);
158
158
 
159
159
  // package.json
160
- var version = "2.60.0";
160
+ var version = "2.61.0";
161
161
 
162
162
  // src/constants.ts
163
163
  var VERSION = version;
@@ -563,7 +563,7 @@ var copyServerFiles = async () => {
563
563
  if (!isServerOutput) {
564
564
  return;
565
565
  }
566
- import_fs2.default.cpSync((0, import_node_path6.join)(eventCatalogDir, "src/enterprise/eventcatalog-chat/pages/api"), (0, import_node_path6.join)(core, "src/pages/api/server"), {
566
+ import_fs2.default.cpSync((0, import_node_path6.join)(eventCatalogDir, "src/enterprise/eventcatalog-chat/pages/api"), (0, import_node_path6.join)(core, "src/pages/api"), {
567
567
  recursive: true
568
568
  });
569
569
  };
@@ -6,8 +6,8 @@ import {
6
6
  } from "./chunk-PLNJC7NZ.js";
7
7
  import {
8
8
  log_build_default
9
- } from "./chunk-6FRDYEMA.js";
10
- import "./chunk-QMWI6M2I.js";
9
+ } from "./chunk-OEWPHKD6.js";
10
+ import "./chunk-I2B6B3XZ.js";
11
11
  import {
12
12
  catalogToAstro,
13
13
  checkAndConvertMdToMdx
@@ -15,7 +15,7 @@ import {
15
15
  import "./chunk-55D645EH.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-WASYSRBV.js";
18
+ } from "./chunk-3CAAQQUB.js";
19
19
  import {
20
20
  getProjectOutDir,
21
21
  isAuthEnabled,
@@ -78,7 +78,7 @@ var copyServerFiles = async () => {
78
78
  if (!isServerOutput) {
79
79
  return;
80
80
  }
81
- fs.cpSync(join(eventCatalogDir, "src/enterprise/eventcatalog-chat/pages/api"), join(core, "src/pages/api/server"), {
81
+ fs.cpSync(join(eventCatalogDir, "src/enterprise/eventcatalog-chat/pages/api"), join(core, "src/pages/api"), {
82
82
  recursive: true
83
83
  });
84
84
  };
@@ -1,3 +1,4 @@
1
+
1
2
  import { defineConfig } from 'astro/config';
2
3
  import tailwind from '@astrojs/tailwind';
3
4
  import mdx from '@astrojs/mdx';
@@ -1,6 +1,5 @@
1
1
  import Sidebar from './ChatSidebar';
2
2
  import { ChatProvider } from './hooks/ChatProvider';
3
- import ChatWindowWebLLM from './windows/ChatWindow.client';
4
3
  import ChatWindowServer from './windows/ChatWindow.server';
5
4
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
6
5
  import type { ChatPromptCategoryGroup } from '@enterprise/eventcatalog-chat/utils/chat-prompts';
@@ -12,7 +11,6 @@ interface Resource {
12
11
 
13
12
  /**
14
13
  * Chat component has two modes:
15
- * - Client: ChatWindow.client.tsx (uses webllm)
16
14
  * - Server: ChatWindow.server.tsx (uses server-side code, bring your own API key)
17
15
  *
18
16
  * The mode is determined by the config.output property in the eventcatalog.config.js file.
@@ -31,17 +29,29 @@ const Chat = ({
31
29
  }) => {
32
30
  const queryClient = new QueryClient();
33
31
 
32
+ if (output !== 'server') {
33
+ return (
34
+ // Message to turn on server side
35
+ <div className="flex justify-center items-center h-full bg-gray-100 p-4 rounded-lg text-center">
36
+ <div className="space-y-4">
37
+ <h1 className="text-3xl font-semibold text-gray-800">Chat is only supported on server side</h1>
38
+ <p className="text-sm text-gray-500">
39
+ Please switch to server side by setting the <code className="font-mono bg-gray-100 p-0.5 rounded">output</code>{' '}
40
+ property to <code className="font-mono bg-gray-100 p-0.5 rounded">server</code> in your{' '}
41
+ <code className="font-mono bg-gray-100 p-0.5 rounded">eventcatalog.config.js</code> file.
42
+ </p>
43
+ </div>
44
+ </div>
45
+ );
46
+ }
47
+
34
48
  return (
35
49
  <ChatProvider>
36
50
  <div className="flex overflow-hidden w-full">
37
51
  <Sidebar />
38
- {output === 'server' ? (
39
- <QueryClientProvider client={queryClient}>
40
- <ChatWindowServer {...chatConfig} resources={resources} chatPrompts={chatPrompts} />
41
- </QueryClientProvider>
42
- ) : (
43
- <ChatWindowWebLLM {...chatConfig} />
44
- )}
52
+ <QueryClientProvider client={queryClient}>
53
+ <ChatWindowServer {...chatConfig} resources={resources} chatPrompts={chatPrompts} />
54
+ </QueryClientProvider>
45
55
  </div>
46
56
  </ChatProvider>
47
57
  );
@@ -24,45 +24,21 @@ function escapeRegex(string: string): string {
24
24
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
25
25
  }
26
26
 
27
- // Define props for the code component explicitly
28
- interface CodeComponentProps extends React.HTMLAttributes<HTMLElement>, ExtraProps {
29
- inline?: boolean;
30
- }
27
+ // Function to parse and render content with iframes
28
+ function parseContentWithIframes(content: string): React.ReactNode[] {
29
+ const iframeRegex = /<iframe[^>]*>.*?<\/iframe>/gi;
30
+ const parts: React.ReactNode[] = [];
31
+ let lastIndex = 0;
32
+ let match;
31
33
 
32
- const ChatMessage = React.memo(({ message }: ChatMessageProps) => {
33
- const [isModalOpen, setIsModalOpen] = useState(false);
34
- const [modalContent, setModalContent] = useState<{ language: string; code: string } | null>(null);
35
- const [copiedStates, setCopiedStates] = useState<Record<string, boolean>>({}); // State for copy feedback
36
- const [isResourcesCollapsed, setIsResourcesCollapsed] = useState(true); // State for resource section collapse
37
- const [isContextCollapsed, setIsContextCollapsed] = useState(true); // State for additional context collapse
38
-
39
- // Helper to get display name for resource, ensuring a fallback
40
- const getResourceDisplayName = (resource: Resource): string => {
41
- return resource.title || resource.name || resource.id || 'Resource'; // Added fallback
42
- };
43
-
44
- const handleCopy = (codeToCopy: string, id: string) => {
45
- navigator.clipboard
46
- .writeText(codeToCopy)
47
- .then(() => {
48
- setCopiedStates((prev) => ({ ...prev, [id]: true }));
49
- setTimeout(() => {
50
- setCopiedStates((prev) => ({ ...prev, [id]: false }));
51
- }, 1500); // Reset after 1.5 seconds
52
- })
53
- .catch((err) => {
54
- console.error('Failed to copy code: ', err);
55
- });
56
- };
57
-
58
- return (
59
- <div className={`flex ${message.isUser ? 'justify-end' : 'justify-start'} mb-4`}>
60
- <div
61
- className={`max-w-[80%] rounded-lg p-3 ${message.isUser ? 'bg-purple-600 text-white rounded-br-none' : 'bg-gray-100 text-gray-800 rounded-bl-none'}`}
62
- >
63
- {/* Apply prose styles, including prose-invert for user messages for better text contrast */}
64
- <div className={`prose prose-sm max-w-none ${message.isUser ? 'prose-invert' : ''}`}>
34
+ while ((match = iframeRegex.exec(content)) !== null) {
35
+ // Add text before iframe as markdown
36
+ if (match.index > lastIndex) {
37
+ const textBefore = content.slice(lastIndex, match.index);
38
+ if (textBefore.trim()) {
39
+ parts.push(
65
40
  <ReactMarkdown
41
+ key={`text-${lastIndex}`}
66
42
  components={{
67
43
  code({ node, className, children, ...props }: CodeComponentProps) {
68
44
  const inline = props.inline;
@@ -70,37 +46,14 @@ const ChatMessage = React.memo(({ message }: ChatMessageProps) => {
70
46
  const codeString = String(children);
71
47
  const language = match ? match[1] : 'text';
72
48
  const codeBlockId = `code-${React.useId()}`;
73
- const isCopied = copiedStates[codeBlockId];
49
+ const isCopied = false; // We'll handle this in the parent component
74
50
 
75
- const handleOpenModal = () => {
76
- setModalContent({ language, code: codeString.replace(/\n$/, '') });
77
- setIsModalOpen(true);
78
- };
79
-
80
- // Heuristic: Treat as inline if it doesn't contain newlines OR if explicitly inline.
81
- // This handles parser quirks with single-line snippets in lists, etc.
82
51
  const treatAsInline = inline || !codeString.includes('\n');
83
52
 
84
53
  return !treatAsInline ? (
85
54
  <div className="code-block bg-[#1e1e1e] rounded-md overflow-hidden my-4 relative group">
86
55
  <div className="flex justify-between items-center px-4 py-1.5 bg-gray-700 text-gray-300 text-xs">
87
56
  <span>{language}</span>
88
- <div className="flex items-center gap-2">
89
- <button
90
- onClick={() => handleCopy(codeString.replace(/\n$/, ''), codeBlockId)}
91
- className="text-gray-300 hover:text-white flex items-center"
92
- aria-label={isCopied ? 'Copied' : 'Copy code'}
93
- >
94
- {isCopied ? <Check size={14} className="text-green-400" /> : <Clipboard size={14} />}
95
- </button>
96
- <button
97
- onClick={handleOpenModal}
98
- className="text-gray-300 hover:text-white"
99
- aria-label="View code fullscreen"
100
- >
101
- <Fullscreen size={14} />
102
- </button>
103
- </div>
104
57
  </div>
105
58
  <SyntaxHighlighter
106
59
  style={vscDarkPlus as any}
@@ -115,14 +68,9 @@ const ChatMessage = React.memo(({ message }: ChatMessageProps) => {
115
68
  </div>
116
69
  ) : (
117
70
  <code
118
- className={`px-1 py-0.5 rounded text-xs font-mono ${
119
- message.isUser
120
- ? 'bg-purple-800/70 text-purple-100' // Darker purple bg, light text for user
121
- : 'bg-gray-300/70 text-gray-900' // Darker gray bg, dark text for assistant
122
- } ${className || ''}`}
71
+ className={`px-1 py-0.5 rounded text-xs font-mono bg-gray-300/70 text-gray-900 ${className || ''}`}
123
72
  {...props}
124
73
  >
125
- {/* Render trimmed version for inline code */}
126
74
  {codeString.trim()}
127
75
  </code>
128
76
  );
@@ -133,9 +81,219 @@ const ChatMessage = React.memo(({ message }: ChatMessageProps) => {
133
81
  p: ({ node, ...props }) => <p {...props} className="mb-2 last:mb-0" />,
134
82
  }}
135
83
  >
136
- {/* Use raw content again */}
137
- {message.content || ''}
84
+ {textBefore}
138
85
  </ReactMarkdown>
86
+ );
87
+ }
88
+ }
89
+
90
+ // Parse iframe attributes
91
+ const iframeHtml = match[0];
92
+ const srcMatch = iframeHtml.match(/src=["']([^"']+)["']/i);
93
+ const widthMatch = iframeHtml.match(/width=["']([^"']+)["']/i);
94
+ const heightMatch = iframeHtml.match(/height=["']([^"']+)["']/i);
95
+ const titleMatch = iframeHtml.match(/title=["']([^"']+)["']/i);
96
+
97
+ if (srcMatch) {
98
+ parts.push(
99
+ <iframe
100
+ key={`iframe-${match.index}`}
101
+ src={srcMatch[1]}
102
+ width={widthMatch ? widthMatch[1] : '100%'}
103
+ height={heightMatch ? heightMatch[1] : '400'}
104
+ title={titleMatch ? titleMatch[1] : 'Embedded content'}
105
+ className="w-full h-96 border rounded-lg my-4"
106
+ sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
107
+ loading="lazy"
108
+ />
109
+ );
110
+ }
111
+
112
+ lastIndex = match.index + match[0].length;
113
+ }
114
+
115
+ // Add remaining text after last iframe
116
+ if (lastIndex < content.length) {
117
+ const textAfter = content.slice(lastIndex);
118
+ if (textAfter.trim()) {
119
+ parts.push(
120
+ <ReactMarkdown
121
+ key={`text-${lastIndex}`}
122
+ components={{
123
+ code({ node, className, children, ...props }: CodeComponentProps) {
124
+ const inline = props.inline;
125
+ const match = /language-(\w+)/.exec(className || '');
126
+ const codeString = String(children);
127
+ const language = match ? match[1] : 'text';
128
+ const codeBlockId = `code-${React.useId()}`;
129
+ const isCopied = false;
130
+
131
+ const treatAsInline = inline || !codeString.includes('\n');
132
+
133
+ return !treatAsInline ? (
134
+ <div className="code-block bg-[#1e1e1e] rounded-md overflow-hidden my-4 relative group">
135
+ <div className="flex justify-between items-center px-4 py-1.5 bg-gray-700 text-gray-300 text-xs">
136
+ <span>{language}</span>
137
+ </div>
138
+ <SyntaxHighlighter
139
+ style={vscDarkPlus as any}
140
+ language={language}
141
+ PreTag="div"
142
+ showLineNumbers
143
+ wrapLines={true}
144
+ customStyle={{ margin: 0, borderRadius: '0 0 0.375rem 0.375rem', padding: '1rem' }}
145
+ >
146
+ {codeString.replace(/\n$/, '')}
147
+ </SyntaxHighlighter>
148
+ </div>
149
+ ) : (
150
+ <code
151
+ className={`px-1 py-0.5 rounded text-xs font-mono bg-gray-300/70 text-gray-900 ${className || ''}`}
152
+ {...props}
153
+ >
154
+ {codeString.trim()}
155
+ </code>
156
+ );
157
+ },
158
+ a: ({ node, ...props }) => (
159
+ <a {...props} target="_blank" rel="noopener noreferrer" className="text-purple-600 hover:text-purple-800" />
160
+ ),
161
+ p: ({ node, ...props }) => <p {...props} className="mb-2 last:mb-0" />,
162
+ }}
163
+ >
164
+ {textAfter}
165
+ </ReactMarkdown>
166
+ );
167
+ }
168
+ }
169
+
170
+ return parts;
171
+ }
172
+
173
+ // Define props for the code component explicitly
174
+ interface CodeComponentProps extends React.HTMLAttributes<HTMLElement>, ExtraProps {
175
+ inline?: boolean;
176
+ }
177
+
178
+ const ChatMessage = React.memo(({ message }: ChatMessageProps) => {
179
+ const [isModalOpen, setIsModalOpen] = useState(false);
180
+ const [modalContent, setModalContent] = useState<{ language: string; code: string } | null>(null);
181
+ const [copiedStates, setCopiedStates] = useState<Record<string, boolean>>({}); // State for copy feedback
182
+ const [isResourcesCollapsed, setIsResourcesCollapsed] = useState(true); // State for resource section collapse
183
+ const [isContextCollapsed, setIsContextCollapsed] = useState(true); // State for additional context collapse
184
+
185
+ // Helper to get display name for resource, ensuring a fallback
186
+ const getResourceDisplayName = (resource: Resource): string => {
187
+ return resource.title || resource.name || resource.id || 'Resource'; // Added fallback
188
+ };
189
+
190
+ const handleCopy = (codeToCopy: string, id: string) => {
191
+ navigator.clipboard
192
+ .writeText(codeToCopy)
193
+ .then(() => {
194
+ setCopiedStates((prev) => ({ ...prev, [id]: true }));
195
+ setTimeout(() => {
196
+ setCopiedStates((prev) => ({ ...prev, [id]: false }));
197
+ }, 1500); // Reset after 1.5 seconds
198
+ })
199
+ .catch((err) => {
200
+ console.error('Failed to copy code: ', err);
201
+ });
202
+ };
203
+
204
+ return (
205
+ <div className={`flex ${message.isUser ? 'justify-end' : 'justify-start'} mb-4`}>
206
+ <div
207
+ className={`max-w-[80%] rounded-lg p-3 ${message.isUser ? 'bg-purple-600 text-white rounded-br-none' : 'bg-gray-100 text-gray-800 rounded-bl-none'}`}
208
+ >
209
+ {/* Apply prose styles, including prose-invert for user messages for better text contrast */}
210
+ <div className={`prose prose-sm max-w-none ${message.isUser ? 'prose-invert' : ''}`}>
211
+ {(() => {
212
+ const content = message.content || '';
213
+ const iframeRegex = /<iframe[^>]*>.*?<\/iframe>/gi;
214
+
215
+ // If content contains iframes, use custom parser
216
+ if (iframeRegex.test(content)) {
217
+ return parseContentWithIframes(content);
218
+ }
219
+
220
+ // Otherwise, use regular ReactMarkdown
221
+ return (
222
+ <ReactMarkdown
223
+ components={{
224
+ code({ node, className, children, ...props }: CodeComponentProps) {
225
+ const inline = props.inline;
226
+ const match = /language-(\w+)/.exec(className || '');
227
+ const codeString = String(children);
228
+ const language = match ? match[1] : 'text';
229
+ const codeBlockId = `code-${React.useId()}`;
230
+ const isCopied = copiedStates[codeBlockId];
231
+
232
+ const handleOpenModal = () => {
233
+ setModalContent({ language, code: codeString.replace(/\n$/, '') });
234
+ setIsModalOpen(true);
235
+ };
236
+
237
+ // Heuristic: Treat as inline if it doesn't contain newlines OR if explicitly inline.
238
+ // This handles parser quirks with single-line snippets in lists, etc.
239
+ const treatAsInline = inline || !codeString.includes('\n');
240
+
241
+ return !treatAsInline ? (
242
+ <div className="code-block bg-[#1e1e1e] rounded-md overflow-hidden my-4 relative group">
243
+ <div className="flex justify-between items-center px-4 py-1.5 bg-gray-700 text-gray-300 text-xs">
244
+ <span>{language}</span>
245
+ <div className="flex items-center gap-2">
246
+ <button
247
+ onClick={() => handleCopy(codeString.replace(/\n$/, ''), codeBlockId)}
248
+ className="text-gray-300 hover:text-white flex items-center"
249
+ aria-label={isCopied ? 'Copied' : 'Copy code'}
250
+ >
251
+ {isCopied ? <Check size={14} className="text-green-400" /> : <Clipboard size={14} />}
252
+ </button>
253
+ <button
254
+ onClick={handleOpenModal}
255
+ className="text-gray-300 hover:text-white"
256
+ aria-label="View code fullscreen"
257
+ >
258
+ <Fullscreen size={14} />
259
+ </button>
260
+ </div>
261
+ </div>
262
+ <SyntaxHighlighter
263
+ style={vscDarkPlus as any}
264
+ language={language}
265
+ PreTag="div"
266
+ showLineNumbers
267
+ wrapLines={true}
268
+ customStyle={{ margin: 0, borderRadius: '0 0 0.375rem 0.375rem', padding: '1rem' }}
269
+ >
270
+ {codeString.replace(/\n$/, '')}
271
+ </SyntaxHighlighter>
272
+ </div>
273
+ ) : (
274
+ <code
275
+ className={`px-1 py-0.5 rounded text-xs font-mono ${
276
+ message.isUser
277
+ ? 'bg-purple-800/70 text-purple-100' // Darker purple bg, light text for user
278
+ : 'bg-gray-300/70 text-gray-900' // Darker gray bg, dark text for assistant
279
+ } ${className || ''}`}
280
+ {...props}
281
+ >
282
+ {/* Render trimmed version for inline code */}
283
+ {codeString.trim()}
284
+ </code>
285
+ );
286
+ },
287
+ a: ({ node, ...props }) => (
288
+ <a {...props} target="_blank" rel="noopener noreferrer" className="text-purple-600 hover:text-purple-800" />
289
+ ),
290
+ p: ({ node, ...props }) => <p {...props} className="mb-2 last:mb-0" />,
291
+ }}
292
+ >
293
+ {content}
294
+ </ReactMarkdown>
295
+ );
296
+ })()}
139
297
  </div>
140
298
 
141
299
  {/* Additional Context section (for user messages) */}
@@ -1,3 +1,4 @@
1
+ import type { UIMessage } from '@ai-sdk/react';
1
2
  import React, { createContext, useContext, useEffect, useState } from 'react';
2
3
  import type { ReactNode } from 'react';
3
4
 
@@ -31,7 +32,7 @@ interface ChatContextType {
31
32
  setCurrentSession: (session: ChatSession | null) => void;
32
33
  isStreaming: boolean;
33
34
  setIsStreaming: (isStreaming: boolean) => void;
34
- storeMessagesToSession: (sessionId: string, messages: Message[]) => void;
35
+ storeMessagesToSession: (sessionId: string, messages: UIMessage[]) => void;
35
36
  }
36
37
 
37
38
  const ChatContext = createContext<ChatContextType | undefined>(undefined);
@@ -93,13 +94,13 @@ export function ChatProvider({ children }: { children: ReactNode }) {
93
94
  };
94
95
 
95
96
  // Set all messages to the session
96
- const storeMessagesToSession = (sessionId: string, messages: Message[]) => {
97
+ const storeMessagesToSession = (sessionId: string, messages: UIMessage[]) => {
97
98
  setSessions((prev) => {
98
99
  const index = prev.findIndex((s) => s.id === sessionId);
99
100
  if (index === -1) return prev;
100
101
 
101
102
  const updated = [...prev];
102
- updated[index].messages = messages;
103
+ updated[index].messages = messages as unknown as Message[];
103
104
  updated[index].lastUpdated = Date.now();
104
105
  localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
105
106
  return updated;