@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
@@ -14,37 +14,70 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  */
17
- import { StopCircle } from "@mui/icons-material";
17
+ import StopCircle from "@mui/icons-material/StopCircle";
18
18
  import Box from "@mui/material/Box";
19
+ import Collapse from "@mui/material/Collapse";
19
20
  import Grid from "@mui/material/Grid";
20
21
  import Slide from "@mui/material/Slide";
21
- import { useCallback, useEffect, useRef, useState } from "react";
22
- import { ReactFlowProvider } from "reactflow";
22
+ import { ReactFlowProvider } from "@xyflow/react";
23
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
24
+ import { extractConversations } from "./AgentConversations.js";
25
+ import { getUpdatedAgentCounts } from "./AgentCounts.js";
23
26
  import { AgentFlow } from "./AgentFlow.js";
24
- import { Sidebar } from "./Sidebar.js";
25
- import { getAgentNetworks, getConnectivity } from "../../controller/agent/Agent.js";
26
- import { processChatChunk } from "../../utils/agentConversations.js";
27
+ import { TEMPORARY_NETWORK_FOLDER } from "./const.js";
28
+ import { Sidebar } from "./Sidebar/Sidebar.js";
29
+ import { extractReservations } from "./TemporaryNetworks.js";
30
+ import { getAgentIconSuggestions, getAgentNetworks, getConnectivity, getNetworkIconSuggestions, } from "../../controller/agent/Agent.js";
31
+ import { useSettingsStore } from "../../state/Settings.js";
32
+ import { useTempNetworksStore } from "../../state/TemporaryNetworks.js";
27
33
  import { useLocalStorage } from "../../utils/useLocalStorage.js";
28
34
  import { ChatCommon } from "../AgentChat/ChatCommon.js";
29
35
  import { SmallLlmChatButton } from "../AgentChat/LlmChatButton.js";
30
- import { cleanUpAgentName } from "../AgentChat/Utils.js";
36
+ import { chatMessageFromChunk, cleanUpAgentName, removeTrailingUuid } from "../AgentChat/Utils.js";
37
+ import { ConfirmationModal } from "../Common/ConfirmationModal.js";
38
+ import { MUIAlert } from "../Common/MUIAlert.js";
31
39
  import { closeNotification, NotificationType, sendNotification } from "../Common/notification.js";
40
+ // Display expired temporary networks for this amount of time after they expire so users can see what happened
41
+ const GRACE_PERIOD_MS = 5 * 60 * 1000; // 5 minutes
42
+ // Animation time for the left and right panels to slide in or out when launching the animation
43
+ const GROW_ANIMATION_TIME_MS = 800;
44
+ /**
45
+ * Helper function to convert agent reservations received from the backend into temporary networks that can be displayed
46
+ * in the tree.
47
+ * @param agentReservations List of "agent reservations" (temporary networks) received from the backend
48
+ * @returns List of TemporaryNetwork objects that can be displayed in the UI
49
+ */
50
+ const convertReservationsToNetworks = (agentReservations) => {
51
+ return agentReservations.map((reservation) => ({
52
+ reservation,
53
+ agentInfo: {
54
+ agent_name: `${TEMPORARY_NETWORK_FOLDER}/${reservation.reservation_id}`,
55
+ origin: reservation.reservation_id,
56
+ status: "active",
57
+ },
58
+ }));
59
+ };
32
60
  /**
33
61
  * Main Multi-Agent Accelerator component that contains the sidebar, agent flow, and chat components.
34
62
  * @param backendNeuroSanApiUrl Initial URL of the backend Neuro-San API. User can change this in the UI.
35
63
  * @param darkMode Whether dark mode is enabled.
36
64
  * @param userInfo Information about the current user, including userName and userImage.
37
65
  */
38
- export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, darkMode, userInfo, }) => {
39
- // Animation time for the left and right panels to slide in or out when launching the animation
40
- const GROW_ANIMATION_TIME_MS = 800;
66
+ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
67
+ const enableZenMode = useSettingsStore((state) => state.settings.behavior.enableZenMode);
41
68
  // Stores whether are currently awaiting LLM response (for knowing when to show spinners)
42
69
  const [isAwaitingLlm, setIsAwaitingLlm] = useState(false);
43
70
  // Track streaming state - controls thought bubble cleanup timer, and enables "zen mode" (hides outer panels after
44
71
  // animation)
45
72
  const [isStreaming, setIsStreaming] = useState(false);
46
73
  const [networks, setNetworks] = useState([]);
74
+ // List of known temporary networks (agent reservations) received from the backend
75
+ const temporaryNetworks = useTempNetworksStore((state) => state.tempNetworks);
76
+ // Track newly added temp networks so we can highlight them
77
+ const [newlyAddedTemporaryNetworks, setNewlyAddedTemporaryNetworks] = useState(new Set());
78
+ const [networkIconSuggestions, setNetworkIconSuggestions] = useState({});
47
79
  const [agentsInNetwork, setAgentsInNetwork] = useState([]);
80
+ const [agentIconSuggestions, setAgentIconSuggestions] = useState(null);
48
81
  const [selectedNetwork, setSelectedNetwork] = useState(null);
49
82
  // Track whether we've shown the info popup so we don't keep bugging the user with it
50
83
  const [haveShownPopup, setHaveShownPopup] = useState(false);
@@ -53,13 +86,22 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, darkMode, userInf
53
86
  const [neuroSanURL, setNeuroSanURL] = useState(customURLLocalStorage?.replaceAll('"', "") || backendNeuroSanApiUrl);
54
87
  const agentCountsRef = useRef(new Map());
55
88
  const conversationsRef = useRef(null);
56
- const [currentConversations, setCurrentConversations] = useState(null);
89
+ const [currentConversations, setCurrentConversations] = useState([]);
90
+ const [networkToBeDeleted, setNetworkToBeDeleted] = useState(null);
57
91
  // State to hold thought bubble edges - avoids duplicates across layout recalculations
58
92
  const [thoughtBubbleEdges, setThoughtBubbleEdges] = useState(new Map());
93
+ // For controlling alert when temporary network is created
94
+ const [alertContents, setAlertContents] = useState(null);
59
95
  const customURLCallback = useCallback((url) => {
60
96
  setNeuroSanURL(url || backendNeuroSanApiUrl);
61
97
  setCustomURLLocalStorage(url === "" ? null : url);
62
98
  }, [backendNeuroSanApiUrl, setCustomURLLocalStorage]);
99
+ // Memoized key for agent names to trigger icon suggestion updates when the set of agents changes, not just
100
+ // when sorting/other operations on the agents list
101
+ const agentNamesKey = useMemo(() => agentsInNetwork
102
+ .map((agent) => agent.origin)
103
+ .sort()
104
+ .join(","), [agentsInNetwork]);
63
105
  const resetState = useCallback(() => {
64
106
  setThoughtBubbleEdges(new Map());
65
107
  setIsStreaming(false);
@@ -72,25 +114,36 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, darkMode, userInf
72
114
  resetState();
73
115
  }, []);
74
116
  useEffect(() => {
75
- async function getNetworks() {
117
+ ;
118
+ (async () => {
76
119
  try {
77
120
  const networksTmp = await getAgentNetworks(neuroSanURL);
78
- const sortedNetworks = networksTmp?.sort((a, b) => a.localeCompare(b));
79
- setNetworks(sortedNetworks);
80
- // Set the first network as the selected network
81
- setSelectedNetwork(sortedNetworks[0]);
121
+ setNetworks(networksTmp);
82
122
  closeNotification();
83
123
  }
84
124
  catch (e) {
85
- sendNotification(NotificationType.error, "Connection error",
86
- // eslint-disable-next-line max-len
87
- `Unable to get list of Agent Networks. Verify that ${neuroSanURL} is a valid Multi-Agent Accelerator Server. Error: ${e}.`);
125
+ sendNotification(NotificationType.error, "Connection error", `Unable to get list of Agent Networks. Verify that ${neuroSanURL} is a valid ` +
126
+ `Multi-Agent Accelerator Server. Error: ${e}.`);
88
127
  setNetworks([]);
89
128
  setSelectedNetwork(null);
90
129
  }
91
- }
92
- void getNetworks();
130
+ })();
93
131
  }, [neuroSanURL]);
132
+ useEffect(() => {
133
+ ;
134
+ (async () => {
135
+ if (networks?.length > 0) {
136
+ try {
137
+ const suggestions = await getNetworkIconSuggestions(networks);
138
+ setNetworkIconSuggestions(suggestions);
139
+ }
140
+ catch (e) {
141
+ console.warn("Unable to get network icon suggestions from LLM:", e);
142
+ setNetworkIconSuggestions({});
143
+ }
144
+ }
145
+ })();
146
+ }, [networks]);
94
147
  useEffect(() => {
95
148
  ;
96
149
  (async () => {
@@ -101,17 +154,37 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, darkMode, userInf
101
154
  .concat()
102
155
  .sort((a, b) => a?.origin.localeCompare(b?.origin));
103
156
  setAgentsInNetwork(agentsInNetworkSorted);
157
+ setAgentIconSuggestions(null);
158
+ closeNotification();
104
159
  }
105
160
  catch (e) {
106
161
  const networkName = cleanUpAgentName(selectedNetwork);
107
- sendNotification(NotificationType.error, "Connection error",
108
- // eslint-disable-next-line max-len
109
- `Unable to get agent list for "${networkName}". Verify that ${neuroSanURL} is a valid Multi-Agent Accelerator Server. Error: ${e}.`);
162
+ sendNotification(NotificationType.error, "Connection error", `Unable to get agent list for "${networkName}". Verify that ${neuroSanURL} is a valid ` +
163
+ `Multi-Agent Accelerator Server. Error: ${e}.`);
110
164
  setAgentsInNetwork([]);
111
165
  }
112
166
  }
167
+ else {
168
+ setAgentsInNetwork([]);
169
+ }
113
170
  })();
114
171
  }, [neuroSanURL, selectedNetwork]);
172
+ useEffect(() => {
173
+ ;
174
+ (async () => {
175
+ if (agentsInNetwork.length > 0) {
176
+ try {
177
+ const connectivity = { connectivity_info: agentsInNetwork };
178
+ const agentIconSuggestionsTmp = await getAgentIconSuggestions(connectivity);
179
+ setAgentIconSuggestions(agentIconSuggestionsTmp);
180
+ }
181
+ catch (e) {
182
+ console.warn("Unable to get agent icon suggestions:", e);
183
+ setAgentIconSuggestions(null);
184
+ }
185
+ }
186
+ })();
187
+ }, [agentNamesKey]);
115
188
  // Set up handler to allow Escape key to stop the interaction with the LLM.
116
189
  useEffect(() => {
117
190
  if (!isAwaitingLlm) {
@@ -131,16 +204,61 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, darkMode, userInf
131
204
  setIsStreaming(false);
132
205
  }
133
206
  }, [isAwaitingLlm]);
207
+ // Reaper: remove temporary networks that have been expired for more than GRACE_PERIOD_MS
208
+ useEffect(() => {
209
+ if (temporaryNetworks.length === 0)
210
+ return undefined;
211
+ const interval = setInterval(() => {
212
+ const now = Date.now() / 1000; // convert to seconds since epoch
213
+ const currentTemporaryNetworks = useTempNetworksStore.getState().tempNetworks;
214
+ // Remove networks that have been expired for more than GRACE_PERIOD_MS
215
+ useTempNetworksStore
216
+ .getState()
217
+ .setTempNetworks(currentTemporaryNetworks.filter((n) => n.reservation.expiration_time_in_seconds > now - GRACE_PERIOD_MS / 1000));
218
+ // Figure out which networks have expired on the server (not including our grace period) so we can
219
+ // deselect them if they're currently selected
220
+ const expiredNetwork = currentTemporaryNetworks.filter((network) => network.reservation.expiration_time_in_seconds <= now);
221
+ // If the selected network is one of the expired ones, deselect it
222
+ if (expiredNetwork.some((n) => n.agentInfo.agent_name === selectedNetwork)) {
223
+ setSelectedNetwork(null);
224
+ agentCountsRef.current = new Map();
225
+ }
226
+ }, 10_000); // check every 10s
227
+ return () => clearInterval(interval);
228
+ }, [temporaryNetworks, selectedNetwork]);
134
229
  const onChunkReceived = useCallback((chunk) => {
135
- const result = processChatChunk(chunk, agentCountsRef.current, conversationsRef.current);
136
- if (result.success) {
137
- agentCountsRef.current = result.newCounts;
138
- conversationsRef.current = result.newConversations;
139
- setCurrentConversations(result.newConversations);
230
+ // Extract ChatMessage structure
231
+ const chatMessage = chatMessageFromChunk(chunk);
232
+ if (!chatMessage) {
233
+ return true;
234
+ }
235
+ // Conversations between agents
236
+ const result = extractConversations(chatMessage, conversationsRef.current);
237
+ if (result != null) {
238
+ conversationsRef.current = result;
239
+ setCurrentConversations(result);
140
240
  }
141
- return result.success;
241
+ // Agent hit counts
242
+ agentCountsRef.current = getUpdatedAgentCounts(agentCountsRef.current, chatMessage?.origin);
243
+ // Temporary networks/reservations
244
+ const reservationsResult = extractReservations(chatMessage);
245
+ // Handle agent reservations (temporary networks) that come in through the chat stream.
246
+ if (reservationsResult?.length > 0) {
247
+ const newTemporaryNetworks = convertReservationsToNetworks(reservationsResult);
248
+ const currentNetworks = useTempNetworksStore.getState().tempNetworks;
249
+ useTempNetworksStore.getState().setTempNetworks([...currentNetworks, ...newTemporaryNetworks]);
250
+ // record the new temporary networks so we can select them for the user. For now, we only
251
+ // care about the first one.
252
+ setNewlyAddedTemporaryNetworks(new Set(newTemporaryNetworks.map((network) => network.agentInfo.agent_name)));
253
+ }
254
+ return true;
142
255
  }, []);
143
256
  const onStreamingStarted = useCallback(() => {
257
+ // Reset agent counts
258
+ agentCountsRef.current = new Map();
259
+ // Reset newly added temporary networks
260
+ setNewlyAddedTemporaryNetworks(new Set());
261
+ setAlertContents(null);
144
262
  // Show info popup only once per session
145
263
  if (!haveShownPopup) {
146
264
  sendNotification(NotificationType.info, "Agents working", "Click the stop button or hit Escape to exit.");
@@ -150,21 +268,55 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, darkMode, userInf
150
268
  setIsStreaming(true);
151
269
  }, [haveShownPopup]);
152
270
  const onStreamingComplete = useCallback(() => {
271
+ const network = newlyAddedTemporaryNetworks?.values().next().value;
272
+ if (network?.length > 0) {
273
+ // We show an Alert after streaming completes (in case of Zen mode where the user might miss it)
274
+ const agentNameDisplay = cleanUpAgentName(removeTrailingUuid(network));
275
+ setAlertContents(`A temporary network "${agentNameDisplay}" has been created.`);
276
+ }
153
277
  // When streaming is complete, clean up any refs and state
154
278
  conversationsRef.current = null;
155
- agentCountsRef.current = new Map();
156
279
  setCurrentConversations(null);
157
280
  resetState();
158
- }, []);
281
+ }, [newlyAddedTemporaryNetworks]);
282
+ useEffect(() => {
283
+ if (alertContents?.length > 0) {
284
+ // Set a timer to clear the alert after a few seconds so it doesn't overstay its welcome
285
+ const timeoutId = window.setTimeout(() => {
286
+ setAlertContents(null);
287
+ }, 10_000);
288
+ return () => {
289
+ window.clearTimeout(timeoutId);
290
+ };
291
+ }
292
+ else {
293
+ return undefined;
294
+ }
295
+ }, [alertContents]);
296
+ const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
297
+ const handleDeleteNetwork = (networkId, isExpired) => {
298
+ if (isExpired) {
299
+ // It's expired so just delete it without confirmation
300
+ const tempNetworksWithoutThisOne = temporaryNetworks.filter((network) => network.agentInfo.agent_name !== networkId);
301
+ useTempNetworksStore.getState().setTempNetworks(tempNetworksWithoutThisOne);
302
+ }
303
+ else {
304
+ setNetworkToBeDeleted(networkId);
305
+ setConfirmationModalOpen(true);
306
+ }
307
+ };
159
308
  const getLeftPanel = () => {
160
- return (_jsx(Slide, { id: "multi-agent-accelerator-grid-sidebar-slide", in: !isAwaitingLlm, direction: "right", timeout: GROW_ANIMATION_TIME_MS, onExited: () => {
309
+ return (_jsx(Slide, { id: "multi-agent-accelerator-grid-sidebar-slide", in: !enableZenMode || !isAwaitingLlm, direction: "right", timeout: GROW_ANIMATION_TIME_MS, onExited: () => {
161
310
  setIsStreaming(true);
162
- }, children: _jsx(Grid, { id: "multi-agent-accelerator-grid-sidebar", size: isStreaming ? 0 : 3.25, sx: {
311
+ }, children: _jsx(Grid, { id: "multi-agent-accelerator-grid-sidebar", size: enableZenMode && isStreaming ? 0 : 3.25, sx: {
163
312
  height: "100%",
164
- }, children: _jsx(Sidebar, { customURLLocalStorage: customURLLocalStorage, customURLCallback: customURLCallback, id: "multi-agent-accelerator-sidebar", isAwaitingLlm: isAwaitingLlm, networks: networks, selectedNetwork: selectedNetwork, setSelectedNetwork: setSelectedNetwork }) }) }));
313
+ }, children: _jsx(Sidebar, { customURLLocalStorage: customURLLocalStorage, customURLCallback: customURLCallback, id: "multi-agent-accelerator-sidebar", isAwaitingLlm: isAwaitingLlm, networks: networks, networkIconSuggestions: networkIconSuggestions, newlyAddedTemporaryNetworks: newlyAddedTemporaryNetworks, onDeleteNetwork: handleDeleteNetwork, setSelectedNetwork: (newNetwork) => {
314
+ agentCountsRef.current = new Map();
315
+ setSelectedNetwork(newNetwork);
316
+ }, temporaryNetworks: temporaryNetworks }) }) }));
165
317
  };
166
318
  const getCenterPanel = () => {
167
- return (_jsx(Grid, { id: "multi-agent-accelerator-grid-agent-flow", size: isStreaming ? 18 : 8.25, sx: {
319
+ return (_jsx(Grid, { id: "multi-agent-accelerator-grid-agent-flow", size: enableZenMode && isStreaming ? 18 : 8.25, sx: {
168
320
  height: "100%",
169
321
  }, children: _jsx(ReactFlowProvider, { children: _jsx(Box, { id: "multi-agent-accelerator-agent-flow-container", sx: {
170
322
  display: "flex",
@@ -174,35 +326,42 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, darkMode, userInf
174
326
  height: "100%",
175
327
  maxWidth: 1000,
176
328
  margin: "0 auto",
177
- }, children: _jsx(AgentFlow, { agentCounts: agentCountsRef.current, agentsInNetwork: agentsInNetwork, id: "multi-agent-accelerator-agent-flow", currentConversations: currentConversations, isAwaitingLlm: isAwaitingLlm, isStreaming: isStreaming, thoughtBubbleEdges: thoughtBubbleEdges, setThoughtBubbleEdges: setThoughtBubbleEdges }) }) }) }));
329
+ }, children: _jsx(AgentFlow, { agentCounts: agentCountsRef.current, agentsInNetwork: agentsInNetwork, agentIconSuggestions: agentIconSuggestions, id: "multi-agent-accelerator-agent-flow", currentConversations: currentConversations, isAwaitingLlm: isAwaitingLlm, isStreaming: isStreaming, thoughtBubbleEdges: thoughtBubbleEdges, setThoughtBubbleEdges: setThoughtBubbleEdges }) }) }) }));
178
330
  };
179
331
  const getRightPanel = () => {
180
- return (_jsx(Slide, { id: "multi-agent-accelerator-grid-agent-chat-common-slide", in: !isAwaitingLlm, direction: "left", timeout: GROW_ANIMATION_TIME_MS, onExited: () => {
332
+ return (_jsx(Slide, { id: "multi-agent-accelerator-grid-agent-chat-common-slide", in: !enableZenMode || !isAwaitingLlm, direction: "left", timeout: GROW_ANIMATION_TIME_MS, onExited: () => {
181
333
  setIsStreaming(true);
182
- }, children: _jsx(Grid, { id: "multi-agent-accelerator-grid-agent-chat-common", size: isStreaming ? 0 : 6.5, sx: {
334
+ }, children: _jsx(Grid, { id: "multi-agent-accelerator-grid-agent-chat-common", size: enableZenMode && isStreaming ? 0 : 6.5, sx: {
183
335
  height: "100%",
184
- }, children: _jsx(ChatCommon, { ref: chatRef, neuroSanURL: neuroSanURL, id: "agent-network-ui", currentUser: userInfo.userName, userImage: userInfo.userImage, setIsAwaitingLlm: setIsAwaitingLlm, isAwaitingLlm: isAwaitingLlm, targetAgent: selectedNetwork, onChunkReceived: onChunkReceived, onStreamingComplete: onStreamingComplete, onStreamingStarted: onStreamingStarted, clearChatOnNewAgent: true, backgroundColor: darkMode ? "var(--bs-dark-mode-dim)" : "var(--bs-secondary-blue)" }) }) }));
336
+ }, children: _jsx(ChatCommon, { ref: chatRef, neuroSanURL: neuroSanURL, id: "agent-network-ui", currentUser: userInfo.userName, userImage: userInfo.userImage, setIsAwaitingLlm: setIsAwaitingLlm, isAwaitingLlm: isAwaitingLlm, targetAgent: selectedNetwork, onChunkReceived: onChunkReceived, onStreamingComplete: onStreamingComplete, onStreamingStarted: onStreamingStarted, clearChatOnNewAgent: true }, selectedNetwork ?? "no-network") }) }));
185
337
  };
186
338
  const getStopButton = () => {
187
- return (_jsx(_Fragment, { children: isAwaitingLlm && (_jsx(Box, { id: "stop-button-container", sx: {
339
+ return (_jsx(_Fragment, { children: isAwaitingLlm && enableZenMode && (_jsx(Box, { id: "stop-button-container", sx: {
188
340
  position: "absolute",
189
341
  bottom: "1rem",
190
342
  right: "1rem",
191
343
  zIndex: 10,
192
344
  }, children: _jsx(SmallLlmChatButton, { "aria-label": "Stop", disabled: !isAwaitingLlm, id: "stop-output-button", onClick: handleExternalStop, posBottom: 8, posRight: 23, children: _jsx(StopCircle, { fontSize: "small", id: "stop-button-icon", sx: { color: "var(--bs-white)" } }) }) })) }));
193
345
  };
194
- return (_jsxs(Grid, { id: "multi-agent-accelerator-grid", container: true, columns: 18, sx: {
195
- border: "solid 1px #CFCFDC",
196
- borderRadius: "var(--bs-border-radius)",
197
- display: "flex",
198
- flex: 1,
199
- height: "85%",
200
- marginTop: "1rem",
201
- overflow: "hidden",
202
- padding: "1rem",
203
- background: darkMode ? "var(--bs-dark-mode-dim)" : "var(--bs-white)",
204
- color: darkMode ? "var(--bs-white)" : "var(--bs-primary)",
205
- justifyContent: isAwaitingLlm ? "center" : "unset",
206
- position: "relative",
207
- }, children: [getLeftPanel(), getCenterPanel(), getRightPanel(), getStopButton()] }));
346
+ const getConfirmationModal = () => confirmationModalOpen ? (_jsx(ConfirmationModal, { id: "delete-network-confirmation-modal", content: `The network "${cleanUpAgentName(removeTrailingUuid(networkToBeDeleted))}" will be deleted. ` +
347
+ "This action cannot be undone. Are you sure you want to proceed?", handleCancel: () => {
348
+ setConfirmationModalOpen(false);
349
+ setNetworkToBeDeleted(null);
350
+ }, handleOk: () => {
351
+ useTempNetworksStore
352
+ .getState()
353
+ .setTempNetworks(temporaryNetworks.filter((network) => network.agentInfo.agent_name !== networkToBeDeleted));
354
+ setNetworkToBeDeleted(null);
355
+ setConfirmationModalOpen(false);
356
+ }, title: "Delete Network" })) : null;
357
+ const getAlert = () => (_jsx(Collapse, { in: alertContents?.length > 0, timeout: 1000, children: _jsx(MUIAlert, { id: "temporary-network-created-alert", closeable: true, severity: "success", sx: { marginTop: "1rem", marginBottom: 0 }, children: alertContents }) }));
358
+ return (_jsxs(_Fragment, { children: [getAlert(), getConfirmationModal(), _jsxs(Grid, { id: "multi-agent-accelerator-grid", container: true, columns: 18, sx: {
359
+ display: "flex",
360
+ flex: 1,
361
+ height: "85%",
362
+ justifyContent: isAwaitingLlm ? "center" : "unset",
363
+ marginTop: "1rem",
364
+ overflow: "hidden",
365
+ position: "relative",
366
+ }, children: [getLeftPanel(), getCenterPanel(), getRightPanel(), getStopButton()] })] }));
208
367
  };
@@ -1,3 +1,3 @@
1
+ import { EdgeProps } from "@xyflow/react";
1
2
  import { FC } from "react";
2
- import { EdgeProps } from "reactflow";
3
3
  export declare const PlasmaEdge: FC<EdgeProps>;
@@ -14,12 +14,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  */
17
+ import { getBezierPath } from "@xyflow/react";
17
18
  import { useEffect, useRef } from "react";
18
- import { getBezierPath } from "reactflow";
19
- function createFunnelParticleOnPath(pathEl, canvasOffset, baseProgress) {
20
- // Prettier and ESlint conflict over this
21
- // eslint-disable-next-line newline-per-chained-call
22
- const green = getComputedStyle(document.documentElement).getPropertyValue("--bs-green").trim();
19
+ import { useSettingsStore } from "../../state/Settings.js";
20
+ const createFunnelParticleOnPath = (pathEl, canvasOffset, baseProgress, plasmaColor) => {
23
21
  const totalLength = pathEl.getTotalLength();
24
22
  const speed = 0.02 + Math.random() * 0.003;
25
23
  const life = 100;
@@ -55,8 +53,8 @@ function createFunnelParticleOnPath(pathEl, canvasOffset, baseProgress) {
55
53
  ctx.beginPath();
56
54
  ctx.globalAlpha = alpha * 0.9 * pulse;
57
55
  ctx.shadowBlur = 8 + 8 * pulse; // Lowered for performance
58
- ctx.shadowColor = green;
59
- ctx.fillStyle = green;
56
+ ctx.shadowColor = plasmaColor;
57
+ ctx.fillStyle = plasmaColor;
60
58
  ctx.arc(x, y, 2, 0, Math.PI * 2);
61
59
  ctx.fill();
62
60
  ctx.globalAlpha = 1;
@@ -65,12 +63,13 @@ function createFunnelParticleOnPath(pathEl, canvasOffset, baseProgress) {
65
63
  };
66
64
  const isAlive = () => progress * totalLength < totalLength * 0.98;
67
65
  return { update, draw, isAlive };
68
- }
66
+ };
69
67
  export const PlasmaEdge = ({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, }) => {
70
68
  const canvasRef = useRef(null);
71
69
  const pathRef = useRef(null);
72
- const animationRef = useRef();
70
+ const animationRef = useRef(null);
73
71
  const particles = useRef([]);
72
+ const plasmaColor = useSettingsStore((state) => state.settings.appearance.plasmaColor);
74
73
  const [edgePath] = getBezierPath({
75
74
  sourceX,
76
75
  sourceY,
@@ -106,7 +105,7 @@ export const PlasmaEdge = ({ sourceX, sourceY, targetX, targetY, sourcePosition,
106
105
  if (particles.current.length < MAX_PARTICLES) {
107
106
  const t = Math.random();
108
107
  if (Math.random() < 1 - t) {
109
- particles.current.push(createFunnelParticleOnPath(pathEl, canvasOffset, t));
108
+ particles.current.push(createFunnelParticleOnPath(pathEl, canvasOffset, t, plasmaColor));
110
109
  }
111
110
  }
112
111
  }
@@ -118,7 +117,10 @@ export const PlasmaEdge = ({ sourceX, sourceY, targetX, targetY, sourcePosition,
118
117
  animationRef.current = requestAnimationFrame(animate);
119
118
  };
120
119
  animate();
121
- return () => cancelAnimationFrame(animationRef.current);
122
- }, [edgePath, width, height, x, y]);
120
+ return () => {
121
+ if (animationRef.current !== undefined)
122
+ cancelAnimationFrame(animationRef.current);
123
+ };
124
+ }, [edgePath, width, height, plasmaColor, x, y]);
123
125
  return (_jsxs(_Fragment, { children: [_jsx("foreignObject", { id: `foreign-object-${x}-${y}`, width: width, height: height, x: x, y: y, style: { pointerEvents: "none", overflow: "visible" }, children: _jsx("canvas", { id: `canvas-${x}-${y}`, ref: canvasRef }) }), _jsx("path", { id: `path-${edgePath}`, ref: pathRef, d: edgePath, fill: "none", stroke: "none", style: { position: "absolute", visibility: "hidden" } })] }));
124
126
  };
@@ -0,0 +1,15 @@
1
+ import { TreeItemProps } from "@mui/x-tree-view/TreeItem";
2
+ import { FC } from "react";
3
+ import { NodeIndex } from "./TreeBuilder.js";
4
+ export interface AgentNetworkNodeProps extends TreeItemProps {
5
+ readonly nodeIndex: NodeIndex;
6
+ readonly onDeleteNetwork?: (network: string, isExpired: boolean) => void;
7
+ readonly networkIconSuggestions: Record<string, string>;
8
+ readonly temporaryNetworkExpirationTimes?: Record<string, Date>;
9
+ }
10
+ /**
11
+ * Custom Tree Item for MUI RichTreeView to display agent networks with tags
12
+ * @param props - see AgentNetworkNode interface
13
+ * @returns JSX.Element containing the custom tree item
14
+ */
15
+ export declare const AgentNetworkTreeItem: FC<AgentNetworkNodeProps>;
@@ -0,0 +1,104 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Need to disable restricted imports because we want to import all MUI icons dynamically
3
+ // eslint-disable-next-line no-restricted-imports
4
+ import * as MuiIcons from "@mui/icons-material";
5
+ import BookmarkIcon from "@mui/icons-material/Bookmark";
6
+ import Delete from "@mui/icons-material/Delete";
7
+ import Box from "@mui/material/Box";
8
+ import Chip from "@mui/material/Chip";
9
+ import { useTheme } from "@mui/material/styles";
10
+ import Tooltip from "@mui/material/Tooltip";
11
+ import { TreeItemContent, TreeItemGroupTransition, TreeItemLabel, TreeItemRoot, } from "@mui/x-tree-view/TreeItem";
12
+ import { TreeItemProvider } from "@mui/x-tree-view/TreeItemProvider";
13
+ import { useTreeItem } from "@mui/x-tree-view/useTreeItem";
14
+ import { useRef } from "react";
15
+ import { cleanUpAgentName } from "../../AgentChat/Utils.js";
16
+ // Palette of colors we can use for tags
17
+ const TAG_COLORS = [
18
+ "--bs-accent2-light",
19
+ "--bs-accent1-medium",
20
+ "--bs-accent3-medium",
21
+ "--bs-accent3-dark",
22
+ "--bs-green",
23
+ "--bs-orange",
24
+ "--bs-pink",
25
+ "--bs-secondary",
26
+ "--bs-yellow",
27
+ ];
28
+ // Keep track of which tags have which colors so that the same tag always has the same color
29
+ const tagsToColors = new Map();
30
+ /**
31
+ * Helper function to determine if a temporary network is expired based on its expiration date
32
+ * @param expirationDate - Date object representing the expiration time of the temporary network
33
+ * @returns boolean indicating whether the temporary network is expired
34
+ */
35
+ const isTemporaryNetworkExpired = (expirationDate) => {
36
+ return Date.now() > expirationDate.getTime();
37
+ };
38
+ /**
39
+ * Custom Tree Item for MUI RichTreeView to display agent networks with tags
40
+ * @param props - see AgentNetworkNode interface
41
+ * @returns JSX.Element containing the custom tree item
42
+ */
43
+ export const AgentNetworkTreeItem = ({ children, disabled, itemId, label, networkIconSuggestions, nodeIndex, onDeleteNetwork, temporaryNetworkExpirationTimes, }) => {
44
+ const theme = useTheme();
45
+ // We know all labels are strings because we set them that way in the tree view items
46
+ const labelString = label;
47
+ const displayLabel = nodeIndex.get(itemId)?.displayName || cleanUpAgentName(labelString);
48
+ const { getContextProviderProps, getRootProps, getContentProps, getLabelProps, getGroupTransitionProps } = useTreeItem({ itemId, children, label, disabled });
49
+ const rootRef = useRef(null);
50
+ const isParent = Array.isArray(children) && children.length > 0;
51
+ const isChild = !isParent;
52
+ const agentNode = nodeIndex?.get(itemId)?.agentInfo;
53
+ // Only child items (the actual networks, not the containing folders) have tags. Retrieve tags from the
54
+ // networkFolders data structure passed in as a prop. This could in theory be a custom property for the
55
+ // RichTreeView item, but that isn't well-supported at this time.
56
+ // Discussion: https://stackoverflow.com/questions/69481071/material-ui-how-to-pass-custom-props-to-a-custom-treeitem
57
+ const tags = isChild ? agentNode?.tags || [] : [];
58
+ // Assign colors to tags as needed and store in tagsToColors map
59
+ for (const tag of tags) {
60
+ if (!tagsToColors.has(tag)) {
61
+ const color = TAG_COLORS[tagsToColors.size % TAG_COLORS.length];
62
+ tagsToColors.set(tag, color);
63
+ }
64
+ }
65
+ // Determine if expired (temporary networks only)
66
+ const expirationTime = temporaryNetworkExpirationTimes?.[itemId];
67
+ const isTemporaryNetwork = Boolean(expirationTime);
68
+ const isExpired = isChild && isTemporaryNetwork && isTemporaryNetworkExpired(expirationTime);
69
+ const iconNameSuggestion = isTemporaryNetwork ? "HourglassTop" : isChild ? networkIconSuggestions?.[itemId] : null;
70
+ let muiIconElement = null;
71
+ if (iconNameSuggestion && MuiIcons[iconNameSuggestion]) {
72
+ const IconComponent = MuiIcons[iconNameSuggestion];
73
+ muiIconElement = _jsx(IconComponent, { sx: { fontSize: "1rem" } });
74
+ }
75
+ else if (iconNameSuggestion) {
76
+ console.warn(`Icon "${iconNameSuggestion}" not found in MUI icons library.`);
77
+ }
78
+ return (_jsx(TreeItemProvider, { ...getContextProviderProps(), children: _jsxs(TreeItemRoot, { ...getRootProps(), ref: rootRef, children: [_jsx(TreeItemContent, { ...getContentProps(), sx: {
79
+ cursor: isExpired ? "not-allowed" : "pointer",
80
+ }, children: _jsxs(Box, { sx: { display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }, children: [_jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: "0.25rem" }, children: [_jsx(Tooltip, { title: isChild && isExpired
81
+ ? "Expired"
82
+ : expirationTime && `Expires at ${expirationTime.toLocaleString()}`, children: _jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: "0.25rem" }, children: [muiIconElement, _jsx(TreeItemLabel, { ...getLabelProps(), sx: {
83
+ fontWeight: isParent ? "bold" : "normal",
84
+ fontSize: isParent ? "1rem" : "0.9rem",
85
+ color: isParent ? "var(--heading-color)" : null,
86
+ opacity: isExpired ? 0.25 : 1,
87
+ "&:hover": {
88
+ textDecoration: "underline",
89
+ },
90
+ }, children: displayLabel })] }) }), isChild && tags?.length > 0 ? (_jsx(Tooltip, { title: tags
91
+ .slice()
92
+ .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
93
+ .map((tag) => (_jsx(Chip, { label: tag, style: {
94
+ margin: "0.25rem",
95
+ backgroundColor: `var(${tagsToColors.get(tag) || TAG_COLORS[0]})`,
96
+ } }, tag))), placement: "right", arrow: true, children: _jsx(BookmarkIcon, { sx: { fontSize: "0.75rem", color: "var(--bs-accent1-medium)" } }) })) : null] }), isChild && isTemporaryNetwork && (_jsx(Tooltip, { title: "Delete network", children: _jsx(Delete, { onClick: (e) => {
97
+ e.stopPropagation();
98
+ onDeleteNetwork?.(itemId, isExpired);
99
+ }, sx: {
100
+ cursor: "pointer",
101
+ fontSize: "1rem",
102
+ "&:hover": { color: theme.palette.warning.main },
103
+ } }) }))] }) }, labelString), children && _jsx(TreeItemGroupTransition, { ...getGroupTransitionProps() })] }) }));
104
+ };
@@ -0,0 +1,17 @@
1
+ import { FC } from "react";
2
+ import { NetworkIconSuggestions } from "../../../controller/Types/NetworkIconSuggestions.js";
3
+ import { AgentInfo } from "../../../generated/neuro-san/NeuroSanClient.js";
4
+ import { TemporaryNetwork } from "../../../state/TemporaryNetworks.js";
5
+ export interface SidebarProps {
6
+ readonly customURLCallback: (url: string) => void;
7
+ readonly customURLLocalStorage?: string;
8
+ readonly id: string;
9
+ readonly isAwaitingLlm: boolean;
10
+ readonly networkIconSuggestions?: NetworkIconSuggestions;
11
+ readonly networks: readonly AgentInfo[];
12
+ readonly onDeleteNetwork?: (network: string, isExpired: boolean) => void;
13
+ readonly setSelectedNetwork: (network: string) => void;
14
+ readonly temporaryNetworks?: readonly TemporaryNetwork[];
15
+ readonly newlyAddedTemporaryNetworks?: Set<string>;
16
+ }
17
+ export declare const Sidebar: FC<SidebarProps>;