@eventcatalog/core 3.0.0-beta.23 → 3.0.0-beta.25
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/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-F6UWJOIP.js → chunk-3SWCGDD7.js} +1 -1
- package/dist/{chunk-C3DPW2RT.js → chunk-DFHXF3VF.js} +1 -1
- package/dist/{chunk-U5LM336G.js → chunk-NAW6EPCS.js} +1 -1
- package/dist/{chunk-AJIDWPJI.js → chunk-OJA6CNVO.js} +1 -1
- package/dist/{chunk-XPNI6MQE.js → chunk-RA6AYXGJ.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +289 -116
- package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +3 -3
- package/eventcatalog/src/components/Search/SearchDataLoader.astro +25 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +1 -1
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +1 -1
- package/eventcatalog/src/components/SideNav/SideNav.astro +0 -15
- package/eventcatalog/src/enterprise/ai/chat-api.ts +242 -144
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +126 -121
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +5 -2
- package/eventcatalog/src/pages/schemas/explorer/index.astro +2 -2
- package/eventcatalog/src/stores/{sidebar-store.ts → sidebar-store/index.ts} +1 -1
- package/eventcatalog/src/utils/collections/schemas.ts +14 -0
- package/package.json +2 -1
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/container.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/domain.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/flow.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/message.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/service.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/shared.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar/sidebar-builder.ts → stores/sidebar-store/state.ts} +0 -0
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { useEffect, useRef, useCallback, useState } from 'react';
|
|
2
|
-
import { X, Sparkles, Square, Trash2, BookOpen, Copy, Check, Maximize2, Minimize2 } from 'lucide-react';
|
|
1
|
+
import { useEffect, useRef, useCallback, useState, useMemo } from 'react';
|
|
2
|
+
import { X, Sparkles, Square, Trash2, BookOpen, Copy, Check, Maximize2, Minimize2, Wrench } from 'lucide-react';
|
|
3
3
|
import { useChat } from '@ai-sdk/react';
|
|
4
4
|
import ReactMarkdown from 'react-markdown';
|
|
5
5
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
6
6
|
import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
|
7
7
|
import { lastAssistantMessageIsCompleteWithToolCalls } from 'ai';
|
|
8
8
|
import * as Dialog from '@radix-ui/react-dialog';
|
|
9
|
+
import * as Popover from '@radix-ui/react-popover';
|
|
10
|
+
|
|
11
|
+
interface ToolMetadata {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
isCustom?: boolean;
|
|
15
|
+
}
|
|
9
16
|
|
|
10
17
|
// Code block component with copy functionality
|
|
11
18
|
const CodeBlock = ({ language, children }: { language: string; children: string }) => {
|
|
@@ -119,6 +126,52 @@ const suggestedQuestionsConfig: QuestionConfig[] = [
|
|
|
119
126
|
{ label: 'Who owns this domain?', prompt: 'Who owns this domain and how do I contact them?' },
|
|
120
127
|
],
|
|
121
128
|
},
|
|
129
|
+
// Match /schemas with specType=graphql as query parameter
|
|
130
|
+
{
|
|
131
|
+
pattern: /^\/schemas.*[?&]specType=graphql/,
|
|
132
|
+
questions: [
|
|
133
|
+
{ label: 'Tell me more about this GraphQL schema', prompt: 'Tell me more about this GraphQL schema' },
|
|
134
|
+
{ label: 'What queries are available?', prompt: 'What queries are available in this GraphQL schema?' },
|
|
135
|
+
{ label: 'What mutations are available?', prompt: 'What mutations are available in this GraphQL schema?' },
|
|
136
|
+
{ label: 'Show me the types defined', prompt: 'Show me the types defined in this GraphQL schema' },
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
pattern: /^\/schemas.*[?&]specType=openapi/,
|
|
141
|
+
questions: [
|
|
142
|
+
{ label: 'Tell me more about this OpenAPI schema', prompt: 'Tell me more about this OpenAPI schema' },
|
|
143
|
+
{ label: 'Show me the endpoints available', prompt: 'Show me the endpoints available in this OpenAPI schema' },
|
|
144
|
+
{
|
|
145
|
+
label: 'Show me the request and response formats',
|
|
146
|
+
prompt: 'Show me the request and response formats in this OpenAPI schema',
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
pattern: /^\/schemas.*[?&]specType=asyncapi/,
|
|
152
|
+
questions: [
|
|
153
|
+
{ label: 'Tell me more about this AsyncAPI schema', prompt: 'Tell me more about this AsyncAPI schema' },
|
|
154
|
+
{ label: 'Show me the channels available', prompt: 'Show me the channels available in this AsyncAPI schema' },
|
|
155
|
+
{ label: 'Show me the messages available', prompt: 'Show me the messages available in this AsyncAPI schema' },
|
|
156
|
+
{
|
|
157
|
+
label: 'Show me the request and response formats',
|
|
158
|
+
prompt: 'Show me the request and response formats in this AsyncAPI schema',
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
pattern: /^\/schemas/,
|
|
164
|
+
questions: [
|
|
165
|
+
{ label: 'Tell me more about this schema?', prompt: 'Tell me more about this schema' },
|
|
166
|
+
{ label: 'Who producers or consumes this schema?', prompt: 'Who producers or consumes this schema?' },
|
|
167
|
+
{ label: 'What fields are required?', prompt: 'What fields are required for this schema?' },
|
|
168
|
+
{
|
|
169
|
+
label: 'Generate code using this schema',
|
|
170
|
+
prompt: 'Create a code example of using this schema, ask me for the programming language I want the example in.',
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
|
|
122
175
|
// Any other docs page
|
|
123
176
|
{
|
|
124
177
|
pattern: /^\/docs\/.+/,
|
|
@@ -158,16 +211,23 @@ interface ChatPanelProps {
|
|
|
158
211
|
|
|
159
212
|
const PANEL_WIDTH = 400;
|
|
160
213
|
|
|
161
|
-
// Staggered fade-in animation styles
|
|
214
|
+
// Staggered fade-in animation styles (delays account for 800ms panel slide)
|
|
162
215
|
const fadeInStyles = {
|
|
163
216
|
header: {
|
|
164
|
-
animation: '
|
|
217
|
+
animation: 'fadeIn 0.5s ease-out 0.3s both',
|
|
165
218
|
},
|
|
166
|
-
|
|
167
|
-
animation: '
|
|
219
|
+
welcome: {
|
|
220
|
+
animation: 'fadeIn 0.6s ease-out 0.4s both',
|
|
168
221
|
},
|
|
169
|
-
|
|
170
|
-
|
|
222
|
+
// Label and questions will be staggered individually in the component
|
|
223
|
+
questionsLabel: {
|
|
224
|
+
animation: 'fadeIn 0.5s ease-out 0.7s both',
|
|
225
|
+
},
|
|
226
|
+
getQuestionStyle: (index: number) => ({
|
|
227
|
+
animation: `fadeIn 0.5s ease-out ${0.85 + index * 0.12}s both`,
|
|
228
|
+
}),
|
|
229
|
+
inputFocus: {
|
|
230
|
+
animation: 'focusIn 0.6s ease-out 1.4s both',
|
|
171
231
|
},
|
|
172
232
|
};
|
|
173
233
|
|
|
@@ -199,18 +259,47 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
199
259
|
const [isWaitingForResponse, setIsWaitingForResponse] = useState(false);
|
|
200
260
|
const [pathname, setPathname] = useState('');
|
|
201
261
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
262
|
+
const [tools, setTools] = useState<ToolMetadata[]>([]);
|
|
263
|
+
|
|
264
|
+
// Sort tools with custom ones first
|
|
265
|
+
const sortedTools = useMemo(() => {
|
|
266
|
+
return [...tools].sort((a, b) => {
|
|
267
|
+
if (a.isCustom && !b.isCustom) return -1;
|
|
268
|
+
if (!a.isCustom && b.isCustom) return 1;
|
|
269
|
+
return 0;
|
|
270
|
+
});
|
|
271
|
+
}, [tools]);
|
|
272
|
+
|
|
273
|
+
// Fetch available tools when panel opens
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
if (isOpen && tools.length === 0) {
|
|
276
|
+
fetch('/api/chat')
|
|
277
|
+
.then((res) => res.json())
|
|
278
|
+
.then((data) => {
|
|
279
|
+
if (data.tools) {
|
|
280
|
+
setTools(data.tools);
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
.catch(() => {
|
|
284
|
+
// Silently fail - tools info is optional
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}, [isOpen, tools.length]);
|
|
202
288
|
|
|
203
|
-
// Get current pathname on mount and when panel opens
|
|
289
|
+
// Get current URL (pathname + search) on mount and when panel opens
|
|
204
290
|
useEffect(() => {
|
|
205
|
-
setPathname(window.location.pathname);
|
|
291
|
+
setPathname(window.location.pathname + window.location.search);
|
|
206
292
|
}, [isOpen]);
|
|
207
293
|
|
|
208
294
|
const suggestedQuestions = getSuggestedQuestions(pathname);
|
|
209
295
|
|
|
210
|
-
const { messages, sendMessage, stop, status, setMessages } = useChat({
|
|
296
|
+
const { messages, sendMessage, stop, status, setMessages, error } = useChat({
|
|
211
297
|
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
|
|
212
298
|
});
|
|
213
299
|
|
|
300
|
+
// Extract user-friendly error message
|
|
301
|
+
const errorMessage = error?.message || 'Something went wrong. Please try again.';
|
|
302
|
+
|
|
214
303
|
// Check if the assistant has started outputting content
|
|
215
304
|
const lastAssistantMessage = [...messages].reverse().find((m) => m.role === 'assistant');
|
|
216
305
|
const assistantHasContent = lastAssistantMessage?.parts?.some(
|
|
@@ -273,26 +362,45 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
273
362
|
}
|
|
274
363
|
}, [isOpen, isLoading]);
|
|
275
364
|
|
|
276
|
-
// Add/remove padding to main
|
|
365
|
+
// Add/remove padding to main application and header when sidebar panel is open
|
|
277
366
|
useEffect(() => {
|
|
278
|
-
const
|
|
279
|
-
|
|
367
|
+
const appEl = document.getElementById('eventcatalog-application');
|
|
368
|
+
const headerEl = document.getElementById('eventcatalog-header');
|
|
369
|
+
const docsSidebarEl = document.getElementById('eventcatalog-docs-sidebar');
|
|
280
370
|
|
|
281
|
-
|
|
282
|
-
if (!contentEl.style.transition) {
|
|
283
|
-
contentEl.style.transition = 'padding-right 300ms cubic-bezier(0.16, 1, 0.3, 1)';
|
|
284
|
-
}
|
|
371
|
+
const elements = [appEl, headerEl].filter(Boolean) as HTMLElement[];
|
|
285
372
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
373
|
+
elements.forEach((el) => {
|
|
374
|
+
// Add transition if not already present
|
|
375
|
+
if (!el.style.transition) {
|
|
376
|
+
el.style.transition = 'padding-right 800ms cubic-bezier(0.16, 1, 0.3, 1)';
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Only add padding when panel is open AND not in fullscreen mode
|
|
380
|
+
if (isOpen && !isFullscreen) {
|
|
381
|
+
el.style.paddingRight = `${PANEL_WIDTH}px`;
|
|
382
|
+
} else {
|
|
383
|
+
el.style.paddingRight = '0';
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Hide docs sidebar when chat panel is open
|
|
388
|
+
if (docsSidebarEl) {
|
|
389
|
+
if (isOpen && !isFullscreen) {
|
|
390
|
+
docsSidebarEl.style.display = 'none';
|
|
391
|
+
} else {
|
|
392
|
+
docsSidebarEl.style.display = '';
|
|
393
|
+
}
|
|
291
394
|
}
|
|
292
395
|
|
|
293
396
|
// Cleanup on unmount
|
|
294
397
|
return () => {
|
|
295
|
-
|
|
398
|
+
elements.forEach((el) => {
|
|
399
|
+
el.style.paddingRight = '0';
|
|
400
|
+
});
|
|
401
|
+
if (docsSidebarEl) {
|
|
402
|
+
docsSidebarEl.style.display = '';
|
|
403
|
+
}
|
|
296
404
|
};
|
|
297
405
|
}, [isOpen, isFullscreen]);
|
|
298
406
|
|
|
@@ -341,14 +449,22 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
341
449
|
<>
|
|
342
450
|
{/* Keyframes for fade-in animation */}
|
|
343
451
|
<style>{`
|
|
344
|
-
@keyframes
|
|
452
|
+
@keyframes fadeIn {
|
|
345
453
|
from {
|
|
346
454
|
opacity: 0;
|
|
347
|
-
transform: translateY(-8px);
|
|
348
455
|
}
|
|
349
456
|
to {
|
|
350
457
|
opacity: 1;
|
|
351
|
-
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
@keyframes focusIn {
|
|
461
|
+
from {
|
|
462
|
+
box-shadow: 0 0 0 0 rgba(168, 85, 247, 0);
|
|
463
|
+
border-color: #e5e7eb;
|
|
464
|
+
}
|
|
465
|
+
to {
|
|
466
|
+
box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.15);
|
|
467
|
+
border-color: #c084fc;
|
|
352
468
|
}
|
|
353
469
|
}
|
|
354
470
|
@keyframes pulse-glow {
|
|
@@ -360,32 +476,61 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
360
476
|
{/* Panel - hidden when fullscreen modal is open */}
|
|
361
477
|
{!isFullscreen && (
|
|
362
478
|
<div
|
|
363
|
-
className="fixed top-0 right-0 h-[100vh] z-[200] bg-
|
|
479
|
+
className="fixed top-0 right-0 h-[100vh] z-[200] bg-white border-l border-gray-200 flex flex-col overflow-hidden"
|
|
364
480
|
style={{
|
|
365
481
|
width: `${PANEL_WIDTH}px`,
|
|
366
482
|
transform: isOpen ? 'translateX(0)' : `translateX(${PANEL_WIDTH}px)`,
|
|
367
|
-
transition: 'transform
|
|
368
|
-
boxShadow: '-8px 0 24px -4px rgba(0, 0, 0, 0.1)',
|
|
483
|
+
transition: 'transform 800ms cubic-bezier(0.16, 1, 0.3, 1)',
|
|
369
484
|
}}
|
|
370
485
|
>
|
|
371
|
-
{/* Purple accent line at top */}
|
|
372
|
-
<div className="h-1 bg-gradient-to-r from-purple-500 via-purple-600 to-indigo-600" />
|
|
373
|
-
|
|
374
486
|
{/* Header */}
|
|
375
|
-
<div
|
|
376
|
-
className="flex
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
<div className="flex items-center justify-between px-5 py-3">
|
|
381
|
-
<div className="flex items-center space-x-2.5">
|
|
382
|
-
<div className="p-1.5 bg-purple-100 rounded-lg relative">
|
|
383
|
-
<BookOpen size={16} className="text-purple-600" />
|
|
384
|
-
<Sparkles size={8} className="text-purple-400 absolute -top-0.5 -right-0.5" />
|
|
487
|
+
<div className="flex-none border-b border-gray-100 shrink-0 pb-1">
|
|
488
|
+
<div className="flex items-center justify-between px-4 py-3">
|
|
489
|
+
<div className="flex items-center space-x-2">
|
|
490
|
+
<div className="p-1.5 bg-purple-50 rounded-md">
|
|
491
|
+
<BookOpen size={14} className="text-purple-600" />
|
|
385
492
|
</div>
|
|
386
|
-
<span className="font-
|
|
493
|
+
<span className="font-medium text-gray-900 text-sm">EventCatalog Assistant</span>
|
|
387
494
|
</div>
|
|
388
495
|
<div className="flex items-center space-x-1">
|
|
496
|
+
{tools.length > 0 && (
|
|
497
|
+
<Popover.Root>
|
|
498
|
+
<Popover.Trigger asChild>
|
|
499
|
+
<button
|
|
500
|
+
className="p-2 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600 transition-colors"
|
|
501
|
+
aria-label="View available tools"
|
|
502
|
+
title="Available tools"
|
|
503
|
+
>
|
|
504
|
+
<Wrench size={16} />
|
|
505
|
+
</button>
|
|
506
|
+
</Popover.Trigger>
|
|
507
|
+
<Popover.Portal>
|
|
508
|
+
<Popover.Content
|
|
509
|
+
className="w-72 bg-white rounded-lg shadow-lg border border-gray-200 p-3 z-[250]"
|
|
510
|
+
sideOffset={5}
|
|
511
|
+
align="end"
|
|
512
|
+
>
|
|
513
|
+
<div className="text-[10px] font-medium text-gray-500 mb-2">Available Tools ({sortedTools.length})</div>
|
|
514
|
+
<div className="max-h-64 overflow-y-auto divide-y divide-gray-100">
|
|
515
|
+
{sortedTools.map((tool) => (
|
|
516
|
+
<div key={tool.name} className="py-1.5 first:pt-0 last:pb-0">
|
|
517
|
+
<div className="flex items-center gap-1.5">
|
|
518
|
+
<span className="text-[11px] font-medium text-gray-700">{tool.name}</span>
|
|
519
|
+
{tool.isCustom && (
|
|
520
|
+
<span className="px-1.5 py-0.5 text-[9px] font-medium bg-purple-100 text-purple-700 rounded">
|
|
521
|
+
Custom
|
|
522
|
+
</span>
|
|
523
|
+
)}
|
|
524
|
+
</div>
|
|
525
|
+
<div className="text-[10px] text-gray-500 mt-0.5">{tool.description}</div>
|
|
526
|
+
</div>
|
|
527
|
+
))}
|
|
528
|
+
</div>
|
|
529
|
+
<Popover.Arrow className="fill-white" />
|
|
530
|
+
</Popover.Content>
|
|
531
|
+
</Popover.Portal>
|
|
532
|
+
</Popover.Root>
|
|
533
|
+
)}
|
|
389
534
|
<button
|
|
390
535
|
onClick={() => setIsFullscreen(true)}
|
|
391
536
|
className="p-2 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600 transition-colors"
|
|
@@ -415,49 +560,51 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
415
560
|
</div>
|
|
416
561
|
{/* Thinking indicator */}
|
|
417
562
|
{isThinking && (
|
|
418
|
-
<div className="px-
|
|
419
|
-
<div className="w-
|
|
563
|
+
<div className="px-4 pb-2 flex items-center gap-2">
|
|
564
|
+
<div className="w-1.5 h-1.5 bg-purple-500 rounded-full animate-pulse" />
|
|
420
565
|
<span className="text-xs text-gray-500">Thinking...</span>
|
|
421
566
|
</div>
|
|
422
567
|
)}
|
|
423
568
|
</div>
|
|
424
569
|
|
|
425
570
|
{/* Content */}
|
|
426
|
-
<div
|
|
427
|
-
className="flex-1 flex flex-col min-h-0 relative overflow-hidden"
|
|
428
|
-
style={isOpen ? fadeInStyles.content : undefined}
|
|
429
|
-
key={isOpen ? 'content-open' : 'content-closed'}
|
|
430
|
-
>
|
|
571
|
+
<div className="flex-1 flex flex-col min-h-0 relative overflow-hidden" key={isOpen ? 'content-open' : 'content-closed'}>
|
|
431
572
|
{/* Messages or Welcome area */}
|
|
432
|
-
<div className="flex-1 overflow-y-auto px-
|
|
573
|
+
<div className="flex-1 overflow-y-auto px-4 scrollbar-hide">
|
|
433
574
|
{!hasMessages ? (
|
|
434
575
|
/* Welcome area */
|
|
435
|
-
<div className="flex flex-col h-full justify-between pt-
|
|
576
|
+
<div className="flex flex-col h-full justify-between pt-4 pb-2">
|
|
436
577
|
{/* Center content */}
|
|
437
|
-
<div
|
|
578
|
+
<div
|
|
579
|
+
className="flex-1 flex flex-col items-center justify-center"
|
|
580
|
+
style={isOpen ? fadeInStyles.welcome : undefined}
|
|
581
|
+
>
|
|
438
582
|
{/* Animated Icon */}
|
|
439
|
-
<div className="relative mb-
|
|
583
|
+
<div className="relative mb-5">
|
|
440
584
|
<div className="absolute inset-0 bg-purple-400/20 rounded-2xl blur-xl animate-pulse" />
|
|
441
|
-
<div className="relative w-
|
|
442
|
-
<BookOpen size={
|
|
443
|
-
<Sparkles size={
|
|
585
|
+
<div className="relative w-14 h-14 rounded-2xl bg-gradient-to-br from-purple-500 to-purple-600 flex items-center justify-center shadow-lg">
|
|
586
|
+
<BookOpen size={26} className="text-white" strokeWidth={1.5} />
|
|
587
|
+
<Sparkles size={10} className="text-purple-200 absolute -top-1 -right-1 animate-pulse" />
|
|
444
588
|
</div>
|
|
445
589
|
</div>
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
<h2 className="text-xl font-semibold bg-gradient-to-r from-purple-600 to-indigo-600 bg-clip-text text-transparent mb-1">
|
|
449
|
-
{getGreeting()}
|
|
450
|
-
</h2>
|
|
451
|
-
<p className="text-sm text-gray-500 text-center">I'm here to help you explore your architecture.</p>
|
|
590
|
+
<h2 className="text-base font-medium text-gray-900 mb-1">{getGreeting()}</h2>
|
|
591
|
+
<p className="text-sm text-gray-500 text-center">Ask me anything about your catalog.</p>
|
|
452
592
|
</div>
|
|
453
593
|
|
|
454
594
|
{/* Suggested questions */}
|
|
455
|
-
<div className="
|
|
595
|
+
<div className="space-y-1.5 mt-4">
|
|
596
|
+
<p
|
|
597
|
+
className="text-xs font-medium text-gray-500 uppercase tracking-wide mb-2"
|
|
598
|
+
style={isOpen ? fadeInStyles.questionsLabel : undefined}
|
|
599
|
+
>
|
|
600
|
+
Example questions
|
|
601
|
+
</p>
|
|
456
602
|
{suggestedQuestions.map((question, index) => (
|
|
457
603
|
<button
|
|
458
604
|
key={index}
|
|
459
605
|
onClick={() => handleSuggestedAction(question.prompt)}
|
|
460
|
-
className="px-3 py-
|
|
606
|
+
className="w-full text-left px-3 py-2.5 text-sm text-gray-700 bg-gray-100 hover:bg-purple-50 hover:text-purple-700 border border-gray-200 hover:border-purple-200 rounded-lg transition-colors"
|
|
607
|
+
style={isOpen ? fadeInStyles.getQuestionStyle(index) : undefined}
|
|
461
608
|
>
|
|
462
609
|
{question.label}
|
|
463
610
|
</button>
|
|
@@ -472,8 +619,8 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
472
619
|
return (
|
|
473
620
|
<div key={message.id} className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
|
474
621
|
{message.role === 'user' ? (
|
|
475
|
-
<div className="max-w-[85%] rounded-2xl rounded-br-md px-4 py-2.5 bg-
|
|
476
|
-
<p className="text-sm
|
|
622
|
+
<div className="max-w-[85%] rounded-2xl rounded-br-md px-4 py-2.5 bg-purple-600 text-white">
|
|
623
|
+
<p className="text-sm whitespace-pre-wrap">{content}</p>
|
|
477
624
|
</div>
|
|
478
625
|
) : (
|
|
479
626
|
<div className="w-full text-gray-700">
|
|
@@ -529,7 +676,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
529
676
|
<div className="w-full">
|
|
530
677
|
<div className="flex items-start gap-2 text-red-600 text-sm">
|
|
531
678
|
<span className="shrink-0">⚠️</span>
|
|
532
|
-
<span>
|
|
679
|
+
<span>{errorMessage}</span>
|
|
533
680
|
</div>
|
|
534
681
|
</div>
|
|
535
682
|
</div>
|
|
@@ -541,13 +688,9 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
541
688
|
</div>
|
|
542
689
|
|
|
543
690
|
{/* Input area (Fixed at bottom) */}
|
|
544
|
-
<div
|
|
545
|
-
className="flex-none px-4 py-3 pb-2 bg-gradient-to-t from-gray-50 to-transparent border-t border-gray-100"
|
|
546
|
-
style={isOpen ? fadeInStyles.input : undefined}
|
|
547
|
-
key={isOpen ? 'input-open' : 'input-closed'}
|
|
548
|
-
>
|
|
691
|
+
<div className="flex-none px-4 py-3 border-t border-gray-100" key={isOpen ? 'input-open' : 'input-closed'}>
|
|
549
692
|
<form onSubmit={handleSubmit}>
|
|
550
|
-
<div className="relative bg-
|
|
693
|
+
<div className="relative bg-gray-50 rounded-lg border-2 border-gray-200 focus-within:border-purple-300 focus-within:bg-white transition-all">
|
|
551
694
|
<input
|
|
552
695
|
ref={inputRef}
|
|
553
696
|
type="text"
|
|
@@ -559,16 +702,16 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
559
702
|
submitMessage(inputValue);
|
|
560
703
|
}
|
|
561
704
|
}}
|
|
562
|
-
placeholder="Ask
|
|
705
|
+
placeholder="Ask a question..."
|
|
563
706
|
disabled={isLoading}
|
|
564
|
-
className="w-full px-3 py-2.5 pr-
|
|
707
|
+
className="w-full px-3 py-2.5 pr-14 bg-transparent text-gray-900 placeholder-gray-400 focus:outline-none text-sm disabled:opacity-50 rounded-lg"
|
|
565
708
|
/>
|
|
566
709
|
<div className="absolute right-1.5 top-1/2 -translate-y-1/2 z-10">
|
|
567
710
|
{isStreaming ? (
|
|
568
711
|
<button
|
|
569
712
|
type="button"
|
|
570
713
|
onClick={() => stop()}
|
|
571
|
-
className="p-1.5 text-red-500 hover:bg-red-50 rounded-
|
|
714
|
+
className="p-1.5 text-red-500 hover:bg-red-50 rounded-md transition-colors"
|
|
572
715
|
aria-label="Stop generating"
|
|
573
716
|
>
|
|
574
717
|
<Square size={12} fill="currentColor" />
|
|
@@ -577,7 +720,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
577
720
|
<button
|
|
578
721
|
type="submit"
|
|
579
722
|
disabled={!inputValue.trim() || isLoading}
|
|
580
|
-
className="px-
|
|
723
|
+
className="px-2.5 py-1 bg-purple-600 text-white text-xs font-medium rounded-md hover:bg-purple-700 disabled:bg-gray-200 disabled:text-gray-400 transition-colors"
|
|
581
724
|
aria-label="Send message"
|
|
582
725
|
>
|
|
583
726
|
Send
|
|
@@ -586,7 +729,6 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
586
729
|
</div>
|
|
587
730
|
</div>
|
|
588
731
|
</form>
|
|
589
|
-
|
|
590
732
|
<p className="text-[9px] text-gray-400 mt-2 text-center">AI can make mistakes. Verify important info.</p>
|
|
591
733
|
</div>
|
|
592
734
|
</div>
|
|
@@ -605,21 +747,56 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
605
747
|
}}
|
|
606
748
|
>
|
|
607
749
|
<Dialog.Portal>
|
|
608
|
-
<Dialog.Overlay className="fixed inset-0 bg-
|
|
609
|
-
<Dialog.Content className="fixed inset-y-4 left-1/2 -translate-x-1/2 w-[95%] max-w-
|
|
610
|
-
{/* Purple accent line at top */}
|
|
611
|
-
<div className="h-1 bg-gradient-to-r from-purple-500 via-purple-600 to-indigo-600" />
|
|
612
|
-
|
|
750
|
+
<Dialog.Overlay className="fixed inset-0 bg-gray-900/50 backdrop-blur-sm z-[300]" />
|
|
751
|
+
<Dialog.Content className="fixed inset-y-4 left-1/2 -translate-x-1/2 w-[95%] max-w-4xl md:inset-y-8 rounded-xl bg-white shadow-2xl z-[301] flex flex-col overflow-hidden focus:outline-none border border-gray-200">
|
|
613
752
|
{/* Modal Header */}
|
|
614
|
-
<div className="flex items-center justify-between px-
|
|
615
|
-
<div className="flex items-center space-x-
|
|
616
|
-
<div className="p-
|
|
617
|
-
<BookOpen size={
|
|
618
|
-
<Sparkles size={10} className="text-purple-400 absolute -top-0.5 -right-0.5" />
|
|
753
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-gray-100 flex-shrink-0">
|
|
754
|
+
<div className="flex items-center space-x-2.5">
|
|
755
|
+
<div className="p-1.5 bg-purple-50 rounded-lg">
|
|
756
|
+
<BookOpen size={18} className="text-purple-600" />
|
|
619
757
|
</div>
|
|
620
|
-
<Dialog.Title className="text-
|
|
758
|
+
<Dialog.Title className="text-base font-medium text-gray-900">Ask AI</Dialog.Title>
|
|
621
759
|
</div>
|
|
622
760
|
<div className="flex items-center space-x-2">
|
|
761
|
+
{tools.length > 0 && (
|
|
762
|
+
<Popover.Root>
|
|
763
|
+
<Popover.Trigger asChild>
|
|
764
|
+
<button
|
|
765
|
+
className="p-2 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600 transition-colors"
|
|
766
|
+
aria-label="View available tools"
|
|
767
|
+
title="Available tools"
|
|
768
|
+
>
|
|
769
|
+
<Wrench size={18} />
|
|
770
|
+
</button>
|
|
771
|
+
</Popover.Trigger>
|
|
772
|
+
<Popover.Portal>
|
|
773
|
+
<Popover.Content
|
|
774
|
+
className="w-80 bg-white rounded-lg shadow-lg border border-gray-200 p-4 z-[350]"
|
|
775
|
+
sideOffset={5}
|
|
776
|
+
align="end"
|
|
777
|
+
style={{ maxHeight: 'calc(100vh - 200px)' }}
|
|
778
|
+
>
|
|
779
|
+
<div className="text-[11px] font-medium text-gray-500 mb-2">Available Tools ({sortedTools.length})</div>
|
|
780
|
+
<div className="overflow-y-auto divide-y divide-gray-100" style={{ maxHeight: 'calc(100vh - 280px)' }}>
|
|
781
|
+
{sortedTools.map((tool) => (
|
|
782
|
+
<div key={tool.name} className="py-2 first:pt-0 last:pb-0">
|
|
783
|
+
<div className="flex items-center gap-1.5">
|
|
784
|
+
<span className="text-xs font-medium text-gray-700">{tool.name}</span>
|
|
785
|
+
{tool.isCustom && (
|
|
786
|
+
<span className="px-1.5 py-0.5 text-[9px] font-medium bg-purple-100 text-purple-700 rounded">
|
|
787
|
+
Custom
|
|
788
|
+
</span>
|
|
789
|
+
)}
|
|
790
|
+
</div>
|
|
791
|
+
<div className="text-[11px] text-gray-500 mt-0.5">{tool.description}</div>
|
|
792
|
+
</div>
|
|
793
|
+
))}
|
|
794
|
+
</div>
|
|
795
|
+
<Popover.Arrow className="fill-white" />
|
|
796
|
+
</Popover.Content>
|
|
797
|
+
</Popover.Portal>
|
|
798
|
+
</Popover.Root>
|
|
799
|
+
)}
|
|
623
800
|
{hasMessages && (
|
|
624
801
|
<button
|
|
625
802
|
onClick={() => setMessages([])}
|
|
@@ -653,39 +830,35 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
653
830
|
|
|
654
831
|
{/* Thinking indicator */}
|
|
655
832
|
{isThinking && (
|
|
656
|
-
<div className="px-
|
|
657
|
-
<div className="w-
|
|
833
|
+
<div className="px-5 py-2 flex items-center gap-2 border-b border-gray-100">
|
|
834
|
+
<div className="w-1.5 h-1.5 bg-purple-500 rounded-full animate-pulse" />
|
|
658
835
|
<span className="text-sm text-gray-500">Thinking...</span>
|
|
659
836
|
</div>
|
|
660
837
|
)}
|
|
661
838
|
|
|
662
839
|
{/* Modal Content */}
|
|
663
|
-
<div className="flex-1 overflow-y-auto px-
|
|
840
|
+
<div className="flex-1 overflow-y-auto px-5 py-4">
|
|
664
841
|
{!hasMessages ? (
|
|
665
842
|
/* Welcome area */
|
|
666
843
|
<div className="flex flex-col h-full justify-center items-center">
|
|
667
844
|
{/* Animated Icon */}
|
|
668
|
-
<div className="relative mb-
|
|
845
|
+
<div className="relative mb-6">
|
|
669
846
|
<div className="absolute inset-0 bg-purple-400/20 rounded-2xl blur-xl animate-pulse" />
|
|
670
|
-
<div className="relative w-
|
|
671
|
-
<BookOpen size={
|
|
672
|
-
<Sparkles size={
|
|
847
|
+
<div className="relative w-16 h-16 rounded-2xl bg-gradient-to-br from-purple-500 to-purple-600 flex items-center justify-center shadow-lg">
|
|
848
|
+
<BookOpen size={30} className="text-white" strokeWidth={1.5} />
|
|
849
|
+
<Sparkles size={12} className="text-purple-200 absolute -top-1 -right-1 animate-pulse" />
|
|
673
850
|
</div>
|
|
674
851
|
</div>
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
<h2 className="text-2xl font-semibold bg-gradient-to-r from-purple-600 to-indigo-600 bg-clip-text text-transparent mb-2">
|
|
678
|
-
{getGreeting()}
|
|
679
|
-
</h2>
|
|
680
|
-
<p className="text-gray-500 text-center mb-10">I'm here to help you explore your architecture.</p>
|
|
852
|
+
<h2 className="text-xl font-medium text-gray-900 mb-1">{getGreeting()}</h2>
|
|
853
|
+
<p className="text-gray-500 text-center mb-8">Ask me anything about your catalog.</p>
|
|
681
854
|
|
|
682
855
|
{/* Suggested questions */}
|
|
683
|
-
<div className="
|
|
856
|
+
<div className="grid grid-cols-2 gap-2 max-w-lg">
|
|
684
857
|
{suggestedQuestions.map((question, index) => (
|
|
685
858
|
<button
|
|
686
859
|
key={index}
|
|
687
860
|
onClick={() => handleSuggestedAction(question.prompt)}
|
|
688
|
-
className="px-4 py-2 text-sm text-gray-600 bg-
|
|
861
|
+
className="px-4 py-2.5 text-sm text-gray-600 bg-gray-50 hover:bg-gray-100 rounded-lg transition-colors text-left"
|
|
689
862
|
>
|
|
690
863
|
{question.label}
|
|
691
864
|
</button>
|
|
@@ -700,8 +873,8 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
700
873
|
return (
|
|
701
874
|
<div key={message.id} className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
|
702
875
|
{message.role === 'user' ? (
|
|
703
|
-
<div className="max-w-[75%] rounded-2xl rounded-br-md px-
|
|
704
|
-
<p className="text-sm
|
|
876
|
+
<div className="max-w-[75%] rounded-2xl rounded-br-md px-4 py-2.5 bg-purple-600 text-white">
|
|
877
|
+
<p className="text-sm whitespace-pre-wrap">{content}</p>
|
|
705
878
|
</div>
|
|
706
879
|
) : (
|
|
707
880
|
<div className="w-full text-gray-700">
|
|
@@ -756,7 +929,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
756
929
|
<div className="w-full">
|
|
757
930
|
<div className="flex items-start gap-2 text-red-600 text-sm">
|
|
758
931
|
<span className="shrink-0">⚠️</span>
|
|
759
|
-
<span>
|
|
932
|
+
<span>{errorMessage}</span>
|
|
760
933
|
</div>
|
|
761
934
|
</div>
|
|
762
935
|
</div>
|
|
@@ -768,9 +941,9 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
768
941
|
</div>
|
|
769
942
|
|
|
770
943
|
{/* Modal Input area */}
|
|
771
|
-
<div className="flex-shrink-0 px-
|
|
944
|
+
<div className="flex-shrink-0 px-5 py-4 border-t border-gray-100">
|
|
772
945
|
<form onSubmit={handleSubmit} className="max-w-3xl mx-auto">
|
|
773
|
-
<div className="relative bg-
|
|
946
|
+
<div className="relative bg-gray-50 rounded-lg border border-gray-200 focus-within:border-purple-300 focus-within:bg-white transition-all">
|
|
774
947
|
<input
|
|
775
948
|
ref={modalInputRef}
|
|
776
949
|
type="text"
|
|
@@ -782,25 +955,25 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
782
955
|
submitMessage(inputValue);
|
|
783
956
|
}
|
|
784
957
|
}}
|
|
785
|
-
placeholder="Ask
|
|
958
|
+
placeholder="Ask a question..."
|
|
786
959
|
disabled={isLoading}
|
|
787
|
-
className="w-full px-4 py-3 pr-
|
|
960
|
+
className="w-full px-4 py-3 pr-16 bg-transparent text-gray-900 placeholder-gray-400 focus:outline-none text-sm disabled:opacity-50 rounded-lg"
|
|
788
961
|
/>
|
|
789
962
|
<div className="absolute right-2 top-1/2 -translate-y-1/2 z-10">
|
|
790
963
|
{isStreaming ? (
|
|
791
964
|
<button
|
|
792
965
|
type="button"
|
|
793
966
|
onClick={() => stop()}
|
|
794
|
-
className="p-
|
|
967
|
+
className="p-1.5 text-red-500 hover:bg-red-50 rounded-md transition-colors"
|
|
795
968
|
aria-label="Stop generating"
|
|
796
969
|
>
|
|
797
|
-
<Square size={
|
|
970
|
+
<Square size={14} fill="currentColor" />
|
|
798
971
|
</button>
|
|
799
972
|
) : (
|
|
800
973
|
<button
|
|
801
974
|
type="submit"
|
|
802
975
|
disabled={!inputValue.trim() || isLoading}
|
|
803
|
-
className="px-
|
|
976
|
+
className="px-3 py-1.5 bg-purple-600 text-white text-sm font-medium rounded-md hover:bg-purple-700 disabled:bg-gray-200 disabled:text-gray-400 disabled:cursor-not-allowed transition-colors"
|
|
804
977
|
aria-label="Send message"
|
|
805
978
|
>
|
|
806
979
|
Send
|