@cognizant-ai-lab/ui-common 1.5.0 → 1.6.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 (81) hide show
  1. package/dist/Theme/Theme.js +3 -3
  2. package/dist/components/AgentChat/ChatCommon/ChatCommon.d.ts +11 -1
  3. package/dist/components/AgentChat/ChatCommon/ChatCommon.js +284 -285
  4. package/dist/components/AgentChat/ChatCommon/ChatHistory.d.ts +1 -7
  5. package/dist/components/AgentChat/ChatCommon/ChatHistory.js +33 -22
  6. package/dist/components/AgentChat/ChatCommon/ControlButtons.js +2 -2
  7. package/dist/components/AgentChat/ChatCommon/Conversation.d.ts +13 -0
  8. package/dist/components/AgentChat/ChatCommon/Conversation.js +80 -0
  9. package/dist/components/AgentChat/ChatCommon/ConversationTurn.d.ts +23 -0
  10. package/dist/components/AgentChat/ChatCommon/ConversationTurn.js +11 -0
  11. package/dist/components/AgentChat/ChatCommon/FormattedMarkdown.js +5 -3
  12. package/dist/components/AgentChat/ChatCommon/SampleQueries.d.ts +3 -0
  13. package/dist/components/AgentChat/ChatCommon/SampleQueries.js +6 -3
  14. package/dist/components/AgentChat/ChatCommon/Thinking.d.ts +12 -0
  15. package/dist/components/AgentChat/ChatCommon/Thinking.js +51 -0
  16. package/dist/components/AgentChat/Common/LlmChatButton.d.ts +2 -2
  17. package/dist/components/AgentChat/Common/Types.d.ts +6 -5
  18. package/dist/components/AgentChat/Common/Types.js +5 -0
  19. package/dist/components/AgentChat/Common/Utils.d.ts +1 -1
  20. package/dist/components/AgentChat/Common/Utils.js +14 -9
  21. package/dist/components/Common/AccordionLite.d.ts +14 -0
  22. package/dist/components/Common/AccordionLite.js +25 -0
  23. package/dist/components/Common/ConfirmationModal.d.ts +1 -0
  24. package/dist/components/Common/ConfirmationModal.js +1 -1
  25. package/dist/components/Common/CustomerLogo.js +1 -1
  26. package/dist/components/Common/MUIAlert.d.ts +1 -0
  27. package/dist/components/Common/MUIAlert.js +3 -4
  28. package/dist/components/Common/Navbar.d.ts +2 -1
  29. package/dist/components/Common/Navbar.js +8 -4
  30. package/dist/components/Common/notification.d.ts +1 -1
  31. package/dist/components/Common/notification.js +17 -12
  32. package/dist/components/MultiAgentAccelerator/AgentFlow.d.ts +8 -0
  33. package/dist/components/MultiAgentAccelerator/AgentFlow.js +282 -82
  34. package/dist/components/MultiAgentAccelerator/AgentNode.d.ts +3 -1
  35. package/dist/components/MultiAgentAccelerator/AgentNode.js +64 -28
  36. package/dist/components/MultiAgentAccelerator/AgentNodePopup.d.ts +1 -4
  37. package/dist/components/MultiAgentAccelerator/AgentNodePopup.js +4 -5
  38. package/dist/components/MultiAgentAccelerator/GraphLayouts.js +19 -9
  39. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +2 -2
  40. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +268 -60
  41. package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.d.ts +1 -0
  42. package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.js +28 -12
  43. package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.d.ts +1 -0
  44. package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.js +21 -5
  45. package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.d.ts +4 -3
  46. package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +8 -2
  47. package/dist/components/MultiAgentAccelerator/TemporaryNetworks.d.ts +19 -2
  48. package/dist/components/MultiAgentAccelerator/TemporaryNetworks.js +40 -5
  49. package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.js +27 -14
  50. package/dist/components/MultiAgentAccelerator/Tour/MainTourSteps.d.ts +7 -0
  51. package/dist/components/MultiAgentAccelerator/Tour/MainTourSteps.js +88 -0
  52. package/dist/components/MultiAgentAccelerator/const.d.ts +7 -10
  53. package/dist/components/MultiAgentAccelerator/const.js +9 -10
  54. package/dist/const.d.ts +5 -1
  55. package/dist/const.js +5 -2
  56. package/dist/controller/agent/Agent.d.ts +10 -0
  57. package/dist/controller/agent/Agent.js +17 -1
  58. package/dist/controller/llm/LlmChat.js +2 -2
  59. package/dist/index.d.ts +0 -1
  60. package/dist/index.js +0 -1
  61. package/dist/state/TemporaryNetworks.d.ts +5 -15
  62. package/dist/state/TemporaryNetworks.js +15 -34
  63. package/dist/state/Tour.d.ts +29 -0
  64. package/dist/state/Tour.js +22 -0
  65. package/dist/state/UserInfo.d.ts +2 -1
  66. package/dist/tsconfig.build.tsbuildinfo +1 -1
  67. package/dist/utils/Authentication.js +12 -3
  68. package/dist/utils/File.d.ts +7 -0
  69. package/dist/utils/File.js +14 -3
  70. package/dist/utils/text.js +2 -2
  71. package/dist/utils/title.js +1 -1
  72. package/dist/utils/zIndexLayers.js +3 -0
  73. package/package.json +15 -11
  74. package/dist/components/AgentChat/ChatCommon/AgentConnectivity.d.ts +0 -14
  75. package/dist/components/AgentChat/ChatCommon/AgentConnectivity.js +0 -23
  76. package/dist/components/AgentChat/ChatCommon/Greetings.d.ts +0 -1
  77. package/dist/components/AgentChat/ChatCommon/Greetings.js +0 -38
  78. package/dist/components/AgentChat/ChatCommon/UserQueryDisplay.d.ts +0 -7
  79. package/dist/components/AgentChat/ChatCommon/UserQueryDisplay.js +0 -32
  80. package/dist/components/Common/LlmChatOptionsButton.d.ts +0 -6
  81. package/dist/components/Common/LlmChatOptionsButton.js +0 -31
@@ -22,23 +22,29 @@ import { useTheme } from "@mui/material/styles";
22
22
  import Typography from "@mui/material/Typography";
23
23
  import { ReactFlowProvider } from "@xyflow/react";
24
24
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
25
+ import { useJoyride } from "react-joyride";
25
26
  import { extractConversations } from "./AgentConversations.js";
26
27
  import { getUpdatedAgentCounts } from "./AgentCounts.js";
27
28
  import { AgentFlow } from "./AgentFlow.js";
28
29
  import { extractAgentNetworkDesignerProgress } from "./AgentNetworkDesigner.js";
29
- import { AGENT_NETWORK_DEFINITION_KEY, AGENT_NETWORK_DESIGNER_ID, AGENT_NETWORK_HOCON, AGENT_NETWORK_NAME_KEY, } from "./const.js";
30
+ import { AGENT_NETWORK_DEFINITION_KEY, AGENT_NETWORK_DESIGNER_ID, AGENT_NETWORK_HOCON, AGENT_NETWORK_NAME_KEY, TRIGGER_APP_TOUR_EVENT_NAME, } from "./const.js";
30
31
  import { Sidebar } from "./Sidebar/Sidebar.js";
31
- import { convertReservationsToNetworks, extractNetworkHocon, extractReservations } from "./TemporaryNetworks.js";
32
- import { getAgentIconSuggestions, getAgentNetworks, getConnectivity, getNetworkIconSuggestions, } from "../../controller/agent/Agent.js";
32
+ import { extractTemporaryNetworksFromMessage, isTemporaryNetwork, mergeNetworks } from "./TemporaryNetworks.js";
33
+ import { getAgentFunction, getAgentIconSuggestions, getAgentNetworks, getConnectivity, getNetworkIconSuggestions, sendNetworkDesignerUpdate, } from "../../controller/agent/Agent.js";
34
+ import { useAgentChatHistoryStore } from "../../state/ChatHistory.js";
33
35
  import { useSettingsStore } from "../../state/Settings.js";
34
36
  import { useTempNetworksStore } from "../../state/TemporaryNetworks.js";
37
+ import { TourPromptState, useTourStore } from "../../state/Tour.js";
35
38
  import { useLocalStorage } from "../../utils/useLocalStorage.js";
36
39
  import { getZIndex } from "../../utils/zIndexLayers.js";
37
40
  import { ChatCommon } from "../AgentChat/ChatCommon/ChatCommon.js";
38
41
  import { SmallLlmChatButton } from "../AgentChat/Common/LlmChatButton.js";
42
+ import { isLegacyAgentType } from "../AgentChat/Common/Types.js";
39
43
  import { chatMessageFromChunk, cleanUpAgentName, removeTrailingUuid } from "../AgentChat/Common/Utils.js";
40
- import { ConfirmationModal } from "../Common/ConfirmationModal.js";
44
+ import { ConfirmationModal, StyledButton } from "../Common/ConfirmationModal.js";
41
45
  import { closeNotification, NotificationType, sendNotification } from "../Common/notification.js";
46
+ import { MAIN_TOUR_STEPS } from "./Tour/MainTourSteps.js";
47
+ import { MUIDialog } from "../Common/MUIDialog.js";
42
48
  // Check for expired networks every this many milliseconds
43
49
  const EXPIRED_NETWORKS_CHECK_INTERVAL_MS = 10 * 1000;
44
50
  // Display expired temporary networks for this amount of time after they expire so users can see what happened
@@ -47,6 +53,40 @@ export const GRACE_PERIOD_MS = 5 * 60 * 1000; // 5 minutes
47
53
  const GROW_ANIMATION_TIME_MS = 800;
48
54
  // Optimization to avoid creating a new empty map on every render
49
55
  const EMPTY_THOUGHT_BUBBLE_EDGES = new Map();
56
+ // We show the tour modal after this amount of time so as not to "pounce" on the user when they first open the app
57
+ export const SHOW_TOUR_DELAY_MS = 5000;
58
+ // #region: Agent-save helpers
59
+ /**
60
+ * Extracts TemporaryNetworks from a single streamed chunk, merging into `accumulated`.
61
+ * Returns `accumulated` unchanged if the chunk yields no reservations or on parse error.
62
+ */
63
+ const collectNetworksFromChunk = (chunk, updated, accumulated) => {
64
+ try {
65
+ const chatMessage = chatMessageFromChunk(chunk);
66
+ if (!chatMessage)
67
+ return accumulated;
68
+ // Always use the user's edited definition as the authoritative value.
69
+ const converted = extractTemporaryNetworksFromMessage(chatMessage, updated);
70
+ if (converted.length === 0)
71
+ return accumulated;
72
+ return mergeNetworks(accumulated, converted);
73
+ }
74
+ catch (e) {
75
+ console.warn("Failed to process chunk from network designer:", e);
76
+ return accumulated;
77
+ }
78
+ };
79
+ /** Logs and notifies about a save error. Suppresses AbortError (user-cancelled). */
80
+ const notifySaveError = (agentName, e) => {
81
+ if (e instanceof DOMException && e.name === "AbortError")
82
+ return;
83
+ console.error("Failed to submit agent network update:", e);
84
+ const detail = e instanceof DOMException && e.name === "TimeoutError"
85
+ ? "The request timed out waiting for the server. Please try again."
86
+ : String(e);
87
+ sendNotification(NotificationType.error, `Failed to update network "${agentName}".`, detail);
88
+ };
89
+ // #endregion: Agent-save helpers
50
90
  /**
51
91
  * Main Multi-Agent Accelerator component that contains the sidebar, agent flow, and chat components.
52
92
  * @param backendNeuroSanApiUrl Initial URL of the backend Neuro-San API. User can change this in the UI.
@@ -59,6 +99,7 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
59
99
  const enableZenMode = useSettingsStore((state) => state.settings.behavior.enableZenMode);
60
100
  // Stores whether are currently awaiting LLM response (for knowing when to show spinners)
61
101
  const [isAwaitingLlm, setIsAwaitingLlm] = useState(false);
102
+ const [isEditingNetwork, setIsEditingNetwork] = useState(false);
62
103
  // Track streaming state - controls thought bubble cleanup timer, and enables "zen mode" (hides outer panels after
63
104
  // animation)
64
105
  const [isStreaming, setIsStreaming] = useState(false);
@@ -69,23 +110,32 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
69
110
  const [newlyAddedTemporaryNetworks, setNewlyAddedTemporaryNetworks] = useState(new Set());
70
111
  const [networkIconSuggestions, setNetworkIconSuggestions] = useState({});
71
112
  const [agentsInNetwork, setAgentsInNetwork] = useState([]);
113
+ const [sampleQueries, setSampleQueries] = useState([]);
72
114
  // Agents in network under construction by Agent Network Designer -
73
115
  // updated in real time as we receive progress messages from the backend.
74
116
  const [agentsInNetworkDesigner, setAgentsInNetworkDesigner] = useState([]);
75
117
  const [agentIconSuggestions, setAgentIconSuggestions] = useState(null);
76
118
  const [selectedNetwork, setSelectedNetwork] = useState(null);
119
+ const [networkDescription, setNetworkDescription] = useState("");
77
120
  const networkDisplayName = useMemo(() => cleanUpAgentName(removeTrailingUuid(selectedNetwork)), [selectedNetwork]);
78
- // Track whether we've shown the info popup so we don't keep bugging the user with it
79
- const [haveShownPopup, setHaveShownPopup] = useState(false);
80
121
  const [customURLLocalStorage, setCustomURLLocalStorage] = useLocalStorage("customAgentNetworkURL", null);
81
122
  // An extra set of quotes is making it in the string in local storage.
82
123
  const [neuroSanURL, setNeuroSanURL] = useState(customURLLocalStorage?.replaceAll('"', "") || backendNeuroSanApiUrl);
83
- const agentCountsRef = useRef(new Map());
124
+ // Tracks how many times each agent has been involved in the conversation
125
+ const [agentCounts, setAgentCounts] = useState(new Map());
126
+ //common function to change the selected network and reset related state
127
+ const changeSelectedNetwork = useCallback((next) => {
128
+ setSelectedNetwork(next);
129
+ setAgentCounts(new Map());
130
+ }, []);
84
131
  const conversationsRef = useRef(null);
85
132
  const [currentConversations, setCurrentConversations] = useState([]);
86
133
  const [networkToBeDeleted, setNetworkToBeDeleted] = useState(null);
87
134
  // State to hold thought bubble edges - avoids duplicates across layout recalculations
88
135
  const [thoughtBubbleEdges, setThoughtBubbleEdges] = useState(new Map());
136
+ const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
137
+ const [tourModalOpen, setTourModalOpen] = useState(false);
138
+ const [haveShownTourModal, setHaveShownTourModal] = useState(false);
89
139
  const customURLCallback = useCallback((url) => {
90
140
  setNeuroSanURL(url || backendNeuroSanApiUrl);
91
141
  setCustomURLLocalStorage(url === "" ? null : url);
@@ -96,31 +146,75 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
96
146
  .map((agent) => agent.origin)
97
147
  .sort()
98
148
  .join(","), [agentsInNetwork]);
149
+ // Introductory tour
150
+ // Track that the user requested the tour, and we should start it once network data is available
151
+ const [tourRequested, setTourRequested] = useState(false);
152
+ // Tour persisted status
153
+ const tourStatus = useTourStore((s) => s.status);
154
+ const setTourStatus = useTourStore((s) => s.setStatus);
155
+ const { controls, Tour } = useJoyride({
156
+ continuous: true,
157
+ steps: MAIN_TOUR_STEPS,
158
+ options: {
159
+ buttons: ["back", "close", "primary", "skip"],
160
+ backgroundColor: "var(--bs-secondary)",
161
+ textColor: "var(--bs-white)",
162
+ primaryColor: "var(--bs-accent3-medium)",
163
+ arrowColor: "var(--bs-secondary)",
164
+ overlayColor: "rgba(var(--bs-primary-rgb), 0.82)",
165
+ showProgress: true,
166
+ zIndex: getZIndex(3, theme),
167
+ skipBeacon: true,
168
+ skipScroll: true,
169
+ },
170
+ locale: {
171
+ last: "End Tour",
172
+ skip: "Exit Tour",
173
+ },
174
+ });
99
175
  const resetState = useCallback(() => {
100
176
  setThoughtBubbleEdges(new Map());
101
177
  setIsStreaming(false);
102
178
  }, []);
103
179
  // Reference to the ChatCommon component to allow external stop button to call its handleStop method
104
180
  const chatRef = useRef(null);
181
+ // Clear chat whenever the user navigates to the Agent Network Designer, so they start fresh each time
182
+ useEffect(() => {
183
+ if (selectedNetwork === AGENT_NETWORK_DESIGNER_ID) {
184
+ chatRef.current?.handleClearChat();
185
+ }
186
+ }, [selectedNetwork]);
105
187
  // Special mode of operation where user is using Agent Network Designer to create a new network
106
188
  const isNetworkDesignerMode = selectedNetwork === AGENT_NETWORK_DESIGNER_ID;
107
189
  // Whether the currently selected network is a temporary network (agent reservation)
108
- const isSelectedNetworkTemporary = selectedNetwork !== null && temporaryNetworks.some((n) => n.agentInfo.agent_name === selectedNetwork);
190
+ const isSelectedNetworkTemporary = isTemporaryNetwork(selectedNetwork, temporaryNetworks);
109
191
  // For temp networks, agent_network_definition and agent_network_name live in localStorage (not IndexedDB slyData).
110
192
  // Supply them as extraSlyData so ChatCommon bounces them back to the backend on every request.
111
193
  const currentTempNetwork = isSelectedNetworkTemporary
112
194
  ? temporaryNetworks.find((n) => n.agentInfo.agent_name === selectedNetwork)
113
195
  : undefined;
196
+ // For Agent Network Designer: the backend echoes agent_network_name into IndexedDB sly_data as the conversation
197
+ // progresses. We read it back here to find the matching temp network and override agent_network_definition in the
198
+ // outgoing request — leaving what's in IndexedDB untouched.
199
+ const designerSlyData = useAgentChatHistoryStore((state) => state.history[AGENT_NETWORK_DESIGNER_ID]?.slyData);
200
+ const designerNetworkName = isNetworkDesignerMode
201
+ ? designerSlyData?.[AGENT_NETWORK_NAME_KEY]
202
+ : undefined;
203
+ const designerTempNetwork = designerNetworkName
204
+ ? temporaryNetworks.find((n) => n.agentNetworkName === designerNetworkName)
205
+ : undefined;
114
206
  const extraSlyData = currentTempNetwork
115
207
  ? {
116
208
  [AGENT_NETWORK_DEFINITION_KEY]: currentTempNetwork.agentNetworkDefinition,
117
- // Use the name the backend originally sent, not the local UUID-based key.
209
+ // Use the agentNetworkName, not reservation_id
118
210
  ...(currentTempNetwork.agentNetworkName
119
211
  ? { [AGENT_NETWORK_NAME_KEY]: currentTempNetwork.agentNetworkName }
120
212
  : {}),
121
213
  ...(currentTempNetwork.networkHocon ? { [AGENT_NETWORK_HOCON]: currentTempNetwork.networkHocon } : {}),
122
214
  }
123
- : undefined;
215
+ : designerTempNetwork
216
+ ? { [AGENT_NETWORK_DEFINITION_KEY]: designerTempNetwork.agentNetworkDefinition }
217
+ : undefined;
124
218
  // Handle external stop button click - stops streaming and exits zen mode
125
219
  const handleExternalStop = useCallback(() => {
126
220
  chatRef.current?.handleStop();
@@ -138,10 +232,27 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
138
232
  sendNotification(NotificationType.error, "Connection error", `Unable to get list of Agent Networks. Verify that ${neuroSanURL} is a valid ` +
139
233
  `Multi-Agent Accelerator Server. Error: ${e}.`);
140
234
  setNetworks([]);
141
- setSelectedNetwork(null);
235
+ changeSelectedNetwork(null);
142
236
  }
143
237
  })();
144
- }, [neuroSanURL]);
238
+ }, [neuroSanURL, changeSelectedNetwork]);
239
+ useEffect(() => {
240
+ const fetchAgentDetails = async () => {
241
+ // It is a Neuro-san agent, so get the function and connectivity info
242
+ try {
243
+ const agentFunction = await getAgentFunction(neuroSanURL, selectedNetwork, userInfo.userName);
244
+ setNetworkDescription(agentFunction?.function?.description || "");
245
+ }
246
+ catch {
247
+ // Ignore. May be a legacy agent without a functional description in Neuro-san.
248
+ }
249
+ };
250
+ // Clear out existing
251
+ setNetworkDescription("");
252
+ if (selectedNetwork && !isLegacyAgentType(selectedNetwork)) {
253
+ void fetchAgentDetails();
254
+ }
255
+ }, [neuroSanURL, selectedNetwork, userInfo.userName]);
145
256
  useEffect(() => {
146
257
  ;
147
258
  (async () => {
@@ -163,10 +274,15 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
163
274
  if (selectedNetwork) {
164
275
  try {
165
276
  const connectivity = await getConnectivity(neuroSanURL, selectedNetwork, userInfo.userName);
166
- const agentsInNetworkSorted = connectivity.connectivity_info
167
- .concat()
168
- .sort((a, b) => a?.origin.localeCompare(b?.origin));
277
+ const agentsInNetworkSorted = [...connectivity.connectivity_info].sort((a, b) => a?.origin.localeCompare(b?.origin));
169
278
  setAgentsInNetwork(agentsInNetworkSorted);
279
+ const sampleQueriesTmp = connectivity?.metadata?.["sample_queries"];
280
+ if (Array.isArray(sampleQueriesTmp)) {
281
+ setSampleQueries(sampleQueriesTmp);
282
+ }
283
+ else {
284
+ setSampleQueries([]);
285
+ }
170
286
  setAgentIconSuggestions(null);
171
287
  closeNotification();
172
288
  }
@@ -222,22 +338,47 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
222
338
  return undefined;
223
339
  const interval = setInterval(() => {
224
340
  const now = Date.now() / 1000; // convert to seconds since epoch
341
+ const reapCutoff = now - GRACE_PERIOD_MS / 1000;
225
342
  const currentTemporaryNetworks = useTempNetworksStore.getState().tempNetworks;
226
- // Remove networks that have been expired for more than GRACE_PERIOD_MS
227
- useTempNetworksStore
228
- .getState()
229
- .setTempNetworks(currentTemporaryNetworks.filter((n) => n.reservation.expiration_time_in_seconds > now - GRACE_PERIOD_MS / 1000));
230
- // Figure out which networks have expired on the server (not including our grace period) so we can
231
- // deselect them if they're currently selected
232
- const expiredNetwork = currentTemporaryNetworks.filter((network) => network.reservation.expiration_time_in_seconds <= now);
233
- // If the selected network is one of the expired ones, deselect it
234
- if (expiredNetwork.some((n) => n.agentInfo.agent_name === selectedNetwork)) {
235
- setSelectedNetwork(null);
236
- agentCountsRef.current = new Map();
343
+ // Networks past the grace period get fully purged: removed from the store AND have their
344
+ // chat history / sly_data cleared from IndexedDB
345
+ const survivingNetworks = [];
346
+ for (const network of currentTemporaryNetworks) {
347
+ if (network.reservation.expiration_time_in_seconds <= reapCutoff) {
348
+ // This network has been expired for more than the grace period, so purge it
349
+ useAgentChatHistoryStore.getState().resetHistory(network.agentInfo.agent_name);
350
+ }
351
+ else {
352
+ survivingNetworks.push(network);
353
+ }
354
+ }
355
+ useTempNetworksStore.getState().setTempNetworks(survivingNetworks);
356
+ // If the selected network has expired on the server (not including our grace period), deselect it
357
+ const selectedHasExpired = currentTemporaryNetworks.some((network) => network.agentInfo.agent_name === selectedNetwork &&
358
+ network.reservation.expiration_time_in_seconds <= now);
359
+ if (selectedHasExpired) {
360
+ changeSelectedNetwork(null);
237
361
  }
238
362
  }, EXPIRED_NETWORKS_CHECK_INTERVAL_MS);
239
363
  return () => clearInterval(interval);
240
- }, [temporaryNetworks, selectedNetwork]);
364
+ }, [changeSelectedNetwork, temporaryNetworks, selectedNetwork]);
365
+ const dismissTourModal = () => {
366
+ setTourModalOpen(false);
367
+ setHaveShownTourModal(true);
368
+ };
369
+ const handleExternalTourRequest = useCallback(() => {
370
+ // If nothing is selected, prime the network selection first
371
+ if (selectedNetwork == null && networks?.length > 0) {
372
+ setSelectedNetwork(networks[0].agent_name);
373
+ }
374
+ // Close the modal if open
375
+ setTourModalOpen(false);
376
+ setTourRequested(true);
377
+ }, [networks, selectedNetwork]);
378
+ useEffect(() => {
379
+ window.addEventListener(TRIGGER_APP_TOUR_EVENT_NAME, handleExternalTourRequest);
380
+ return () => window.removeEventListener(TRIGGER_APP_TOUR_EVENT_NAME, handleExternalTourRequest);
381
+ }, [handleExternalTourRequest, networks, selectedNetwork]);
241
382
  const onChunkReceived = useCallback((chunk) => {
242
383
  // Extract ChatMessage structure
243
384
  const chatMessage = chatMessageFromChunk(chunk);
@@ -250,8 +391,10 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
250
391
  conversationsRef.current = result;
251
392
  setCurrentConversations(result);
252
393
  }
253
- // Agent hit counts
254
- agentCountsRef.current = getUpdatedAgentCounts(agentCountsRef.current, chatMessage?.origin);
394
+ // Update agent hit counts
395
+ setAgentCounts((prevCounts) => {
396
+ return getUpdatedAgentCounts(prevCounts, chatMessage.origin);
397
+ });
255
398
  // Agent network designer progress messages
256
399
  if (isNetworkDesignerMode) {
257
400
  const networkInProgress = extractAgentNetworkDesignerProgress(chatMessage);
@@ -259,16 +402,9 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
259
402
  setAgentsInNetworkDesigner(networkInProgress);
260
403
  }
261
404
  }
262
- // Temporary networks/reservations
263
- const reservationsResult = extractReservations(chatMessage);
264
405
  // Handle agent reservations (temporary networks) that come in through the chat stream.
265
- if (reservationsResult?.length > 0) {
266
- // Retrieve network definition, if present
267
- const networkHocon = extractNetworkHocon(chatMessage);
268
- const agentNetworkDefinition = chatMessage.sly_data?.[AGENT_NETWORK_DEFINITION_KEY];
269
- // Capture the backend's canonical name so we can bounce it back unchanged on future requests.
270
- const agentNetworkName = chatMessage.sly_data?.[AGENT_NETWORK_NAME_KEY];
271
- const newTemporaryNetworks = convertReservationsToNetworks(reservationsResult, networkHocon, agentNetworkDefinition, agentNetworkName);
406
+ const newTemporaryNetworks = extractTemporaryNetworksFromMessage(chatMessage);
407
+ if (newTemporaryNetworks.length > 0) {
272
408
  const upserted = useTempNetworksStore.getState().upsertTempNetworks(newTemporaryNetworks);
273
409
  // Record the new temporary networks so we can highlight them for the user.
274
410
  // For now, we only care about the first one since that's the only active use case
@@ -276,21 +412,48 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
276
412
  }
277
413
  return true;
278
414
  }, [isNetworkDesignerMode]);
415
+ /**
416
+ * Handles a save from the AgentFlow popup: streams the updated definition to the network designer,
417
+ * collects reservations from each chunk, then upserts the result and navigates if the network changed.
418
+ */
419
+ const onSaveAgent = useCallback(async (agentName, updated, agentNetworkName, signal) => {
420
+ try {
421
+ let newNetworks = [];
422
+ await sendNetworkDesignerUpdate(neuroSanURL, signal, agentName, updated, agentNetworkName, userInfo.userName, (chunk) => {
423
+ newNetworks = collectNetworksFromChunk(chunk, updated, newNetworks);
424
+ });
425
+ if (newNetworks.length === 0) {
426
+ sendNotification(NotificationType.error, `Failed to update network "${agentName}".`, "The network designer did not return a reservation. Please try again.");
427
+ return;
428
+ }
429
+ const replacement = newNetworks.find((n) => n.agentNetworkName === agentNetworkName);
430
+ if (replacement) {
431
+ useTempNetworksStore.getState().upsertTempNetworks(newNetworks);
432
+ if (selectedNetwork) {
433
+ useAgentChatHistoryStore
434
+ .getState()
435
+ .copyHistory(selectedNetwork, replacement.agentInfo.agent_name);
436
+ setSelectedNetwork(replacement.agentInfo.agent_name);
437
+ }
438
+ }
439
+ else {
440
+ sendNotification(NotificationType.error, `Failed to update network "${agentName}".`, "A reservation was returned but did not match the current network. Please try again.");
441
+ }
442
+ }
443
+ catch (e) {
444
+ notifySaveError(agentName, e);
445
+ }
446
+ }, [neuroSanURL, userInfo.userName, selectedNetwork]);
279
447
  const onStreamingStarted = useCallback(() => {
280
448
  // Reset agent counts
281
- agentCountsRef.current = new Map();
449
+ setAgentCounts(new Map());
282
450
  // Reset newly added temporary networks
283
451
  setNewlyAddedTemporaryNetworks(new Set());
284
452
  // Reset Agent Network Designer preview
285
453
  setAgentsInNetworkDesigner([]);
286
- // Show info popup only once per session
287
- if (!haveShownPopup) {
288
- sendNotification(NotificationType.info, "Agents working", "Click the stop button or hit Escape to exit.");
289
- setHaveShownPopup(true);
290
- }
291
454
  // Mark that streaming has started
292
455
  setIsStreaming(true);
293
- }, [haveShownPopup]);
456
+ }, []);
294
457
  const onStreamingComplete = useCallback(() => {
295
458
  // When streaming is complete, clean up any refs and state
296
459
  conversationsRef.current = null;
@@ -298,27 +461,57 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
298
461
  setAgentsInNetworkDesigner([]);
299
462
  resetState();
300
463
  }, [resetState]);
301
- const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
302
464
  const handleDeleteNetwork = (networkId, isExpired) => {
303
465
  if (isExpired) {
304
466
  // It's expired so just delete it without confirmation
305
467
  const tempNetworksWithoutThisOne = temporaryNetworks.filter((network) => network.agentInfo.agent_name !== networkId);
306
468
  useTempNetworksStore.getState().setTempNetworks(tempNetworksWithoutThisOne);
469
+ useAgentChatHistoryStore.getState().resetHistory(networkId);
470
+ if (selectedNetwork === networkId) {
471
+ changeSelectedNetwork(null);
472
+ }
307
473
  }
308
474
  else {
309
475
  setNetworkToBeDeleted(networkId);
310
476
  setConfirmationModalOpen(true);
311
477
  }
312
478
  };
479
+ const handleEditNetwork = (_networkId) => {
480
+ setIsEditingNetwork(true);
481
+ };
482
+ useEffect(() => {
483
+ // Don't show the tour modal if any of these are true
484
+ if (haveShownTourModal ||
485
+ tourStatus === TourPromptState.Taken ||
486
+ tourStatus === TourPromptState.DontShowAgain ||
487
+ tourRequested) {
488
+ return undefined;
489
+ }
490
+ // Show tour modal after a delay
491
+ const timer = setTimeout(() => {
492
+ setTourModalOpen(true);
493
+ }, SHOW_TOUR_DELAY_MS);
494
+ return () => {
495
+ clearTimeout(timer);
496
+ };
497
+ }, [haveShownTourModal, tourRequested, tourStatus]);
498
+ useEffect(() => {
499
+ if (!tourRequested)
500
+ return;
501
+ // Determine whether the sample network for the tour is ready
502
+ const networkReady = selectedNetwork != null && agentsInNetwork?.length > 0;
503
+ if (networkReady) {
504
+ setTourStatus(TourPromptState.Taken);
505
+ controls.start();
506
+ setTourRequested(false);
507
+ }
508
+ }, [tourRequested, selectedNetwork, agentsInNetwork, networks, controls, setTourStatus]);
313
509
  const getLeftPanel = () => {
314
510
  return (_jsx(Slide, { id: "multi-agent-accelerator-grid-sidebar-slide", in: !enableZenMode || !isAwaitingLlm, direction: "right", timeout: GROW_ANIMATION_TIME_MS, onExited: () => {
315
511
  setIsStreaming(true);
316
512
  }, children: _jsx(Grid, { id: "multi-agent-accelerator-grid-sidebar", size: enableZenMode && isStreaming ? 0 : 3.25, sx: {
317
513
  height: "100%",
318
- }, children: _jsx(Sidebar, { customURLLocalStorage: customURLLocalStorage, customURLCallback: customURLCallback, id: "multi-agent-accelerator-sidebar", isAwaitingLlm: isAwaitingLlm, networks: networks, networkIconSuggestions: networkIconSuggestions, newlyAddedTemporaryNetworks: newlyAddedTemporaryNetworks, onDeleteNetwork: handleDeleteNetwork, setSelectedNetwork: (newNetwork) => {
319
- agentCountsRef.current = new Map();
320
- setSelectedNetwork(newNetwork);
321
- }, temporaryNetworks: temporaryNetworks }) }) }));
514
+ }, children: _jsx(Sidebar, { customURLLocalStorage: customURLLocalStorage, customURLCallback: customURLCallback, id: "multi-agent-accelerator-sidebar", isAwaitingLlm: isAwaitingLlm, networks: networks, networkIconSuggestions: networkIconSuggestions, newlyAddedTemporaryNetworks: newlyAddedTemporaryNetworks, onEditNetwork: handleEditNetwork, onDeleteNetwork: handleDeleteNetwork, setSelectedNetwork: (newNetwork) => changeSelectedNetwork(newNetwork), temporaryNetworks: temporaryNetworks }) }) }));
322
515
  };
323
516
  const getCenterPanel = () => {
324
517
  return (_jsx(Grid, { id: "multi-agent-accelerator-grid-agent-flow", size: enableZenMode && isStreaming ? 18 : 8.25, sx: {
@@ -331,23 +524,18 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
331
524
  height: "100%",
332
525
  maxWidth: 1000,
333
526
  margin: "0 auto",
334
- }, children: _jsx(AgentFlow, { agentCounts: agentCountsRef.current, agentsInNetwork: agentsInNetwork, agentIconSuggestions: agentIconSuggestions, currentUser: userInfo.userName, id: "multi-agent-accelerator-agent-flow", currentConversations: currentConversations, isAwaitingLlm: isAwaitingLlm, isStreaming: isStreaming, isSelectedNetworkTemporary: isSelectedNetworkTemporary, networkId: isSelectedNetworkTemporary ? selectedNetwork : undefined, neuroSanURL: neuroSanURL, onNetworkReplaced: (oldNetworkId, newNetworkId) => {
335
- if (selectedNetwork === oldNetworkId) {
336
- agentCountsRef.current = new Map();
337
- setSelectedNetwork(newNetworkId);
338
- }
339
- }, thoughtBubbleEdges: thoughtBubbleEdges, setThoughtBubbleEdges: setThoughtBubbleEdges }, "multi-agent-accelerator-agent-flow") }) }) }));
527
+ }, children: _jsx(AgentFlow, { agentCounts: agentCounts, agentsInNetwork: agentsInNetwork, agentIconSuggestions: agentIconSuggestions, id: "multi-agent-accelerator-agent-flow", currentConversations: currentConversations, currentUser: userInfo.userName, isAwaitingLlm: isAwaitingLlm, isEditMode: isEditingNetwork, isStreaming: isStreaming, isSelectedNetworkTemporary: isSelectedNetworkTemporary, networkDisplayName: selectedNetwork || undefined, networkId: isSelectedNetworkTemporary ? selectedNetwork : undefined, neuroSanURL: neuroSanURL, onEnterEditMode: () => setIsEditingNetwork(true), onExitEditMode: () => setIsEditingNetwork(false), onNetworkReplaced: (_old, newId) => changeSelectedNetwork(newId), onSaveAgent: onSaveAgent, thoughtBubbleEdges: thoughtBubbleEdges, setThoughtBubbleEdges: setThoughtBubbleEdges }, "multi-agent-accelerator-agent-flow") }) }) }));
340
528
  };
341
529
  const getRightPanel = () => {
342
530
  return (_jsx(Slide, { id: "multi-agent-accelerator-grid-agent-chat-common-slide", in: !enableZenMode || !isAwaitingLlm, direction: "left", timeout: GROW_ANIMATION_TIME_MS, onExited: () => {
343
531
  setIsStreaming(true);
344
532
  }, children: _jsx(Grid, { id: "multi-agent-accelerator-grid-agent-chat-common", size: enableZenMode && isStreaming ? 0 : 6.5, sx: {
345
533
  height: "100%",
346
- }, children: _jsx(ChatCommon, { agentGreetings: {
534
+ }, children: _jsx(ChatCommon, { customAgentGreetings: {
347
535
  [AGENT_NETWORK_DESIGNER_ID]: "Let's build a network together!",
348
536
  }, agentPlaceholders: {
349
537
  [AGENT_NETWORK_DESIGNER_ID]: "Describe in plain language the network you would like to build.",
350
- }, currentUser: userInfo.userName, extraSlyData: extraSlyData, id: "agent-network-ui", isAwaitingLlm: isAwaitingLlm, neuroSanURL: neuroSanURL, onChunkReceived: onChunkReceived, onStreamingComplete: onStreamingComplete, onStreamingStarted: onStreamingStarted, ref: chatRef, setIsAwaitingLlm: setIsAwaitingLlm, targetAgent: selectedNetwork, userImage: userInfo.userImage }, selectedNetwork ?? "no-network") }) }));
538
+ }, currentUser: userInfo.userName, extraSlyData: extraSlyData, id: "agent-network-ui", isAwaitingLlm: isAwaitingLlm, networkDescription: networkDescription, neuroSanURL: neuroSanURL, onChunkReceived: onChunkReceived, onStreamingComplete: onStreamingComplete, onStreamingStarted: onStreamingStarted, ref: chatRef, sampleQueries: sampleQueries, setIsAwaitingLlm: setIsAwaitingLlm, targetAgent: selectedNetwork, userImage: userInfo.userImage }, selectedNetwork ?? "no-network") }) }));
351
539
  };
352
540
  const getStopButton = () => {
353
541
  return (_jsx(_Fragment, { children: isAwaitingLlm && enableZenMode && (_jsx(Box, { id: "stop-button-container", sx: {
@@ -357,7 +545,7 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
357
545
  zIndex: 10,
358
546
  }, 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)" } }) }) })) }));
359
547
  };
360
- const getConfirmationModal = () => confirmationModalOpen ? (_jsx(ConfirmationModal, { id: "delete-network-confirmation-modal", content: `The network "${cleanUpAgentName(removeTrailingUuid(networkToBeDeleted))}" will be deleted. ` +
548
+ const getDeleteNetworkConfirmationModal = () => confirmationModalOpen ? (_jsx(ConfirmationModal, { id: "delete-network-confirmation-modal", content: `The network "${cleanUpAgentName(removeTrailingUuid(networkToBeDeleted))}" will be deleted. ` +
361
549
  "This action cannot be undone. Are you sure you want to proceed?", handleCancel: () => {
362
550
  setConfirmationModalOpen(false);
363
551
  setNetworkToBeDeleted(null);
@@ -365,9 +553,29 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
365
553
  useTempNetworksStore
366
554
  .getState()
367
555
  .setTempNetworks(temporaryNetworks.filter((network) => network.agentInfo.agent_name !== networkToBeDeleted));
556
+ useAgentChatHistoryStore.getState().resetHistory(networkToBeDeleted);
557
+ if (selectedNetwork === networkToBeDeleted) {
558
+ changeSelectedNetwork(null);
559
+ }
368
560
  setNetworkToBeDeleted(null);
369
561
  setConfirmationModalOpen(false);
370
562
  }, title: "Delete Network" })) : null;
563
+ const getTourModal = () => tourModalOpen && (_jsx(MUIDialog, { contentSx: { fontSize: "0.8rem", minWidth: "550px", paddingTop: "0" }, footer: _jsxs(_Fragment, { children: [_jsx(StyledButton, { id: "tour-dont-show-again", onClick: () => {
564
+ setTourStatus(TourPromptState.DontShowAgain);
565
+ dismissTourModal();
566
+ }, variant: "outlined", children: "Don't show this again" }), _jsx(StyledButton, { id: "tour-not-now", onClick: () => {
567
+ dismissTourModal();
568
+ }, variant: "outlined", children: "Not now" }), _jsx(StyledButton, { id: "tour-take", onClick: () => {
569
+ // If no network selected, select one so we have something to show
570
+ if (selectedNetwork == null) {
571
+ setSelectedNetwork(networks?.[0]?.agent_name ?? null);
572
+ }
573
+ dismissTourModal();
574
+ // Defer starting the tour until the selected network's data is available.
575
+ setTourRequested(true);
576
+ }, variant: "contained", children: "Take the tour" })] }), id: "multi-agent-accelerator-tour-modal", isOpen: tourModalOpen, onClose: () => {
577
+ dismissTourModal();
578
+ }, title: "Tour", children: "Would you like to take a tour of the application? (2 mins)" }));
371
579
  /**
372
580
  * Popper to show real-time progress of the Agent Network Designer output as we receive it from the backend.
373
581
  * Only displayed when Agent Network Designer is active.
@@ -393,7 +601,7 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
393
601
  opacity: "95%",
394
602
  width: "100%",
395
603
  }, children: [_jsx(Typography, { variant: "h6", sx: { padding: "0.5rem 1rem", fontWeight: "bold", color: "white" }, children: "Network Preview" }), agentsInNetworkDesigner?.length > 0 ? (_jsx(AgentFlow, { id: "and-network-preview", agentsInNetwork: agentsInNetworkDesigner, isAgentNetworkDesignerMode: true, isAwaitingLlm: false, isStreaming: false, thoughtBubbleEdges: EMPTY_THOUGHT_BUBBLE_EDGES }, "and-network-preview")) : (_jsx(Typography, { variant: "body1", sx: { color: "white" }, children: "Awaiting status from Agent Network Designer..." }))] }) }) }));
396
- return (_jsxs(_Fragment, { children: [getProgressPopper(), getConfirmationModal(), _jsxs(Grid, { id: "multi-agent-accelerator-grid", container: true, columns: 18, sx: {
604
+ return (_jsxs(_Fragment, { children: [Tour, getTourModal(), getProgressPopper(), getDeleteNetworkConfirmationModal(), _jsxs(Grid, { id: "multi-agent-accelerator-grid", container: true, columns: 18, sx: {
397
605
  display: "flex",
398
606
  flex: 1,
399
607
  height: "85%",
@@ -4,6 +4,7 @@ import { NodeIndex } from "./TreeBuilder.js";
4
4
  export interface AgentNetworkNodeProps extends TreeItemProps {
5
5
  readonly nodeIndex: NodeIndex;
6
6
  readonly onDeleteNetwork?: (network: string, isExpired: boolean) => void;
7
+ readonly onEditNetwork?: (network: string) => void;
7
8
  readonly networkIconSuggestions: Record<string, string>;
8
9
  readonly temporaryNetworkExpirationTimes?: Record<string, Date>;
9
10
  readonly temporaryNetworkHoconStrings?: Record<string, string | null>;
@@ -5,6 +5,7 @@ import * as MuiIcons from "@mui/icons-material";
5
5
  import BookmarkIcon from "@mui/icons-material/Bookmark";
6
6
  import Delete from "@mui/icons-material/Delete";
7
7
  import DownloadIcon from "@mui/icons-material/Download";
8
+ import Edit from "@mui/icons-material/Edit";
8
9
  import Box from "@mui/material/Box";
9
10
  import Chip from "@mui/material/Chip";
10
11
  import IconButton from "@mui/material/IconButton";
@@ -43,7 +44,7 @@ const isTemporaryNetworkExpired = (expirationDate) => {
43
44
  * @param props - see AgentNetworkNode interface
44
45
  * @returns JSX.Element containing the custom tree item
45
46
  */
46
- export const AgentNetworkTreeItem = ({ children, disabled, itemId, label, networkIconSuggestions, nodeIndex, onDeleteNetwork, temporaryNetworkExpirationTimes, temporaryNetworkHoconStrings, }) => {
47
+ export const AgentNetworkTreeItem = ({ children, disabled, itemId, label, networkIconSuggestions, nodeIndex, onDeleteNetwork, onEditNetwork, temporaryNetworkExpirationTimes, temporaryNetworkHoconStrings, }) => {
47
48
  const theme = useTheme();
48
49
  // We know all labels are strings because we set them that way in the tree view items
49
50
  const labelString = label;
@@ -91,13 +92,12 @@ export const AgentNetworkTreeItem = ({ children, disabled, itemId, label, networ
91
92
  "&:hover": {
92
93
  textDecoration: "underline",
93
94
  },
94
- }, children: displayLabel })] }) }), isChild && tags?.length > 0 ? (_jsx(Tooltip, { title: tags
95
- .slice()
95
+ }, children: displayLabel })] }) }), isChild && tags?.length > 0 ? (_jsx(Tooltip, { title: [...tags]
96
96
  .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
97
97
  .map((tag) => (_jsx(Chip, { label: tag, style: {
98
98
  margin: "0.25rem",
99
99
  backgroundColor: `var(${tagsToColors.get(tag) || TAG_COLORS[0]})`,
100
- } }, tag))), placement: "right", arrow: true, children: _jsx(BookmarkIcon, { sx: { fontSize: "0.75rem", color: "var(--bs-accent1-medium)" } }) })) : null, isTemporaryNetwork && networkHocon && (_jsx(Tooltip, { title: isExpired ? "Expired" : "Download network definition", children: _jsx("span", { children: _jsx(IconButton, { onClick: (e) => {
100
+ } }, tag))), placement: "right", arrow: true, children: _jsx(BookmarkIcon, { sx: { fontSize: "0.75rem", color: "var(--bs-accent1-medium)" } }) })) : null] }), isChild && isTemporaryNetwork && (_jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: "0.25rem", marginLeft: "auto" }, children: [networkHocon && (_jsx(Tooltip, { title: isExpired ? "Expired" : "Download network definition", children: _jsx("span", { children: _jsx(IconButton, { onClick: (e) => {
101
101
  e.stopPropagation();
102
102
  if (isExpired) {
103
103
  return;
@@ -112,12 +112,28 @@ export const AgentNetworkTreeItem = ({ children, disabled, itemId, label, networ
112
112
  color: "var(--bs-secondary)",
113
113
  opacity: 0.3,
114
114
  },
115
- }, children: _jsx(DownloadIcon, { sx: { fontSize: "0.75rem" } }) }) }) }))] }), isChild && isTemporaryNetwork && (_jsx(Tooltip, { title: "Delete network", children: _jsx(Delete, { onClick: (e) => {
116
- e.stopPropagation();
117
- onDeleteNetwork?.(itemId, isExpired);
118
- }, sx: {
119
- cursor: "pointer",
120
- fontSize: "1rem",
121
- "&:hover": { color: theme.palette.warning.main },
122
- } }) }))] }) }), children && _jsx(TreeItemGroupTransition, { ...getGroupTransitionProps() })] }) }));
115
+ }, children: _jsx(DownloadIcon, { sx: { fontSize: "0.75rem" } }) }) }) })), _jsx(Tooltip, { title: "Edit this network", children: _jsx("span", { children: _jsx(IconButton, { onClick: (e) => {
116
+ e.stopPropagation();
117
+ onEditNetwork?.(itemId);
118
+ }, disabled: isExpired, size: "small", sx: {
119
+ padding: 0,
120
+ color: "var(--bs-secondary)",
121
+ "&:hover": { color: "var(--bs-secondary-dark)" },
122
+ "&.Mui-disabled": {
123
+ color: "var(--bs-secondary)",
124
+ opacity: 0.3,
125
+ },
126
+ }, children: _jsx(Edit, { sx: { fontSize: "0.75rem" } }) }) }) }), _jsx(Tooltip, { title: "Delete network", children: _jsx(Delete, { onClick: (e) => {
127
+ e.stopPropagation();
128
+ onDeleteNetwork?.(itemId, isExpired);
129
+ }, sx: {
130
+ color: "var(--bs-secondary)",
131
+ "&:hover": { color: theme.palette.warning.main },
132
+ "&.Mui-disabled": {
133
+ color: "var(--bs-secondary)",
134
+ opacity: 0.3,
135
+ },
136
+ cursor: "pointer",
137
+ fontSize: "1rem",
138
+ } }) })] }))] }) }), children && _jsx(TreeItemGroupTransition, { ...getGroupTransitionProps() })] }) }));
123
139
  };
@@ -10,6 +10,7 @@ export interface SidebarProps {
10
10
  readonly isAwaitingLlm: boolean;
11
11
  readonly networkIconSuggestions?: NetworkIconSuggestions;
12
12
  readonly networks: readonly AgentInfo[];
13
+ readonly onEditNetwork?: (network: string) => void;
13
14
  readonly onDeleteNetwork?: (network: string, isExpired: boolean) => void;
14
15
  readonly setSelectedNetwork: (network: string) => void;
15
16
  readonly temporaryNetworks?: readonly TemporaryNetwork[];