@eventcatalog/core 4.10.11 → 4.10.13
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-N6U5CNT7.js → chunk-34RMFKFB.js} +1 -1
- package/dist/{chunk-TWZKZIRW.js → chunk-IIECZFXN.js} +1 -1
- package/dist/{chunk-RLGMIZSH.js → chunk-JHUICVBT.js} +1 -1
- package/dist/{chunk-3XTFNVGA.js → chunk-PADMH2RJ.js} +1 -1
- package/dist/{chunk-VFRR3M72.js → chunk-SSSN5FXC.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.config.d.cts +9 -0
- package/dist/eventcatalog.config.d.ts +9 -0
- 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/astro.config.mjs +2 -0
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +208 -119
- package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +28 -8
- package/eventcatalog/src/components/ChatPanel/OfflineReply.tsx +45 -0
- package/eventcatalog/src/components/Header.astro +11 -6
- package/eventcatalog/src/components/MDX/Design/Design.astro +2 -2
- package/eventcatalog/src/components/MDX/EntityMap/EntityMap.astro +2 -2
- package/eventcatalog/src/components/MDX/Flow/Flow.astro +2 -2
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +2 -2
- package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +22 -40
- package/eventcatalog/src/components/Search/Search.astro +11 -4
- package/eventcatalog/src/components/Settings/AssistantSettingsForm.tsx +29 -24
- package/eventcatalog/src/content.config.ts +1 -1
- package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +3 -3
- package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +2 -2
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +5 -3
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +10 -4
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +2 -2
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +3 -3
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename].astro +2 -2
- package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +2 -2
- package/eventcatalog/src/plugins/link-validation.ts +42 -0
- package/eventcatalog/src/utils/collections/glob-loader.spec.ts +69 -2
- package/eventcatalog/src/utils/collections/glob-loader.ts +5 -3
- package/eventcatalog/src/utils/collections/schema-loader.ts +1 -1
- package/eventcatalog/src/utils/feature.ts +1 -0
- package/eventcatalog/src/utils/link-validation.ts +224 -0
- package/eventcatalog/src/utils/resource-reference-links.ts +29 -0
- package/package.json +5 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useCallback, useState, useMemo, memo } from 'react';
|
|
2
|
-
import { X, Square, Trash2,
|
|
2
|
+
import { X, Square, Trash2, Sparkles, Copy, Check, Maximize2, Minimize2, Wrench, ChevronDown, MessageSquare } from 'lucide-react';
|
|
3
3
|
import { useChat } from '@ai-sdk/react';
|
|
4
4
|
import { DefaultChatTransport } from 'ai';
|
|
5
5
|
import ReactMarkdown from 'react-markdown';
|
|
@@ -9,6 +9,7 @@ import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
|
|
9
9
|
import * as Dialog from '@radix-ui/react-dialog';
|
|
10
10
|
import * as Popover from '@radix-ui/react-popover';
|
|
11
11
|
import { buildUrl } from '@utils/url-builder';
|
|
12
|
+
import OfflineReply, { offlineReplyText } from './OfflineReply';
|
|
12
13
|
|
|
13
14
|
interface ToolMetadata {
|
|
14
15
|
name: string;
|
|
@@ -18,6 +19,44 @@ interface ToolMetadata {
|
|
|
18
19
|
|
|
19
20
|
// CSS keyframes - defined once outside component to avoid re-injection
|
|
20
21
|
const CHAT_PANEL_STYLES = `
|
|
22
|
+
@keyframes ec-chat-breathe {
|
|
23
|
+
0%, 100% { transform: scale(1); opacity: .7; }
|
|
24
|
+
50% { transform: scale(1.06); opacity: 1; }
|
|
25
|
+
}
|
|
26
|
+
@keyframes ec-chat-dot {
|
|
27
|
+
0%, 80%, 100% { transform: translateY(0); opacity: .35; }
|
|
28
|
+
40% { transform: translateY(-3px); opacity: 1; }
|
|
29
|
+
}
|
|
30
|
+
.ec-chat-surface { font-weight: 400; }
|
|
31
|
+
.ec-chat-avatar { background: rgb(var(--ec-accent) / .06); animation: ec-chat-breathe 5s ease-in-out infinite; }
|
|
32
|
+
.ec-chat-suggestion { width: fit-content; max-width: 100%; transition: background 180ms, transform 180ms; }
|
|
33
|
+
.ec-chat-suggestion:hover { transform: translateX(3px); }
|
|
34
|
+
.ec-chat-message { animation: fadeInUp 280ms ease-out both; }
|
|
35
|
+
.ec-chat-offline > * { animation: fadeInUp 350ms ease-out both; }
|
|
36
|
+
.ec-chat-offline > :nth-child(2) { animation-delay: 100ms; }
|
|
37
|
+
.ec-chat-offline > :nth-child(3) { animation-delay: 220ms; }
|
|
38
|
+
.ec-chat-offline > :nth-child(4) { animation-delay: 340ms; }
|
|
39
|
+
.ec-chat-dot { width: 4px; height: 4px; border-radius: 50%; background: rgb(var(--ec-accent)); animation: ec-chat-dot 1.2s infinite; }
|
|
40
|
+
.ec-chat-dot:nth-child(2) { animation-delay: 150ms; }
|
|
41
|
+
.ec-chat-dot:nth-child(3) { animation-delay: 300ms; }
|
|
42
|
+
.ec-chat-composer {
|
|
43
|
+
border-radius: 20px;
|
|
44
|
+
border-color: rgb(var(--ec-accent) / .22);
|
|
45
|
+
background: rgb(var(--ec-page-bg));
|
|
46
|
+
box-shadow: 0 0 0 3px rgb(var(--ec-accent) / .04), 0 8px 28px rgb(var(--ec-accent) / .04);
|
|
47
|
+
transition: border-color 200ms, box-shadow 200ms;
|
|
48
|
+
}
|
|
49
|
+
.ec-chat-composer:focus-within { border-color: rgb(var(--ec-accent) / .5); box-shadow: 0 0 0 3px rgb(var(--ec-accent) / .09); }
|
|
50
|
+
.ec-chat-composer textarea { display: block; resize: none; min-height: 104px; padding: 14px 16px 46px; border-radius: 20px; }
|
|
51
|
+
.ec-chat-composer-actions { top: auto; bottom: 10px; transform: none; }
|
|
52
|
+
@media (prefers-reduced-motion: reduce) {
|
|
53
|
+
.ec-chat-surface, .ec-chat-surface *, #eventcatalog-header, #eventcatalog-application {
|
|
54
|
+
animation: none !important;
|
|
55
|
+
transition: none !important;
|
|
56
|
+
scroll-behavior: auto !important;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
21
60
|
@keyframes fadeIn {
|
|
22
61
|
from { opacity: 0; }
|
|
23
62
|
to { opacity: 1; }
|
|
@@ -335,26 +374,27 @@ const getSuggestedQuestions = (pathname: string): SuggestedQuestion[] => {
|
|
|
335
374
|
};
|
|
336
375
|
|
|
337
376
|
interface ChatPanelProps {
|
|
377
|
+
configured?: boolean;
|
|
338
378
|
isOpen: boolean;
|
|
339
379
|
onClose: () => void;
|
|
340
380
|
}
|
|
341
381
|
|
|
342
382
|
const PANEL_WIDTH = 400;
|
|
343
383
|
|
|
344
|
-
// Staggered fade-in animation styles (delays account for
|
|
384
|
+
// Staggered fade-in animation styles (delays account for 420ms panel slide)
|
|
345
385
|
const fadeInStyles = {
|
|
346
386
|
header: {
|
|
347
387
|
animation: 'fadeIn 0.5s ease-out 0.3s both',
|
|
348
388
|
},
|
|
349
389
|
welcome: {
|
|
350
|
-
animation: '
|
|
390
|
+
animation: 'fadeInUp 0.45s ease-out 0.12s both',
|
|
351
391
|
},
|
|
352
392
|
// Label and questions will be staggered individually in the component
|
|
353
393
|
questionsLabel: {
|
|
354
394
|
animation: 'fadeIn 0.5s ease-out 0.7s both',
|
|
355
395
|
},
|
|
356
396
|
getQuestionStyle: (index: number) => ({
|
|
357
|
-
animation: `fadeIn 0.5s ease-out ${0.
|
|
397
|
+
animation: `fadeIn 0.5s ease-out ${0.22 + index * 0.07}s both`,
|
|
358
398
|
}),
|
|
359
399
|
inputFocus: {
|
|
360
400
|
animation: 'focusIn 0.6s ease-out 1.4s both',
|
|
@@ -436,12 +476,14 @@ const getCompletedTools = (message: { parts?: Array<any> }): string[] => {
|
|
|
436
476
|
};
|
|
437
477
|
|
|
438
478
|
// Skeleton loading component - memoized since it never changes
|
|
439
|
-
const SkeletonLoader = memo(() => (
|
|
440
|
-
<div className="
|
|
441
|
-
<
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
479
|
+
const SkeletonLoader = memo(({ label = 'Thinking it through…' }: { label?: string }) => (
|
|
480
|
+
<div role="status" className="ec-chat-message flex items-center gap-3 py-3 text-xs text-[rgb(var(--ec-page-text-muted))]">
|
|
481
|
+
<span className="flex items-center gap-1" aria-hidden="true">
|
|
482
|
+
<span className="ec-chat-dot" />
|
|
483
|
+
<span className="ec-chat-dot" />
|
|
484
|
+
<span className="ec-chat-dot" />
|
|
485
|
+
</span>
|
|
486
|
+
{label}
|
|
445
487
|
</div>
|
|
446
488
|
));
|
|
447
489
|
SkeletonLoader.displayName = 'SkeletonLoader';
|
|
@@ -517,13 +559,22 @@ const modalMarkdownComponents = {
|
|
|
517
559
|
),
|
|
518
560
|
};
|
|
519
561
|
|
|
520
|
-
const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
521
|
-
const inputRef = useRef<
|
|
522
|
-
const modalInputRef = useRef<
|
|
562
|
+
const ChatPanel = ({ isOpen, onClose, configured = false }: ChatPanelProps) => {
|
|
563
|
+
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
564
|
+
const modalInputRef = useRef<HTMLTextAreaElement>(null);
|
|
523
565
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
524
566
|
const modalMessagesEndRef = useRef<HTMLDivElement>(null);
|
|
525
567
|
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
|
526
568
|
const [inputValue, setInputValue] = useState('');
|
|
569
|
+
const [isSetupThinking, setIsSetupThinking] = useState(false);
|
|
570
|
+
const setupReplyTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
571
|
+
|
|
572
|
+
useEffect(
|
|
573
|
+
() => () => {
|
|
574
|
+
if (setupReplyTimer.current !== null) clearTimeout(setupReplyTimer.current);
|
|
575
|
+
},
|
|
576
|
+
[]
|
|
577
|
+
);
|
|
527
578
|
const [isWaitingForResponse, setIsWaitingForResponse] = useState(false);
|
|
528
579
|
const [pathname, setPathname] = useState('');
|
|
529
580
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
@@ -543,7 +594,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
543
594
|
|
|
544
595
|
// Fetch available tools when panel opens
|
|
545
596
|
useEffect(() => {
|
|
546
|
-
if (isOpen && tools.length === 0) {
|
|
597
|
+
if (configured && isOpen && tools.length === 0) {
|
|
547
598
|
fetch(chatApiUrl)
|
|
548
599
|
.then((res) => res.json())
|
|
549
600
|
.then((data) => {
|
|
@@ -555,7 +606,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
555
606
|
// Silently fail - tools info is optional
|
|
556
607
|
});
|
|
557
608
|
}
|
|
558
|
-
}, [isOpen, tools.length]);
|
|
609
|
+
}, [configured, isOpen, tools.length, chatApiUrl]);
|
|
559
610
|
|
|
560
611
|
// Get current URL (pathname + search) on mount and when panel opens
|
|
561
612
|
useEffect(() => {
|
|
@@ -624,7 +675,9 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
624
675
|
|
|
625
676
|
const isStreaming = status === 'streaming' && assistantHasContent;
|
|
626
677
|
const isThinking =
|
|
627
|
-
|
|
678
|
+
isSetupThinking ||
|
|
679
|
+
isWaitingForResponse ||
|
|
680
|
+
(messages.length > 0 && (status === 'submitted' || status === 'streaming') && !assistantHasContent);
|
|
628
681
|
const isLoading = isThinking || isStreaming;
|
|
629
682
|
|
|
630
683
|
// Get currently running tools from the last assistant message
|
|
@@ -643,12 +696,13 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
643
696
|
|
|
644
697
|
// Focus modal input when fullscreen opens
|
|
645
698
|
useEffect(() => {
|
|
646
|
-
if (isFullscreen && !isLoading) {
|
|
647
|
-
setTimeout(() => {
|
|
699
|
+
if (isOpen && isFullscreen && !isLoading) {
|
|
700
|
+
const timer = setTimeout(() => {
|
|
648
701
|
modalInputRef.current?.focus();
|
|
649
702
|
}, 100);
|
|
703
|
+
return () => clearTimeout(timer);
|
|
650
704
|
}
|
|
651
|
-
}, [isFullscreen, isLoading]);
|
|
705
|
+
}, [isOpen, isFullscreen, isLoading]);
|
|
652
706
|
|
|
653
707
|
// Handle escape key to close
|
|
654
708
|
useEffect(() => {
|
|
@@ -657,23 +711,24 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
657
711
|
onClose();
|
|
658
712
|
}
|
|
659
713
|
// Focus input on CMD+I or CTRL+I
|
|
660
|
-
if ((e.metaKey || e.ctrlKey) && e.key === 'i' && isOpen) {
|
|
714
|
+
if ((e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey && e.key.toLowerCase() === 'i' && isOpen) {
|
|
661
715
|
e.preventDefault();
|
|
662
|
-
inputRef.current?.focus();
|
|
716
|
+
(isFullscreen ? modalInputRef : inputRef).current?.focus();
|
|
663
717
|
}
|
|
664
718
|
};
|
|
665
719
|
document.addEventListener('keydown', handleEscape);
|
|
666
720
|
return () => document.removeEventListener('keydown', handleEscape);
|
|
667
|
-
}, [isOpen, onClose]);
|
|
721
|
+
}, [isOpen, isFullscreen, onClose]);
|
|
668
722
|
|
|
669
723
|
// Focus input when opened and not loading
|
|
670
724
|
useEffect(() => {
|
|
671
|
-
if (isOpen && !isLoading) {
|
|
672
|
-
setTimeout(() => {
|
|
725
|
+
if (isOpen && !isFullscreen && !isLoading) {
|
|
726
|
+
const timer = setTimeout(() => {
|
|
673
727
|
inputRef.current?.focus();
|
|
674
728
|
}, 100);
|
|
729
|
+
return () => clearTimeout(timer);
|
|
675
730
|
}
|
|
676
|
-
}, [isOpen, isLoading]);
|
|
731
|
+
}, [isOpen, isFullscreen, isLoading]);
|
|
677
732
|
|
|
678
733
|
// Add/remove padding to main application and header when sidebar panel is open
|
|
679
734
|
useEffect(() => {
|
|
@@ -683,31 +738,23 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
683
738
|
|
|
684
739
|
const elements = [appEl, headerEl].filter(Boolean) as HTMLElement[];
|
|
685
740
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
// Hide docs sidebar when chat panel is open
|
|
701
|
-
if (docsSidebarEl) {
|
|
702
|
-
if (isOpen && !isFullscreen) {
|
|
703
|
-
docsSidebarEl.style.display = 'none';
|
|
704
|
-
} else {
|
|
705
|
-
docsSidebarEl.style.display = '';
|
|
706
|
-
}
|
|
707
|
-
}
|
|
741
|
+
const desktop = window.matchMedia('(min-width: 1024px)');
|
|
742
|
+
const updateLayout = () => {
|
|
743
|
+
const pushContent = desktop.matches && isOpen && !isFullscreen;
|
|
744
|
+
elements.forEach((el) => {
|
|
745
|
+
if (!el.style.transition) {
|
|
746
|
+
el.style.transition = 'padding-right 420ms cubic-bezier(0.16, 1, 0.3, 1)';
|
|
747
|
+
}
|
|
748
|
+
el.style.paddingRight = pushContent ? `${PANEL_WIDTH}px` : '0';
|
|
749
|
+
});
|
|
750
|
+
if (docsSidebarEl) docsSidebarEl.style.display = pushContent ? 'none' : '';
|
|
751
|
+
};
|
|
752
|
+
updateLayout();
|
|
753
|
+
desktop.addEventListener('change', updateLayout);
|
|
708
754
|
|
|
709
755
|
// Cleanup on unmount
|
|
710
756
|
return () => {
|
|
757
|
+
desktop.removeEventListener('change', updateLayout);
|
|
711
758
|
elements.forEach((el) => {
|
|
712
759
|
el.style.paddingRight = '0';
|
|
713
760
|
});
|
|
@@ -722,12 +769,49 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
722
769
|
(text: string) => {
|
|
723
770
|
if (!text.trim() || isLoading) return;
|
|
724
771
|
setInputValue('');
|
|
772
|
+
if (!configured) {
|
|
773
|
+
setMessages((previous) => [...previous, { id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text }] }]);
|
|
774
|
+
setIsSetupThinking(true);
|
|
775
|
+
setupReplyTimer.current = setTimeout(() => {
|
|
776
|
+
setMessages((previous) => [
|
|
777
|
+
...previous,
|
|
778
|
+
{
|
|
779
|
+
id: crypto.randomUUID(),
|
|
780
|
+
role: 'assistant',
|
|
781
|
+
parts: [
|
|
782
|
+
{
|
|
783
|
+
type: 'text',
|
|
784
|
+
text: offlineReplyText,
|
|
785
|
+
},
|
|
786
|
+
],
|
|
787
|
+
},
|
|
788
|
+
]);
|
|
789
|
+
setupReplyTimer.current = null;
|
|
790
|
+
setIsSetupThinking(false);
|
|
791
|
+
}, 1200);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
725
794
|
setIsWaitingForResponse(true);
|
|
726
795
|
sendMessage({ text });
|
|
727
796
|
},
|
|
728
|
-
[isLoading, sendMessage]
|
|
797
|
+
[configured, isLoading, sendMessage, setMessages]
|
|
729
798
|
);
|
|
730
799
|
|
|
800
|
+
const stopResponse = useCallback(() => {
|
|
801
|
+
if (setupReplyTimer.current !== null) {
|
|
802
|
+
clearTimeout(setupReplyTimer.current);
|
|
803
|
+
setupReplyTimer.current = null;
|
|
804
|
+
}
|
|
805
|
+
setIsSetupThinking(false);
|
|
806
|
+
setIsWaitingForResponse(false);
|
|
807
|
+
stop();
|
|
808
|
+
}, [stop]);
|
|
809
|
+
|
|
810
|
+
const clearMessages = useCallback(() => {
|
|
811
|
+
stopResponse();
|
|
812
|
+
setMessages([]);
|
|
813
|
+
}, [stopResponse, setMessages]);
|
|
814
|
+
|
|
731
815
|
// Handle suggested action clicks
|
|
732
816
|
const handleSuggestedAction = useCallback(
|
|
733
817
|
(prompt: string) => {
|
|
@@ -738,7 +822,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
738
822
|
|
|
739
823
|
// Handle input enter key
|
|
740
824
|
const handleInputKeyDown = useCallback(
|
|
741
|
-
(e: React.KeyboardEvent<
|
|
825
|
+
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
742
826
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
743
827
|
e.preventDefault();
|
|
744
828
|
submitMessage(inputValue);
|
|
@@ -769,13 +853,17 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
769
853
|
{/* Panel - hidden when fullscreen modal is open */}
|
|
770
854
|
{!isFullscreen && (
|
|
771
855
|
<div
|
|
772
|
-
|
|
856
|
+
ref={(element) => {
|
|
857
|
+
element?.toggleAttribute('inert', !isOpen);
|
|
858
|
+
}}
|
|
859
|
+
aria-hidden={!isOpen}
|
|
860
|
+
className="ec-chat-surface fixed top-0 right-0 h-[100dvh] z-[200] border-l border-[rgb(var(--ec-page-border))] flex flex-col overflow-hidden"
|
|
773
861
|
style={{
|
|
774
|
-
width:
|
|
775
|
-
transform: isOpen ? 'translateX(0)' :
|
|
776
|
-
transition: 'transform
|
|
862
|
+
width: `min(${PANEL_WIDTH}px, 100vw)`,
|
|
863
|
+
transform: isOpen ? 'translateX(0)' : 'translateX(100%)',
|
|
864
|
+
transition: 'transform 420ms cubic-bezier(0.16, 1, 0.3, 1)',
|
|
777
865
|
background: `
|
|
778
|
-
radial-gradient(ellipse 100% 40% at 50% 100%, rgb(var(--ec-accent) / 0.
|
|
866
|
+
radial-gradient(ellipse 100% 40% at 50% 100%, rgb(var(--ec-accent) / 0.045) 0%, transparent 100%),
|
|
779
867
|
rgb(var(--ec-page-bg))
|
|
780
868
|
`,
|
|
781
869
|
}}
|
|
@@ -784,8 +872,8 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
784
872
|
<div className="flex-none shrink-0 border-b border-[rgb(var(--ec-page-border))]">
|
|
785
873
|
<div className="flex items-center justify-between px-4 py-3">
|
|
786
874
|
<div className="flex items-center space-x-2">
|
|
787
|
-
<
|
|
788
|
-
<span className="font-medium text-[rgb(var(--ec-header-text))] text-sm">
|
|
875
|
+
<Sparkles size={16} className="text-[rgb(var(--ec-accent))]" />
|
|
876
|
+
<span className="font-medium text-[rgb(var(--ec-header-text))] text-sm">Assistant</span>
|
|
789
877
|
</div>
|
|
790
878
|
<div className="flex items-center space-x-1">
|
|
791
879
|
{tools.length > 0 && (
|
|
@@ -838,7 +926,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
838
926
|
</button>
|
|
839
927
|
{hasMessages && (
|
|
840
928
|
<button
|
|
841
|
-
onClick={
|
|
929
|
+
onClick={clearMessages}
|
|
842
930
|
className="p-2 rounded-lg hover:bg-[rgb(var(--ec-header-border))] text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-header-text))] transition-colors"
|
|
843
931
|
aria-label="Clear chat"
|
|
844
932
|
title="Clear chat"
|
|
@@ -855,21 +943,6 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
855
943
|
</button>
|
|
856
944
|
</div>
|
|
857
945
|
</div>
|
|
858
|
-
{/* Thinking indicator */}
|
|
859
|
-
{isThinking && (
|
|
860
|
-
<div className="px-4 pb-2 flex items-center gap-2">
|
|
861
|
-
<div className="w-1.5 h-1.5 bg-[rgb(var(--ec-accent))] rounded-full animate-pulse" />
|
|
862
|
-
<span className="text-xs text-[rgb(var(--ec-icon-color))]">
|
|
863
|
-
{runningTools.length > 0 ? (
|
|
864
|
-
<>
|
|
865
|
-
Using <span className="font-medium text-[rgb(var(--ec-accent))]">{runningTools[0]}</span>...
|
|
866
|
-
</>
|
|
867
|
-
) : (
|
|
868
|
-
'Thinking...'
|
|
869
|
-
)}
|
|
870
|
-
</span>
|
|
871
|
-
</div>
|
|
872
|
-
)}
|
|
873
946
|
</div>
|
|
874
947
|
|
|
875
948
|
{/* Content */}
|
|
@@ -886,23 +959,23 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
886
959
|
>
|
|
887
960
|
{/* Icon with circular background */}
|
|
888
961
|
<div className="relative mb-6">
|
|
889
|
-
<div className="w-
|
|
890
|
-
<MessageSquare size={
|
|
962
|
+
<div className="ec-chat-avatar w-24 h-24 rounded-full flex items-center justify-center">
|
|
963
|
+
<MessageSquare size={42} className="text-[rgb(var(--ec-accent))]" strokeWidth={1.5} />
|
|
891
964
|
</div>
|
|
892
965
|
</div>
|
|
893
|
-
<h2 className="text-lg font-
|
|
966
|
+
<h2 className="text-lg font-medium text-[rgb(var(--ec-page-text))] mb-1">{greeting}</h2>
|
|
894
967
|
<p className="text-sm font-normal text-[rgb(var(--ec-content-text))]">
|
|
895
968
|
I'm here to help with your architecture
|
|
896
969
|
</p>
|
|
897
970
|
</div>
|
|
898
971
|
|
|
899
972
|
{/* Suggested questions - pill style */}
|
|
900
|
-
<div className="flex-none
|
|
973
|
+
<div className="flex-none flex flex-col items-start gap-2">
|
|
901
974
|
{suggestedQuestions.map((question, index) => (
|
|
902
975
|
<button
|
|
903
976
|
key={index}
|
|
904
977
|
onClick={() => handleSuggestedAction(question.prompt)}
|
|
905
|
-
className="
|
|
978
|
+
className="ec-chat-suggestion text-left px-3 py-2 text-xs text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))] bg-[rgb(var(--ec-accent)/0.04)] hover:bg-[rgb(var(--ec-accent)/0.09)] rounded-full transition-all duration-200"
|
|
906
979
|
style={isOpen ? fadeInStyles.getQuestionStyle(index) : undefined}
|
|
907
980
|
>
|
|
908
981
|
{question.label}
|
|
@@ -919,11 +992,16 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
919
992
|
const completedTools = message.role === 'assistant' ? getCompletedTools(message) : [];
|
|
920
993
|
const isLastMessage = messageIndex === messages.length - 1;
|
|
921
994
|
return (
|
|
922
|
-
<div
|
|
995
|
+
<div
|
|
996
|
+
key={message.id}
|
|
997
|
+
className={`ec-chat-message ${!configured && message.role === 'assistant' ? 'ec-chat-setup' : ''} flex flex-col ${message.role === 'user' ? 'items-end' : 'items-start'}`}
|
|
998
|
+
>
|
|
923
999
|
{message.role === 'user' ? (
|
|
924
1000
|
<div className="max-w-[85%] rounded-2xl rounded-br-md px-4 py-2.5 bg-[rgb(var(--ec-page-text)/0.05)]">
|
|
925
1001
|
<p className="text-sm font-normal whitespace-pre-wrap text-[rgb(var(--ec-page-text))]">{content}</p>
|
|
926
1002
|
</div>
|
|
1003
|
+
) : !configured ? (
|
|
1004
|
+
<OfflineReply />
|
|
927
1005
|
) : (
|
|
928
1006
|
<>
|
|
929
1007
|
{/* Tools used indicator */}
|
|
@@ -956,7 +1034,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
956
1034
|
<button
|
|
957
1035
|
key={index}
|
|
958
1036
|
onClick={() => handleSuggestedAction(suggestion)}
|
|
959
|
-
className="px-
|
|
1037
|
+
className="ec-chat-suggestion px-3 py-2 text-xs text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))] bg-[rgb(var(--ec-accent)/0.04)] hover:bg-[rgb(var(--ec-accent)/0.09)] rounded-full transition-all duration-200 text-left"
|
|
960
1038
|
style={fadeInStyles.getFollowUpStyle(index)}
|
|
961
1039
|
>
|
|
962
1040
|
{suggestion}
|
|
@@ -973,7 +1051,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
973
1051
|
{/* Skeleton loading indicator */}
|
|
974
1052
|
{isThinking && (
|
|
975
1053
|
<div className="w-full">
|
|
976
|
-
<SkeletonLoader />
|
|
1054
|
+
<SkeletonLoader label={runningTools.length > 0 ? `Using ${runningTools[0]}…` : undefined} />
|
|
977
1055
|
</div>
|
|
978
1056
|
)}
|
|
979
1057
|
|
|
@@ -1008,10 +1086,11 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1008
1086
|
{/* Input area (Fixed at bottom) */}
|
|
1009
1087
|
<div className="flex-none px-4 py-3" key={isOpen ? 'input-open' : 'input-closed'}>
|
|
1010
1088
|
<form onSubmit={handleSubmit}>
|
|
1011
|
-
<div className="relative bg-[rgb(var(--ec-page-bg)/0.5)] backdrop-blur-sm rounded-xl border border-[rgb(var(--ec-accent)/0.3)] focus-within:border-[rgb(var(--ec-accent)/0.5)] focus-within:ring-2 focus-within:ring-[rgb(var(--ec-accent)/0.1)] transition-all">
|
|
1012
|
-
<
|
|
1089
|
+
<div className="ec-chat-composer relative bg-[rgb(var(--ec-page-bg)/0.5)] backdrop-blur-sm rounded-xl border border-[rgb(var(--ec-accent)/0.3)] focus-within:border-[rgb(var(--ec-accent)/0.5)] focus-within:ring-2 focus-within:ring-[rgb(var(--ec-accent)/0.1)] transition-all">
|
|
1090
|
+
<textarea
|
|
1013
1091
|
ref={inputRef}
|
|
1014
|
-
|
|
1092
|
+
rows={2}
|
|
1093
|
+
aria-label="Ask AI a question"
|
|
1015
1094
|
value={inputValue}
|
|
1016
1095
|
onChange={(e) => setInputValue(e.target.value)}
|
|
1017
1096
|
onKeyDown={handleInputKeyDown}
|
|
@@ -1019,11 +1098,21 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1019
1098
|
disabled={isLoading}
|
|
1020
1099
|
className="w-full px-4 py-3 pr-16 bg-transparent text-[rgb(var(--ec-input-text))] placeholder-[rgb(var(--ec-input-placeholder))] focus:outline-hidden text-sm disabled:opacity-50 rounded-xl"
|
|
1021
1100
|
/>
|
|
1022
|
-
<div className="absolute right-
|
|
1023
|
-
|
|
1101
|
+
<div className="absolute bottom-4 left-4 right-20 truncate text-[10px] text-[rgb(var(--ec-page-text-muted))]">
|
|
1102
|
+
<span className="mr-1.5 rounded bg-[rgb(var(--ec-accent)/0.08)] px-1 py-0.5 font-medium text-[rgb(var(--ec-accent))]">
|
|
1103
|
+
AI
|
|
1104
|
+
</span>
|
|
1105
|
+
{configured
|
|
1106
|
+
? pageContext
|
|
1107
|
+
? `Based on ${pageContext.name}`
|
|
1108
|
+
: 'Your architecture, connected'
|
|
1109
|
+
: 'Explore your catalog with AI'}
|
|
1110
|
+
</div>
|
|
1111
|
+
<div className="ec-chat-composer-actions absolute right-2 z-10">
|
|
1112
|
+
{isStreaming || isSetupThinking ? (
|
|
1024
1113
|
<button
|
|
1025
1114
|
type="button"
|
|
1026
|
-
onClick={
|
|
1115
|
+
onClick={stopResponse}
|
|
1027
1116
|
className="p-2 text-red-500 hover:bg-red-500/10 rounded-lg transition-colors"
|
|
1028
1117
|
aria-label="Stop generating"
|
|
1029
1118
|
>
|
|
@@ -1074,10 +1163,10 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1074
1163
|
<Dialog.Portal>
|
|
1075
1164
|
<Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[300]" />
|
|
1076
1165
|
<Dialog.Content
|
|
1077
|
-
className="fixed inset-y-4 left-1/2 -translate-x-1/2 w-[95%] max-w-4xl md:inset-y-8 rounded-xl shadow-2xl z-[301] flex flex-col overflow-hidden focus:outline-hidden border border-[rgb(var(--ec-page-border))]"
|
|
1166
|
+
className="ec-chat-surface fixed inset-y-4 left-1/2 -translate-x-1/2 w-[95%] max-w-4xl md:inset-y-8 rounded-xl shadow-2xl z-[301] flex flex-col overflow-hidden focus:outline-hidden border border-[rgb(var(--ec-page-border))]"
|
|
1078
1167
|
style={{
|
|
1079
1168
|
background: `
|
|
1080
|
-
radial-gradient(ellipse 100% 40% at 50% 100%, rgb(var(--ec-accent) / 0.
|
|
1169
|
+
radial-gradient(ellipse 100% 40% at 50% 100%, rgb(var(--ec-accent) / 0.045) 0%, transparent 100%),
|
|
1081
1170
|
rgb(var(--ec-page-bg))
|
|
1082
1171
|
`,
|
|
1083
1172
|
}}
|
|
@@ -1086,7 +1175,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1086
1175
|
<div className="flex items-center justify-between px-5 py-3 border-b border-[rgb(var(--ec-page-border))] flex-shrink-0">
|
|
1087
1176
|
<div className="flex items-center space-x-2.5">
|
|
1088
1177
|
<div className="p-1.5 bg-[rgb(var(--ec-accent-subtle))] rounded-lg">
|
|
1089
|
-
<
|
|
1178
|
+
<Sparkles size={18} className="text-[rgb(var(--ec-accent))]" />
|
|
1090
1179
|
</div>
|
|
1091
1180
|
<Dialog.Title className="text-base font-medium text-[rgb(var(--ec-page-text))]">Ask AI</Dialog.Title>
|
|
1092
1181
|
</div>
|
|
@@ -1137,7 +1226,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1137
1226
|
)}
|
|
1138
1227
|
{hasMessages && (
|
|
1139
1228
|
<button
|
|
1140
|
-
onClick={
|
|
1229
|
+
onClick={clearMessages}
|
|
1141
1230
|
className="p-2 rounded-lg hover:bg-[rgb(var(--ec-content-hover))] text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-page-text))] transition-colors"
|
|
1142
1231
|
aria-label="Clear chat"
|
|
1143
1232
|
title="Clear chat"
|
|
@@ -1166,22 +1255,6 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1166
1255
|
</div>
|
|
1167
1256
|
</div>
|
|
1168
1257
|
|
|
1169
|
-
{/* Thinking indicator */}
|
|
1170
|
-
{isThinking && (
|
|
1171
|
-
<div className="px-5 py-2 flex items-center gap-2 border-b border-[rgb(var(--ec-page-border))]">
|
|
1172
|
-
<div className="w-1.5 h-1.5 bg-[rgb(var(--ec-accent))] rounded-full animate-pulse" />
|
|
1173
|
-
<span className="text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
1174
|
-
{runningTools.length > 0 ? (
|
|
1175
|
-
<>
|
|
1176
|
-
Using <span className="font-medium text-[rgb(var(--ec-accent))]">{runningTools[0]}</span>...
|
|
1177
|
-
</>
|
|
1178
|
-
) : (
|
|
1179
|
-
'Thinking...'
|
|
1180
|
-
)}
|
|
1181
|
-
</span>
|
|
1182
|
-
</div>
|
|
1183
|
-
)}
|
|
1184
|
-
|
|
1185
1258
|
{/* Modal Content */}
|
|
1186
1259
|
<div className="flex-1 overflow-y-auto px-6 py-6">
|
|
1187
1260
|
{!hasMessages ? (
|
|
@@ -1191,11 +1264,11 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1191
1264
|
<div className="flex-1 flex flex-col justify-center items-center text-center">
|
|
1192
1265
|
{/* Icon with circular background */}
|
|
1193
1266
|
<div className="relative mb-8">
|
|
1194
|
-
<div className="w-
|
|
1195
|
-
<MessageSquare size={
|
|
1267
|
+
<div className="ec-chat-avatar w-28 h-28 rounded-full flex items-center justify-center">
|
|
1268
|
+
<MessageSquare size={48} className="text-[rgb(var(--ec-accent))]" strokeWidth={1.5} />
|
|
1196
1269
|
</div>
|
|
1197
1270
|
</div>
|
|
1198
|
-
<h2 className="text-2xl font-
|
|
1271
|
+
<h2 className="text-2xl font-medium text-[rgb(var(--ec-page-text))] mb-2">{greeting}</h2>
|
|
1199
1272
|
<p className="font-normal text-[rgb(var(--ec-content-text))] text-center">
|
|
1200
1273
|
I'm here to help with your architecture
|
|
1201
1274
|
</p>
|
|
@@ -1207,7 +1280,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1207
1280
|
<button
|
|
1208
1281
|
key={index}
|
|
1209
1282
|
onClick={() => handleSuggestedAction(question.prompt)}
|
|
1210
|
-
className="px-
|
|
1283
|
+
className="ec-chat-suggestion px-3 py-2 text-xs text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))] bg-[rgb(var(--ec-accent)/0.04)] hover:bg-[rgb(var(--ec-accent)/0.09)] rounded-full transition-all duration-200 text-left"
|
|
1211
1284
|
>
|
|
1212
1285
|
{question.label}
|
|
1213
1286
|
</button>
|
|
@@ -1223,11 +1296,16 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1223
1296
|
const completedTools = message.role === 'assistant' ? getCompletedTools(message) : [];
|
|
1224
1297
|
const isLastMessage = messageIndex === messages.length - 1;
|
|
1225
1298
|
return (
|
|
1226
|
-
<div
|
|
1299
|
+
<div
|
|
1300
|
+
key={message.id}
|
|
1301
|
+
className={`ec-chat-message ${!configured && message.role === 'assistant' ? 'ec-chat-setup' : ''} flex flex-col ${message.role === 'user' ? 'items-end' : 'items-start'}`}
|
|
1302
|
+
>
|
|
1227
1303
|
{message.role === 'user' ? (
|
|
1228
1304
|
<div className="max-w-[75%] rounded-2xl rounded-br-md px-4 py-2.5 bg-[rgb(var(--ec-page-text)/0.05)]">
|
|
1229
1305
|
<p className="text-sm font-normal whitespace-pre-wrap text-[rgb(var(--ec-page-text))]">{content}</p>
|
|
1230
1306
|
</div>
|
|
1307
|
+
) : !configured ? (
|
|
1308
|
+
<OfflineReply />
|
|
1231
1309
|
) : (
|
|
1232
1310
|
<>
|
|
1233
1311
|
{/* Tools used indicator */}
|
|
@@ -1260,7 +1338,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1260
1338
|
<button
|
|
1261
1339
|
key={index}
|
|
1262
1340
|
onClick={() => handleSuggestedAction(suggestion)}
|
|
1263
|
-
className="px-
|
|
1341
|
+
className="ec-chat-suggestion px-3 py-2 text-xs text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))] bg-[rgb(var(--ec-accent)/0.04)] hover:bg-[rgb(var(--ec-accent)/0.09)] rounded-full transition-all duration-200 text-left"
|
|
1264
1342
|
style={fadeInStyles.getFollowUpStyle(index)}
|
|
1265
1343
|
>
|
|
1266
1344
|
{suggestion}
|
|
@@ -1276,7 +1354,7 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1276
1354
|
|
|
1277
1355
|
{isThinking && (
|
|
1278
1356
|
<div className="w-full max-w-md">
|
|
1279
|
-
<SkeletonLoader />
|
|
1357
|
+
<SkeletonLoader label={runningTools.length > 0 ? `Using ${runningTools[0]}…` : undefined} />
|
|
1280
1358
|
</div>
|
|
1281
1359
|
)}
|
|
1282
1360
|
|
|
@@ -1300,10 +1378,11 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1300
1378
|
{/* Modal Input area */}
|
|
1301
1379
|
<div className="flex-shrink-0 px-6 py-4">
|
|
1302
1380
|
<form onSubmit={handleSubmit} className="max-w-3xl mx-auto">
|
|
1303
|
-
<div className="relative bg-[rgb(var(--ec-page-bg)/0.5)] backdrop-blur-sm rounded-xl border border-[rgb(var(--ec-accent)/0.3)] focus-within:border-[rgb(var(--ec-accent)/0.5)] focus-within:ring-2 focus-within:ring-[rgb(var(--ec-accent)/0.1)] transition-all">
|
|
1304
|
-
<
|
|
1381
|
+
<div className="ec-chat-composer relative bg-[rgb(var(--ec-page-bg)/0.5)] backdrop-blur-sm rounded-xl border border-[rgb(var(--ec-accent)/0.3)] focus-within:border-[rgb(var(--ec-accent)/0.5)] focus-within:ring-2 focus-within:ring-[rgb(var(--ec-accent)/0.1)] transition-all">
|
|
1382
|
+
<textarea
|
|
1305
1383
|
ref={modalInputRef}
|
|
1306
|
-
|
|
1384
|
+
rows={2}
|
|
1385
|
+
aria-label="Ask AI a question"
|
|
1307
1386
|
value={inputValue}
|
|
1308
1387
|
onChange={(e) => setInputValue(e.target.value)}
|
|
1309
1388
|
onKeyDown={handleInputKeyDown}
|
|
@@ -1311,11 +1390,21 @@ const ChatPanel = ({ isOpen, onClose }: ChatPanelProps) => {
|
|
|
1311
1390
|
disabled={isLoading}
|
|
1312
1391
|
className="w-full px-4 py-3.5 pr-20 bg-transparent text-[rgb(var(--ec-input-text))] placeholder-[rgb(var(--ec-input-placeholder))] focus:outline-hidden text-sm disabled:opacity-50 rounded-xl"
|
|
1313
1392
|
/>
|
|
1314
|
-
<div className="absolute right-
|
|
1315
|
-
|
|
1393
|
+
<div className="absolute bottom-4 left-4 right-20 truncate text-[10px] text-[rgb(var(--ec-page-text-muted))]">
|
|
1394
|
+
<span className="mr-1.5 rounded bg-[rgb(var(--ec-accent)/0.08)] px-1 py-0.5 font-medium text-[rgb(var(--ec-accent))]">
|
|
1395
|
+
AI
|
|
1396
|
+
</span>
|
|
1397
|
+
{configured
|
|
1398
|
+
? pageContext
|
|
1399
|
+
? `Based on ${pageContext.name}`
|
|
1400
|
+
: 'Your architecture, connected'
|
|
1401
|
+
: 'Explore your catalog with AI'}
|
|
1402
|
+
</div>
|
|
1403
|
+
<div className="ec-chat-composer-actions absolute right-2 z-10">
|
|
1404
|
+
{isStreaming || isSetupThinking ? (
|
|
1316
1405
|
<button
|
|
1317
1406
|
type="button"
|
|
1318
|
-
onClick={
|
|
1407
|
+
onClick={stopResponse}
|
|
1319
1408
|
className="p-2 text-red-500 hover:bg-red-500/10 rounded-lg transition-colors"
|
|
1320
1409
|
aria-label="Stop generating"
|
|
1321
1410
|
>
|