@cognizant-ai-lab/ui-common 1.4.1 → 1.4.2

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.
@@ -16,7 +16,6 @@ limitations under the License.
16
16
  */
17
17
  import StopCircle from "@mui/icons-material/StopCircle";
18
18
  import Box from "@mui/material/Box";
19
- import Collapse from "@mui/material/Collapse";
20
19
  import Grid from "@mui/material/Grid";
21
20
  import Slide from "@mui/material/Slide";
22
21
  import { ReactFlowProvider } from "@xyflow/react";
@@ -26,7 +25,7 @@ import { getUpdatedAgentCounts } from "./AgentCounts.js";
26
25
  import { AgentFlow } from "./AgentFlow.js";
27
26
  import { TEMPORARY_NETWORK_FOLDER } from "./const.js";
28
27
  import { Sidebar } from "./Sidebar/Sidebar.js";
29
- import { extractReservations } from "./TemporaryNetworks.js";
28
+ import { extractNetworkHocon, extractReservations } from "./TemporaryNetworks.js";
30
29
  import { getAgentIconSuggestions, getAgentNetworks, getConnectivity, getNetworkIconSuggestions, } from "../../controller/agent/Agent.js";
31
30
  import { useSettingsStore } from "../../state/Settings.js";
32
31
  import { useTempNetworksStore } from "../../state/TemporaryNetworks.js";
@@ -35,7 +34,6 @@ import { ChatCommon } from "../AgentChat/ChatCommon.js";
35
34
  import { SmallLlmChatButton } from "../AgentChat/LlmChatButton.js";
36
35
  import { chatMessageFromChunk, cleanUpAgentName, removeTrailingUuid } from "../AgentChat/Utils.js";
37
36
  import { ConfirmationModal } from "../Common/ConfirmationModal.js";
38
- import { MUIAlert } from "../Common/MUIAlert.js";
39
37
  import { closeNotification, NotificationType, sendNotification } from "../Common/notification.js";
40
38
  // Display expired temporary networks for this amount of time after they expire so users can see what happened
41
39
  const GRACE_PERIOD_MS = 5 * 60 * 1000; // 5 minutes
@@ -45,9 +43,13 @@ const GROW_ANIMATION_TIME_MS = 800;
45
43
  * Helper function to convert agent reservations received from the backend into temporary networks that can be displayed
46
44
  * in the tree.
47
45
  * @param agentReservations List of "agent reservations" (temporary networks) received from the backend
46
+ * @param networkHocon Optional network HOCON string that may be included in the same message as the
47
+ * reservations. Note: for now we assume that all reservations are associated with the same network definition.
48
+ * This will fail if ever we get multiple reservations for different networks in a single chat stream, but that is
49
+ * not a valid scenario currently; we are focusing on Agent Network Design which has a simple output.
48
50
  * @returns List of TemporaryNetwork objects that can be displayed in the UI
49
51
  */
50
- const convertReservationsToNetworks = (agentReservations) => {
52
+ const convertReservationsToNetworks = (agentReservations, networkHocon) => {
51
53
  return agentReservations.map((reservation) => ({
52
54
  reservation,
53
55
  agentInfo: {
@@ -55,6 +57,7 @@ const convertReservationsToNetworks = (agentReservations) => {
55
57
  origin: reservation.reservation_id,
56
58
  status: "active",
57
59
  },
60
+ networkHocon,
58
61
  }));
59
62
  };
60
63
  /**
@@ -90,8 +93,6 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
90
93
  const [networkToBeDeleted, setNetworkToBeDeleted] = useState(null);
91
94
  // State to hold thought bubble edges - avoids duplicates across layout recalculations
92
95
  const [thoughtBubbleEdges, setThoughtBubbleEdges] = useState(new Map());
93
- // For controlling alert when temporary network is created
94
- const [alertContents, setAlertContents] = useState(null);
95
96
  const customURLCallback = useCallback((url) => {
96
97
  setNeuroSanURL(url || backendNeuroSanApiUrl);
97
98
  setCustomURLLocalStorage(url === "" ? null : url);
@@ -244,7 +245,9 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
244
245
  const reservationsResult = extractReservations(chatMessage);
245
246
  // Handle agent reservations (temporary networks) that come in through the chat stream.
246
247
  if (reservationsResult?.length > 0) {
247
- const newTemporaryNetworks = convertReservationsToNetworks(reservationsResult);
248
+ // Retrieve network definition, if present
249
+ const networkHocon = extractNetworkHocon(chatMessage);
250
+ const newTemporaryNetworks = convertReservationsToNetworks(reservationsResult, networkHocon);
248
251
  const currentNetworks = useTempNetworksStore.getState().tempNetworks;
249
252
  useTempNetworksStore.getState().setTempNetworks([...currentNetworks, ...newTemporaryNetworks]);
250
253
  // record the new temporary networks so we can select them for the user. For now, we only
@@ -258,7 +261,6 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
258
261
  agentCountsRef.current = new Map();
259
262
  // Reset newly added temporary networks
260
263
  setNewlyAddedTemporaryNetworks(new Set());
261
- setAlertContents(null);
262
264
  // Show info popup only once per session
263
265
  if (!haveShownPopup) {
264
266
  sendNotification(NotificationType.info, "Agents working", "Click the stop button or hit Escape to exit.");
@@ -268,31 +270,11 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
268
270
  setIsStreaming(true);
269
271
  }, [haveShownPopup]);
270
272
  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
- }
277
273
  // When streaming is complete, clean up any refs and state
278
274
  conversationsRef.current = null;
279
275
  setCurrentConversations(null);
280
276
  resetState();
281
277
  }, [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
278
  const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
297
279
  const handleDeleteNetwork = (networkId, isExpired) => {
298
280
  if (isExpired) {
@@ -354,8 +336,7 @@ export const MultiAgentAccelerator = ({ backendNeuroSanApiUrl, userInfo, }) => {
354
336
  setNetworkToBeDeleted(null);
355
337
  setConfirmationModalOpen(false);
356
338
  }, 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: {
339
+ return (_jsxs(_Fragment, { children: [getConfirmationModal(), _jsxs(Grid, { id: "multi-agent-accelerator-grid", container: true, columns: 18, sx: {
359
340
  display: "flex",
360
341
  flex: 1,
361
342
  height: "85%",
@@ -6,6 +6,7 @@ export interface AgentNetworkNodeProps extends TreeItemProps {
6
6
  readonly onDeleteNetwork?: (network: string, isExpired: boolean) => void;
7
7
  readonly networkIconSuggestions: Record<string, string>;
8
8
  readonly temporaryNetworkExpirationTimes?: Record<string, Date>;
9
+ readonly temporaryNetworkHoconStrings?: Record<string, string | null>;
9
10
  }
10
11
  /**
11
12
  * Custom Tree Item for MUI RichTreeView to display agent networks with tags
@@ -4,14 +4,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  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
+ import DownloadIcon from "@mui/icons-material/Download";
7
8
  import Box from "@mui/material/Box";
8
9
  import Chip from "@mui/material/Chip";
10
+ import IconButton from "@mui/material/IconButton";
9
11
  import { useTheme } from "@mui/material/styles";
10
12
  import Tooltip from "@mui/material/Tooltip";
11
13
  import { TreeItemContent, TreeItemGroupTransition, TreeItemLabel, TreeItemRoot, } from "@mui/x-tree-view/TreeItem";
12
14
  import { TreeItemProvider } from "@mui/x-tree-view/TreeItemProvider";
13
15
  import { useTreeItem } from "@mui/x-tree-view/useTreeItem";
14
16
  import { useRef } from "react";
17
+ import { downloadFile, toSafeFilename } from "../../../utils/File.js";
15
18
  import { cleanUpAgentName } from "../../AgentChat/Utils.js";
16
19
  // Palette of colors we can use for tags
17
20
  const TAG_COLORS = [
@@ -40,7 +43,7 @@ const isTemporaryNetworkExpired = (expirationDate) => {
40
43
  * @param props - see AgentNetworkNode interface
41
44
  * @returns JSX.Element containing the custom tree item
42
45
  */
43
- export const AgentNetworkTreeItem = ({ children, disabled, itemId, label, networkIconSuggestions, nodeIndex, onDeleteNetwork, temporaryNetworkExpirationTimes, }) => {
46
+ export const AgentNetworkTreeItem = ({ children, disabled, itemId, label, networkIconSuggestions, nodeIndex, onDeleteNetwork, temporaryNetworkExpirationTimes, temporaryNetworkHoconStrings, }) => {
44
47
  const theme = useTheme();
45
48
  // We know all labels are strings because we set them that way in the tree view items
46
49
  const labelString = label;
@@ -66,6 +69,7 @@ export const AgentNetworkTreeItem = ({ children, disabled, itemId, label, networ
66
69
  const expirationTime = temporaryNetworkExpirationTimes?.[itemId];
67
70
  const isTemporaryNetwork = Boolean(expirationTime);
68
71
  const isExpired = isChild && isTemporaryNetwork && isTemporaryNetworkExpired(expirationTime);
72
+ const networkHocon = isTemporaryNetwork ? temporaryNetworkHoconStrings?.[itemId] : null;
69
73
  const iconNameSuggestion = isTemporaryNetwork ? "HourglassTop" : isChild ? networkIconSuggestions?.[itemId] : null;
70
74
  let muiIconElement = null;
71
75
  if (iconNameSuggestion && MuiIcons[iconNameSuggestion]) {
@@ -93,7 +97,22 @@ export const AgentNetworkTreeItem = ({ children, disabled, itemId, label, networ
93
97
  .map((tag) => (_jsx(Chip, { label: tag, style: {
94
98
  margin: "0.25rem",
95
99
  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) => {
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 ? "Network expired" : "Download network definition", children: _jsx("span", { children: _jsx(IconButton, { onClick: (e) => {
101
+ e.stopPropagation();
102
+ if (isExpired) {
103
+ return;
104
+ }
105
+ const fileName = `${toSafeFilename(labelString)}.hocon`;
106
+ downloadFile(networkHocon, fileName);
107
+ }, disabled: isExpired, "aria-label": "Download network definition", size: "small", sx: {
108
+ padding: 0,
109
+ color: "var(--bs-secondary)",
110
+ "&:hover": { color: "var(--bs-secondary-dark)" },
111
+ "&.Mui-disabled": {
112
+ color: "var(--bs-secondary)",
113
+ opacity: 0.3,
114
+ },
115
+ }, children: _jsx(DownloadIcon, { sx: { fontSize: "0.75rem" } }) }) }) }))] }), isChild && isTemporaryNetwork && (_jsx(Tooltip, { title: "Delete network", children: _jsx(Delete, { onClick: (e) => {
97
116
  e.stopPropagation();
98
117
  onDeleteNetwork?.(itemId, isExpired);
99
118
  }, sx: {
@@ -203,6 +203,10 @@ export const Sidebar = ({ customURLCallback, customURLLocalStorage, id, isAwaiti
203
203
  acc[tempNetwork.agentInfo.agent_name] = new Date(tempNetwork.reservation.expiration_time_in_seconds * 1000);
204
204
  return acc;
205
205
  }, {});
206
+ const temporaryNetworkHoconStrings = temporaryNetworks.reduce((acc, tempNetwork) => {
207
+ acc[tempNetwork.agentInfo.agent_name] = tempNetwork.networkHocon;
208
+ return acc;
209
+ }, {});
206
210
  const [selectedItem, setSelectedItem] = useState(null);
207
211
  const handleSelectedItemsChange = (_event, itemId) => {
208
212
  if (!itemId) {
@@ -263,6 +267,7 @@ export const Sidebar = ({ customURLCallback, customURLLocalStorage, id, isAwaiti
263
267
  nodeIndex,
264
268
  onDeleteNetwork,
265
269
  temporaryNetworkExpirationTimes,
270
+ temporaryNetworkHoconStrings,
266
271
  },
267
272
  } }, Object.keys(networkIconSuggestions || {}).length)] }), _jsxs(Popover, { id: "agent-network-settings-popover", open: settingsPopoverOpen, anchorEl: settingsAnchorEl, onClose: () => handleSettingsClose(true), anchorOrigin: {
268
273
  vertical: "bottom",
@@ -24,3 +24,10 @@ export type AgentReservation = {
24
24
  * if the message is not of the expected type.
25
25
  */
26
26
  export declare const extractReservations: (message: ChatMessage) => AgentReservation[];
27
+ /**
28
+ * Extracts the agent network HOCON from a chat message, if it exists.
29
+ * @param message The chat message to extract network HOCON from.
30
+ * We expect the network definition to be present in messages of type AGENT_FRAMEWORK only.
31
+ * @return The network HOCON as a string, or null if not found or not the right type of message.
32
+ */
33
+ export declare const extractNetworkHocon: (message: ChatMessage) => string | null;
@@ -1,6 +1,5 @@
1
+ import { AGENT_NETWORK_HOCON, AGENT_RESERVATIONS_KEY } from "./const.js";
1
2
  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
3
  /**
5
4
  * Extracts agent reservations from a chat message, if they exist.
6
5
  * @param message The chat message to extract reservations from. We expect reservations to be present in messages of
@@ -18,3 +17,19 @@ export const extractReservations = (message) => {
18
17
  return [];
19
18
  }
20
19
  };
20
+ /**
21
+ * Extracts the agent network HOCON from a chat message, if it exists.
22
+ * @param message The chat message to extract network HOCON from.
23
+ * We expect the network definition to be present in messages of type AGENT_FRAMEWORK only.
24
+ * @return The network HOCON as a string, or null if not found or not the right type of message.
25
+ */
26
+ export const extractNetworkHocon = (message) => {
27
+ // Check for agent network HOCON in sly_data
28
+ if (message?.type === ChatMessageType.AGENT_FRAMEWORK && message?.sly_data?.[AGENT_NETWORK_HOCON]) {
29
+ return message.sly_data[AGENT_NETWORK_HOCON];
30
+ }
31
+ else {
32
+ // Not the type of message that would contain a network HOCON, or no network HOCON found, return null
33
+ return null;
34
+ }
35
+ };
@@ -3,3 +3,5 @@ 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
5
  export declare const TEMPORARY_NETWORK_FOLDER = "temporary";
6
+ export declare const AGENT_RESERVATIONS_KEY = "agent_reservations";
7
+ export declare const AGENT_NETWORK_HOCON = "agent_network_hocon_text";
@@ -23,3 +23,7 @@ export const LEVEL_SPACING = 150;
23
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
24
  // temporary.
25
25
  export const TEMPORARY_NETWORK_FOLDER = "temporary";
26
+ // We expect the agent reservations to be stored in sly_data under this key
27
+ export const AGENT_RESERVATIONS_KEY = "agent_reservations";
28
+ // We expect the agent network HOCON to be stored in sly_data under this key, if it is provided by the backend
29
+ export const AGENT_NETWORK_HOCON = "agent_network_hocon_text";
@@ -7,6 +7,7 @@ type AgentReservation = {
7
7
  export type TemporaryNetwork = {
8
8
  readonly reservation: AgentReservation;
9
9
  readonly agentInfo: AgentInfo;
10
+ readonly networkHocon?: string | null;
10
11
  };
11
12
  /**
12
13
  * Zustand state store for temporary networks, such as vibe coded networks created by the user.