@cognizant-ai-lab/ui-common 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/Theme/Palettes.d.ts +0 -9
  2. package/dist/Theme/Palettes.js +0 -18
  3. package/dist/components/AgentChat/ChatCommon/ChatCommon.d.ts +4 -1
  4. package/dist/components/AgentChat/ChatCommon/ChatCommon.js +74 -47
  5. package/dist/components/AgentChat/ChatCommon/Const.d.ts +1 -0
  6. package/dist/components/AgentChat/ChatCommon/Const.js +2 -0
  7. package/dist/components/AgentChat/ChatCommon/ControlButtons.d.ts +1 -1
  8. package/dist/components/AgentChat/ChatCommon/ControlButtons.js +7 -4
  9. package/dist/components/AgentChat/ChatCommon/FormattedMarkdown.js +3 -0
  10. package/dist/components/AgentChat/ChatCommon/SendButton.js +7 -6
  11. package/dist/components/AgentChat/ChatCommon/SyntaxHighlighterThemes.js +1 -1
  12. package/dist/components/AgentChat/ChatCommon/Thinking.d.ts +1 -0
  13. package/dist/components/AgentChat/ChatCommon/Thinking.js +10 -7
  14. package/dist/components/AgentChat/Common/LlmChatButton.d.ts +0 -2
  15. package/dist/components/AgentChat/Common/LlmChatButton.js +6 -9
  16. package/dist/components/AgentChat/VoiceChat/MicrophoneButton.js +12 -10
  17. package/dist/components/Common/Navbar.js +24 -22
  18. package/dist/components/Common/StatusLight.d.ts +9 -0
  19. package/dist/components/Common/StatusLight.js +21 -0
  20. package/dist/components/ErrorPage/ErrorBoundary.js +1 -1
  21. package/dist/components/ErrorPage/ErrorPage.d.ts +3 -3
  22. package/dist/components/ErrorPage/ErrorPage.js +4 -3
  23. package/dist/components/Logo/Common.d.ts +4 -0
  24. package/dist/components/Logo/Common.js +8 -0
  25. package/dist/components/Logo/CustomerLogo.d.ts +23 -0
  26. package/dist/components/Logo/CustomerLogo.js +41 -0
  27. package/dist/components/MultiAgentAccelerator/AgentConversations.js +0 -1
  28. package/dist/components/MultiAgentAccelerator/AgentFlow.js +85 -53
  29. package/dist/components/MultiAgentAccelerator/AgentNode.js +23 -27
  30. package/dist/components/MultiAgentAccelerator/GraphLayouts.d.ts +17 -10
  31. package/dist/components/MultiAgentAccelerator/GraphLayouts.js +4 -6
  32. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +3 -9
  33. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +28 -32
  34. package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.d.ts +0 -5
  35. package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.js +30 -25
  36. package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.d.ts +3 -4
  37. package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.js +84 -168
  38. package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.d.ts +30 -13
  39. package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +157 -100
  40. package/dist/components/MultiAgentAccelerator/Tour/MainTourSteps.js +10 -8
  41. package/dist/components/MultiAgentAccelerator/const.d.ts +2 -0
  42. package/dist/components/MultiAgentAccelerator/const.js +4 -0
  43. package/dist/components/Settings/ApiKeyInput.js +7 -8
  44. package/dist/components/Settings/InfoTip.d.ts +8 -0
  45. package/dist/components/Settings/InfoTip.js +14 -0
  46. package/dist/components/Settings/SettingsDialog.js +355 -181
  47. package/dist/components/Settings/SettingsRow.d.ts +18 -0
  48. package/dist/components/Settings/SettingsRow.js +35 -0
  49. package/dist/controller/agent/Agent.d.ts +3 -20
  50. package/dist/controller/agent/Agent.js +9 -43
  51. package/dist/controller/agent/IconSuggestions.d.ts +35 -0
  52. package/dist/controller/agent/IconSuggestions.js +85 -0
  53. package/dist/index.d.ts +0 -2
  54. package/dist/index.js +0 -2
  55. package/dist/state/IconSuggestions.d.ts +27 -0
  56. package/dist/state/IconSuggestions.js +50 -0
  57. package/dist/state/Settings.d.ts +17 -3
  58. package/dist/state/Settings.js +23 -1
  59. package/dist/tsconfig.build.tsbuildinfo +1 -1
  60. package/dist/utils/title.js +1 -1
  61. package/package.json +5 -1
  62. package/dist/components/Common/CustomerLogo.d.ts +0 -17
  63. package/dist/components/Common/CustomerLogo.js +0 -47
  64. package/dist/components/Common/MUIAccordion.d.ts +0 -17
  65. package/dist/components/Common/MUIAccordion.js +0 -66
  66. package/dist/utils/useLocalStorage.d.ts +0 -1
  67. package/dist/utils/useLocalStorage.js +0 -55
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /*
3
3
  Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
4
4
 
@@ -18,6 +18,7 @@ limitations under the License.
18
18
  * Main navigation bar that appears at the top of each page
19
19
  */
20
20
  import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
21
+ import Business from "@mui/icons-material/Business";
21
22
  import DarkModeIcon from "@mui/icons-material/DarkMode";
22
23
  import SettingsIcon from "@mui/icons-material/Settings";
23
24
  import Box from "@mui/material/Box";
@@ -28,14 +29,14 @@ import MenuItem from "@mui/material/MenuItem";
28
29
  import { useColorScheme } from "@mui/material/styles";
29
30
  import Tooltip from "@mui/material/Tooltip";
30
31
  import Typography from "@mui/material/Typography";
31
- import { useEffect, useState } from "react";
32
+ import { useState } from "react";
32
33
  import { ConfirmationModal } from "./ConfirmationModal.js";
33
- import { CustomerLogo, getCognizantLogoImage } from "./CustomerLogo.js";
34
- import { LoadingSpinner } from "./LoadingSpinner.js";
35
34
  import { authenticationEnabled, DEFAULT_USER_IMAGE, getContactUsConfirmationText, NEURO_SAN_UI_VERSION, } from "../../const.js";
36
35
  import { useSettingsStore } from "../../state/Settings.js";
37
36
  import { isDarkMode } from "../../Theme/Theme.js";
38
37
  import { navigateToUrl } from "../../utils/BrowserNavigation.js";
38
+ import { getCognizantLogoImage } from "../Logo/Common.js";
39
+ import { CustomerLogo } from "../Logo/CustomerLogo.js";
39
40
  import { SettingsDialog } from "../Settings/SettingsDialog.js";
40
41
  const MENU_ITEM_TEXT_PROPS = {
41
42
  color: "var(--bs-white)",
@@ -58,12 +59,6 @@ export const Navbar = ({ authenticationType, id, logo, logoServiceToken, onStart
58
59
  // Dark mode
59
60
  const { mode, setMode, systemMode } = useColorScheme();
60
61
  const darkMode = isDarkMode(mode, systemMode);
61
- // Gate to make sure we only attempt to render after Next.js has completed its rehydration
62
- const [hydrated, setHydrated] = useState(false);
63
- useEffect(() => {
64
- // Indicate that the component has been hydrated
65
- setHydrated(true);
66
- }, []);
67
62
  // Help menu wiring
68
63
  const [helpMenuAnchorEl, setHelpMenuAnchorEl] = useState(null);
69
64
  const helpMenuOpen = Boolean(helpMenuAnchorEl);
@@ -88,18 +83,25 @@ export const Navbar = ({ authenticationType, id, logo, logoServiceToken, onStart
88
83
  const customer = useSettingsStore((state) => state.settings.branding.customer);
89
84
  const primary = useSettingsStore((state) => state.settings.branding.primary);
90
85
  const hasCustomer = customer?.trim().length > 0;
91
- return hydrated ? (_jsxs(Grid, { id: "nav-bar-container", container: true, sx: {
86
+ // Logo settings
87
+ const iconSuggestion = useSettingsStore((state) => state.settings.branding.iconSuggestion);
88
+ const logoSource = useSettingsStore((state) => state.settings.branding.logoSource);
89
+ return (_jsxs(Grid, { id: "nav-bar-container", container: true, sx: {
92
90
  alignItems: "center",
93
91
  ...MENU_ITEM_TEXT_PROPS,
94
92
  padding: "0.5rem",
95
- }, children: [settingsDialogOpen && (_jsx(SettingsDialog, { id: "settings-dialog", isOpen: settingsDialogOpen, logoServiceToken: logoServiceToken, onClose: () => setSettingsDialogOpen(false) })), _jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 2 }, children: [_jsx(CustomerLogo, { logoServiceToken: logoServiceToken }), hasCustomer && (_jsx(Typography, { "data-testid": "customer-branding", sx: {
96
- fontSize: "20px",
97
- fontWeight: "600",
98
- paddingLeft: "0.15rem",
99
- width: "200px",
100
- display: "flex",
101
- alignItems: "center",
102
- }, children: customer }))] }), _jsxs(Grid, { id: id, sx: { display: "flex", alignItems: "center" }, children: [hasCustomer ? getCognizantLogoImage() : null, _jsx(Typography, { id: "nav-bar-brand", sx: {
93
+ }, children: [settingsDialogOpen && (_jsx(SettingsDialog, { id: "settings-dialog", isOpen: settingsDialogOpen, logoServiceToken: logoServiceToken, onClose: () => setSettingsDialogOpen(false) })), _jsx(Box, { sx: {
94
+ alignItems: "center",
95
+ display: "flex",
96
+ gap: 2,
97
+ }, children: hasCustomer ? (_jsxs(_Fragment, { children: [_jsx(CustomerLogo, { customer: customer, fallbackIcon: Business, iconSuggestion: iconSuggestion, logoServiceToken: logoServiceToken, logoSource: logoSource }), _jsx(Typography, { "data-testid": "customer-branding", sx: {
98
+ fontSize: "20px",
99
+ fontWeight: "600",
100
+ paddingLeft: "0.15rem",
101
+ width: "200px",
102
+ display: "flex",
103
+ alignItems: "center",
104
+ }, children: customer })] })) : (getCognizantLogoImage()) }), _jsxs(Grid, { id: id, sx: { display: "flex", alignItems: "center" }, children: [hasCustomer ? getCognizantLogoImage() : null, _jsx(Typography, { id: "nav-bar-brand", sx: {
103
105
  ...MENU_ITEM_TEXT_PROPS,
104
106
  marginLeft: "0.85rem",
105
107
  fontSize: "16px",
@@ -125,13 +127,13 @@ export const Navbar = ({ authenticationType, id, logo, logoServiceToken, onStart
125
127
  alignItems: "center",
126
128
  }, onClick: (event) => {
127
129
  setExploreMenuAnchorEl(event.currentTarget);
128
- }, children: ["Explore", _jsx(ArrowDropDownIcon, { id: "nav-explore-dropdown-arrow", sx: { color: primary || "var(--bs-white)", fontSize: 22 } })] }), _jsxs(Menu, { id: "explore-menu", anchorEl: exploreMenuAnchorEl, open: exploreMenuOpen, onClose: handleCloseExploreMenu, children: [_jsx(MenuItem, { id: "explore-neuro-san-studio", component: "a", href: "https://github.com/cognizant-ai-lab/neuro-san-studio", target: "_blank", sx: { ...DISABLE_OUTLINE_PROPS }, children: "Neuro-san studio (examples)" }, "explore-neuro-san-studio"), _jsx(MenuItem, { id: "explore-neuro-san", component: "a", href: "https://github.com/cognizant-ai-lab/neuro-san", target: "_blank", sx: { ...DISABLE_OUTLINE_PROPS }, children: "Neuro-san (core)" }, "explore-neuro-san")] })] }), _jsxs(Grid, { id: "help-dropdown", sx: { cursor: "pointer", marginRight: "30px" }, children: [_jsxs(Typography, { id: "help-toggle", sx: {
130
+ }, children: ["Explore", _jsx(ArrowDropDownIcon, { id: "nav-explore-dropdown-arrow", sx: { color: primary || "var(--bs-white)", fontSize: 22 } })] }), _jsxs(Menu, { id: "explore-menu", anchorEl: exploreMenuAnchorEl, open: exploreMenuOpen, onClose: handleCloseExploreMenu, children: [_jsx(MenuItem, { id: "explore-neuro-san-studio", component: "a", href: "https://github.com/cognizant-ai-lab/neuro-san-studio", rel: "noopener noreferrer", sx: { ...DISABLE_OUTLINE_PROPS }, target: "_blank", children: "Neuro-san studio (examples)" }, "explore-neuro-san-studio"), _jsx(MenuItem, { id: "explore-neuro-san", component: "a", href: "https://github.com/cognizant-ai-lab/neuro-san", rel: "noopener noreferrer", sx: { ...DISABLE_OUTLINE_PROPS }, target: "_blank", children: "Neuro-san (core)" }, "explore-neuro-san")] })] }), _jsxs(Grid, { id: "help-dropdown", sx: { cursor: "pointer", marginRight: "30px" }, children: [_jsxs(Typography, { id: "help-toggle", sx: {
129
131
  ...MENU_ITEM_TEXT_PROPS,
130
132
  display: "flex",
131
133
  alignItems: "center",
132
134
  }, onClick: (event) => {
133
135
  setHelpMenuAnchorEl(event.currentTarget);
134
- }, children: ["Help", _jsx(ArrowDropDownIcon, { id: "nav-help-dropdown-arrow", sx: { color: primary || "var(--bs-white)", fontSize: 22 } })] }), _jsxs(Menu, { id: "help-menu", anchorEl: helpMenuAnchorEl, open: helpMenuOpen, onClose: handleCloseHelpMenu, children: [_jsx(MenuItem, { id: "user-guide", component: "a", href: "/userguide", target: "_blank", sx: { ...DISABLE_OUTLINE_PROPS }, children: "User guide" }, "user-guide"), _jsx(MenuItem, { id: "take-a-tour", onClick: () => {
136
+ }, children: ["Help", _jsx(ArrowDropDownIcon, { id: "nav-help-dropdown-arrow", sx: { color: primary || "var(--bs-white)", fontSize: 22 } })] }), _jsxs(Menu, { id: "help-menu", anchorEl: helpMenuAnchorEl, open: helpMenuOpen, onClose: handleCloseHelpMenu, children: [_jsx(MenuItem, { id: "user-guide", component: "a", href: "/UserGuide", rel: "noopener noreferrer", sx: { ...DISABLE_OUTLINE_PROPS }, target: "_blank", children: "User guide" }, "user-guide"), _jsx(MenuItem, { id: "take-a-tour", onClick: () => {
135
137
  handleCloseHelpMenu();
136
138
  onStartTour?.();
137
139
  }, sx: { ...DISABLE_OUTLINE_PROPS }, children: "Take a tour" }, "take-a-tour"), _jsx(MenuItem, { href: null, id: "contact-us-help", onClick: () => setEmailDialogOpen(true), children: "Contact Us" }, "contact-us-help")] })] }), emailDialogOpen ? (_jsx(ConfirmationModal, { id: "email-dialog", content: getContactUsConfirmationText(supportEmailAddress), handleCancel: () => {
@@ -163,5 +165,5 @@ export const Navbar = ({ authenticationType, id, logo, logoServiceToken, onStart
163
165
  cursor: "pointer",
164
166
  }, onClick: () => {
165
167
  setSettingsDialogOpen(true);
166
- } }) }))] })) : (_jsx(LoadingSpinner, { id: "navbar-loading-spinner" }));
168
+ } }) }))] }));
167
169
  };
@@ -0,0 +1,9 @@
1
+ import { FC, ReactElement } from "react";
2
+ type Status = "green" | "red" | "unknown";
3
+ interface StatusLightProps {
4
+ readonly id?: string;
5
+ readonly statusValue: Status;
6
+ readonly tooltip?: ReactElement;
7
+ }
8
+ export declare const StatusLight: FC<StatusLightProps>;
9
+ export {};
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import Box from "@mui/material/Box";
3
+ import Tooltip from "@mui/material/Tooltip";
4
+ const statusToColor = (statusValue) => {
5
+ switch (statusValue) {
6
+ case "green":
7
+ return "success.main";
8
+ case "red":
9
+ return "error.main";
10
+ case "unknown":
11
+ default:
12
+ return "action.disabled";
13
+ }
14
+ };
15
+ export const StatusLight = ({ id, statusValue, tooltip }) => (_jsx(Tooltip, { title: tooltip, placement: "top", children: _jsx(Box, { id: id, "data-testid": id, "data-status": statusValue, sx: {
16
+ backgroundColor: statusToColor(statusValue),
17
+ borderRadius: "50%",
18
+ display: "inline-block",
19
+ minWidth: "0.75rem",
20
+ minHeight: "0.75rem",
21
+ } }) }));
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  */
17
17
  import { Component } from "react";
18
- import ErrorPage from "./ErrorPage.js";
18
+ import { default as ErrorPage } from "./ErrorPage.js";
19
19
  /**
20
20
  * Implements a system-wide error handler for Next.js pages in our app.
21
21
  * Taken from here: https://nextjs.org/docs/advanced-features/error-handling
@@ -1,4 +1,4 @@
1
- import { ReactElement } from "react";
1
+ import { FC } from "react";
2
2
  interface ErrorPageProps {
3
3
  id: string;
4
4
  errorText: string;
@@ -8,5 +8,5 @@ interface ErrorPageProps {
8
8
  * @param id HTML id for the <code>div</code> for this page
9
9
  * @param errorText Error text to be displayed
10
10
  */
11
- export default function ErrorPage({ id, errorText }: ErrorPageProps): ReactElement;
12
- export {};
11
+ declare const ErrorPage: FC<ErrorPageProps>;
12
+ export default ErrorPage;
@@ -28,9 +28,9 @@ import { Navbar } from "../Common/Navbar.js";
28
28
  * @param id HTML id for the <code>div</code> for this page
29
29
  * @param errorText Error text to be displayed
30
30
  */
31
- export default function ErrorPage({ id, errorText }) {
31
+ const ErrorPage = ({ id, errorText }) => {
32
32
  const { auth0ClientId, auth0Domain, supportEmailAddress, logoServiceToken } = useEnvironmentStore();
33
- // Access NextJS router
33
+ // Access Next.js router
34
34
  const router = useRouter();
35
35
  // Access user info store
36
36
  const { currentUser, setCurrentUser, setPicture, oidcProvider } = useUserInfoStore();
@@ -44,4 +44,5 @@ export default function ErrorPage({ id, errorText }) {
44
44
  await smartSignOut(currentUser, auth0Domain, auth0ClientId, oidcProvider);
45
45
  };
46
46
  return (_jsxs(_Fragment, { children: [_jsx(Navbar, { authenticationType: authenticationType, id: "nav-bar", logo: LOGO, logoServiceToken: logoServiceToken, pathname: router.pathname, query: router.query, signOut: handleSignOut, supportEmailAddress: supportEmailAddress, userInfo: userInfo }), _jsxs(Box, { id: id, sx: { marginLeft: "1rem" }, children: [_jsx(NeuroAIBreadcrumbs, {}), _jsx("h4", { id: "error-header", style: { color: "var(--bs-red)", marginTop: "1rem" }, children: "Oops, an internal error occurred on our end." }), _jsxs("div", { id: "error-div-1", children: [_jsxs(Box, { id: "no-need-to-worry", sx: { marginBottom: "1.5rem" }, children: [_jsx("b", { id: "bold-1", children: "There's no need to worry \u2013 you didn't do anything wrong." }), " This is a bug in our server and we'll fix it as soon as possible."] }), _jsx(Box, { id: "try-these-steps", sx: { marginBottom: "1.5rem" }, children: "To attempt to recover from the error, try these steps:" }), _jsx(Box, { id: "refresh-page", sx: { marginBottom: "1.5rem" }, children: _jsxs("ul", { id: "error-boundary-advice-list", className: "list-decimal list-inside", children: [_jsx("li", { id: "error-advice-refresh", children: "Refresh the page to see if the error goes away." }), _jsx("li", { id: "error-advice-reload", children: "Close and re-open your browser, then navigate back to the page where you encountered the error." })] }) }), "If none of that helps, please report the following error to the LEAF team:"] }), _jsx("div", { id: "error-text-div", style: { fontFamily: "monospace", marginTop: "1.5rem", marginBottom: "1.5rem" }, children: errorText }), "More information may be available in the browser console. Please include such information in your report if possible."] }), _jsx(Footer, { supportEmailAddress: supportEmailAddress, logoLinkUrl: "https://www.cognizant.com/", logoUrl: "/cognizant-logo-white.svg" })] }));
47
- }
47
+ };
48
+ export default ErrorPage;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Returns the Cognizant logo with a link to the corporate website.
3
+ */
4
+ export declare const getCognizantLogoImage: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Returns the Cognizant logo with a link to the corporate website.
4
+ */
5
+ export const getCognizantLogoImage = () => (_jsx("a", { id: "splash-logo-link", href: "https://www.cognizant.com/us/en", style: {
6
+ display: "flex",
7
+ paddingLeft: "0.15rem",
8
+ }, target: "_blank", rel: "noopener noreferrer", children: _jsx("img", { id: "logo-img", width: "200", height: "45", src: "/cognizant-logo-white.svg", alt: "Cognizant Logo" }) }));
@@ -0,0 +1,23 @@
1
+ import { SvgIconProps } from "@mui/material/SvgIcon";
2
+ import { FC } from "react";
3
+ import { LogoSource } from "../../state/Settings.js";
4
+ interface CustomerLogoProps {
5
+ readonly customer: string;
6
+ readonly fallbackIcon?: FC<SvgIconProps>;
7
+ readonly iconSuggestion?: string;
8
+ readonly logoServiceToken?: string;
9
+ readonly logoSource: LogoSource;
10
+ }
11
+ export declare const LOGO_DEV_URL = "https://img.logo.dev/name";
12
+ /**
13
+ * Component to display the customer's logo based on the settings.
14
+ * @param customer - Name of the customer, used for fetching the logo from logo.dev when logoSource is "auto"
15
+ * @param fallbackIcon - Optional MUI icon to use as a fallback if other approaches fail
16
+ * @param iconSuggestion - Optional MUI icon name to use when logoSource is "generic"
17
+ * @param logoServiceToken - Optional token for fetching the logo from logo.dev
18
+ * @param logoSource - Source of the logo, determines how the logo is rendered ("none", "generic", or "auto")
19
+ * @returns JSX element representing the customer's logo, MUI icon, or Cognizant fallback, or null if no logo
20
+ * should be displayed or if the logo.dev token is missing/invalid.
21
+ */
22
+ export declare const CustomerLogo: FC<CustomerLogoProps>;
23
+ export {};
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Have to suppress this lint error to allow dynamic access of MUI icons
3
+ // eslint-disable-next-line no-restricted-imports
4
+ import * as MuiIcons from "@mui/icons-material";
5
+ //#endregion
6
+ export const LOGO_DEV_URL = "https://img.logo.dev/name";
7
+ /**
8
+ * Component to display the customer's logo based on the settings.
9
+ * @param customer - Name of the customer, used for fetching the logo from logo.dev when logoSource is "auto"
10
+ * @param fallbackIcon - Optional MUI icon to use as a fallback if other approaches fail
11
+ * @param iconSuggestion - Optional MUI icon name to use when logoSource is "generic"
12
+ * @param logoServiceToken - Optional token for fetching the logo from logo.dev
13
+ * @param logoSource - Source of the logo, determines how the logo is rendered ("none", "generic", or "auto")
14
+ * @returns JSX element representing the customer's logo, MUI icon, or Cognizant fallback, or null if no logo
15
+ * should be displayed or if the logo.dev token is missing/invalid.
16
+ */
17
+ export const CustomerLogo = ({ customer, fallbackIcon: FallbackIcon, iconSuggestion, logoServiceToken, logoSource, }) => {
18
+ const hasCustomer = customer?.trim()?.length > 0;
19
+ const fallback = FallbackIcon ? _jsx(FallbackIcon, { sx: { fontSize: "2rem" } }) : null;
20
+ // "generic": check if suggested MUI icon is valid
21
+ switch (logoSource) {
22
+ case "generic": {
23
+ const MuiIcon = MuiIcons[iconSuggestion];
24
+ const LogoIcon = MuiIcon ?? FallbackIcon;
25
+ return LogoIcon ? _jsx(LogoIcon, { sx: { fontSize: "2rem" } }) : null;
26
+ }
27
+ case "auto": {
28
+ if (!hasCustomer || !logoServiceToken || logoServiceToken.trim().length === 0) {
29
+ return fallback;
30
+ }
31
+ // "auto": use logo.dev service
32
+ // eslint-disable-next-line max-len -- better to keep URL together
33
+ const logoUrl = `${LOGO_DEV_URL}/${encodeURIComponent(customer)}?token=${logoServiceToken}&theme=dark&format=png&size=75`;
34
+ return (_jsx("img", { src: logoUrl, alt: `${customer} Logo`, width: 40, height: 40, style: { borderRadius: "50%" } }));
35
+ }
36
+ case "none":
37
+ return null;
38
+ default:
39
+ return fallback;
40
+ }
41
+ };
@@ -22,7 +22,6 @@ export const isFinalMessage = (chatMessage) => {
22
22
  export const createConversation = (agents, text, type) => ({
23
23
  // Could use crypto.randomUUID, but it's only available under HTTPS, and don't want to use a different
24
24
  // solution for HTTP on localhost.
25
- // eslint-disable-next-line newline-per-chained-call
26
25
  id: `conv_${Date.now()}${Math.random().toString(36).slice(2, 10)}`,
27
26
  agents: new Set(agents),
28
27
  startedAt: new Date(),
@@ -45,8 +45,8 @@ import { ThoughtBubbleOverlay } from "./ThoughtBubbleOverlay.js";
45
45
  import { sendChatQuery } from "../../controller/agent/Agent.js";
46
46
  import { StreamingUnit } from "../../controller/llm/LlmChat.js";
47
47
  import { useAgentChatHistoryStore } from "../../state/ChatHistory.js";
48
+ import { usePalette, useSettingsStore } from "../../state/Settings.js";
48
49
  import { useTempNetworksStore } from "../../state/TemporaryNetworks.js";
49
- import { usePalette } from "../../Theme/Palettes.js";
50
50
  import { getZIndex } from "../../utils/zIndexLayers.js";
51
51
  import { chatMessageFromChunk } from "../AgentChat/Common/Utils.js";
52
52
  import { MUIAlert } from "../Common/MUIAlert.js";
@@ -55,7 +55,7 @@ import { NotificationType, sendNotification } from "../Common/notification.js";
55
55
  // #region: Constants
56
56
  // Timeout for thought bubbles is set to 10 seconds
57
57
  const THOUGHT_BUBBLE_TIMEOUT_MS = 10_000;
58
- // How long the dock'sstatus banner stays visible before auto-dismissing. Error banners persist until dismissed.
58
+ // How long the dock's status banner stays visible before auto-dismissing. Error banners persist until dismissed.
59
59
  // Exported for tests.
60
60
  export const DOCK_BANNER_AUTO_DISMISS_MS = 5_000;
61
61
  // #endregion: Constants
@@ -113,6 +113,8 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
113
113
  }, []);
114
114
  // Ref for isStreaming, read inside the cleanup interval.
115
115
  const isStreamingRef = useRef(isStreaming);
116
+ // Display option for agent/network names
117
+ const useNativeNames = useSettingsStore((state) => state.settings.appearance.useNativeNames);
116
118
  // Keep the ref current after every render.
117
119
  useEffect(() => {
118
120
  isStreamingRef.current = isStreaming;
@@ -166,7 +168,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
166
168
  return edgesMap ?? prev;
167
169
  });
168
170
  }, [currentConversations, showThoughtBubbles, setThoughtBubbleEdges]);
169
- // Cleanup expired thought bubble edges — created once on mount, reads isStreaming via ref.
171
+ // Clean up expired thought bubble edges — created once on mount, reads isStreaming via ref.
170
172
  useEffect(() => {
171
173
  const cleanupInterval = setInterval(() => {
172
174
  if (!isStreamingRef.current)
@@ -187,9 +189,6 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
187
189
  }, 1000);
188
190
  return () => clearInterval(cleanupInterval);
189
191
  }, [setThoughtBubbleEdges]); // mount/unmount only
190
- // Shadow color for icon
191
- const isDarkMode = theme.palette.mode === "dark";
192
- const foregroundColor = isDarkMode ? theme.palette.common.white : theme.palette.common.black;
193
192
  const isHeatmap = coloringOption === "heatmap";
194
193
  const palette = usePalette();
195
194
  // Merge agents from active thought bubbles with agentsInNetwork for layout
@@ -216,8 +215,28 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
216
215
  // Create the flow layout depending on user preference
217
216
  // Memoize layoutResult so it only recalculates when relevant data changes
218
217
  const layoutResult = useMemo(() => layout === "linear"
219
- ? layoutLinear(isHeatmap ? agentCounts : undefined, mergedAgentsInNetwork, currentConversations, isAwaitingLlm, isAgentNetworkDesignerMode, thoughtBubbleEdges, agentIconSuggestions, isTemporaryNetwork)
220
- : layoutRadial(isHeatmap ? agentCounts : undefined, mergedAgentsInNetwork, currentConversations, isAwaitingLlm, isAgentNetworkDesignerMode, thoughtBubbleEdges, agentIconSuggestions, isTemporaryNetwork), [
218
+ ? layoutLinear({
219
+ agentCounts: isHeatmap ? agentCounts : undefined,
220
+ agentIconSuggestions,
221
+ agentsInNetwork: mergedAgentsInNetwork,
222
+ currentConversations,
223
+ isAgentNetworkDesignerMode,
224
+ isAwaitingLlm,
225
+ isTemporaryNetwork,
226
+ thoughtBubbleEdges,
227
+ useNativeNames,
228
+ })
229
+ : layoutRadial({
230
+ agentCounts: isHeatmap ? agentCounts : undefined,
231
+ agentIconSuggestions,
232
+ agentsInNetwork: mergedAgentsInNetwork,
233
+ currentConversations,
234
+ isAgentNetworkDesignerMode,
235
+ isAwaitingLlm,
236
+ isTemporaryNetwork,
237
+ thoughtBubbleEdges,
238
+ useNativeNames,
239
+ }), [
221
240
  agentCounts,
222
241
  agentIconSuggestions,
223
242
  currentConversations,
@@ -228,6 +247,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
228
247
  layout,
229
248
  mergedAgentsInNetwork,
230
249
  thoughtBubbleEdges,
250
+ useNativeNames,
231
251
  ]);
232
252
  const [nodes, setNodes] = useState(layoutResult.nodes);
233
253
  // Sync up the nodes with the layout result
@@ -237,7 +257,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
237
257
  // Track which node the user clicked on so we can open the popup
238
258
  const [selectedAgent, setSelectedAgent] = useState(null);
239
259
  const [isPopupOpen, setIsPopupOpen] = useState(false);
240
- // True while the agent-edit request is in-flight so we can disable the Save button.
260
+ // True, while the agent-edit request is in-flight so we can disable the Save button.
241
261
  const [isSavingAgent, setIsSavingAgent] = useState(false);
242
262
  // AbortController for the in-flight save request — stored in a ref so handlePopupClose can cancel it.
243
263
  const saveAbortControllerRef = useRef(null);
@@ -247,7 +267,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
247
267
  const dockAbortControllerRef = useRef(null);
248
268
  // Stop-confirm overlay state: null = not shown, "confirming" = abort dialog open.
249
269
  const [stopState, setStopState] = useState(null);
250
- // Inline status banner shown above the dock header after an apply succeeds, is cancelled, or fails.
270
+ // Inline status banner shown above the dock header after an "apply" succeeds, is canceled, or fails.
251
271
  const [dockBanner, setDockBanner] = useState(null);
252
272
  const bannerTimeoutRef = useRef(null);
253
273
  // Clear the banner auto-dismiss timer on unmount.
@@ -333,7 +353,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
333
353
  }
334
354
  return true;
335
355
  }
336
- // Reservations came back but none matched the current network — surface this in the dock banner.
356
+ // Reservations came back, but none matched the current network — surface this in the dock banner.
337
357
  showDockBanner({
338
358
  severity: "error",
339
359
  title: "Failed to apply network change.",
@@ -370,15 +390,17 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
370
390
  }
371
391
  catch (e) {
372
392
  const isAbort = e instanceof DOMException && e.name === "AbortError";
373
- if (!isAbort) {
374
- showDockBanner({ severity: "error", title: "Failed to apply network change.", detail: String(e) });
393
+ if (isAbort) {
394
+ if (hasTimedOut) {
395
+ showDockBanner({
396
+ severity: "error",
397
+ title: "Failed to apply network change.",
398
+ detail: "The request timed out. Please try again.",
399
+ });
400
+ }
375
401
  }
376
- else if (hasTimedOut) {
377
- showDockBanner({
378
- severity: "error",
379
- title: "Failed to apply network change.",
380
- detail: "The request timed out. Please try again.",
381
- });
402
+ else {
403
+ showDockBanner({ severity: "error", title: "Failed to apply network change.", detail: String(e) });
382
404
  }
383
405
  }
384
406
  finally {
@@ -445,8 +467,8 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
445
467
  }, [agentsInNetwork, fitView, layout]);
446
468
  const onNodesChange = useCallback((changes) => {
447
469
  setNodes((currentNodes) => applyNodeChanges(
448
- // For now, we only allow dragging, no updates. In agent network designer mode, doesn't make sense
449
- // to allow position changes since the user isn't actually manipulating a real network
470
+ // For now, we only allow dragging, no updates. In agent network designer mode, it doesn't make
471
+ // sense to allow position changes since the user isn't actually manipulating a real network
450
472
  changes.filter((c) => c.type === "position" && !isAgentNetworkDesignerMode), currentNodes));
451
473
  }, [isAgentNetworkDesignerMode]);
452
474
  const transform = useStore((state) => state.transform);
@@ -479,53 +501,62 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
479
501
  const getLegend = () => {
480
502
  const length = isHeatmap ? palette.length : Math.min(maxDepth, palette.length);
481
503
  return (_jsxs(Box, { id: `${id}-legend`, sx: {
482
- position: "absolute",
483
- top: "1.5rem",
484
- right: "10px",
485
- padding: "5px",
504
+ alignItems: "center",
505
+ backgroundColor: theme.palette.background.paper,
506
+ border: `1px solid ${theme.palette.divider}`,
507
+ boxShadow: `0 2px 8px ${alpha(theme.palette.text.primary, 0.18)}`,
486
508
  borderRadius: "5px",
487
- boxShadow: `0 0 5px color-mix(in srgb, ${foregroundColor} 30%, transparent)`,
488
509
  display: "flex",
489
- alignItems: "center",
510
+ padding: theme.spacing(0.5),
511
+ position: "absolute",
512
+ right: theme.spacing(2),
513
+ top: theme.spacing(4),
490
514
  zIndex: getZIndex(2, theme),
491
- }, children: [Array.from({ length }, (_, i) => (_jsx(Box, { id: `${id}-legend-depth-${i}`, style: {
515
+ }, children: [Array.from({ length }, (_, i) => (_jsx(Box, { id: `${id}-legend-depth-${i}`, sx: {
492
516
  alignItems: "center",
493
517
  backgroundColor: palette[i],
494
518
  borderRadius: "50%",
495
519
  color: theme.palette.getContrastText(palette[i]),
496
520
  display: "flex",
497
- height: "15px",
521
+ fontSize: "0.5rem",
498
522
  justifyContent: "center",
499
- marginLeft: "5px",
523
+ marginLeft: theme.spacing(0.75),
500
524
  width: "15px",
501
- }, children: _jsx(Typography, { id: `${id}-legend-depth-${i}-text`, sx: {
502
- fontSize: "8px",
503
- }, children: i }) }, i))), _jsxs(ToggleButtonGroup, { id: `${id}-coloring-toggle`, value: coloringOption, exclusive: true, onChange: (_, newValue) => {
525
+ }, children: i }, i))), _jsxs(ToggleButtonGroup, { id: `${id}-coloring-toggle`, value: coloringOption, exclusive: true, onChange: (_, newValue) => {
504
526
  if (newValue !== null) {
505
527
  setColoringOption(newValue);
506
528
  }
507
- }, sx: {
508
- fontSize: "2rem",
509
- zIndex: 10,
510
- marginLeft: "0.5rem",
511
- }, size: "small", children: [_jsx(ToggleButton, { id: `${id}-depth-toggle`, size: "small", value: "depth", sx: {
529
+ }, size: "small", sx: {
530
+ marginLeft: theme.spacing(2),
531
+ "& .MuiToggleButton-root": {
532
+ backgroundColor: theme.palette.background.paper,
533
+ borderColor: theme.palette.divider,
534
+ color: theme.palette.text.primary,
535
+ minHeight: 22,
536
+ px: 1,
537
+ "&:hover": {
538
+ backgroundColor: theme.palette.action.hover,
539
+ },
540
+ "&.Mui-selected": {
541
+ backgroundColor: theme.palette.action.selected,
542
+ borderColor: theme.palette.text.primary,
543
+ },
544
+ "&.Mui-selected:hover": {
545
+ backgroundColor: theme.palette.action.selected,
546
+ borderColor: theme.palette.text.primary,
547
+ },
548
+ },
549
+ }, children: [_jsx(ToggleButton, { id: `${id}-depth-toggle`, value: "depth", sx: {
512
550
  fontSize: "0.5rem",
513
551
  height: "1rem",
514
- }, children: _jsx(Typography, { id: `${id}-depth-label`, sx: {
515
- fontSize: "10px",
516
- }, children: "Depth" }) }), _jsx(ToggleButton, { id: `${id}-heatmap-toggle`, size: "small", value: "heatmap", sx: {
552
+ }, children: "Depth" }), _jsx(ToggleButton, { id: `${id}-heatmap-toggle`, value: "heatmap", sx: {
517
553
  fontSize: "0.5rem",
518
554
  height: "1rem",
519
- }, children: _jsx(Typography, { id: `${id}-heatmap-label`, sx: {
520
- fontSize: "10px",
521
- }, children: "Heatmap" }) })] })] }));
555
+ }, children: "Heatmap" })] })] }));
522
556
  };
523
557
  // Get the background color for the control buttons based on the layout and dark mode setting
524
558
  const getControlButtonBackgroundColor = (isActive) => {
525
- if (!isActive) {
526
- return undefined;
527
- }
528
- return isDarkMode ? theme.palette.grey[800] : theme.palette.grey[200];
559
+ return isActive ? theme.palette.action.selected : undefined;
529
560
  };
530
561
  // Only show radial guides if radial layout is selected, radial guides are enabled, and it's not just Frontman
531
562
  const shouldShowRadialGuides = enableRadialGuides && layout === "radial" && maxDepth > 1;
@@ -604,9 +635,12 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
604
635
  fill: theme.palette.text.primary,
605
636
  },
606
637
  }, children: [_jsxs(Box, { id: `${id}-react-flow-wrapper`, sx: { position: "relative", flex: 1, minHeight: 0 }, children: [networkDisplayName ? _jsx(Box, { sx: { marginBottom: "1rem" }, children: getTitle() }) : null, _jsx(ReactFlow, { id: `${id}-react-flow`, nodes: nodes, edges: edges, onNodesChange: onNodesChange, onNodeClick: handleNodeClick, fitView: true, nodeTypes: nodeTypes, edgeTypes: edgeTypes, connectionMode: ConnectionMode.Loose, children: !isAwaitingLlm && (_jsxs(_Fragment, { children: [agentsInNetwork?.length && !isAgentNetworkDesignerMode && !isEditMode ? getLegend() : null, _jsx(Background, { id: `${id}-background` }), !isAgentNetworkDesignerMode && !isEditMode && getControls(), shouldShowRadialGuides ? getRadialGuides() : null] })) }), _jsx(ThoughtBubbleOverlay, { nodes: nodes, edges: thoughtBubbleEdgesForOverlay, showThoughtBubbles: showThoughtBubbles, isStreaming: isStreaming, onBubbleHoverChange: handleBubbleHoverChange })] }), isEditMode && isTemporaryNetwork && !isAwaitingLlm && (_jsxs(Box, { sx: {
638
+ backdropFilter: "blur(8px)",
639
+ backgroundColor: alpha(theme.palette.background.paper, 0.2),
607
640
  borderTop: `2px solid ${theme.palette.primary.main}`,
608
- backgroundColor: theme.palette.background.paper,
609
641
  flexShrink: 0,
642
+ position: "relative",
643
+ zIndex: getZIndex(2, theme),
610
644
  }, children: [dockBanner && (_jsx(MUIAlert, { closeable: true, id: `${id}-dock-banner`, onClose: handleDismissBanner, severity: dockBanner.severity, sx: {
611
645
  borderRadius: 0,
612
646
  // Override MUIAlert's default 1rem bottom margin so the banner sits flush
@@ -617,10 +651,8 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
617
651
  // vertically with the header's close X below it.
618
652
  paddingRight: 0.5,
619
653
  alignItems: "center",
620
- // Frost the banner like the dock header so the graph doesn't show through the
621
- // app's translucent paper background; keep a tinted, mostly-opaque severity wash.
622
- backdropFilter: "blur(8px)",
623
- backgroundColor: alpha(theme.palette[dockBanner.severity].main, isDarkMode ? 0.28 : 0.16),
654
+ // Frost the banner like the dock header, so the graph doesn't show through the
655
+ // app's translucent paper background; keep a tinted, mostly opaque severity wash.
624
656
  "& .MuiAlert-action": {
625
657
  alignItems: "center",
626
658
  marginRight: 0,
@@ -29,8 +29,7 @@ import Typography from "@mui/material/Typography";
29
29
  import { Handle, Position } from "@xyflow/react";
30
30
  import { useState } from "react";
31
31
  import { DisplayAs } from "./const.js";
32
- import { useSettingsStore } from "../../state/Settings.js";
33
- import { usePalette } from "../../Theme/Palettes.js";
32
+ import { usePalette, useSettingsStore } from "../../state/Settings.js";
34
33
  import { isLightColor } from "../../Theme/Theme.js";
35
34
  import { getZIndex } from "../../utils/zIndexLayers.js";
36
35
  // A zero-width space character, used to give the browser a hint where to break long names
@@ -125,32 +124,28 @@ export const AgentNode = (props) => {
125
124
  // Determine which icon to display based on the agent type whether it is Frontman or not
126
125
  const getDisplayAsIcon = () => {
127
126
  const id = `${agentId}-icon`;
128
- if (agentIconSuggestion && MuiIcons[agentIconSuggestion]) {
129
- const IconComponent = MuiIcons[agentIconSuggestion];
130
- return _jsx(IconComponent, { sx: { fontSize: AGENT_ICON_SIZE } });
127
+ // Check for valid MUI icon suggestion from the LLM suggestion service
128
+ const SuggestedIcon = agentIconSuggestion ? MuiIcons[agentIconSuggestion] : undefined;
129
+ // If the LLM provided a valid icon suggestion, use it. Otherwise, fall back to default icons based on
130
+ // agent type.
131
+ if (SuggestedIcon) {
132
+ return (_jsx(SuggestedIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
131
133
  }
132
- else {
133
- if (agentIconSuggestion) {
134
- console.warn(`Invalid MUI icon suggestion: ${agentIconSuggestion}`);
135
- }
136
- if (isFrontman) {
137
- return (
138
- // Use special icon and larger size for Frontman
139
- _jsx(PersonIcon, { id: id, sx: { fontSize: FRONTMAN_ICON_SIZE } }));
140
- }
141
- else {
142
- switch (displayAs) {
143
- case DisplayAs.EXTERNAL_AGENT:
144
- return (_jsx(TravelExploreIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
145
- // This should be a supported type but we're not seeing it?
146
- case DisplayAs.LANGCHAIN_TOOL:
147
- case DisplayAs.CODED_TOOL:
148
- return (_jsx(HandymanIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
149
- case DisplayAs.LLM_AGENT:
150
- default:
151
- return (_jsx(AutoAwesomeIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
152
- }
153
- }
134
+ if (isFrontman) {
135
+ return (
136
+ // Use special icon and larger size for Frontman
137
+ _jsx(PersonIcon, { id: id, sx: { fontSize: FRONTMAN_ICON_SIZE } }));
138
+ }
139
+ switch (displayAs) {
140
+ case DisplayAs.EXTERNAL_AGENT:
141
+ return (_jsx(TravelExploreIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
142
+ // This should be a supported type but we're not seeing it?
143
+ case DisplayAs.LANGCHAIN_TOOL:
144
+ case DisplayAs.CODED_TOOL:
145
+ return (_jsx(HandymanIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
146
+ case DisplayAs.LLM_AGENT:
147
+ default:
148
+ return (_jsx(AutoAwesomeIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
154
149
  }
155
150
  };
156
151
  // Determine icon color based on settings. If auto color is enabled, use contrasting color for readability.
@@ -160,6 +155,7 @@ export const AgentNode = (props) => {
160
155
  ? theme.palette.common.black
161
156
  : theme.palette.common.white;
162
157
  const [isHovered, setIsHovered] = useState(false);
158
+ // Insert zero-width spaces into the agent name to give the browser hints where to wrap.
163
159
  const wrappedAgentName = agentName
164
160
  // Allow wrap after underscore
165
161
  .replaceAll("_", `_${ZERO_WIDTH_SPACE}`)