@cognizant-ai-lab/ui-common 1.4.2 → 1.5.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.
- package/dist/components/AgentChat/ChatCommon/AgentConnectivity.d.ts +14 -0
- package/dist/components/AgentChat/ChatCommon/AgentConnectivity.js +23 -0
- package/dist/components/AgentChat/{ChatCommon.d.ts → ChatCommon/ChatCommon.d.ts} +8 -4
- package/dist/components/AgentChat/{ChatCommon.js → ChatCommon/ChatCommon.js} +318 -307
- package/dist/components/AgentChat/ChatCommon/ChatHistory.d.ts +17 -0
- package/dist/components/AgentChat/ChatCommon/ChatHistory.js +27 -0
- package/dist/components/AgentChat/{ControlButtons.d.ts → ChatCommon/ControlButtons.d.ts} +1 -1
- package/dist/components/AgentChat/ChatCommon/ControlButtons.js +26 -0
- package/dist/components/AgentChat/{FormattedMarkdown.js → ChatCommon/FormattedMarkdown.js} +1 -1
- package/dist/components/AgentChat/ChatCommon/SampleQueries.d.ts +16 -0
- package/dist/components/AgentChat/ChatCommon/SampleQueries.js +29 -0
- package/dist/components/AgentChat/{SendButton.js → ChatCommon/SendButton.js} +1 -1
- package/dist/components/AgentChat/ChatCommon/UserQueryDisplay.d.ts +7 -0
- package/dist/components/AgentChat/{UserQueryDisplay.js → ChatCommon/UserQueryDisplay.js} +4 -3
- package/dist/components/AgentChat/{LlmChatButton.d.ts → Common/LlmChatButton.d.ts} +2 -2
- package/dist/components/AgentChat/{Utils.d.ts → Common/Utils.d.ts} +1 -1
- package/dist/components/AgentChat/{Utils.js → Common/Utils.js} +2 -1
- package/dist/components/AgentChat/VoiceChat/MicrophoneButton.js +1 -1
- package/dist/components/ChatBot/ChatBot.js +2 -2
- package/dist/components/Common/CustomerLogo.js +1 -1
- package/dist/components/Common/LlmChatOptionsButton.d.ts +1 -1
- package/dist/components/Common/MUIDialog.d.ts +1 -0
- package/dist/components/Common/MUIDialog.js +2 -2
- package/dist/components/MultiAgentAccelerator/AgentCounts.d.ts +2 -2
- package/dist/components/MultiAgentAccelerator/AgentFlow.d.ts +13 -1
- package/dist/components/MultiAgentAccelerator/AgentFlow.js +193 -20
- package/dist/components/MultiAgentAccelerator/AgentNetworkDesigner.d.ts +10 -0
- package/dist/components/MultiAgentAccelerator/AgentNetworkDesigner.js +20 -0
- package/dist/components/MultiAgentAccelerator/AgentNode.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/AgentNode.js +9 -4
- package/dist/components/MultiAgentAccelerator/AgentNodePopup.d.ts +33 -0
- package/dist/components/MultiAgentAccelerator/AgentNodePopup.js +81 -0
- package/dist/components/MultiAgentAccelerator/GraphLayouts.d.ts +4 -4
- package/dist/components/MultiAgentAccelerator/GraphLayouts.js +12 -8
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +101 -44
- package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.js +4 -4
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.js +29 -23
- package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +1 -1
- package/dist/components/MultiAgentAccelerator/TemporaryNetworks.d.ts +14 -0
- package/dist/components/MultiAgentAccelerator/TemporaryNetworks.js +26 -1
- package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.js +8 -7
- package/dist/components/MultiAgentAccelerator/const.d.ts +24 -0
- package/dist/components/MultiAgentAccelerator/const.js +19 -0
- package/dist/controller/llm/LlmChat.js +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.js +8 -7
- package/dist/state/ChatHistory.d.ts +50 -0
- package/dist/state/ChatHistory.js +98 -0
- package/dist/state/IndexedDBStorage.d.ts +14 -0
- package/dist/state/IndexedDBStorage.js +65 -0
- package/dist/state/TemporaryNetworks.d.ts +23 -0
- package/dist/state/TemporaryNetworks.js +43 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +8 -2
- package/dist/components/AgentChat/ControlButtons.js +0 -26
- package/dist/components/AgentChat/UserQueryDisplay.d.ts +0 -5
- /package/dist/components/AgentChat/{FormattedMarkdown.d.ts → ChatCommon/FormattedMarkdown.d.ts} +0 -0
- /package/dist/components/AgentChat/{Greetings.d.ts → ChatCommon/Greetings.d.ts} +0 -0
- /package/dist/components/AgentChat/{Greetings.js → ChatCommon/Greetings.js} +0 -0
- /package/dist/components/AgentChat/{SendButton.d.ts → ChatCommon/SendButton.d.ts} +0 -0
- /package/dist/components/AgentChat/{SyntaxHighlighterThemes.d.ts → ChatCommon/SyntaxHighlighterThemes.d.ts} +0 -0
- /package/dist/components/AgentChat/{SyntaxHighlighterThemes.js → ChatCommon/SyntaxHighlighterThemes.js} +0 -0
- /package/dist/components/AgentChat/{LlmChatButton.js → Common/LlmChatButton.js} +0 -0
- /package/dist/components/AgentChat/{Types.d.ts → Common/Types.d.ts} +0 -0
- /package/dist/components/AgentChat/{Types.js → Common/Types.js} +0 -0
|
@@ -27,24 +27,81 @@ import Typography from "@mui/material/Typography";
|
|
|
27
27
|
import { applyNodeChanges, Background, ConnectionMode, ControlButton, Controls, ReactFlow, useReactFlow, useStore, } from "@xyflow/react";
|
|
28
28
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
29
29
|
import { AgentNode, NODE_HEIGHT, NODE_WIDTH } from "./AgentNode.js";
|
|
30
|
-
import {
|
|
30
|
+
import { AgentNodePopup } from "./AgentNodePopup.js";
|
|
31
|
+
import { AGENT_NETWORK_DEFINITION_KEY, AGENT_NETWORK_DESIGNER_ID, AGENT_NETWORK_NAME_KEY, BASE_RADIUS, DEFAULT_FRONTMAN_X_POS, DEFAULT_FRONTMAN_Y_POS, isEditableAgent, LEVEL_SPACING, } from "./const.js";
|
|
31
32
|
import { addThoughtBubbleEdge, layoutLinear, layoutRadial } from "./GraphLayouts.js";
|
|
32
33
|
import { PlasmaEdge } from "./PlasmaEdge.js";
|
|
34
|
+
import { convertReservationsToNetworks, extractNetworkHocon, extractReservations } from "./TemporaryNetworks.js";
|
|
33
35
|
import { ThoughtBubbleEdge } from "./ThoughtBubbleEdge.js";
|
|
34
36
|
import { ThoughtBubbleOverlay } from "./ThoughtBubbleOverlay.js";
|
|
37
|
+
import { sendChatQuery } from "../../controller/agent/Agent.js";
|
|
38
|
+
import { StreamingUnit } from "../../controller/llm/LlmChat.js";
|
|
39
|
+
import { useAgentChatHistoryStore } from "../../state/ChatHistory.js";
|
|
40
|
+
import { useTempNetworksStore } from "../../state/TemporaryNetworks.js";
|
|
35
41
|
import { usePalette } from "../../Theme/Palettes.js";
|
|
36
42
|
import { getZIndex } from "../../utils/zIndexLayers.js";
|
|
43
|
+
import { chatMessageFromChunk } from "../AgentChat/Common/Utils.js";
|
|
44
|
+
import { NotificationType, sendNotification } from "../Common/notification.js";
|
|
37
45
|
// #endregion: Types
|
|
38
46
|
// #region: Constants
|
|
39
47
|
// Timeout for thought bubbles is set to 10 seconds
|
|
40
48
|
const THOUGHT_BUBBLE_TIMEOUT_MS = 10_000;
|
|
41
49
|
// #endregion: Constants
|
|
42
|
-
|
|
50
|
+
// #region: Helpers
|
|
51
|
+
/** Merges incoming networks into target, keeping the entry with the highest expiration time. */
|
|
52
|
+
const mergeNetworks = (target, incoming) => {
|
|
53
|
+
for (const n of incoming) {
|
|
54
|
+
const key = n.agentNetworkName ?? n.reservation.reservation_id;
|
|
55
|
+
const existingIdx = target.findIndex((e) => (e.agentNetworkName ?? e.reservation.reservation_id) === key);
|
|
56
|
+
if (existingIdx < 0) {
|
|
57
|
+
target.push(n);
|
|
58
|
+
}
|
|
59
|
+
else if (n.reservation.expiration_time_in_seconds > target[existingIdx].reservation.expiration_time_in_seconds) {
|
|
60
|
+
target[existingIdx] = n;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Streams the Agent Network Designer endpoint with the updated definition and collects
|
|
66
|
+
* the resulting reservations. Returns the deduplicated list of new networks.
|
|
67
|
+
*/
|
|
68
|
+
const streamNetworkDesignerUpdate = async (neuroSanURL, signal, agentName, updated, agentNetworkName, currentUser) => {
|
|
69
|
+
const newNetworks = [];
|
|
70
|
+
await sendChatQuery(neuroSanURL, signal,
|
|
71
|
+
// Shouldn't have to pass a user message, but API behaves different without it
|
|
72
|
+
`Update instructions for agent "${agentName}"`, AGENT_NETWORK_DESIGNER_ID, (chunk) => {
|
|
73
|
+
const chatMessage = chatMessageFromChunk(chunk);
|
|
74
|
+
if (!chatMessage)
|
|
75
|
+
return;
|
|
76
|
+
const reservations = extractReservations(chatMessage);
|
|
77
|
+
if (reservations.length === 0)
|
|
78
|
+
return;
|
|
79
|
+
const networkHocon = extractNetworkHocon(chatMessage);
|
|
80
|
+
// Always use the user's edited definition as the authoritative value.
|
|
81
|
+
// The backend may not echo agent_network_definition back, may return
|
|
82
|
+
// an empty array, or may return the pre-edit version.
|
|
83
|
+
const agentNetworkNameFromMessage = chatMessage.sly_data?.[AGENT_NETWORK_NAME_KEY];
|
|
84
|
+
// Prefer the locally-known name so upsert can match the existing entry even
|
|
85
|
+
// when the backend response omits AGENT_NETWORK_NAME_KEY.
|
|
86
|
+
const networkName = agentNetworkName ?? agentNetworkNameFromMessage;
|
|
87
|
+
const converted = convertReservationsToNetworks(reservations, networkHocon, updated, networkName);
|
|
88
|
+
mergeNetworks(newNetworks, converted);
|
|
89
|
+
}, null, {
|
|
90
|
+
[AGENT_NETWORK_DEFINITION_KEY]: updated,
|
|
91
|
+
// Use the backend's canonical name, not the local UUID-based key.
|
|
92
|
+
...(agentNetworkName ? { [AGENT_NETWORK_NAME_KEY]: agentNetworkName } : {}),
|
|
93
|
+
// skip_designer prevents the backend from using a reasoning model for edits
|
|
94
|
+
skip_designer: true,
|
|
95
|
+
}, currentUser, StreamingUnit.Line);
|
|
96
|
+
return newNetworks;
|
|
97
|
+
};
|
|
98
|
+
// #endregion: Helpers
|
|
99
|
+
export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork, currentConversations, currentUser, id, isAgentNetworkDesignerMode, isAwaitingLlm, isStreaming, isSelectedNetworkTemporary: isTemporaryNetwork, networkId, neuroSanURL, onNetworkReplaced, thoughtBubbleEdges, setThoughtBubbleEdges, }) => {
|
|
43
100
|
const theme = useTheme();
|
|
44
101
|
const { fitView } = useReactFlow();
|
|
45
102
|
const handleResize = useCallback(() => {
|
|
46
|
-
fitView(); // Adjusts the view to fit after resizing
|
|
47
|
-
}, [fitView
|
|
103
|
+
void fitView(); // Adjusts the view to fit after resizing
|
|
104
|
+
}, [fitView]);
|
|
48
105
|
useEffect(() => {
|
|
49
106
|
window.addEventListener("resize", handleResize);
|
|
50
107
|
return () => window.removeEventListener("resize", handleResize);
|
|
@@ -53,6 +110,9 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
53
110
|
const [coloringOption, setColoringOption] = useState("depth");
|
|
54
111
|
const [enableRadialGuides, setEnableRadialGuides] = useState(true);
|
|
55
112
|
const [showThoughtBubbles, setShowThoughtBubbles] = useState(true);
|
|
113
|
+
// Read temporary networks to find agent_network_definition for the currently selected network.
|
|
114
|
+
const tempNetworks = useTempNetworksStore((state) => state.tempNetworks);
|
|
115
|
+
const updateTempNetworkDefinition = useTempNetworksStore((state) => state.updateTempNetworkDefinition);
|
|
56
116
|
// Track conversation IDs we've already processed to prevent re-adding after expiry
|
|
57
117
|
const processedConversationIdsRef = useRef(new Set());
|
|
58
118
|
// Track which bubble is currently being hovered
|
|
@@ -76,7 +136,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
76
136
|
useEffect(() => {
|
|
77
137
|
if (!currentConversations || currentConversations.length === 0)
|
|
78
138
|
return;
|
|
79
|
-
setThoughtBubbleEdges((prev) => {
|
|
139
|
+
setThoughtBubbleEdges?.((prev) => {
|
|
80
140
|
const processedText = new Set();
|
|
81
141
|
for (const entry of prev.values()) {
|
|
82
142
|
const text = entry.edge.data?.text?.trim();
|
|
@@ -121,7 +181,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
121
181
|
if (!isStreamingRef.current)
|
|
122
182
|
return;
|
|
123
183
|
const now = Date.now();
|
|
124
|
-
setThoughtBubbleEdges((prev) => {
|
|
184
|
+
setThoughtBubbleEdges?.((prev) => {
|
|
125
185
|
let changed = false;
|
|
126
186
|
const edgesMap = new Map(prev);
|
|
127
187
|
for (const [convId, entry] of prev) {
|
|
@@ -135,7 +195,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
135
195
|
});
|
|
136
196
|
}, 1000);
|
|
137
197
|
return () => clearInterval(cleanupInterval);
|
|
138
|
-
}, []); // mount/unmount only
|
|
198
|
+
}, [setThoughtBubbleEdges]); // mount/unmount only
|
|
139
199
|
// Shadow color for icon
|
|
140
200
|
const shadowColor = theme.palette.mode === "dark" ? theme.palette.common.white : theme.palette.common.black;
|
|
141
201
|
const isHeatmap = coloringOption === "heatmap";
|
|
@@ -164,16 +224,17 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
164
224
|
// Create the flow layout depending on user preference
|
|
165
225
|
// Memoize layoutResult so it only recalculates when relevant data changes
|
|
166
226
|
const layoutResult = useMemo(() => layout === "linear"
|
|
167
|
-
? layoutLinear(isHeatmap ? agentCounts : undefined, mergedAgentsInNetwork, currentConversations, isAwaitingLlm, thoughtBubbleEdges, agentIconSuggestions)
|
|
168
|
-
: layoutRadial(isHeatmap ? agentCounts : undefined, mergedAgentsInNetwork, currentConversations, isAwaitingLlm, thoughtBubbleEdges, agentIconSuggestions), [
|
|
227
|
+
? layoutLinear(isHeatmap ? agentCounts : undefined, mergedAgentsInNetwork, currentConversations, isAwaitingLlm, isAgentNetworkDesignerMode, thoughtBubbleEdges, agentIconSuggestions, isTemporaryNetwork)
|
|
228
|
+
: layoutRadial(isHeatmap ? agentCounts : undefined, mergedAgentsInNetwork, currentConversations, isAwaitingLlm, isAgentNetworkDesignerMode, thoughtBubbleEdges, agentIconSuggestions, isTemporaryNetwork), [
|
|
169
229
|
agentCounts,
|
|
170
230
|
agentIconSuggestions,
|
|
171
|
-
coloringOption,
|
|
172
231
|
currentConversations,
|
|
232
|
+
isAgentNetworkDesignerMode,
|
|
173
233
|
isAwaitingLlm,
|
|
234
|
+
isHeatmap,
|
|
235
|
+
isTemporaryNetwork,
|
|
174
236
|
layout,
|
|
175
237
|
mergedAgentsInNetwork,
|
|
176
|
-
showThoughtBubbles,
|
|
177
238
|
thoughtBubbleEdges,
|
|
178
239
|
]);
|
|
179
240
|
const [nodes, setNodes] = useState(layoutResult.nodes);
|
|
@@ -181,31 +242,143 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
181
242
|
useEffect(() => {
|
|
182
243
|
setNodes(layoutResult.nodes);
|
|
183
244
|
}, [layoutResult.nodes]);
|
|
245
|
+
// Track which node the user clicked on so we can open the popup
|
|
246
|
+
const [selectedAgent, setSelectedAgent] = useState(null);
|
|
247
|
+
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
|
248
|
+
// True while the agent-edit request is in-flight so we can disable the Save button.
|
|
249
|
+
const [isSavingAgent, setIsSavingAgent] = useState(false);
|
|
250
|
+
// AbortController for the in-flight save request — stored in a ref so handlePopupClose can cancel it.
|
|
251
|
+
const saveAbortControllerRef = useRef(null);
|
|
252
|
+
const handleNodeClick = useCallback((_event, node) => {
|
|
253
|
+
// Popup is only available for temporary networks.
|
|
254
|
+
if (!isTemporaryNetwork)
|
|
255
|
+
return;
|
|
256
|
+
// Only llm_agent nodes support instructions/description editing.
|
|
257
|
+
if (!isEditableAgent(node.data.displayAs))
|
|
258
|
+
return;
|
|
259
|
+
// Find the clicked agent's existing instructions and description from the temp network definition.
|
|
260
|
+
const currentTempNetwork = networkId
|
|
261
|
+
? tempNetworks.find((n) => n.agentInfo.agent_name === networkId)
|
|
262
|
+
: undefined;
|
|
263
|
+
const found = (currentTempNetwork?.agentNetworkDefinition ?? []).find((e) => e.origin === node.id);
|
|
264
|
+
setSelectedAgent({
|
|
265
|
+
agentId: node.id,
|
|
266
|
+
agentName: node.data.agentName,
|
|
267
|
+
initialInstructions: found?.instructions ?? "",
|
|
268
|
+
initialDescription: found?.description ?? "",
|
|
269
|
+
});
|
|
270
|
+
setIsPopupOpen(true);
|
|
271
|
+
}, [tempNetworks, isTemporaryNetwork, networkId]);
|
|
272
|
+
const handlePopupClose = useCallback(() => {
|
|
273
|
+
// If a save is in-flight, abort it immediately so the stream doesn't hang.
|
|
274
|
+
saveAbortControllerRef.current?.abort();
|
|
275
|
+
saveAbortControllerRef.current = null;
|
|
276
|
+
setIsPopupOpen(false);
|
|
277
|
+
setIsSavingAgent(false);
|
|
278
|
+
}, []);
|
|
279
|
+
/** Applies the networks returned by the designer: upserts them and triggers navigation if needed. */
|
|
280
|
+
const applyNetworkSaveResult = useCallback((agentName, newNetworksFromSave, currentAgentNetworkName) => {
|
|
281
|
+
if (newNetworksFromSave.length === 0) {
|
|
282
|
+
sendNotification(NotificationType.error, `Failed to update agent "${agentName}".`, "The network designer did not return a reservation. Please try again.");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const replacement = newNetworksFromSave.find((n) => n.agentNetworkName === currentAgentNetworkName);
|
|
286
|
+
if (replacement) {
|
|
287
|
+
useTempNetworksStore.getState().upsertTempNetworks(newNetworksFromSave);
|
|
288
|
+
if (networkId && onNetworkReplaced) {
|
|
289
|
+
useAgentChatHistoryStore.getState().copyHistory(networkId, replacement.agentInfo.agent_name);
|
|
290
|
+
onNetworkReplaced(networkId, replacement.agentInfo.agent_name);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
// Reservations came back but none matched the current network — surface this to the user.
|
|
295
|
+
sendNotification(NotificationType.error, `Failed to update agent "${agentName}".`, "A reservation was returned but did not match the current network. Please try again.");
|
|
296
|
+
}
|
|
297
|
+
}, [networkId, onNetworkReplaced]);
|
|
298
|
+
const handlePopupSave = useCallback(async (agentName, instructionsText, descriptionText) => {
|
|
299
|
+
if (!selectedAgent)
|
|
300
|
+
return;
|
|
301
|
+
// Find the temp network entry for the currently selected network.
|
|
302
|
+
const currentTempNetwork = networkId
|
|
303
|
+
? tempNetworks.find((n) => n.agentInfo.agent_name === networkId)
|
|
304
|
+
: undefined;
|
|
305
|
+
// Produce a new array with the saved agent's fields updated; all other entries pass through unchanged.
|
|
306
|
+
const currentDefinitions = currentTempNetwork?.agentNetworkDefinition ?? [];
|
|
307
|
+
const updated = currentDefinitions.map((entry) => entry.origin === selectedAgent.agentId
|
|
308
|
+
? { ...entry, instructions: instructionsText, description: descriptionText }
|
|
309
|
+
: entry);
|
|
310
|
+
if (networkId) {
|
|
311
|
+
updateTempNetworkDefinition(networkId, updated);
|
|
312
|
+
}
|
|
313
|
+
// POST the updated definition to the Agent Network Designer and wait for the response.
|
|
314
|
+
// The backend is immutable for temporary networks, so a new reservation will always be created.
|
|
315
|
+
// We need to capture it and replace the old network in the store.
|
|
316
|
+
if (!neuroSanURL || !currentUser || updated.length === 0) {
|
|
317
|
+
setIsPopupOpen(false);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
setIsSavingAgent(true);
|
|
321
|
+
const saveController = new AbortController();
|
|
322
|
+
saveAbortControllerRef.current = saveController;
|
|
323
|
+
// 60-second hard timeout — belt-and-suspenders in case the server never closes the stream.
|
|
324
|
+
const saveTimeoutId = setTimeout(() => saveController.abort(new DOMException("Save timed out", "TimeoutError")), 60_000);
|
|
325
|
+
try {
|
|
326
|
+
const newNetworksFromSave = await streamNetworkDesignerUpdate(neuroSanURL, saveController.signal, agentName, updated, currentTempNetwork?.agentNetworkName, currentUser);
|
|
327
|
+
applyNetworkSaveResult(agentName, newNetworksFromSave, currentTempNetwork?.agentNetworkName);
|
|
328
|
+
}
|
|
329
|
+
catch (e) {
|
|
330
|
+
const isAbort = e instanceof DOMException && e.name === "AbortError";
|
|
331
|
+
const isTimeout = e instanceof DOMException && e.name === "TimeoutError";
|
|
332
|
+
if (!isAbort) {
|
|
333
|
+
console.error("Failed to submit agent network update:", e);
|
|
334
|
+
const detail = isTimeout
|
|
335
|
+
? "The request timed out waiting for the server. Please try again."
|
|
336
|
+
: String(e);
|
|
337
|
+
sendNotification(NotificationType.error, `Failed to update agent "${agentName}".`, detail);
|
|
338
|
+
}
|
|
339
|
+
// isAbort: user dismissed the dialog — no toast needed.
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
clearTimeout(saveTimeoutId);
|
|
343
|
+
saveAbortControllerRef.current = null;
|
|
344
|
+
setIsSavingAgent(false);
|
|
345
|
+
setIsPopupOpen(false);
|
|
346
|
+
}
|
|
347
|
+
}, [
|
|
348
|
+
selectedAgent,
|
|
349
|
+
tempNetworks,
|
|
350
|
+
updateTempNetworkDefinition,
|
|
351
|
+
neuroSanURL,
|
|
352
|
+
currentUser,
|
|
353
|
+
networkId,
|
|
354
|
+
applyNetworkSaveResult,
|
|
355
|
+
]);
|
|
184
356
|
const edges = layoutResult.edges;
|
|
185
357
|
// Make sure to extract only thought bubble edges for the overlay.
|
|
186
358
|
const thoughtBubbleEdgesForOverlay = useMemo(() => edges.filter((e) => e.type === "thoughtBubbleEdge"), [edges]);
|
|
187
359
|
useEffect(() => {
|
|
188
360
|
// Schedule a fitView after the layout is set to ensure the view is adjusted correctly
|
|
189
361
|
setTimeout(() => {
|
|
190
|
-
fitView();
|
|
362
|
+
void fitView();
|
|
191
363
|
}, 50);
|
|
192
|
-
}, [agentsInNetwork, layout]);
|
|
364
|
+
}, [agentsInNetwork, fitView, layout]);
|
|
193
365
|
const onNodesChange = useCallback((changes) => {
|
|
194
|
-
setNodes((
|
|
195
|
-
// For now, we only allow dragging, no updates
|
|
196
|
-
changes
|
|
197
|
-
|
|
366
|
+
setNodes((currentNodes) => applyNodeChanges(
|
|
367
|
+
// For now, we only allow dragging, no updates. In agent network designer mode, doesn't make sense
|
|
368
|
+
// to allow position changes since the user isn't actually manipulating a real network
|
|
369
|
+
changes.filter((c) => c.type === "position" && !isAgentNetworkDesignerMode), currentNodes));
|
|
370
|
+
}, [isAgentNetworkDesignerMode]);
|
|
198
371
|
const transform = useStore((state) => state.transform);
|
|
199
372
|
// Why not just a "const"? See: https://reactflow.dev/learn/customization/custom-nodes
|
|
200
373
|
// "It’s important that the nodeTypes are memoized or defined outside the component. Otherwise, React creates
|
|
201
374
|
// a new object on every render which leads to performance issues and bugs."
|
|
202
375
|
const nodeTypes = useMemo(() => ({
|
|
203
376
|
agentNode: AgentNode,
|
|
204
|
-
}), [
|
|
377
|
+
}), []);
|
|
205
378
|
const edgeTypes = useMemo(() => ({
|
|
206
379
|
plasmaEdge: PlasmaEdge,
|
|
207
380
|
thoughtBubbleEdge: ThoughtBubbleEdge,
|
|
208
|
-
}), [
|
|
381
|
+
}), []);
|
|
209
382
|
// Figure out the maximum depth of the network
|
|
210
383
|
const maxDepth = useMemo(() => {
|
|
211
384
|
return nodes?.reduce((max, node) => Math.max(node.data.depth, max), 0) + 1;
|
|
@@ -311,5 +484,5 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
311
484
|
color: theme.palette.text.primary,
|
|
312
485
|
fill: theme.palette.text.primary,
|
|
313
486
|
},
|
|
314
|
-
}, children: [_jsx(ReactFlow, { id: `${id}-react-flow`, nodes: nodes, edges: edges, onNodesChange: onNodesChange, fitView: true, nodeTypes: nodeTypes, edgeTypes: edgeTypes, connectionMode: ConnectionMode.Loose, children: !isAwaitingLlm && (_jsxs(_Fragment, { children: [agentsInNetwork?.length ? getLegend() : null, _jsx(Background, { id: `${id}-background` }), getControls(), shouldShowRadialGuides ? getRadialGuides() : null] })) }), _jsx(ThoughtBubbleOverlay, { nodes: nodes, edges: thoughtBubbleEdgesForOverlay, showThoughtBubbles: showThoughtBubbles, isStreaming: isStreaming, onBubbleHoverChange: handleBubbleHoverChange })] }));
|
|
487
|
+
}, children: [_jsx(ReactFlow, { id: `${id}-react-flow`, nodes: nodes, edges: edges, onNodesChange: onNodesChange, onNodeClick: handleNodeClick, fitView: true, nodeTypes: nodeTypes, edgeTypes: edgeTypes, connectionMode: ConnectionMode.Loose, children: !isAwaitingLlm && (_jsxs(_Fragment, { children: [agentsInNetwork?.length && !isAgentNetworkDesignerMode ? getLegend() : null, _jsx(Background, { id: `${id}-background` }), !isAgentNetworkDesignerMode && getControls(), shouldShowRadialGuides ? getRadialGuides() : null] })) }), _jsx(ThoughtBubbleOverlay, { nodes: nodes, edges: thoughtBubbleEdgesForOverlay, showThoughtBubbles: showThoughtBubbles, isStreaming: isStreaming, onBubbleHoverChange: handleBubbleHoverChange }), selectedAgent && !isAwaitingLlm && (_jsx(AgentNodePopup, { agentName: selectedAgent.agentName, initialInstructions: selectedAgent.initialInstructions, initialDescription: selectedAgent.initialDescription, isOpen: isPopupOpen, isSaving: isSavingAgent, onClose: handlePopupClose, onSave: handlePopupSave }))] }));
|
|
315
488
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ChatMessage, ConnectivityInfo } from "../../generated/neuro-san/NeuroSanClient.js";
|
|
2
|
+
/**
|
|
3
|
+
* Extracts network progress information from a chat message, if present. Only messages of type AGENT_PROGRESS
|
|
4
|
+
* can have this information. When present, it should be under the key defined by AGENT_PROGRESS_CONNECTIVITY_KEY
|
|
5
|
+
* in the message structure.
|
|
6
|
+
* @param message The chat message to extract network progress from
|
|
7
|
+
* @returns An array of ConnectivityInfo objects if the message contains network progress information, or an empty
|
|
8
|
+
* array if not
|
|
9
|
+
*/
|
|
10
|
+
export declare const extractAgentNetworkDesignerProgress: (message: ChatMessage) => ConnectivityInfo[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AGENT_PROGRESS_CONNECTIVITY_KEY } from "./const.js";
|
|
2
|
+
import { ChatMessageType } from "../../generated/neuro-san/NeuroSanClient.js";
|
|
3
|
+
/**
|
|
4
|
+
* Extracts network progress information from a chat message, if present. Only messages of type AGENT_PROGRESS
|
|
5
|
+
* can have this information. When present, it should be under the key defined by AGENT_PROGRESS_CONNECTIVITY_KEY
|
|
6
|
+
* in the message structure.
|
|
7
|
+
* @param message The chat message to extract network progress from
|
|
8
|
+
* @returns An array of ConnectivityInfo objects if the message contains network progress information, or an empty
|
|
9
|
+
* array if not
|
|
10
|
+
*/
|
|
11
|
+
export const extractAgentNetworkDesignerProgress = (message) => {
|
|
12
|
+
const agentProgress = message?.structure?.[AGENT_PROGRESS_CONNECTIVITY_KEY];
|
|
13
|
+
if (message?.type === ChatMessageType.AGENT_PROGRESS && agentProgress) {
|
|
14
|
+
return agentProgress;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
// Not the type of message that would contain network progress information, or the key is not present
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -10,6 +10,7 @@ export interface AgentNodeProps extends Record<string, unknown> {
|
|
|
10
10
|
readonly getConversations: () => AgentConversation[] | null;
|
|
11
11
|
readonly isAwaitingLlm?: boolean;
|
|
12
12
|
readonly agentIconSuggestion?: string;
|
|
13
|
+
readonly isTemporaryNetwork?: boolean;
|
|
13
14
|
}
|
|
14
15
|
export declare const NODE_HEIGHT = 100;
|
|
15
16
|
export declare const NODE_WIDTH = 100;
|
|
@@ -25,6 +25,7 @@ import { keyframes, styled, useTheme } from "@mui/material/styles";
|
|
|
25
25
|
import Tooltip from "@mui/material/Tooltip";
|
|
26
26
|
import Typography from "@mui/material/Typography";
|
|
27
27
|
import { Handle, Position } from "@xyflow/react";
|
|
28
|
+
import { DISPLAY_AS_CODED_TOOL, DISPLAY_AS_EXTERNAL_AGENT, DISPLAY_AS_LANGCHAIN_TOOL, DISPLAY_AS_LLM_AGENT, isEditableAgent, } from "./const.js";
|
|
28
29
|
import { useSettingsStore } from "../../state/Settings.js";
|
|
29
30
|
import { usePalette } from "../../Theme/Palettes.js";
|
|
30
31
|
import { isLightColor } from "../../Theme/Theme.js";
|
|
@@ -81,7 +82,7 @@ export const AgentNode = (props) => {
|
|
|
81
82
|
const palette = usePalette();
|
|
82
83
|
// Unpack the node-specific data
|
|
83
84
|
const data = props.data;
|
|
84
|
-
const { agentCounts, agentName, depth, displayAs, getConversations, agentIconSuggestion, isAwaitingLlm } = data;
|
|
85
|
+
const { agentCounts, agentName, depth, displayAs, getConversations, agentIconSuggestion, isAwaitingLlm, isTemporaryNetwork, } = data;
|
|
85
86
|
// Determine if this is the Frontman node (depth 0)
|
|
86
87
|
const isFrontman = depth === 0;
|
|
87
88
|
// Determine max agent count for heatmap scaling
|
|
@@ -133,11 +134,13 @@ export const AgentNode = (props) => {
|
|
|
133
134
|
}
|
|
134
135
|
else {
|
|
135
136
|
switch (displayAs) {
|
|
136
|
-
case
|
|
137
|
+
case DISPLAY_AS_EXTERNAL_AGENT:
|
|
137
138
|
return (_jsx(TravelExploreIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
|
|
138
|
-
|
|
139
|
+
// This should be a supported type but we're not seeing it?
|
|
140
|
+
case DISPLAY_AS_LANGCHAIN_TOOL:
|
|
141
|
+
case DISPLAY_AS_CODED_TOOL:
|
|
139
142
|
return (_jsx(HandymanIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
|
|
140
|
-
case
|
|
143
|
+
case DISPLAY_AS_LLM_AGENT:
|
|
141
144
|
default:
|
|
142
145
|
return (_jsx(AutoAwesomeIcon, { id: id, sx: { fontSize: AGENT_ICON_SIZE } }));
|
|
143
146
|
}
|
|
@@ -150,9 +153,11 @@ export const AgentNode = (props) => {
|
|
|
150
153
|
const glowColor = isLightColor(theme.palette.background.default)
|
|
151
154
|
? theme.palette.common.black
|
|
152
155
|
: theme.palette.common.white;
|
|
156
|
+
const isClickableNode = isTemporaryNetwork && isEditableAgent(displayAs);
|
|
153
157
|
return (_jsxs(_Fragment, { children: [_jsxs(AnimatedNode, { id: agentId, "data-testid": agentId, glowColor: glowColor, isActive: isActiveAgent, sx: {
|
|
154
158
|
backgroundColor,
|
|
155
159
|
color,
|
|
160
|
+
cursor: isClickableNode ? "pointer" : "grab",
|
|
156
161
|
height: NODE_HEIGHT * (isFrontman ? 1.25 : 1.0),
|
|
157
162
|
width: NODE_WIDTH * (isFrontman ? 1.25 : 1.0),
|
|
158
163
|
zIndex: getZIndex(1, theme),
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
export interface AgentNodePopupProps {
|
|
3
|
+
/** The agent's display name — shown read-only in the dialog header area. */
|
|
4
|
+
readonly agentName: string;
|
|
5
|
+
/** Whether the dialog is open. */
|
|
6
|
+
readonly isOpen: boolean;
|
|
7
|
+
/** Called when the user closes or cancels the dialog without saving. */
|
|
8
|
+
readonly onClose: () => void;
|
|
9
|
+
/**
|
|
10
|
+
* Called when the user saves the edited fields.
|
|
11
|
+
* @param agentName The agent's name (unchanged).
|
|
12
|
+
* @param instructions The updated instructions text.
|
|
13
|
+
* @param description The updated description text.
|
|
14
|
+
*/
|
|
15
|
+
readonly onSave: (agentName: string, instructions: string, description: string) => void;
|
|
16
|
+
/** Initial instructions text shown in the editable field. Defaults to an empty string. */
|
|
17
|
+
readonly initialInstructions?: string;
|
|
18
|
+
/** Initial description text shown in the editable field. Defaults to an empty string. */
|
|
19
|
+
readonly initialDescription?: string;
|
|
20
|
+
/**
|
|
21
|
+
* When true the dialog is in a saving state: the Save button is disabled and shows a spinner.
|
|
22
|
+
* Defaults to false.
|
|
23
|
+
*/
|
|
24
|
+
readonly isSaving?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A popup dialog for viewing and editing an agent node's instructions and description.
|
|
28
|
+
*
|
|
29
|
+
* - Agent name is displayed read-only in the dialog header.
|
|
30
|
+
* - Both instructions and description are editable.
|
|
31
|
+
* - Saving is a no-op until the API endpoint is wired up; `onSave` receives the current values.
|
|
32
|
+
*/
|
|
33
|
+
export declare const AgentNodePopup: FC<AgentNodePopupProps>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
Copyright 2025 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import Box from "@mui/material/Box";
|
|
18
|
+
import Button from "@mui/material/Button";
|
|
19
|
+
import CircularProgress from "@mui/material/CircularProgress";
|
|
20
|
+
import LinearProgress from "@mui/material/LinearProgress";
|
|
21
|
+
import TextField from "@mui/material/TextField";
|
|
22
|
+
import { useEffect, useState } from "react";
|
|
23
|
+
import { ConfirmationModal } from "../Common/ConfirmationModal.js";
|
|
24
|
+
import { MUIDialog } from "../Common/MUIDialog.js";
|
|
25
|
+
// #endregion: Types
|
|
26
|
+
/**
|
|
27
|
+
* A popup dialog for viewing and editing an agent node's instructions and description.
|
|
28
|
+
*
|
|
29
|
+
* - Agent name is displayed read-only in the dialog header.
|
|
30
|
+
* - Both instructions and description are editable.
|
|
31
|
+
* - Saving is a no-op until the API endpoint is wired up; `onSave` receives the current values.
|
|
32
|
+
*/
|
|
33
|
+
export const AgentNodePopup = ({ agentName, isOpen, onClose, onSave, initialInstructions = "", initialDescription = "", isSaving = false, }) => {
|
|
34
|
+
const [instructionsText, setInstructionsText] = useState(initialInstructions);
|
|
35
|
+
const [descriptionText, setDescriptionText] = useState(initialDescription);
|
|
36
|
+
const isDirty = instructionsText !== initialInstructions || descriptionText !== initialDescription;
|
|
37
|
+
const [displayConfirmationModal, setDisplayConfirmationModal] = useState(false);
|
|
38
|
+
// Keep local fields in sync when the dialog opens or if initial values change while open.
|
|
39
|
+
// Guarding on isOpen prevents resetting the text during the close animation, which would cause a visible flash.
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (isOpen) {
|
|
42
|
+
setInstructionsText(initialInstructions);
|
|
43
|
+
setDescriptionText(initialDescription);
|
|
44
|
+
}
|
|
45
|
+
}, [initialInstructions, initialDescription, isOpen]);
|
|
46
|
+
const handleSave = () => {
|
|
47
|
+
onSave(agentName, instructionsText, descriptionText);
|
|
48
|
+
};
|
|
49
|
+
const handleClose = () => {
|
|
50
|
+
if (isSaving) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (isDirty) {
|
|
54
|
+
setDisplayConfirmationModal(true);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
setInstructionsText(initialInstructions);
|
|
58
|
+
setDescriptionText(initialDescription);
|
|
59
|
+
onClose();
|
|
60
|
+
};
|
|
61
|
+
const footer = (_jsx(Box, { sx: {
|
|
62
|
+
display: "flex",
|
|
63
|
+
alignItems: "flex-start",
|
|
64
|
+
justifyContent: "flex-end",
|
|
65
|
+
width: "100%",
|
|
66
|
+
gap: 1,
|
|
67
|
+
}, children: _jsxs(Box, { sx: { display: "flex", gap: 1 }, children: [_jsx(Button, { id: "agent-node-popup-cancel-btn", onClick: handleClose, variant: "outlined", size: "small", disabled: isSaving, children: "Cancel" }), _jsx(Button, { id: "agent-node-popup-save-btn", onClick: handleSave, variant: "contained", size: "small", disabled: isSaving || !isDirty, startIcon: isSaving ? (_jsx(CircularProgress, { size: 14, color: "inherit" })) : undefined, children: isSaving ? "Applying changes…" : "Save" })] }) }));
|
|
68
|
+
const getConfirmationModal = () => (_jsx(ConfirmationModal, { id: "agent-node-popup-unsaved-changes-modal", cancelBtnLabel: "Discard changes", closeable: false, content: _jsx("p", { children: "You have unsaved edits. Are you sure you want to discard your changes and close the dialog?" }), handleCancel: () => {
|
|
69
|
+
setDisplayConfirmationModal(false);
|
|
70
|
+
setInstructionsText(initialInstructions);
|
|
71
|
+
setDescriptionText(initialDescription);
|
|
72
|
+
onClose();
|
|
73
|
+
}, handleOk: handleSave, maskCloseable: false, okBtnLabel: "Save changes", title: "Unsaved Changes" }));
|
|
74
|
+
return (_jsxs(_Fragment, { children: [displayConfirmationModal && getConfirmationModal(), _jsxs(MUIDialog, { dialogSx: isSaving ? { "& *, & *::before, & *::after": { cursor: "wait !important" } } : undefined, footer: footer, id: "agent-node-popup", isOpen: isOpen, onClose: handleClose, paperProps: { minWidth: "480px", maxWidth: "600px", width: "100%" }, title: agentName, children: [isSaving && (_jsx(LinearProgress, { "aria-label": "Saving agent", sx: { mb: 2, borderRadius: 1 } })), _jsx(TextField, { disabled: isSaving, fullWidth: true, id: "agent-node-popup-description-field", label: "Description", multiline: true, onChange: (e) => setDescriptionText(e.target.value), onKeyDown: (e) => {
|
|
75
|
+
if (e.key !== "Escape")
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
}, placeholder: "Enter a short description of this agent\u2026", rows: 6, size: "small", sx: { "& .MuiInputBase-input": { fontSize: "0.85rem" } }, value: descriptionText }), _jsx(TextField, { autoFocus: true, disabled: isSaving, fullWidth: true, id: "agent-node-popup-instructions-field", label: "Instructions", multiline: true, onChange: (e) => setInstructionsText(e.target.value), onKeyDown: (e) => {
|
|
78
|
+
if (e.key !== "Escape")
|
|
79
|
+
e.stopPropagation();
|
|
80
|
+
}, placeholder: "Enter instructions for this agent\u2026", rows: 6, size: "small", sx: { "& .MuiInputBase-input": { fontSize: "0.85rem" }, marginTop: 2 }, value: instructionsText })] })] }));
|
|
81
|
+
};
|
|
@@ -25,12 +25,12 @@ export declare const getThoughtBubbleEdges: (thoughtBubbleEdges: Map<string, {
|
|
|
25
25
|
timestamp: number;
|
|
26
26
|
}>) => ThoughtBubbleEdgeShape[];
|
|
27
27
|
export declare const layoutRadial: (agentCounts: Map<string, number>, agentsInNetwork: ConnectivityInfo[], currentConversations: AgentConversation[] | null, // For plasma edges (live) and node highlighting
|
|
28
|
-
isAwaitingLlm: boolean, thoughtBubbleEdges: Map<string, {
|
|
28
|
+
isAwaitingLlm: boolean, isAgentNetworkDesignerMode: boolean, thoughtBubbleEdges: Map<string, {
|
|
29
29
|
edge: ThoughtBubbleEdgeShape;
|
|
30
30
|
timestamp: number;
|
|
31
|
-
}>, agentIconSuggestions?: AgentIconSuggestions) => LayoutResult;
|
|
31
|
+
}>, agentIconSuggestions?: AgentIconSuggestions, isTemporaryNetwork?: boolean) => LayoutResult;
|
|
32
32
|
export declare const layoutLinear: (agentCounts: Map<string, number>, agentsInNetwork: ConnectivityInfo[], currentConversations: AgentConversation[] | null, // For plasma edges (live) and node highlighting
|
|
33
|
-
isAwaitingLlm: boolean, thoughtBubbleEdges: Map<string, {
|
|
33
|
+
isAwaitingLlm: boolean, isAgentNetworkDesignerMode: boolean, thoughtBubbleEdges: Map<string, {
|
|
34
34
|
edge: ThoughtBubbleEdgeShape;
|
|
35
35
|
timestamp: number;
|
|
36
|
-
}>, agentIconSuggestions?: AgentIconSuggestions) => LayoutResult;
|
|
36
|
+
}>, agentIconSuggestions?: AgentIconSuggestions, isTemporaryNetwork?: boolean) => LayoutResult;
|
|
@@ -21,7 +21,7 @@ import { MarkerType } from "@xyflow/react";
|
|
|
21
21
|
import cloneDeep from "lodash-es/cloneDeep.js";
|
|
22
22
|
import { NODE_HEIGHT, NODE_WIDTH } from "./AgentNode.js";
|
|
23
23
|
import { BASE_RADIUS, DEFAULT_FRONTMAN_X_POS, DEFAULT_FRONTMAN_Y_POS, LEVEL_SPACING } from "./const.js";
|
|
24
|
-
import { cleanUpAgentName, KNOWN_MESSAGE_TYPES_FOR_PLASMA } from "../AgentChat/Utils.js";
|
|
24
|
+
import { cleanUpAgentName, KNOWN_MESSAGE_TYPES_FOR_PLASMA } from "../AgentChat/Common/Utils.js";
|
|
25
25
|
export const MAX_GLOBAL_THOUGHT_BUBBLES = 5;
|
|
26
26
|
export const addThoughtBubbleEdge = (thoughtBubbleEdges, conversationId, edge) => {
|
|
27
27
|
// Add with timestamp for age-based cleanup
|
|
@@ -100,7 +100,7 @@ const getEdgeProperties = (sourceId, targetId, sourceHandle, targetHandle, isAni
|
|
|
100
100
|
};
|
|
101
101
|
};
|
|
102
102
|
export const layoutRadial = (agentCounts, agentsInNetwork, currentConversations, // For plasma edges (live) and node highlighting
|
|
103
|
-
isAwaitingLlm, thoughtBubbleEdges, agentIconSuggestions = null) => {
|
|
103
|
+
isAwaitingLlm, isAgentNetworkDesignerMode, thoughtBubbleEdges, agentIconSuggestions = null, isTemporaryNetwork = false) => {
|
|
104
104
|
const nodesInNetwork = [];
|
|
105
105
|
const edgesInNetwork = [];
|
|
106
106
|
// Compute depth of each node using breadth-first traversal
|
|
@@ -170,8 +170,10 @@ isAwaitingLlm, thoughtBubbleEdges, agentIconSuggestions = null) => {
|
|
|
170
170
|
}
|
|
171
171
|
// Plasma edges based on currentConversations (live, cleared at network end)
|
|
172
172
|
const isEdgeAnimated = areInSameConversation(currentConversations, nodeId, graphNode.id);
|
|
173
|
-
// Add edge from parent to node
|
|
174
|
-
|
|
173
|
+
// Add edge from parent to node.
|
|
174
|
+
// We always show animated edges, all edges when in "idle mode" (not awaiting LLM),
|
|
175
|
+
// and all edges in the designer mode preview.
|
|
176
|
+
if (!isAwaitingLlm || isAgentNetworkDesignerMode || isEdgeAnimated) {
|
|
175
177
|
edgesInNetwork.push(getEdgeProperties(graphNode.id, nodeId, sourceHandle, targetHandle, isEdgeAnimated));
|
|
176
178
|
}
|
|
177
179
|
}
|
|
@@ -188,6 +190,7 @@ isAwaitingLlm, thoughtBubbleEdges, agentIconSuggestions = null) => {
|
|
|
188
190
|
getConversations: () => currentConversations,
|
|
189
191
|
isAwaitingLlm,
|
|
190
192
|
agentIconSuggestion: agentIconSuggestions?.[nodeId],
|
|
193
|
+
isTemporaryNetwork,
|
|
191
194
|
},
|
|
192
195
|
position: isFrontman ? { x: DEFAULT_FRONTMAN_X_POS, y: DEFAULT_FRONTMAN_Y_POS } : { x, y },
|
|
193
196
|
style: {
|
|
@@ -207,7 +210,7 @@ isAwaitingLlm, thoughtBubbleEdges, agentIconSuggestions = null) => {
|
|
|
207
210
|
return { nodes: nodesInNetwork, edges: edgesInNetwork };
|
|
208
211
|
};
|
|
209
212
|
export const layoutLinear = (agentCounts, agentsInNetwork, currentConversations, // For plasma edges (live) and node highlighting
|
|
210
|
-
isAwaitingLlm, thoughtBubbleEdges, agentIconSuggestions = null) => {
|
|
213
|
+
isAwaitingLlm, isAgentNetworkDesignerMode, thoughtBubbleEdges, agentIconSuggestions = null, isTemporaryNetwork = false) => {
|
|
211
214
|
const nodesInNetwork = [];
|
|
212
215
|
const edgesInNetwork = [];
|
|
213
216
|
// Do these calculations outside the loop for efficiency
|
|
@@ -229,6 +232,7 @@ isAwaitingLlm, thoughtBubbleEdges, agentIconSuggestions = null) => {
|
|
|
229
232
|
isAwaitingLlm,
|
|
230
233
|
depth: undefined, // Depth will be computed later,
|
|
231
234
|
agentIconSuggestion: agentIconSuggestions?.[nodeId],
|
|
235
|
+
isTemporaryNetwork,
|
|
232
236
|
},
|
|
233
237
|
position: isFrontman ? { x: DEFAULT_FRONTMAN_X_POS, y: DEFAULT_FRONTMAN_Y_POS } : { x: 0, y: 0 },
|
|
234
238
|
style: {
|
|
@@ -286,9 +290,9 @@ isAwaitingLlm, thoughtBubbleEdges, agentIconSuggestions = null) => {
|
|
|
286
290
|
depth: xPositions.indexOf(nodeWithPosition.x),
|
|
287
291
|
};
|
|
288
292
|
});
|
|
289
|
-
// If we're in "awaiting LLM" mode, we filter edges to only include those
|
|
290
|
-
// Use currentConversations (plasma edges are live and clear at network end)
|
|
291
|
-
const filteredEdges = isAwaitingLlm
|
|
293
|
+
// If we're in "awaiting LLM" mode, but not Agent Network Designer mode, we filter edges to only include those
|
|
294
|
+
// that are between conversation agents. Use currentConversations (plasma edges are live and clear at network end)
|
|
295
|
+
const filteredEdges = isAwaitingLlm && !isAgentNetworkDesignerMode
|
|
292
296
|
? edgesInNetwork.filter((edge) => areInSameConversation(currentConversations, edge.source, edge.target))
|
|
293
297
|
: edgesInNetwork;
|
|
294
298
|
// Add thought bubble edges from cache to avoid duplicates across layout recalculations
|
|
@@ -6,6 +6,7 @@ interface MultiAgentAcceleratorProps {
|
|
|
6
6
|
};
|
|
7
7
|
readonly backendNeuroSanApiUrl: string;
|
|
8
8
|
}
|
|
9
|
+
export declare const GRACE_PERIOD_MS: number;
|
|
9
10
|
/**
|
|
10
11
|
* Main Multi-Agent Accelerator component that contains the sidebar, agent flow, and chat components.
|
|
11
12
|
* @param backendNeuroSanApiUrl Initial URL of the backend Neuro-San API. User can change this in the UI.
|