@cognizant-ai-lab/ui-common 1.3.3 → 1.4.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 (114) hide show
  1. package/README.md +287 -0
  2. package/dist/Theme/Palettes.d.ts +18 -0
  3. package/dist/Theme/Palettes.js +94 -0
  4. package/dist/Theme/Theme.d.ts +22 -0
  5. package/dist/Theme/Theme.js +58 -0
  6. package/dist/components/AgentChat/ChatCommon.d.ts +4 -2
  7. package/dist/components/AgentChat/ChatCommon.js +141 -105
  8. package/dist/components/AgentChat/ControlButtons.js +3 -1
  9. package/dist/components/AgentChat/FormattedMarkdown.d.ts +4 -4
  10. package/dist/components/AgentChat/FormattedMarkdown.js +5 -7
  11. package/dist/components/AgentChat/LlmChatButton.d.ts +2 -6
  12. package/dist/components/AgentChat/LlmChatButton.js +3 -3
  13. package/dist/components/AgentChat/UserQueryDisplay.js +2 -4
  14. package/dist/components/AgentChat/Utils.d.ts +2 -1
  15. package/dist/components/AgentChat/Utils.js +4 -1
  16. package/dist/components/AgentChat/VoiceChat/MicrophoneButton.d.ts +2 -2
  17. package/dist/components/AgentChat/VoiceChat/VoiceChat.d.ts +3 -3
  18. package/dist/components/AgentChat/VoiceChat/VoiceChat.js +5 -5
  19. package/dist/components/ChatBot/ChatBot.js +2 -2
  20. package/dist/components/Common/Breadcrumbs.js +1 -1
  21. package/dist/components/Common/{confirmationModal.js → ConfirmationModal.js} +2 -5
  22. package/dist/components/Common/CustomerLogo.d.ts +17 -0
  23. package/dist/components/Common/CustomerLogo.js +49 -0
  24. package/dist/components/Common/Footer.d.ts +18 -0
  25. package/dist/components/Common/Footer.js +59 -0
  26. package/dist/components/Common/LlmChatOptionsButton.d.ts +1 -4
  27. package/dist/components/Common/LlmChatOptionsButton.js +4 -4
  28. package/dist/components/Common/LoadingSpinner.js +1 -1
  29. package/dist/components/Common/MUIAccordion.d.ts +2 -2
  30. package/dist/components/Common/MUIAccordion.js +2 -12
  31. package/dist/components/Common/MUIAlert.d.ts +2 -1
  32. package/dist/components/Common/MUIAlert.js +4 -1
  33. package/dist/components/Common/MUIDialog.d.ts +1 -1
  34. package/dist/components/Common/MUIDialog.js +1 -1
  35. package/dist/components/Common/Navbar.d.ts +3 -1
  36. package/dist/components/Common/Navbar.js +60 -35
  37. package/dist/components/Common/PageLoader.js +3 -4
  38. package/dist/components/Common/Snackbar.d.ts +4 -1
  39. package/dist/components/Common/Snackbar.js +11 -19
  40. package/dist/components/Common/notification.d.ts +3 -3
  41. package/dist/components/Common/notification.js +6 -6
  42. package/dist/components/ErrorPage/ErrorBoundary.d.ts +2 -2
  43. package/dist/components/ErrorPage/ErrorBoundary.js +1 -1
  44. package/dist/components/ErrorPage/ErrorPage.js +6 -5
  45. package/dist/components/MultiAgentAccelerator/AgentConversations.d.ts +17 -0
  46. package/dist/components/MultiAgentAccelerator/AgentConversations.js +77 -0
  47. package/dist/components/MultiAgentAccelerator/AgentCounts.d.ts +12 -0
  48. package/dist/components/MultiAgentAccelerator/AgentCounts.js +21 -0
  49. package/dist/components/MultiAgentAccelerator/AgentFlow.d.ts +6 -4
  50. package/dist/components/MultiAgentAccelerator/AgentFlow.js +106 -185
  51. package/dist/components/MultiAgentAccelerator/AgentNode.d.ts +7 -5
  52. package/dist/components/MultiAgentAccelerator/AgentNode.js +93 -50
  53. package/dist/components/MultiAgentAccelerator/GraphLayouts.d.ts +20 -17
  54. package/dist/components/MultiAgentAccelerator/GraphLayouts.js +16 -14
  55. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +2 -3
  56. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +214 -55
  57. package/dist/components/MultiAgentAccelerator/PlasmaEdge.d.ts +1 -1
  58. package/dist/components/MultiAgentAccelerator/PlasmaEdge.js +14 -12
  59. package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.d.ts +15 -0
  60. package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.js +104 -0
  61. package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.d.ts +17 -0
  62. package/dist/components/MultiAgentAccelerator/{Sidebar.js → Sidebar/Sidebar.js} +146 -59
  63. package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.d.ts +19 -0
  64. package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +113 -0
  65. package/dist/components/MultiAgentAccelerator/TemporaryNetworks.d.ts +26 -0
  66. package/dist/components/MultiAgentAccelerator/TemporaryNetworks.js +20 -0
  67. package/dist/components/MultiAgentAccelerator/ThoughtBubbleEdge.d.ts +10 -8
  68. package/dist/components/MultiAgentAccelerator/ThoughtBubbleEdge.js +1 -1
  69. package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.d.ts +3 -2
  70. package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.js +10 -13
  71. package/dist/components/MultiAgentAccelerator/const.d.ts +1 -3
  72. package/dist/components/MultiAgentAccelerator/const.js +4 -18
  73. package/dist/components/Settings/FadingCheckmark.d.ts +14 -0
  74. package/dist/components/Settings/FadingCheckmark.js +43 -0
  75. package/dist/components/Settings/SettingsDialog.d.ts +9 -0
  76. package/dist/components/Settings/SettingsDialog.js +265 -0
  77. package/dist/const.d.ts +1 -2
  78. package/dist/const.js +2 -3
  79. package/dist/controller/Types/AgentIconSuggestions.d.ts +4 -0
  80. package/dist/controller/Types/AgentIconSuggestions.js +1 -0
  81. package/dist/controller/Types/Branding.d.ts +12 -0
  82. package/dist/controller/Types/Branding.js +1 -0
  83. package/dist/controller/Types/NetworkIconSuggestions.d.ts +4 -0
  84. package/dist/controller/Types/NetworkIconSuggestions.js +1 -0
  85. package/dist/controller/agent/Agent.d.ts +32 -12
  86. package/dist/controller/agent/Agent.js +71 -19
  87. package/dist/controller/llm/LlmChat.d.ts +1 -1
  88. package/dist/controller/llm/LlmChat.js +2 -2
  89. package/dist/index.d.ts +10 -5
  90. package/dist/index.js +10 -5
  91. package/dist/state/{environment.d.ts → Environment.d.ts} +2 -0
  92. package/dist/state/{environment.js → Environment.js} +2 -0
  93. package/dist/state/Settings.d.ts +62 -0
  94. package/dist/state/Settings.js +62 -0
  95. package/dist/state/TemporaryNetworks.d.ts +32 -0
  96. package/dist/state/TemporaryNetworks.js +26 -0
  97. package/dist/tsconfig.build.tsbuildinfo +1 -1
  98. package/dist/utils/Authentication.d.ts +2 -2
  99. package/dist/utils/Authentication.js +6 -6
  100. package/dist/utils/text.d.ts +2 -2
  101. package/dist/utils/text.js +3 -5
  102. package/dist/utils/title.d.ts +1 -1
  103. package/dist/utils/title.js +2 -2
  104. package/dist/utils/useLocalStorage.d.ts +1 -1
  105. package/dist/utils/useLocalStorage.js +3 -3
  106. package/dist/utils/zIndexLayers.d.ts +1 -1
  107. package/dist/utils/zIndexLayers.js +3 -15
  108. package/package.json +23 -21
  109. package/dist/components/MultiAgentAccelerator/Sidebar.d.ts +0 -12
  110. package/dist/utils/Theme.d.ts +0 -7
  111. package/dist/utils/Theme.js +0 -7
  112. package/dist/utils/agentConversations.d.ts +0 -24
  113. package/dist/utils/agentConversations.js +0 -113
  114. /package/dist/components/Common/{confirmationModal.d.ts → ConfirmationModal.d.ts} +0 -0
@@ -18,36 +18,92 @@ import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
18
18
  import ClearIcon from "@mui/icons-material/Clear";
19
19
  import HighlightOff from "@mui/icons-material/HighlightOff";
20
20
  import SettingsIcon from "@mui/icons-material/Settings";
21
- import { IconButton, InputAdornment, styled, useColorScheme, useTheme } from "@mui/material";
22
21
  import Box from "@mui/material/Box";
23
22
  import Button from "@mui/material/Button";
24
- import List from "@mui/material/List";
25
- import ListItemButton from "@mui/material/ListItemButton";
26
- import ListItemText from "@mui/material/ListItemText";
23
+ import IconButton from "@mui/material/IconButton";
24
+ import InputAdornment from "@mui/material/InputAdornment";
27
25
  import Popover from "@mui/material/Popover";
26
+ import { keyframes, styled, useTheme } from "@mui/material/styles";
28
27
  import TextField from "@mui/material/TextField";
29
28
  import Tooltip from "@mui/material/Tooltip";
30
29
  import Typography from "@mui/material/Typography";
31
- import { useEffect, useRef, useState, } from "react";
32
- import { testConnection } from "../../controller/agent/Agent.js";
33
- import { useEnvironmentStore } from "../../state/environment.js";
34
- import { isDarkMode } from "../../utils/Theme.js";
35
- import { getZIndex } from "../../utils/zIndexLayers.js";
36
- import { cleanUpAgentName } from "../AgentChat/Utils.js";
30
+ import { RichTreeView } from "@mui/x-tree-view/RichTreeView";
31
+ import { useEffect, useState, } from "react";
32
+ import { AgentNetworkTreeItem } from "./AgentNetworkTreeItem.js";
33
+ import { buildTreeViewItems } from "./TreeBuilder.js";
34
+ import { testConnection } from "../../../controller/agent/Agent.js";
35
+ import { useEnvironmentStore } from "../../../state/Environment.js";
36
+ import { getZIndex } from "../../../utils/zIndexLayers.js";
37
+ import { TEMPORARY_NETWORK_FOLDER } from "../const.js";
38
+ // Animation for the sparkle effect when a new temporary network is added.
39
+ const sparkle = keyframes `
40
+ 0% {
41
+ background-position: 0% 50%;
42
+ opacity: 1;
43
+ }
44
+ 10% {
45
+ background-position: 33% 50%;
46
+ opacity: 1;
47
+ }
48
+ 20% {
49
+ background-position: 66% 50%;
50
+ opacity: 1;
51
+ }
52
+ 30% {
53
+ background-position: 100% 50%;
54
+ opacity: 1;
55
+ }
56
+ 60% {
57
+ background-position: 100% 50%;
58
+ opacity: 1;
59
+ }
60
+ 80% {
61
+ background-position: 100% 50%;
62
+ opacity: 0.7;
63
+ }
64
+ 90% {
65
+ background-position: 100% 50%;
66
+ opacity: 0.4;
67
+ }
68
+ 100% {
69
+ background-position: 100% 50%;
70
+ opacity: 0.25;
71
+ }
72
+ `;
37
73
  // #region: Styled Components
38
- const PrimaryButton = styled(Button, {
39
- shouldForwardProp: (prop) => prop !== "darkMode",
40
- })(({ darkMode }) => ({
41
- backgroundColor: "var(--bs-primary)",
74
+ const PrimaryButton = styled(Button)({
42
75
  marginLeft: "0.5rem",
43
76
  marginTop: "2px",
44
- "&:hover": {
45
- backgroundColor: "var(--bs-primary)",
46
- },
47
- "&.Mui-disabled": {
48
- color: darkMode ? "rgba(255, 255, 255, 0.25)" : undefined,
49
- backgroundColor: undefined,
77
+ });
78
+ // Styled component for Sidebar aside element, including styles for the sparkle highlight animation
79
+ // when a new temporary network is added.
80
+ const SidebarAside = styled("aside")({
81
+ borderRightStyle: "solid",
82
+ borderRightWidth: "1px",
83
+ height: "100%",
84
+ overflowY: "auto",
85
+ paddingRight: "0.75rem",
86
+ "& .sparkle-highlight": {
87
+ background: "linear-gradient(90deg, gold, orange, cyan, magenta, gold)",
88
+ backgroundSize: "400% 100%",
89
+ animation: `${sparkle} 5s ease`,
90
+ backgroundClip: "padding-box",
91
+ borderRadius: "4px",
92
+ opacity: 1,
50
93
  },
94
+ });
95
+ // Styled component for the sidebar heading, which is sticky at the top of the sidebar.
96
+ const SidebarHeading = styled("h2")(({ theme }) => ({
97
+ backgroundColor: theme.palette.background.default,
98
+ borderBottomStyle: "solid",
99
+ borderBottomWidth: "1px",
100
+ fontSize: "1.125rem",
101
+ fontWeight: "bold",
102
+ marginBottom: "0.25rem",
103
+ paddingBottom: "0.75rem",
104
+ position: "sticky",
105
+ top: 0,
106
+ zIndex: getZIndex(1, theme),
51
107
  }));
52
108
  // #endregion: Styled Components
53
109
  // #region: Types
@@ -58,7 +114,8 @@ var CONNECTION_STATUS;
58
114
  CONNECTION_STATUS["ERROR"] = "error";
59
115
  })(CONNECTION_STATUS || (CONNECTION_STATUS = {}));
60
116
  // #endregion: Types
61
- export const Sidebar = ({ customURLCallback, customURLLocalStorage, id, isAwaitingLlm, networks, selectedNetwork, setSelectedNetwork, }) => {
117
+ const EMPTY_ARRAY = [];
118
+ export const Sidebar = ({ customURLCallback, customURLLocalStorage, id, isAwaitingLlm, networkIconSuggestions, networks, newlyAddedTemporaryNetworks, onDeleteNetwork, setSelectedNetwork, temporaryNetworks = EMPTY_ARRAY, }) => {
62
119
  // Get default URL from the environment store.
63
120
  const { backendNeuroSanApiUrl } = useEnvironmentStore();
64
121
  const [urlInput, setUrlInput] = useState(customURLLocalStorage || backendNeuroSanApiUrl);
@@ -67,16 +124,13 @@ export const Sidebar = ({ customURLCallback, customURLLocalStorage, id, isAwaiti
67
124
  const connectionStatusSuccess = connectionStatus === CONNECTION_STATUS.SUCCESS;
68
125
  const connectionStatusError = connectionStatus === CONNECTION_STATUS.ERROR;
69
126
  const isDefaultUrl = urlInput === backendNeuroSanApiUrl;
70
- // Enable the Save button if the URL input is not empty and the connection status is successful,
71
- // OR if the URL input is the default URL and connection status is not error
72
127
  const saveEnabled = urlInput && (connectionStatusSuccess || (isDefaultUrl && !connectionStatusError));
73
- const selectedNetworkRef = useRef(null);
74
128
  const [settingsAnchorEl, setSettingsAnchorEl] = useState(null);
75
129
  const settingsPopoverOpen = Boolean(settingsAnchorEl);
130
+ const [expandedItems, setExpandedItems] = useState([]);
76
131
  // Theming/Dark mode
77
132
  const theme = useTheme();
78
- const { mode, systemMode } = useColorScheme();
79
- const darkMode = isDarkMode(mode, systemMode);
133
+ const darkMode = theme.palette.mode === "dark";
80
134
  const handleSettingsClick = (event) => {
81
135
  // On open of Settings popover, reset the connection status to idle
82
136
  setConnectionStatus(CONNECTION_STATUS.IDLE);
@@ -132,12 +186,6 @@ export const Sidebar = ({ customURLCallback, customURLLocalStorage, id, isAwaiti
132
186
  setUrlInput("");
133
187
  setConnectionStatus(CONNECTION_STATUS.IDLE);
134
188
  };
135
- // Make sure selected network in the list is always in view
136
- useEffect(() => {
137
- if (selectedNetworkRef.current) {
138
- selectedNetworkRef.current.scrollIntoView({ behavior: "instant", block: "nearest" });
139
- }
140
- }, [selectedNetwork]);
141
189
  // Get Neuro-san version on initial load
142
190
  useEffect(() => {
143
191
  const fetchVersion = async () => {
@@ -150,34 +198,73 @@ export const Sidebar = ({ customURLCallback, customURLLocalStorage, id, isAwaiti
150
198
  };
151
199
  void fetchVersion();
152
200
  }, []);
153
- const selectNetworkHandler = (network) => {
154
- setSelectedNetwork(network);
201
+ const { treeViewItems, nodeIndex } = buildTreeViewItems(networks, temporaryNetworks);
202
+ const temporaryNetworkExpirationTimes = temporaryNetworks.reduce((acc, tempNetwork) => {
203
+ acc[tempNetwork.agentInfo.agent_name] = new Date(tempNetwork.reservation.expiration_time_in_seconds * 1000);
204
+ return acc;
205
+ }, {});
206
+ const [selectedItem, setSelectedItem] = useState(null);
207
+ const handleSelectedItemsChange = (_event, itemId) => {
208
+ if (!itemId) {
209
+ return;
210
+ }
211
+ // Only select leaf nodes (items in nodeIndex) as networks
212
+ const isLeafNode = nodeIndex.has(itemId);
213
+ if (!isLeafNode) {
214
+ return;
215
+ }
216
+ // Don't allow selecting expired temporary networks
217
+ const expirationTime = temporaryNetworkExpirationTimes[itemId];
218
+ if (expirationTime && expirationTime < new Date()) {
219
+ return;
220
+ }
221
+ setSelectedItem(itemId);
222
+ setSelectedNetwork(itemId);
155
223
  };
156
- return (_jsxs(_Fragment, { children: [_jsxs("aside", { id: `${id}-sidebar`, style: {
157
- borderRightColor: "var(--bs-gray-light)",
158
- borderRightStyle: "solid",
159
- borderRightWidth: "1px",
160
- height: "100%",
161
- overflowY: "auto",
162
- paddingRight: "0.75rem",
163
- }, children: [_jsxs("h2", { id: `${id}-heading`, style: {
164
- borderBottomColor: "var(--bs-gray-light)",
165
- borderBottomStyle: "solid",
166
- borderBottomWidth: "1px",
167
- fontSize: "1.125rem",
168
- fontWeight: "bold",
169
- marginBottom: "0.25rem",
170
- paddingBottom: "0.75rem",
171
- position: "sticky",
172
- top: 0,
173
- zIndex: getZIndex(1, theme),
174
- }, children: ["Agent Networks", _jsx(Button, { "aria-label": "Agent Network Settings", disabled: isAwaitingLlm, id: "agent-network-settings-btn", onClick: handleSettingsClick, sx: { display: "inline-block", minWidth: "40px" }, children: _jsx(Tooltip, { id: "agent-network-settings-tooltip", placement: "top", title: `${customURLLocalStorage || backendNeuroSanApiUrl}\nversion: ${testConnectionResult?.version || "unknown"}`, children: _jsx(SettingsIcon, { id: "agent-network-settings-icon", sx: {
224
+ useEffect(() => {
225
+ let highlightTimeout;
226
+ let removeHighlightTimeout;
227
+ // If we got a new temporary network, select it and expand the temporary category in the tree view
228
+ if (newlyAddedTemporaryNetworks?.size > 0) {
229
+ const firstItem = newlyAddedTemporaryNetworks.values().next().value;
230
+ setSelectedItem(firstItem);
231
+ setSelectedNetwork(firstItem);
232
+ setExpandedItems((prev) => prev.includes(TEMPORARY_NETWORK_FOLDER) ? prev : [...prev, TEMPORARY_NETWORK_FOLDER]);
233
+ highlightTimeout = setTimeout(() => {
234
+ // Scroll the selected node into view and add an animation to draw the user's attention to it.
235
+ // Hacky: use a DOM query to find the node. I tried the various ways to do this programmatically
236
+ // in MUI RichTreeView including the imperative API (https://mui.com/x/react-tree-view/rich-tree-view/selection/#imperative-api)
237
+ // but couldn't get it to work so resorting to this for now.
238
+ const selectedNode = document.querySelector("[role=treeitem][aria-checked=true]");
239
+ if (selectedNode) {
240
+ selectedNode.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });
241
+ selectedNode.classList.add("sparkle-highlight");
242
+ removeHighlightTimeout = setTimeout(() => {
243
+ selectedNode.classList.remove("sparkle-highlight");
244
+ }, 5000);
245
+ }
246
+ }, 50);
247
+ }
248
+ return () => {
249
+ clearTimeout(highlightTimeout);
250
+ clearTimeout(removeHighlightTimeout);
251
+ };
252
+ }, [newlyAddedTemporaryNetworks]);
253
+ return (_jsxs(_Fragment, { children: [_jsxs(SidebarAside, { id: `${id}-sidebar`, children: [_jsxs(SidebarHeading, { id: `${id}-heading`, children: ["Agent Networks", _jsx(Button, { "aria-label": "Agent Network Settings", disabled: isAwaitingLlm, id: "agent-network-settings-btn", onClick: handleSettingsClick, sx: { display: "inline-block", minWidth: "40px" }, children: _jsx(Tooltip, { id: "agent-network-settings-tooltip", placement: "top", title: `${customURLLocalStorage || backendNeuroSanApiUrl}\nversion: ${testConnectionResult?.version || "unknown"}`, children: _jsx(SettingsIcon, { id: "agent-network-settings-icon", sx: {
175
254
  color: isAwaitingLlm ? "rgba(0, 0, 0, 0.12)" : "var(--bs-secondary)",
176
- } }) }) })] }), _jsx(List, { id: `${id}-network-list`, sx: { padding: 0, margin: 0 }, children: networks?.map((network) => (_jsx(ListItemButton, { id: `${network}-btn`, onClick: () => selectNetworkHandler(network), sx: { textAlign: "left" }, selected: selectedNetwork === network, disabled: isAwaitingLlm, ref: selectedNetwork === network ? selectedNetworkRef : null, children: _jsx(ListItemText, { id: `${network}-text`, slotProps: {
177
- primary: {
178
- fontSize: "0.75rem",
179
- },
180
- }, primary: cleanUpAgentName(network) }) }, network))) })] }), _jsxs(Popover, { id: "agent-network-settings-popover", open: settingsPopoverOpen, anchorEl: settingsAnchorEl, onClose: () => handleSettingsClose(true), anchorOrigin: {
255
+ } }) }) })] }), _jsx(RichTreeView, { items: treeViewItems, expandedItems: expandedItems, onExpandedItemsChange: (_event, itemIds) => setExpandedItems(itemIds), multiSelect: false, onSelectedItemsChange: handleSelectedItemsChange, selectedItems: selectedItem, disableSelection: isAwaitingLlm, slots: {
256
+ item: AgentNetworkTreeItem,
257
+ },
258
+ // Pass custom props to tree items via slotProps.
259
+ // Reference: https://github.com/mui/mui-x/issues/13351
260
+ slotProps: {
261
+ item: {
262
+ networkIconSuggestions,
263
+ nodeIndex,
264
+ onDeleteNetwork,
265
+ temporaryNetworkExpirationTimes,
266
+ },
267
+ } }, Object.keys(networkIconSuggestions || {}).length)] }), _jsxs(Popover, { id: "agent-network-settings-popover", open: settingsPopoverOpen, anchorEl: settingsAnchorEl, onClose: () => handleSettingsClose(true), anchorOrigin: {
181
268
  vertical: "bottom",
182
269
  horizontal: "left",
183
270
  }, slotProps: {
@@ -193,7 +280,7 @@ export const Sidebar = ({ customURLCallback, customURLLocalStorage, id, isAwaiti
193
280
  input: {
194
281
  endAdornment: urlInput && urlInput !== backendNeuroSanApiUrl ? (_jsx(InputAdornment, { id: "clear-input-adornment", position: "end", children: _jsx(IconButton, { id: "clear-input-icon-button", edge: "end", onClick: handleClearInput, size: "small", "aria-label": "Clear input", children: _jsx(ClearIcon, { fontSize: "small", id: "clear-input-icon" }) }) })) : undefined,
195
282
  },
196
- } }), _jsx(PrimaryButton, { disabled: !urlInput, id: "agent-network-settings-test-btn", onClick: handleTestConnection, variant: "contained", darkMode: darkMode, children: "Test" }), _jsx(PrimaryButton, { disabled: !saveEnabled, id: "agent-network-settings-save-btn", onClick: handleSaveSettings, variant: "contained", darkMode: darkMode, children: "Save" }), _jsx(PrimaryButton, { id: "agent-network-settings-cancel-btn", onClick: () => handleSettingsClose(true), variant: "contained", darkMode: darkMode, children: "Cancel" }), _jsx(Button, { id: "agent-network-settings-default-btn", onClick: handleDefaultSettings, sx: {
283
+ } }), _jsx(PrimaryButton, { disabled: !urlInput, id: "agent-network-settings-test-btn", onClick: handleTestConnection, variant: "contained", children: "Test" }), _jsx(PrimaryButton, { disabled: !saveEnabled, id: "agent-network-settings-save-btn", onClick: handleSaveSettings, variant: "contained", children: "Save" }), _jsx(PrimaryButton, { id: "agent-network-settings-cancel-btn", onClick: () => handleSettingsClose(true), variant: "contained", children: "Cancel" }), _jsx(Button, { id: "agent-network-settings-default-btn", onClick: handleDefaultSettings, sx: {
197
284
  marginLeft: "0.35rem",
198
285
  marginTop: "2px",
199
286
  color: darkMode ? "var(--bs-dark-mode-link)" : undefined,
@@ -0,0 +1,19 @@
1
+ import { TreeViewBaseItem } from "@mui/x-tree-view/models";
2
+ import { AgentInfo } from "../../../generated/neuro-san/NeuroSanClient.js";
3
+ import { TemporaryNetwork } from "../../../state/TemporaryNetworks.js";
4
+ export type NodeIndex = Map<string, {
5
+ agentInfo: AgentInfo;
6
+ displayName: string;
7
+ }>;
8
+ /**
9
+ * Build a tree view structure from a flat list of networks.
10
+ * The list of networks comes from a call to the Neuro-san /list API
11
+ * The tree structure is used by the RichTreeView component to display the networks
12
+ * @param networks - Array of networks from the Neuro-san /list API
13
+ * @param temporaryNetworks - Array of temporary networks (e.g. ones recently created by the user)
14
+ * @returns Array of TreeViewBaseItem objects representing the tree structure and an index for rapid access
15
+ */
16
+ export declare const buildTreeViewItems: (networks: readonly AgentInfo[], temporaryNetworks: readonly TemporaryNetwork[]) => {
17
+ treeViewItems: TreeViewBaseItem[];
18
+ nodeIndex: NodeIndex;
19
+ };
@@ -0,0 +1,113 @@
1
+ import { cleanUpAgentName, removeTrailingUuid } from "../../AgentChat/Utils.js";
2
+ /**
3
+ * Iteratively sort all children of tree nodes using a queue-based approach
4
+ * @param nodes - Array of tree nodes to sort
5
+ */
6
+ const sortTreeNodes = (nodes, nodeIndex) => {
7
+ // Sort the top level nodes first. We sort by displayName because that's what the user sees
8
+ nodes.sort((a, b) => {
9
+ const aDisplayName = nodeIndex.get(a.id)?.displayName ?? a.label;
10
+ const bDisplayName = nodeIndex.get(b.id)?.displayName ?? b.label;
11
+ return aDisplayName.localeCompare(bDisplayName);
12
+ });
13
+ // Use a queue for breadth-first traversal to avoid recursion
14
+ const queue = [...nodes];
15
+ let index = 0;
16
+ // For each node in the queue, sort its children and add them to the end of the queue
17
+ while (index < queue.length) {
18
+ const node = queue[index];
19
+ index += 1;
20
+ if (node.children && node.children.length > 0) {
21
+ node.children.sort((a, b) => {
22
+ const aDisplayName = nodeIndex.get(a.id)?.displayName ?? a.label;
23
+ const bDisplayName = nodeIndex.get(b.id)?.displayName ?? b.label;
24
+ return aDisplayName.localeCompare(bDisplayName);
25
+ });
26
+ queue.push(...node.children);
27
+ }
28
+ }
29
+ };
30
+ /**
31
+ * Add a single AgentInfo entry into the tree structure
32
+ */
33
+ const addNetworkToTree = (network, result, uncategorized, map, nodeIndex, displayNameCounts) => {
34
+ const parts = network.agent_name.split("/");
35
+ // If there's only one part, it means this network isn't in any folder, so we add it directly under "Uncategorized"
36
+ if (parts.length === 1) {
37
+ uncategorized.children.push({ id: network.agent_name, label: network.agent_name, children: [] });
38
+ nodeIndex.set(network.agent_name, { agentInfo: network, displayName: cleanUpAgentName(network.agent_name) });
39
+ }
40
+ else {
41
+ // Otherwise, we need to build out the tree structure based on the parts of the agent_name. Some paths might
42
+ // already exist if we've processed another network that shares the same parent folders,
43
+ // so we check the map to avoid duplicating nodes.
44
+ let currentLevel = result;
45
+ parts.forEach((part, index) => {
46
+ // Build the full path ID by joining all parts up to the current position
47
+ const nodeId = parts.slice(0, index + 1).join("/");
48
+ let node = map.get(nodeId);
49
+ if (!node) {
50
+ // If we haven't created a node for this path yet, create it and add it to the map
51
+ node = { id: nodeId, label: part, children: [] };
52
+ map.set(nodeId, node);
53
+ if (index === parts.length - 1) {
54
+ const cleanedName = cleanUpAgentName(removeTrailingUuid(part));
55
+ // Handle duplicate display names by appending a number (e.g. "macys", "macys 2", "macys 3", etc.)
56
+ const count = displayNameCounts.get(cleanedName) || 0;
57
+ displayNameCounts.set(cleanedName, count + 1);
58
+ const displayName = count > 0 ? `${cleanedName} ${count + 1}` : cleanedName;
59
+ // Add the AgentInfo to the nodeIndex for quick lookup later, using the full path as the key
60
+ nodeIndex.set(nodeId, { agentInfo: network, displayName });
61
+ }
62
+ // If this is a top-level node (index 0), add it directly to the result.
63
+ // Otherwise, find its parent and add it there.
64
+ if (index === 0) {
65
+ // Top-level node, add directly to result
66
+ currentLevel.push(node);
67
+ }
68
+ else {
69
+ // Not a top-level node, find parent and add to its children
70
+ const parentId = parts.slice(0, index).join("/");
71
+ const parentNode = map.get(parentId);
72
+ if (parentNode) {
73
+ parentNode.children.push(node);
74
+ }
75
+ }
76
+ }
77
+ // Move down to the next level of the tree for the next iteration
78
+ currentLevel = node.children;
79
+ });
80
+ }
81
+ };
82
+ /**
83
+ * Build a tree view structure from a flat list of networks.
84
+ * The list of networks comes from a call to the Neuro-san /list API
85
+ * The tree structure is used by the RichTreeView component to display the networks
86
+ * @param networks - Array of networks from the Neuro-san /list API
87
+ * @param temporaryNetworks - Array of temporary networks (e.g. ones recently created by the user)
88
+ * @returns Array of TreeViewBaseItem objects representing the tree structure and an index for rapid access
89
+ */
90
+ export const buildTreeViewItems = (networks, temporaryNetworks) => {
91
+ // Map to keep track of created nodes in a tree structure
92
+ const treeBuilderMap = new Map();
93
+ // Index to quickly look up AgentInfo by node ID without having to traverse the tree
94
+ const nodeIndex = new Map();
95
+ // Resulting tree view items, ready for consumption by RichTreeView
96
+ const treeViewItems = [];
97
+ // Special parent node for networks that aren't in any folder
98
+ const uncategorized = { id: "uncategorized", label: "Uncategorized", children: [] };
99
+ const displayNameCounts = new Map();
100
+ // Build a tree structure from the flat list of networks.
101
+ // The networks come in as a series of "paths" like "industry/retail/macys" and we need to build a tree
102
+ // structure from that.
103
+ networks.forEach((network) => addNetworkToTree(network, treeViewItems, uncategorized, treeBuilderMap, nodeIndex, displayNameCounts));
104
+ // Now handle temporary networks
105
+ temporaryNetworks?.forEach((temporaryNetwork) => addNetworkToTree(temporaryNetwork.agentInfo, treeViewItems, uncategorized, treeBuilderMap, nodeIndex, displayNameCounts));
106
+ // Add "Uncategorized" to the result if there are any such networks
107
+ if (uncategorized.children.length > 0) {
108
+ treeViewItems.push(uncategorized);
109
+ }
110
+ // Sort all nodes in the tree
111
+ sortTreeNodes(treeViewItems, nodeIndex);
112
+ return { treeViewItems, nodeIndex };
113
+ };
@@ -0,0 +1,26 @@
1
+ import { ChatMessage } from "../../generated/neuro-san/NeuroSanClient.js";
2
+ /**
3
+ * Definition of a temporary network. No schema for this provided by backend so we second-guess it here.
4
+ * @see https://github.com/cognizant-ai-lab/neuro-san/issues/743
5
+ * @example
6
+ * ```json
7
+ * {
8
+ * "reservation_id": "copy_cat-hello_world-14ecb260-4389-44f3-afad-ea315dfa1966",
9
+ * "lifetime_in_seconds": 300.0,
10
+ * "expiration_time_in_seconds": 1771438301.0245166
11
+ * }
12
+ * ```
13
+ */
14
+ export type AgentReservation = {
15
+ readonly reservation_id: string;
16
+ readonly lifetime_in_seconds: number;
17
+ readonly expiration_time_in_seconds: number;
18
+ };
19
+ /**
20
+ * Extracts agent reservations from a chat message, if they exist.
21
+ * @param message The chat message to extract reservations from. We expect reservations to be present in messages of
22
+ * type AGENT_FRAMEWORK only.
23
+ * @return An array of AgentReservation objects if reservations are found, or an empty array if not found or
24
+ * if the message is not of the expected type.
25
+ */
26
+ export declare const extractReservations: (message: ChatMessage) => AgentReservation[];
@@ -0,0 +1,20 @@
1
+ import { ChatMessageType } from "../../generated/neuro-san/NeuroSanClient.js";
2
+ // We expect the agent reservations to be stored in sly_data under this key
3
+ const AGENT_RESERVATIONS_KEY = "agent_reservations";
4
+ /**
5
+ * Extracts agent reservations from a chat message, if they exist.
6
+ * @param message The chat message to extract reservations from. We expect reservations to be present in messages of
7
+ * type AGENT_FRAMEWORK only.
8
+ * @return An array of AgentReservation objects if reservations are found, or an empty array if not found or
9
+ * if the message is not of the expected type.
10
+ */
11
+ export const extractReservations = (message) => {
12
+ // Check for temp networks ("reservations") in sly_data
13
+ if (message?.type === ChatMessageType.AGENT_FRAMEWORK && message?.sly_data?.[AGENT_RESERVATIONS_KEY]) {
14
+ return message.sly_data[AGENT_RESERVATIONS_KEY];
15
+ }
16
+ else {
17
+ // Not the type of message that would contain reservations, or no reservations found, return empty array
18
+ return [];
19
+ }
20
+ };
@@ -1,12 +1,14 @@
1
+ import { Edge, EdgeProps } from "@xyflow/react";
1
2
  import { FC } from "react";
2
- import { EdgeProps } from "reactflow";
3
- interface ThoughtBubbleEdgeProps extends EdgeProps {
4
- data?: {
5
- text?: string;
6
- showAlways?: boolean;
7
- conversationId?: string;
8
- type?: string;
9
- };
3
+ import { ChatMessageType } from "../../generated/neuro-san/NeuroSanClient.js";
4
+ interface ThoughtBubbleEdgeData extends Record<string, unknown> {
5
+ text?: string;
6
+ showAlways?: boolean;
7
+ conversationId?: string;
8
+ type?: ChatMessageType;
9
+ agents?: string[];
10
10
  }
11
+ export type ThoughtBubbleEdgeShape = Edge<ThoughtBubbleEdgeData, "thoughtBubbleEdge">;
12
+ type ThoughtBubbleEdgeProps = EdgeProps<ThoughtBubbleEdgeShape>;
11
13
  export declare const ThoughtBubbleEdge: FC<ThoughtBubbleEdgeProps>;
12
14
  export {};
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { getBezierPath } from "reactflow";
2
+ import { getBezierPath } from "@xyflow/react";
3
3
  // Simplified edge component - visual rendering is handled by ThoughtBubbleOverlay
4
4
  export const ThoughtBubbleEdge = ({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data, }) => {
5
5
  const conversationId = data?.conversationId || "";
@@ -1,8 +1,9 @@
1
+ import type { Node as RFNode } from "@xyflow/react";
1
2
  import { FC } from "react";
2
- import type { Edge, Node as RFNode } from "reactflow";
3
+ import { ThoughtBubbleEdgeShape } from "./ThoughtBubbleEdge.js";
3
4
  interface ThoughtBubbleOverlayProps {
4
5
  readonly nodes: RFNode[];
5
- readonly edges: Edge[];
6
+ readonly edges: ThoughtBubbleEdgeShape[];
6
7
  readonly showThoughtBubbles?: boolean;
7
8
  readonly isStreaming?: boolean;
8
9
  readonly onBubbleHoverChange?: (bubbleId: string | null) => void;
@@ -1,8 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { styled } from "@mui/material";
2
+ import { styled } from "@mui/material/styles";
3
3
  import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { ChatMessageType } from "../../generated/neuro-san/NeuroSanClient.js";
5
- // Note: Removed BubblePosition interface - no longer needed for right-side positioning
6
5
  // #endregion: Types
7
6
  // #region: Constants
8
7
  const BUBBLE_DISTANCE_FROM_RIGHT_EDGE = 20; // Fixed distance from right edge
@@ -104,16 +103,14 @@ export const ThoughtBubbleOverlay = ({ nodes, edges, showThoughtBubbles = true,
104
103
  const mountedRef = useRef(true);
105
104
  // Filter edges with meaningful text (memoized to prevent infinite re-renders)
106
105
  const thoughtBubbleEdges = useMemo(() => edges.filter((e) => {
107
- if (typeof e?.data?.text !== "string") {
108
- return false;
109
- }
110
- return e.data.text;
106
+ const text = e.data?.text;
107
+ return typeof text === "string" && text?.length > 0;
111
108
  }), [edges]);
112
109
  // Find frontman node (depth === 0, similar to isFrontman logic in AgentNode.tsx)
113
110
  const frontmanNode = useMemo(() => {
114
111
  if (!nodes || !Array.isArray(nodes) || nodes.length === 0)
115
112
  return null;
116
- return nodes.find((n) => n.data?.depth === 0);
113
+ return nodes.find((n) => n.data["depth"] === 0);
117
114
  }, [nodes]);
118
115
  // Handle bubble lifecycle (appear/disappear animations)
119
116
  useEffect(() => {
@@ -178,11 +175,11 @@ export const ThoughtBubbleOverlay = ({ nodes, edges, showThoughtBubbles = true,
178
175
  if (!nodes || !Array.isArray(nodes))
179
176
  return set;
180
177
  for (const node of nodes) {
181
- const getConversations = node.data?.getConversations;
178
+ const getConversations = node.data?.["getConversations"];
182
179
  if (typeof getConversations === "function") {
183
180
  const convs = getConversations();
184
181
  if (Array.isArray(convs)) {
185
- const hasSelf = convs.some((conv) => Boolean(conv?.agents?.has?.(node.id)));
182
+ const hasSelf = convs.some((conv) => conv?.agents?.has?.(node.id));
186
183
  if (hasSelf) {
187
184
  set.add(node.id);
188
185
  }
@@ -225,7 +222,7 @@ export const ThoughtBubbleOverlay = ({ nodes, edges, showThoughtBubbles = true,
225
222
  }
226
223
  if (bubbleId === null) {
227
224
  // Delay clearing the hover state when mouse leaves to prevent accidental unhover
228
- // "window." to satisfy typescript
225
+ // "window." to satisfy TypeScript
229
226
  hoverTimeoutRef.current = window.setTimeout(() => {
230
227
  setHoveredBubbleId(null);
231
228
  if (onBubbleHoverChange) {
@@ -263,7 +260,7 @@ export const ThoughtBubbleOverlay = ({ nodes, edges, showThoughtBubbles = true,
263
260
  bubbleY = BUBBLE_STACK_OFFSET_TOP + bubbleIndex * BUBBLE_HEIGHT_PLUS_SPACING + BUBBLE_HEIGHT / 2;
264
261
  }
265
262
  // Determine which agents to point to. If the edge supplies an `agents` array in
266
- // data (provided by AgentFlow), use that. Otherwise fallback to the explicit
263
+ // data (provided by AgentFlow), use that. Otherwise, fallback to the explicit
267
264
  // edge.target/edge.source pair (single target).
268
265
  let agentIds = Array.isArray(edge.data?.agents)
269
266
  ? edge.data?.agents
@@ -338,10 +335,10 @@ export const ThoughtBubbleOverlay = ({ nodes, edges, showThoughtBubbles = true,
338
335
  catch (err) {
339
336
  // Guard against unexpected DOM errors during measurement
340
337
  // Do not throw to avoid breaking the app
341
- console.debug("ThoughtBubbleOverlay: updateAllLines error", err);
338
+ console.error("ThoughtBubbleOverlay: updateAllLines error", err);
342
339
  }
343
340
  }, [renderableBubbles, bubbleStates, calculateLineCoordinates]);
344
- // Schedule post-paint updates with rAF and ResizeObserver. Also optionally run a
341
+ // Schedule post-paint updates with rAF and ResizeObserver. Also, optionally run a
345
342
  // continuous loop while `isStreaming` is true to keep lines in sync during streaming.
346
343
  useEffect(() => {
347
344
  mountedRef.current = true;
@@ -2,6 +2,4 @@ export declare const DEFAULT_FRONTMAN_X_POS = 150;
2
2
  export declare const DEFAULT_FRONTMAN_Y_POS = 450;
3
3
  export declare const BASE_RADIUS = 100;
4
4
  export declare const LEVEL_SPACING = 150;
5
- export declare const BACKGROUND_COLORS: string[];
6
- export declare const BACKGROUND_COLORS_DARK_IDX = 6;
7
- export declare const HEATMAP_COLORS: string[];
5
+ export declare const TEMPORARY_NETWORK_FOLDER = "temporary";
@@ -19,21 +19,7 @@ export const DEFAULT_FRONTMAN_Y_POS = 450;
19
19
  export const BASE_RADIUS = 100;
20
20
  // Distance between depth levels
21
21
  export const LEVEL_SPACING = 150;
22
- // Palette for progressive coloring of nodes based on depth
23
- export const BACKGROUND_COLORS = [
24
- "#f7fbff",
25
- "#deebf7",
26
- "#c6dbef",
27
- "#9ecae1",
28
- "#6baed6",
29
- "#4292c6",
30
- "#2171b5",
31
- "#08519c",
32
- "#08306b",
33
- "#041c45",
34
- ];
35
- // Zero-based index of the one where colors start to get dark. Used for displaying contrasting text colors.
36
- export const BACKGROUND_COLORS_DARK_IDX = 6; // Somewhat subjective
37
- // Palette for heatmap coloring of nodes
38
- // For now, use same palette as background colors
39
- export const HEATMAP_COLORS = BACKGROUND_COLORS;
22
+ // Temporary folder name for networks created from agent reservations. These networks are not "in a folder" when
23
+ // they come from the backend, but we need to put them somewhere in the UI, and this makes it clear that they're
24
+ // temporary.
25
+ export const TEMPORARY_NETWORK_FOLDER = "temporary";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Hook to manage the fading checkmark state.
3
+ */
4
+ export declare const useCheckmarkFade: () => {
5
+ show: boolean;
6
+ trigger: () => void;
7
+ };
8
+ /**
9
+ * A checkmark that fades in and out based on the `show` prop.
10
+ * @param show Whether to show the checkmark.
11
+ */
12
+ export declare const FadingCheckmark: ({ show }: {
13
+ show: boolean;
14
+ }) => import("react/jsx-runtime").JSX.Element;