@cognizant-ai-lab/ui-common 1.3.3
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/components/AgentChat/ChatCommon.d.ts +94 -0
- package/dist/components/AgentChat/ChatCommon.js +581 -0
- package/dist/components/AgentChat/ControlButtons.d.ts +16 -0
- package/dist/components/AgentChat/ControlButtons.js +24 -0
- package/dist/components/AgentChat/FormattedMarkdown.d.ts +32 -0
- package/dist/components/AgentChat/FormattedMarkdown.js +82 -0
- package/dist/components/AgentChat/Greetings.d.ts +1 -0
- package/dist/components/AgentChat/Greetings.js +38 -0
- package/dist/components/AgentChat/LlmChatButton.d.ts +12 -0
- package/dist/components/AgentChat/LlmChatButton.js +33 -0
- package/dist/components/AgentChat/SendButton.d.ts +12 -0
- package/dist/components/AgentChat/SendButton.js +28 -0
- package/dist/components/AgentChat/SyntaxHighlighterThemes.d.ts +14 -0
- package/dist/components/AgentChat/SyntaxHighlighterThemes.js +27 -0
- package/dist/components/AgentChat/Types.d.ts +17 -0
- package/dist/components/AgentChat/Types.js +26 -0
- package/dist/components/AgentChat/UserQueryDisplay.d.ts +5 -0
- package/dist/components/AgentChat/UserQueryDisplay.js +33 -0
- package/dist/components/AgentChat/Utils.d.ts +11 -0
- package/dist/components/AgentChat/Utils.js +64 -0
- package/dist/components/AgentChat/VoiceChat/MicrophoneButton.d.ts +29 -0
- package/dist/components/AgentChat/VoiceChat/MicrophoneButton.js +55 -0
- package/dist/components/AgentChat/VoiceChat/VoiceChat.d.ts +33 -0
- package/dist/components/AgentChat/VoiceChat/VoiceChat.js +180 -0
- package/dist/components/Authentication/Auth.d.ts +14 -0
- package/dist/components/Authentication/Auth.js +58 -0
- package/dist/components/ChatBot/ChatBot.d.ts +20 -0
- package/dist/components/ChatBot/ChatBot.js +75 -0
- package/dist/components/Common/Breadcrumbs.d.ts +6 -0
- package/dist/components/Common/Breadcrumbs.js +36 -0
- package/dist/components/Common/LlmChatOptionsButton.d.ts +9 -0
- package/dist/components/Common/LlmChatOptionsButton.js +31 -0
- package/dist/components/Common/LoadingSpinner.d.ts +10 -0
- package/dist/components/Common/LoadingSpinner.js +24 -0
- package/dist/components/Common/MUIAccordion.d.ts +17 -0
- package/dist/components/Common/MUIAccordion.js +76 -0
- package/dist/components/Common/MUIAlert.d.ts +11 -0
- package/dist/components/Common/MUIAlert.js +41 -0
- package/dist/components/Common/MUIDialog.d.ts +16 -0
- package/dist/components/Common/MUIDialog.js +40 -0
- package/dist/components/Common/Navbar.d.ts +15 -0
- package/dist/components/Common/Navbar.js +137 -0
- package/dist/components/Common/PageLoader.d.ts +5 -0
- package/dist/components/Common/PageLoader.js +26 -0
- package/dist/components/Common/Snackbar.d.ts +5 -0
- package/dist/components/Common/Snackbar.js +84 -0
- package/dist/components/Common/confirmationModal.d.ts +14 -0
- package/dist/components/Common/confirmationModal.js +65 -0
- package/dist/components/Common/notification.d.ts +18 -0
- package/dist/components/Common/notification.js +79 -0
- package/dist/components/ErrorPage/ErrorBoundary.d.ts +38 -0
- package/dist/components/ErrorPage/ErrorBoundary.js +77 -0
- package/dist/components/ErrorPage/ErrorPage.d.ts +12 -0
- package/dist/components/ErrorPage/ErrorPage.js +46 -0
- package/dist/components/MultiAgentAccelerator/AgentFlow.d.ts +21 -0
- package/dist/components/MultiAgentAccelerator/AgentFlow.js +394 -0
- package/dist/components/MultiAgentAccelerator/AgentNode.d.ts +18 -0
- package/dist/components/MultiAgentAccelerator/AgentNode.js +129 -0
- package/dist/components/MultiAgentAccelerator/GraphLayouts.d.ts +33 -0
- package/dist/components/MultiAgentAccelerator/GraphLayouts.js +297 -0
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +17 -0
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +208 -0
- package/dist/components/MultiAgentAccelerator/PlasmaEdge.d.ts +3 -0
- package/dist/components/MultiAgentAccelerator/PlasmaEdge.js +124 -0
- package/dist/components/MultiAgentAccelerator/Sidebar.d.ts +12 -0
- package/dist/components/MultiAgentAccelerator/Sidebar.js +204 -0
- package/dist/components/MultiAgentAccelerator/ThoughtBubbleEdge.d.ts +12 -0
- package/dist/components/MultiAgentAccelerator/ThoughtBubbleEdge.js +15 -0
- package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.d.ts +11 -0
- package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.js +466 -0
- package/dist/components/MultiAgentAccelerator/const.d.ts +7 -0
- package/dist/components/MultiAgentAccelerator/const.js +39 -0
- package/dist/const.d.ts +10 -0
- package/dist/const.js +30 -0
- package/dist/controller/agent/Agent.d.ts +56 -0
- package/dist/controller/agent/Agent.js +162 -0
- package/dist/controller/llm/LlmChat.d.ts +18 -0
- package/dist/controller/llm/LlmChat.js +65 -0
- package/dist/controller/llm/endpoints.d.ts +1 -0
- package/dist/controller/llm/endpoints.js +17 -0
- package/dist/generated/neuro-san/NeuroSanClient.d.ts +413 -0
- package/dist/generated/neuro-san/NeuroSanClient.js +28 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +52 -0
- package/dist/state/UserInfo.d.ts +16 -0
- package/dist/state/UserInfo.js +27 -0
- package/dist/state/environment.d.ts +18 -0
- package/dist/state/environment.js +33 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/utils/Authentication.d.ts +31 -0
- package/dist/utils/Authentication.js +94 -0
- package/dist/utils/BrowserNavigation.d.ts +5 -0
- package/dist/utils/BrowserNavigation.js +22 -0
- package/dist/utils/Theme.d.ts +7 -0
- package/dist/utils/Theme.js +7 -0
- package/dist/utils/agentConversations.d.ts +24 -0
- package/dist/utils/agentConversations.js +113 -0
- package/dist/utils/text.d.ts +28 -0
- package/dist/utils/text.js +64 -0
- package/dist/utils/title.d.ts +1 -0
- package/dist/utils/title.js +20 -0
- package/dist/utils/types.d.ts +17 -0
- package/dist/utils/types.js +16 -0
- package/dist/utils/useLocalStorage.d.ts +1 -0
- package/dist/utils/useLocalStorage.js +55 -0
- package/dist/utils/zIndexLayers.d.ts +2 -0
- package/dist/utils/zIndexLayers.js +29 -0
- package/package.json +69 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
// #endregion: Types
|
|
17
|
+
// Check if browser is Chrome (excluding Edge). Only Chrome (on Mac OS and Windows) has full support for
|
|
18
|
+
// SpeechRecognition. Also, tested that this will exclude Firefox and Safari.
|
|
19
|
+
const isChrome = () => /Chrome/u.test(navigator.userAgent) && !/Edge/u.test(navigator.userAgent) && !/Edg\//u.test(navigator.userAgent);
|
|
20
|
+
// Check browser/platform support
|
|
21
|
+
export const checkSpeechSupport = () => {
|
|
22
|
+
if (typeof window === "undefined")
|
|
23
|
+
return false;
|
|
24
|
+
// Check if browser supports SpeechRecognition
|
|
25
|
+
const hasSpeechRecognition = "SpeechRecognition" in window;
|
|
26
|
+
// Only Chrome provides reliable speech recognition support
|
|
27
|
+
return hasSpeechRecognition && isChrome();
|
|
28
|
+
};
|
|
29
|
+
// Handle speech recognition start
|
|
30
|
+
const handleRecognitionStart = (setVoiceInputState) => () => {
|
|
31
|
+
setVoiceInputState((prev) => ({
|
|
32
|
+
...prev,
|
|
33
|
+
currentTranscript: "",
|
|
34
|
+
isListening: true,
|
|
35
|
+
}));
|
|
36
|
+
};
|
|
37
|
+
// Handle speech recognition end
|
|
38
|
+
const handleRecognitionEnd = (setVoiceInputState) => () => {
|
|
39
|
+
setVoiceInputState((prev) => ({
|
|
40
|
+
...prev,
|
|
41
|
+
isListening: false,
|
|
42
|
+
isProcessingSpeech: false,
|
|
43
|
+
}));
|
|
44
|
+
};
|
|
45
|
+
// Handle speech recognition results
|
|
46
|
+
const handleRecognitionResult = (setVoiceInputState, setChatInput) => (event) => {
|
|
47
|
+
// interimTranscript: accumulates live (non-final) results for real-time feedback (currentTranscript).
|
|
48
|
+
let interimTranscript = "";
|
|
49
|
+
// finalTranscript: accumulates finalized results for input (finalTranscript).
|
|
50
|
+
let finalTranscript = "";
|
|
51
|
+
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
52
|
+
const transcript = event.results[i][0].transcript;
|
|
53
|
+
if (event.results[i].isFinal) {
|
|
54
|
+
// Finalized segment, ready for input
|
|
55
|
+
finalTranscript += transcript;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Live segment, for real-time feedback
|
|
59
|
+
interimTranscript += transcript;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
setVoiceInputState((prev) => ({
|
|
63
|
+
...prev,
|
|
64
|
+
currentTranscript: interimTranscript,
|
|
65
|
+
finalTranscript,
|
|
66
|
+
// Always show loading indicator while listening, until final transcript or recognition end
|
|
67
|
+
isProcessingSpeech: true,
|
|
68
|
+
}));
|
|
69
|
+
// Process final transcript
|
|
70
|
+
if (finalTranscript) {
|
|
71
|
+
setVoiceInputState((prev) => ({
|
|
72
|
+
...prev,
|
|
73
|
+
isProcessingSpeech: false,
|
|
74
|
+
}));
|
|
75
|
+
setChatInput((prev) => {
|
|
76
|
+
const needsSpace = prev && !prev.endsWith(" ");
|
|
77
|
+
return prev + (needsSpace ? " " : "") + finalTranscript;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
// Handle speech recognition errors
|
|
82
|
+
const handleRecognitionError = (setVoiceInputState) => (event) => {
|
|
83
|
+
console.error("Speech recognition error:", event.error);
|
|
84
|
+
setVoiceInputState((prev) => ({
|
|
85
|
+
...prev,
|
|
86
|
+
isListening: false,
|
|
87
|
+
isProcessingSpeech: false,
|
|
88
|
+
}));
|
|
89
|
+
};
|
|
90
|
+
// Remove speech recognition event handlers and stop speech recognition
|
|
91
|
+
export function cleanupAndStopSpeechRecognition(speechRecognitionRef, handlers) {
|
|
92
|
+
const speechRecognition = speechRecognitionRef.current;
|
|
93
|
+
if (!speechRecognition || !handlers)
|
|
94
|
+
return;
|
|
95
|
+
speechRecognition.removeEventListener("end", handlers.end);
|
|
96
|
+
speechRecognition.removeEventListener("error", handlers.error);
|
|
97
|
+
speechRecognition.removeEventListener("result", handlers.result);
|
|
98
|
+
speechRecognition.removeEventListener("start", handlers.start);
|
|
99
|
+
try {
|
|
100
|
+
speechRecognition.stop();
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.warn("Error stopping speechRecognition:", error);
|
|
104
|
+
}
|
|
105
|
+
speechRecognitionRef.current = null;
|
|
106
|
+
}
|
|
107
|
+
export function setupSpeechRecognition(setChatInput, setVoiceInputState, speechRecognitionRef) {
|
|
108
|
+
const speechSupported = checkSpeechSupport();
|
|
109
|
+
if (speechSupported) {
|
|
110
|
+
const speechRecognition = new SpeechRecognition();
|
|
111
|
+
speechRecognition.continuous = true;
|
|
112
|
+
speechRecognition.interimResults = true;
|
|
113
|
+
speechRecognition.lang = navigator.language || "en-US";
|
|
114
|
+
// Create handler references
|
|
115
|
+
const handlers = {
|
|
116
|
+
end: handleRecognitionEnd(setVoiceInputState),
|
|
117
|
+
error: handleRecognitionError(setVoiceInputState),
|
|
118
|
+
result: handleRecognitionResult(setVoiceInputState, setChatInput),
|
|
119
|
+
start: handleRecognitionStart(setVoiceInputState),
|
|
120
|
+
};
|
|
121
|
+
speechRecognition.addEventListener("end", handlers.end);
|
|
122
|
+
speechRecognition.addEventListener("error", handlers.error);
|
|
123
|
+
speechRecognition.addEventListener("result", handlers.result);
|
|
124
|
+
speechRecognition.addEventListener("start", handlers.start);
|
|
125
|
+
speechRecognitionRef.current = speechRecognition;
|
|
126
|
+
return handlers;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
speechRecognitionRef.current = null;
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Request microphone permission
|
|
134
|
+
const requestMicrophonePermission = async () => {
|
|
135
|
+
if (!isChrome())
|
|
136
|
+
return false;
|
|
137
|
+
if ("mediaDevices" in navigator && "getUserMedia" in navigator.mediaDevices) {
|
|
138
|
+
try {
|
|
139
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
140
|
+
audio: {
|
|
141
|
+
echoCancellation: true,
|
|
142
|
+
noiseSuppression: true,
|
|
143
|
+
autoGainControl: true,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (error instanceof Error &&
|
|
151
|
+
(error.name === "NotAllowedError" || error.name === "PermissionDeniedError")) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
// For other errors, still allow recognition to proceed (Chrome supports it)
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
};
|
|
160
|
+
// Toggle listening function
|
|
161
|
+
export const toggleListening = async (isMicOn, recognition) => {
|
|
162
|
+
if (!recognition)
|
|
163
|
+
return;
|
|
164
|
+
if (isMicOn) {
|
|
165
|
+
// Request microphone permission before starting
|
|
166
|
+
const hasPermission = await requestMicrophonePermission();
|
|
167
|
+
if (hasPermission) {
|
|
168
|
+
recognition.start();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// Stop recognition immediately
|
|
173
|
+
try {
|
|
174
|
+
recognition.stop();
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
console.warn("Error stopping speech recognition:", error);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
interface AuthProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Higher-level component to wrap pages that require authentication.
|
|
7
|
+
*
|
|
8
|
+
* Taken from here: https://github.com/nextauthjs/next-auth/issues/1210#issuecomment-782630909
|
|
9
|
+
*
|
|
10
|
+
* @param children Contained components protected by the authentication guard
|
|
11
|
+
* @return children (protected) components if user is authenticated, otherwise "Loading" message.
|
|
12
|
+
*/
|
|
13
|
+
export declare const Auth: ({ children }: AuthProps) => import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Utility functions relating to authentication
|
|
19
|
+
*/
|
|
20
|
+
import { signIn, useSession } from "next-auth/react";
|
|
21
|
+
import { useEffect } from "react";
|
|
22
|
+
import { PageLoader } from "../Common/PageLoader.js";
|
|
23
|
+
// Use this provider for authentication via next-auth. It *must* correspond to one of those listed in
|
|
24
|
+
// ./pages/api/auth/[...nextauth].js
|
|
25
|
+
const AUTHENTICATION_PROVIDER = "auth0";
|
|
26
|
+
/**
|
|
27
|
+
* Higher-level component to wrap pages that require authentication.
|
|
28
|
+
*
|
|
29
|
+
* Taken from here: https://github.com/nextauthjs/next-auth/issues/1210#issuecomment-782630909
|
|
30
|
+
*
|
|
31
|
+
* @param children Contained components protected by the authentication guard
|
|
32
|
+
* @return children (protected) components if user is authenticated, otherwise "Loading" message.
|
|
33
|
+
*/
|
|
34
|
+
export const Auth = ({ children }) => {
|
|
35
|
+
// Suppress no-shadow rule -- we have to use what the API gives us
|
|
36
|
+
// eslint-disable-next-line no-shadow
|
|
37
|
+
const { data: session, status } = useSession();
|
|
38
|
+
const isUser = Boolean(session?.user);
|
|
39
|
+
const loading = status === "loading";
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (loading) {
|
|
42
|
+
// Do nothing while loading
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// If not authenticated, force log in.
|
|
46
|
+
// By explicitly specifying the provider here, it skips the annoying and unnecessary next-auth interstitial
|
|
47
|
+
// screen and goes straight to the login screen of AUTHENTICATION_PROVIDER.
|
|
48
|
+
if (!isUser) {
|
|
49
|
+
void signIn(AUTHENTICATION_PROVIDER);
|
|
50
|
+
}
|
|
51
|
+
}, [isUser, loading]);
|
|
52
|
+
if (isUser && !loading) {
|
|
53
|
+
return _jsx(_Fragment, { children: children });
|
|
54
|
+
}
|
|
55
|
+
// Session is being fetched, or no user.
|
|
56
|
+
// If no user, useEffect() will redirect.
|
|
57
|
+
return _jsx(PageLoader, { id: "authentication-page" });
|
|
58
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
interface ChatBotProps {
|
|
3
|
+
/**
|
|
4
|
+
* id HTML id to use for the outer component
|
|
5
|
+
*/
|
|
6
|
+
readonly id: string;
|
|
7
|
+
/**
|
|
8
|
+
* Path to image for user avatar
|
|
9
|
+
*/
|
|
10
|
+
readonly userAvatar: string;
|
|
11
|
+
/**
|
|
12
|
+
* Text about current page to help the LLM respond intelligently
|
|
13
|
+
*/
|
|
14
|
+
readonly pageContext: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Site-wide Chatbot component.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ChatBot: FC<ChatBotProps>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import ContactSupportIcon from "@mui/icons-material/ContactSupport";
|
|
18
|
+
import { useColorScheme, useTheme } from "@mui/material";
|
|
19
|
+
import Box from "@mui/material/Box";
|
|
20
|
+
import Grow from "@mui/material/Grow";
|
|
21
|
+
import { useState } from "react";
|
|
22
|
+
import { CHATBOT_ENDPOINT } from "../../controller/llm/endpoints.js";
|
|
23
|
+
import { useAuthentication } from "../../utils/Authentication.js";
|
|
24
|
+
import { isDarkMode } from "../../utils/Theme.js";
|
|
25
|
+
import { getZIndex } from "../../utils/zIndexLayers.js";
|
|
26
|
+
import { ChatCommon } from "../AgentChat/ChatCommon.js";
|
|
27
|
+
import { LegacyAgentType } from "../AgentChat/Types.js";
|
|
28
|
+
/**
|
|
29
|
+
* Site-wide Chatbot component.
|
|
30
|
+
*/
|
|
31
|
+
// Temporarily disabled but will be used once we migrated the backend to use Neuro-san RAG.
|
|
32
|
+
export const ChatBot = ({ id, userAvatar, pageContext }) => {
|
|
33
|
+
const [chatOpen, setChatOpen] = useState(false);
|
|
34
|
+
const [isAwaitingLlm, setIsAwaitingLlm] = useState(false);
|
|
35
|
+
const { user: { name: currentUser }, } = useAuthentication().data;
|
|
36
|
+
// MUI theme and dark mode
|
|
37
|
+
const theme = useTheme();
|
|
38
|
+
const { mode, systemMode } = useColorScheme();
|
|
39
|
+
const darkMode = isDarkMode(mode, systemMode);
|
|
40
|
+
// Shadow color for icon. TODO: use MUI theme system instead.
|
|
41
|
+
const shadowColor = darkMode ? "255, 255, 255" : "0, 0, 0";
|
|
42
|
+
return (_jsxs(_Fragment, { children: [_jsx(Grow, { id: `chatbot-window-animation-${id}`, in: chatOpen, timeout: 300, style: { transformOrigin: "bottom right" }, easing: "ease-in-out", children: _jsx(Box, { id: id, sx: {
|
|
43
|
+
position: "fixed",
|
|
44
|
+
bottom: 50,
|
|
45
|
+
right: "2rem",
|
|
46
|
+
height: "60%",
|
|
47
|
+
maxWidth: 400,
|
|
48
|
+
background: darkMode ? "var(--bs-dark-mode-dim)" : "var(--bs-white)",
|
|
49
|
+
boxShadow: `0 0px 2px 0 rgba(${shadowColor}, 0.15)`,
|
|
50
|
+
borderRadius: "var(--bs-border-radius)",
|
|
51
|
+
borderWidth: 1,
|
|
52
|
+
borderColor: darkMode ? "var(--bs-white)" : "var(--bs-gray-light)",
|
|
53
|
+
zIndex: getZIndex(2, theme),
|
|
54
|
+
}, children: _jsx(ChatCommon, { id: "chatbot-window", currentUser: currentUser, setIsAwaitingLlm: setIsAwaitingLlm, isAwaitingLlm: isAwaitingLlm, targetAgent: LegacyAgentType.ChatBot, userImage: userAvatar, legacyAgentEndpoint: CHATBOT_ENDPOINT, extraParams: { pageContext }, backgroundColor: darkMode ? "var(--bs-gray-dark)" : "var(--bs-tertiary-blue)", title: "Cognizant Neuro AI Assistant", onClose: () => setChatOpen(false) }) }) }), !chatOpen && (_jsx(Box, { id: `chatbot-icon-${id}`, sx: {
|
|
55
|
+
display: "flex",
|
|
56
|
+
alignItems: "center",
|
|
57
|
+
backgroundColor: darkMode ? "var(--bs-dark-mode-dim)" : "var(--bs-white)",
|
|
58
|
+
borderRadius: "50%",
|
|
59
|
+
bottom: 16,
|
|
60
|
+
// For now mimic MUI's default box shadow but change color based on dark mode.
|
|
61
|
+
// Eventually we should use MUI's theme system.
|
|
62
|
+
boxShadow: `
|
|
63
|
+
0px 2px 4px -1px rgba(${shadowColor}, 0.2),
|
|
64
|
+
0px 4px 5px 0px rgba(${shadowColor}, 0.14),
|
|
65
|
+
0px 1px 10px 0px rgba(${shadowColor}, 0.12)
|
|
66
|
+
`,
|
|
67
|
+
cursor: "pointer",
|
|
68
|
+
height: 40,
|
|
69
|
+
justifyContent: "center",
|
|
70
|
+
maxWidth: 40,
|
|
71
|
+
padding: "1rem",
|
|
72
|
+
position: "fixed",
|
|
73
|
+
right: 16,
|
|
74
|
+
}, onClick: () => setChatOpen(true), children: _jsx(ContactSupportIcon, { id: `chatbot-icon-${id}` }) }))] }));
|
|
75
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
|
18
|
+
import { Typography } from "@mui/material";
|
|
19
|
+
import Breadcrumbs from "@mui/material/Breadcrumbs";
|
|
20
|
+
import Grid from "@mui/material/Grid";
|
|
21
|
+
import startCase from "lodash-es/startCase.js";
|
|
22
|
+
export const NeuroAIBreadcrumbs = ({ pathname }) => {
|
|
23
|
+
const urlPaths = pathname?.split("/").filter((path) => path !== "");
|
|
24
|
+
const pageName = startCase(urlPaths?.at(-1));
|
|
25
|
+
return (_jsx(Grid, { id: "nav-bar-breadcrumbs-row", container: true, sx: {
|
|
26
|
+
justifyContent: "flex-start",
|
|
27
|
+
width: "100%",
|
|
28
|
+
}, children: _jsxs(Breadcrumbs, { style: {
|
|
29
|
+
marginTop: "1rem",
|
|
30
|
+
}, id: "breadcrumb-nav", "aria-label": "breadcrumb", separator: _jsx(ChevronRightIcon, { id: "breadcrumb-separator", fontSize: "small" }), children: [_jsx("a", { id: "breadcrumb-link-home", href: "/", children: "Home" }), urlPaths?.slice(0, -1).map((urlPath, idx) => {
|
|
31
|
+
const redirectPath = urlPaths.slice(0, idx + 1).join("/");
|
|
32
|
+
return (_jsx("a", { id: `breadcrumb-link__${urlPath}`, style: {
|
|
33
|
+
textDecoration: "underlined",
|
|
34
|
+
}, href: `/${redirectPath}`, children: startCase(urlPath) }, urlPath));
|
|
35
|
+
}), _jsx(Typography, { id: "breadcrumb-link__current", children: pageName })] }) }));
|
|
36
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type LLMChatGroupConfigBtnProps = {
|
|
2
|
+
enabled?: boolean;
|
|
3
|
+
posRight?: number;
|
|
4
|
+
posBottom?: number;
|
|
5
|
+
};
|
|
6
|
+
export declare const LlmChatOptionsButton: import("@emotion/styled").StyledComponent<import("@mui/material").ButtonOwnProps & Omit<import("@mui/material").ButtonBaseOwnProps, "classes"> & import("@mui/material/OverridableComponent").CommonProps & Omit<Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
|
|
7
|
+
ref?: import("react").Ref<HTMLButtonElement>;
|
|
8
|
+
}, "className" | "style" | "classes" | "action" | "centerRipple" | "children" | "disabled" | "disableRipple" | "disableTouchRipple" | "focusRipple" | "focusVisibleClassName" | "LinkComponent" | "onFocusVisible" | "sx" | "tabIndex" | "TouchRippleProps" | "touchRippleRef" | "href" | "color" | "disableElevation" | "disableFocusRipple" | "endIcon" | "fullWidth" | "loading" | "loadingIndicator" | "loadingPosition" | "size" | "startIcon" | "variant"> & import("@mui/system").MUIStyledCommonProps<import("@mui/material").Theme> & LLMChatGroupConfigBtnProps, {}, {}>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { styled } from "@mui/material";
|
|
17
|
+
import Button from "@mui/material/Button";
|
|
18
|
+
import { getZIndex } from "../../utils/zIndexLayers.js";
|
|
19
|
+
export const LlmChatOptionsButton = styled(Button, {
|
|
20
|
+
shouldForwardProp: (prop) => prop !== "enabled" && prop !== "posRight" && prop !== "posBottom",
|
|
21
|
+
})(({ enabled, posRight }) => ({
|
|
22
|
+
position: "absolute",
|
|
23
|
+
top: 10,
|
|
24
|
+
right: posRight || null,
|
|
25
|
+
zIndex: getZIndex(1, null), // Seems to only be needed on Analytics Chat, but apply to all.
|
|
26
|
+
background: `${enabled ? "var(--bs-primary)" : "darkgray"} !important`,
|
|
27
|
+
borderColor: `${enabled ? "var(--bs-primary)" : "darkgray"} !important`,
|
|
28
|
+
borderRadius: "var(--bs-border-radius)",
|
|
29
|
+
width: "30px",
|
|
30
|
+
height: "30px",
|
|
31
|
+
}));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
interface LoadingSpinnerProps {
|
|
3
|
+
id: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* A simple loading spinner component that displays a circular progress indicator
|
|
7
|
+
* @param id The id for the spinner and text elements, for testing
|
|
8
|
+
*/
|
|
9
|
+
export declare const LoadingSpinner: FC<LoadingSpinnerProps>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import CircularProgress from "@mui/material/CircularProgress";
|
|
18
|
+
/**
|
|
19
|
+
* A simple loading spinner component that displays a circular progress indicator
|
|
20
|
+
* @param id The id for the spinner and text elements, for testing
|
|
21
|
+
*/
|
|
22
|
+
export const LoadingSpinner = ({ id }) => {
|
|
23
|
+
return (_jsxs("h3", { id: id, children: [_jsx(CircularProgress, { id: `${id}-spinner`, sx: { color: "var(--bs-primary)" }, size: 35 }), _jsx("span", { id: `${id}-loading-text`, style: { marginLeft: "1em" }, children: "Loading..." })] }));
|
|
24
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SxProps } from "@mui/material";
|
|
2
|
+
import { FC, ReactNode } from "react";
|
|
3
|
+
interface MUIAccordionItem {
|
|
4
|
+
content: ReactNode;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
title: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export interface MUIAccordionProps {
|
|
9
|
+
arrowPosition?: "left" | "right";
|
|
10
|
+
defaultExpandedPanelKey?: number;
|
|
11
|
+
expandOnlyOnePanel?: boolean;
|
|
12
|
+
id: string;
|
|
13
|
+
items: MUIAccordionItem[];
|
|
14
|
+
sx?: SxProps;
|
|
15
|
+
}
|
|
16
|
+
export declare const MUIAccordion: FC<MUIAccordionProps>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import ArrowForwardIosSharpIcon from "@mui/icons-material/ArrowForwardIosSharp";
|
|
18
|
+
import { styled, useColorScheme } from "@mui/material";
|
|
19
|
+
import MuiAccordion from "@mui/material/Accordion";
|
|
20
|
+
import MuiAccordionDetails from "@mui/material/AccordionDetails";
|
|
21
|
+
import MuiAccordionSummary, { accordionSummaryClasses } from "@mui/material/AccordionSummary";
|
|
22
|
+
import Typography from "@mui/material/Typography";
|
|
23
|
+
import { useCallback, useState } from "react";
|
|
24
|
+
import { isDarkMode } from "../../utils/Theme.js";
|
|
25
|
+
// #region: Styled Components
|
|
26
|
+
const Accordion = styled((props) => (_jsx(MuiAccordion, { disableGutters: true, elevation: 0, id: "mui-accordion", ...props })))(({ theme }) => ({
|
|
27
|
+
border: `1px solid ${theme.palette.divider}`,
|
|
28
|
+
"&::before": {
|
|
29
|
+
display: "none",
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
const AccordionSummary = styled((props) => (_jsx(MuiAccordionSummary, { id: "mui-accordion-summary", expandIcon: _jsx(ArrowForwardIosSharpIcon, { id: "arrow-forward", sx: { fontSize: "0.9rem" } }), ...props })))(({ theme }) => ({
|
|
33
|
+
[`& .${accordionSummaryClasses.expandIconWrapper}.${accordionSummaryClasses.expanded}`]: {
|
|
34
|
+
transform: "rotate(90deg)",
|
|
35
|
+
},
|
|
36
|
+
[`& .${accordionSummaryClasses.content}`]: {
|
|
37
|
+
marginLeft: theme.spacing(1),
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
|
|
41
|
+
borderTop: "1px solid rgba(0, 0, 0, .125)",
|
|
42
|
+
padding: theme.spacing(2),
|
|
43
|
+
}));
|
|
44
|
+
// #endregion: Types
|
|
45
|
+
export const MUIAccordion = ({ arrowPosition = "left", defaultExpandedPanelKey, expandOnlyOnePanel = false, id, items, sx, }) => {
|
|
46
|
+
// Dark mode
|
|
47
|
+
const { mode, systemMode } = useColorScheme();
|
|
48
|
+
const darkMode = isDarkMode(mode, systemMode);
|
|
49
|
+
const [expandedList, setExpandedList] = useState(defaultExpandedPanelKey ? [defaultExpandedPanelKey] : []);
|
|
50
|
+
const handleChange = useCallback((panelKey) => (_event, newExpanded) => {
|
|
51
|
+
setExpandedList((prevExpandedList) => {
|
|
52
|
+
if (!expandOnlyOnePanel) {
|
|
53
|
+
return newExpanded
|
|
54
|
+
? [...prevExpandedList, panelKey]
|
|
55
|
+
: prevExpandedList.filter((key) => key !== panelKey);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
return newExpanded ? [panelKey] : [];
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}, [expandOnlyOnePanel]);
|
|
62
|
+
const isExpanded = useCallback((panelKey) => expandedList.includes(panelKey), [expandedList]);
|
|
63
|
+
return (_jsx(_Fragment, { children: items.map(({ content, disabled = false, title }, index) => {
|
|
64
|
+
const panelKey = index + 1; // Start with index 1
|
|
65
|
+
const baseIdAndPanelKey = `${id}-${panelKey}`;
|
|
66
|
+
return (_jsxs(Accordion, { disabled: disabled, expanded: isExpanded(panelKey), id: `${baseIdAndPanelKey}-accordion`, onChange: handleChange(panelKey), sx: sx, children: [_jsx(AccordionSummary, { "aria-controls": `${baseIdAndPanelKey}-summary`, id: `${baseIdAndPanelKey}-summary`, sx: {
|
|
67
|
+
backgroundColor: darkMode ? "var(--bs-dark-mode-dim)" : "var(--bs-gray-background)",
|
|
68
|
+
color: darkMode ? "var(--bs-white)" : "var(--bs-primary)",
|
|
69
|
+
flexDirection: arrowPosition === "left" ? "row-reverse" : undefined,
|
|
70
|
+
}, children: _jsx(Typography, { component: "span", id: `${baseIdAndPanelKey}-summary-typography`, sx: { fontSize: "0.9rem" }, children: title }) }), _jsx(AccordionDetails, { id: `${baseIdAndPanelKey}-details`, sx: {
|
|
71
|
+
backgroundColor: darkMode ? "var(--bs-dark-mode-dim)" : "rgba(0, 0, 0, 0.02)",
|
|
72
|
+
borderColor: darkMode ? "var(--bs-white)" : "var(--bs-border-color)",
|
|
73
|
+
color: darkMode ? "var(--bs-white)" : "var(--bs-primary)",
|
|
74
|
+
}, children: _jsx(Typography, { component: "span", id: `${baseIdAndPanelKey}-details-typography`, sx: { fontSize: "0.85rem" }, children: content }) })] }, `${baseIdAndPanelKey}-accordion`));
|
|
75
|
+
}) }));
|
|
76
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AlertColor, SxProps } from "@mui/material";
|
|
2
|
+
import { FC, ReactNode } from "react";
|
|
3
|
+
interface MUIAlertProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
closeable?: boolean;
|
|
6
|
+
id: string;
|
|
7
|
+
severity: AlertColor;
|
|
8
|
+
sx?: SxProps;
|
|
9
|
+
}
|
|
10
|
+
export declare const MUIAlert: FC<MUIAlertProps>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import CloseIcon from "@mui/icons-material/Close";
|
|
18
|
+
import { Alert, Collapse, IconButton, styled } from "@mui/material";
|
|
19
|
+
import { useState } from "react";
|
|
20
|
+
// #region: Styled Components
|
|
21
|
+
const StyledAlert = styled(Alert)({
|
|
22
|
+
fontSize: "large",
|
|
23
|
+
marginBottom: "1rem",
|
|
24
|
+
"& .MuiAlert-message": {
|
|
25
|
+
paddingTop: "0.25rem",
|
|
26
|
+
paddingBottom: "0.25rem",
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
// #endregion: Types
|
|
30
|
+
export const MUIAlert = ({ children, closeable = false, id, severity, sx }) => {
|
|
31
|
+
const [alertOpen, setAlertOpen] = useState(true);
|
|
32
|
+
const handleClose = () => {
|
|
33
|
+
setAlertOpen(false);
|
|
34
|
+
};
|
|
35
|
+
return (_jsx(Collapse, { id: `${id}-collapse`, in: alertOpen, children: _jsx(StyledAlert, { action: closeable && (_jsx(IconButton, { "aria-label": "close", color: "inherit", id: `${id}-icon-button`, onClick: () => {
|
|
36
|
+
setAlertOpen(false);
|
|
37
|
+
}, size: "small", sx: {
|
|
38
|
+
bottom: "2px",
|
|
39
|
+
position: "relative",
|
|
40
|
+
}, children: _jsx(CloseIcon, { fontSize: "inherit", id: `${id}-close-icon` }) })), id: id, onClose: closeable ? handleClose : undefined, severity: severity, sx: sx, children: children }) }));
|
|
41
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SxProps } from "@mui/material";
|
|
2
|
+
import { FC, JSX as ReactJSX, ReactNode } from "react";
|
|
3
|
+
interface MUIDialogProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
className?: string;
|
|
6
|
+
closeable?: boolean;
|
|
7
|
+
contentSx?: SxProps;
|
|
8
|
+
footer?: ReactJSX.Element;
|
|
9
|
+
id: string;
|
|
10
|
+
isOpen: boolean;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
paperProps?: SxProps;
|
|
13
|
+
title?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
export declare const MUIDialog: FC<MUIDialogProps>;
|
|
16
|
+
export {};
|