@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.
- package/dist/Theme/Theme.js +3 -3
- package/dist/components/AgentChat/ChatCommon/ChatCommon.d.ts +11 -1
- package/dist/components/AgentChat/ChatCommon/ChatCommon.js +284 -285
- package/dist/components/AgentChat/ChatCommon/ChatHistory.d.ts +1 -7
- package/dist/components/AgentChat/ChatCommon/ChatHistory.js +33 -22
- package/dist/components/AgentChat/ChatCommon/ControlButtons.js +2 -2
- package/dist/components/AgentChat/ChatCommon/Conversation.d.ts +13 -0
- package/dist/components/AgentChat/ChatCommon/Conversation.js +80 -0
- package/dist/components/AgentChat/ChatCommon/ConversationTurn.d.ts +23 -0
- package/dist/components/AgentChat/ChatCommon/ConversationTurn.js +11 -0
- package/dist/components/AgentChat/ChatCommon/FormattedMarkdown.js +5 -3
- package/dist/components/AgentChat/ChatCommon/SampleQueries.d.ts +3 -0
- package/dist/components/AgentChat/ChatCommon/SampleQueries.js +6 -3
- package/dist/components/AgentChat/ChatCommon/Thinking.d.ts +12 -0
- package/dist/components/AgentChat/ChatCommon/Thinking.js +51 -0
- package/dist/components/AgentChat/Common/LlmChatButton.d.ts +2 -2
- package/dist/components/AgentChat/Common/Types.d.ts +6 -5
- package/dist/components/AgentChat/Common/Types.js +5 -0
- package/dist/components/AgentChat/Common/Utils.d.ts +1 -1
- package/dist/components/AgentChat/Common/Utils.js +14 -9
- package/dist/components/Common/AccordionLite.d.ts +14 -0
- package/dist/components/Common/AccordionLite.js +25 -0
- package/dist/components/Common/ConfirmationModal.d.ts +1 -0
- package/dist/components/Common/ConfirmationModal.js +1 -1
- package/dist/components/Common/CustomerLogo.js +1 -1
- package/dist/components/Common/MUIAlert.d.ts +1 -0
- package/dist/components/Common/MUIAlert.js +3 -4
- package/dist/components/Common/Navbar.d.ts +2 -1
- package/dist/components/Common/Navbar.js +8 -4
- package/dist/components/Common/notification.d.ts +1 -1
- package/dist/components/Common/notification.js +17 -12
- package/dist/components/MultiAgentAccelerator/AgentFlow.d.ts +8 -0
- package/dist/components/MultiAgentAccelerator/AgentFlow.js +282 -82
- package/dist/components/MultiAgentAccelerator/AgentNode.d.ts +3 -1
- package/dist/components/MultiAgentAccelerator/AgentNode.js +64 -28
- package/dist/components/MultiAgentAccelerator/AgentNodePopup.d.ts +1 -4
- package/dist/components/MultiAgentAccelerator/AgentNodePopup.js +4 -5
- package/dist/components/MultiAgentAccelerator/GraphLayouts.js +19 -9
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +2 -2
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +268 -60
- package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.js +28 -12
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.js +21 -5
- package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.d.ts +4 -3
- package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +8 -2
- package/dist/components/MultiAgentAccelerator/TemporaryNetworks.d.ts +19 -2
- package/dist/components/MultiAgentAccelerator/TemporaryNetworks.js +40 -5
- package/dist/components/MultiAgentAccelerator/ThoughtBubbleOverlay.js +27 -14
- package/dist/components/MultiAgentAccelerator/Tour/MainTourSteps.d.ts +7 -0
- package/dist/components/MultiAgentAccelerator/Tour/MainTourSteps.js +88 -0
- package/dist/components/MultiAgentAccelerator/const.d.ts +7 -10
- package/dist/components/MultiAgentAccelerator/const.js +9 -10
- package/dist/const.d.ts +5 -1
- package/dist/const.js +5 -2
- package/dist/controller/agent/Agent.d.ts +10 -0
- package/dist/controller/agent/Agent.js +17 -1
- package/dist/controller/llm/LlmChat.js +2 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/state/TemporaryNetworks.d.ts +5 -15
- package/dist/state/TemporaryNetworks.js +15 -34
- package/dist/state/Tour.d.ts +29 -0
- package/dist/state/Tour.js +22 -0
- package/dist/state/UserInfo.d.ts +2 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils/Authentication.js +12 -3
- package/dist/utils/File.d.ts +7 -0
- package/dist/utils/File.js +14 -3
- package/dist/utils/text.js +2 -2
- package/dist/utils/title.js +1 -1
- package/dist/utils/zIndexLayers.js +3 -0
- package/package.json +15 -11
- package/dist/components/AgentChat/ChatCommon/AgentConnectivity.d.ts +0 -14
- package/dist/components/AgentChat/ChatCommon/AgentConnectivity.js +0 -23
- package/dist/components/AgentChat/ChatCommon/Greetings.d.ts +0 -1
- package/dist/components/AgentChat/ChatCommon/Greetings.js +0 -38
- package/dist/components/AgentChat/ChatCommon/UserQueryDisplay.d.ts +0 -7
- package/dist/components/AgentChat/ChatCommon/UserQueryDisplay.js +0 -32
- package/dist/components/Common/LlmChatOptionsButton.d.ts +0 -6
- package/dist/components/Common/LlmChatOptionsButton.js +0 -31
|
@@ -15,11 +15,19 @@ See the License for the specific language governing permissions and
|
|
|
15
15
|
limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
import AdjustRoundedIcon from "@mui/icons-material/AdjustRounded";
|
|
18
|
-
import
|
|
18
|
+
import ChatBubbleOutlinedIcon from "@mui/icons-material/ChatBubbleOutlined";
|
|
19
|
+
import CloseIcon from "@mui/icons-material/Close";
|
|
20
|
+
import EditIcon from "@mui/icons-material/Edit";
|
|
19
21
|
import HubOutlinedIcon from "@mui/icons-material/HubOutlined";
|
|
20
22
|
import ScatterPlotOutlinedIcon from "@mui/icons-material/ScatterPlotOutlined";
|
|
23
|
+
import Backdrop from "@mui/material/Backdrop";
|
|
21
24
|
import Box from "@mui/material/Box";
|
|
22
|
-
import
|
|
25
|
+
import Button from "@mui/material/Button";
|
|
26
|
+
import CircularProgress from "@mui/material/CircularProgress";
|
|
27
|
+
import IconButton from "@mui/material/IconButton";
|
|
28
|
+
import Paper from "@mui/material/Paper";
|
|
29
|
+
import { alpha, useTheme } from "@mui/material/styles";
|
|
30
|
+
import TextField from "@mui/material/TextField";
|
|
23
31
|
import ToggleButton from "@mui/material/ToggleButton";
|
|
24
32
|
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
|
|
25
33
|
import Tooltip from "@mui/material/Tooltip";
|
|
@@ -28,10 +36,10 @@ import { applyNodeChanges, Background, ConnectionMode, ControlButton, Controls,
|
|
|
28
36
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
29
37
|
import { AgentNode, NODE_HEIGHT, NODE_WIDTH } from "./AgentNode.js";
|
|
30
38
|
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,
|
|
39
|
+
import { AGENT_NETWORK_DEFINITION_KEY, AGENT_NETWORK_DESIGNER_ID, AGENT_NETWORK_NAME_KEY, BASE_RADIUS, DEFAULT_FRONTMAN_X_POS, DEFAULT_FRONTMAN_Y_POS, LEVEL_SPACING, } from "./const.js";
|
|
32
40
|
import { addThoughtBubbleEdge, layoutLinear, layoutRadial } from "./GraphLayouts.js";
|
|
33
41
|
import { PlasmaEdge } from "./PlasmaEdge.js";
|
|
34
|
-
import { convertReservationsToNetworks, extractNetworkHocon, extractReservations } from "./TemporaryNetworks.js";
|
|
42
|
+
import { convertReservationsToNetworks, extractNetworkHocon, extractReservations, isEditableAgent, mergeNetworks, } from "./TemporaryNetworks.js";
|
|
35
43
|
import { ThoughtBubbleEdge } from "./ThoughtBubbleEdge.js";
|
|
36
44
|
import { ThoughtBubbleOverlay } from "./ThoughtBubbleOverlay.js";
|
|
37
45
|
import { sendChatQuery } from "../../controller/agent/Agent.js";
|
|
@@ -41,35 +49,25 @@ import { useTempNetworksStore } from "../../state/TemporaryNetworks.js";
|
|
|
41
49
|
import { usePalette } from "../../Theme/Palettes.js";
|
|
42
50
|
import { getZIndex } from "../../utils/zIndexLayers.js";
|
|
43
51
|
import { chatMessageFromChunk } from "../AgentChat/Common/Utils.js";
|
|
52
|
+
import { MUIAlert } from "../Common/MUIAlert.js";
|
|
44
53
|
import { NotificationType, sendNotification } from "../Common/notification.js";
|
|
45
54
|
// #endregion: Types
|
|
46
55
|
// #region: Constants
|
|
47
56
|
// Timeout for thought bubbles is set to 10 seconds
|
|
48
57
|
const THOUGHT_BUBBLE_TIMEOUT_MS = 10_000;
|
|
58
|
+
// How long the dock'sstatus banner stays visible before auto-dismissing. Error banners persist until dismissed.
|
|
59
|
+
// Exported for tests.
|
|
60
|
+
export const DOCK_BANNER_AUTO_DISMISS_MS = 5_000;
|
|
49
61
|
// #endregion: Constants
|
|
62
|
+
const DOCK_PROMPT_PLACEHOLDER = "Describe a change to the network";
|
|
50
63
|
// #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
64
|
/**
|
|
65
|
-
* Streams the Agent Network Designer endpoint with
|
|
66
|
-
*
|
|
65
|
+
* Streams the Agent Network Designer endpoint with a natural-language prompt and the current
|
|
66
|
+
* network definition, collecting any returned reservations.
|
|
67
67
|
*/
|
|
68
|
-
const
|
|
69
|
-
|
|
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) => {
|
|
68
|
+
const streamNetworkDesignerPrompt = async (neuroSanURL, signal, userPrompt, currentDefinition, agentNetworkName, currentUser) => {
|
|
69
|
+
let newNetworks = [];
|
|
70
|
+
await sendChatQuery(neuroSanURL, signal, userPrompt, AGENT_NETWORK_DESIGNER_ID, (chunk) => {
|
|
73
71
|
const chatMessage = chatMessageFromChunk(chunk);
|
|
74
72
|
if (!chatMessage)
|
|
75
73
|
return;
|
|
@@ -77,26 +75,19 @@ const streamNetworkDesignerUpdate = async (neuroSanURL, signal, agentName, updat
|
|
|
77
75
|
if (reservations.length === 0)
|
|
78
76
|
return;
|
|
79
77
|
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
78
|
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
79
|
const networkName = agentNetworkName ?? agentNetworkNameFromMessage;
|
|
87
|
-
const
|
|
88
|
-
|
|
80
|
+
const definitionFromMessage = chatMessage.sly_data?.[AGENT_NETWORK_DEFINITION_KEY];
|
|
81
|
+
const converted = convertReservationsToNetworks(reservations, networkHocon, definitionFromMessage ?? currentDefinition, networkName);
|
|
82
|
+
newNetworks = mergeNetworks(newNetworks, converted);
|
|
89
83
|
}, null, {
|
|
90
|
-
[AGENT_NETWORK_DEFINITION_KEY]:
|
|
91
|
-
// Use the backend's canonical name, not the local UUID-based key.
|
|
84
|
+
[AGENT_NETWORK_DEFINITION_KEY]: currentDefinition,
|
|
92
85
|
...(agentNetworkName ? { [AGENT_NETWORK_NAME_KEY]: agentNetworkName } : {}),
|
|
93
|
-
// skip_designer prevents the backend from using a reasoning model for edits
|
|
94
|
-
skip_designer: true,
|
|
95
86
|
}, currentUser, StreamingUnit.Line);
|
|
96
87
|
return newNetworks;
|
|
97
88
|
};
|
|
98
89
|
// #endregion: Helpers
|
|
99
|
-
export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork, currentConversations, currentUser, id, isAgentNetworkDesignerMode, isAwaitingLlm, isStreaming, isSelectedNetworkTemporary: isTemporaryNetwork, networkId, neuroSanURL, onNetworkReplaced, thoughtBubbleEdges, setThoughtBubbleEdges, }) => {
|
|
90
|
+
export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork, currentConversations, currentUser, id, isAgentNetworkDesignerMode, isAwaitingLlm, isEditMode, isStreaming, isSelectedNetworkTemporary: isTemporaryNetwork, networkDisplayName, networkId, neuroSanURL, onEnterEditMode, onNetworkReplaced, onExitEditMode, onSaveAgent, thoughtBubbleEdges, setThoughtBubbleEdges, }) => {
|
|
100
91
|
const theme = useTheme();
|
|
101
92
|
const { fitView } = useReactFlow();
|
|
102
93
|
const handleResize = useCallback(() => {
|
|
@@ -146,7 +137,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
146
137
|
let edgesMap = null;
|
|
147
138
|
for (const conv of currentConversations) {
|
|
148
139
|
const convText = conv.text?.trim();
|
|
149
|
-
const agentList =
|
|
140
|
+
const agentList = [...conv.agents];
|
|
150
141
|
if (convText &&
|
|
151
142
|
agentList.length >= 2 &&
|
|
152
143
|
!processedText.has(convText) &&
|
|
@@ -197,7 +188,8 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
197
188
|
return () => clearInterval(cleanupInterval);
|
|
198
189
|
}, [setThoughtBubbleEdges]); // mount/unmount only
|
|
199
190
|
// Shadow color for icon
|
|
200
|
-
const
|
|
191
|
+
const isDarkMode = theme.palette.mode === "dark";
|
|
192
|
+
const foregroundColor = isDarkMode ? theme.palette.common.white : theme.palette.common.black;
|
|
201
193
|
const isHeatmap = coloringOption === "heatmap";
|
|
202
194
|
const palette = usePalette();
|
|
203
195
|
// Merge agents from active thought bubbles with agentsInNetwork for layout
|
|
@@ -213,7 +205,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
213
205
|
const mergedAgentsInNetwork = useMemo(() => {
|
|
214
206
|
// Add any missing agents from bubbles as minimal ConnectivityInfo
|
|
215
207
|
const existingIds = new Set(agentsInNetwork.map((a) => a.origin));
|
|
216
|
-
const missing =
|
|
208
|
+
const missing = [...bubbleAgentIds].filter((bubbleAgentId) => !existingIds.has(bubbleAgentId));
|
|
217
209
|
const minimalAgents = missing.map((missingId) => ({
|
|
218
210
|
origin: missingId,
|
|
219
211
|
tools: [],
|
|
@@ -249,6 +241,49 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
249
241
|
const [isSavingAgent, setIsSavingAgent] = useState(false);
|
|
250
242
|
// AbortController for the in-flight save request — stored in a ref so handlePopupClose can cancel it.
|
|
251
243
|
const saveAbortControllerRef = useRef(null);
|
|
244
|
+
// Dock (edit-mode prompt bar) state
|
|
245
|
+
const [dockPrompt, setDockPrompt] = useState("");
|
|
246
|
+
const [isDockStreaming, setIsDockStreaming] = useState(false);
|
|
247
|
+
const dockAbortControllerRef = useRef(null);
|
|
248
|
+
// Stop-confirm overlay state: null = not shown, "confirming" = abort dialog open.
|
|
249
|
+
const [stopState, setStopState] = useState(null);
|
|
250
|
+
// Inline status banner shown above the dock header after an apply succeeds, is cancelled, or fails.
|
|
251
|
+
const [dockBanner, setDockBanner] = useState(null);
|
|
252
|
+
const bannerTimeoutRef = useRef(null);
|
|
253
|
+
// Clear the banner auto-dismiss timer on unmount.
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
return () => {
|
|
256
|
+
clearTimeout(bannerTimeoutRef.current);
|
|
257
|
+
};
|
|
258
|
+
}, []);
|
|
259
|
+
const handleDismissBanner = useCallback(() => {
|
|
260
|
+
clearTimeout(bannerTimeoutRef.current);
|
|
261
|
+
setDockBanner(null);
|
|
262
|
+
}, []);
|
|
263
|
+
// Show a dock banner. Success/cancel banners auto-dismiss; error banners persist until dismissed.
|
|
264
|
+
const showDockBanner = useCallback((banner) => {
|
|
265
|
+
clearTimeout(bannerTimeoutRef.current);
|
|
266
|
+
setDockBanner(banner);
|
|
267
|
+
if (banner.severity !== "error") {
|
|
268
|
+
bannerTimeoutRef.current = setTimeout(() => setDockBanner(null), DOCK_BANNER_AUTO_DISMISS_MS);
|
|
269
|
+
}
|
|
270
|
+
}, []);
|
|
271
|
+
const handleStopClick = useCallback(() => {
|
|
272
|
+
setStopState("confirming");
|
|
273
|
+
}, []);
|
|
274
|
+
const handleKeepApplying = useCallback(() => {
|
|
275
|
+
setStopState(null);
|
|
276
|
+
}, []);
|
|
277
|
+
const handleStopAndDiscard = useCallback(() => {
|
|
278
|
+
dockAbortControllerRef.current?.abort();
|
|
279
|
+
dockAbortControllerRef.current = null;
|
|
280
|
+
setStopState(null);
|
|
281
|
+
showDockBanner({
|
|
282
|
+
severity: "info",
|
|
283
|
+
title: "Applying cancelled.",
|
|
284
|
+
detail: "Nothing was changed. Your prompt is restored below.",
|
|
285
|
+
});
|
|
286
|
+
}, [showDockBanner]);
|
|
252
287
|
const handleNodeClick = useCallback((_event, node) => {
|
|
253
288
|
// Popup is only available for temporary networks.
|
|
254
289
|
if (!isTemporaryNetwork)
|
|
@@ -276,11 +311,18 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
276
311
|
setIsPopupOpen(false);
|
|
277
312
|
setIsSavingAgent(false);
|
|
278
313
|
}, []);
|
|
279
|
-
/**
|
|
280
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Applies the networks returned by the designer: upserts them and triggers navigation if needed.
|
|
316
|
+
* Returns true when a matching reservation was applied, false (and surfaces an error banner) otherwise.
|
|
317
|
+
*/
|
|
318
|
+
const applyNetworkSaveResult = useCallback((newNetworksFromSave, currentAgentNetworkName) => {
|
|
281
319
|
if (newNetworksFromSave.length === 0) {
|
|
282
|
-
|
|
283
|
-
|
|
320
|
+
showDockBanner({
|
|
321
|
+
severity: "error",
|
|
322
|
+
title: "Failed to apply network change.",
|
|
323
|
+
detail: "The network designer did not return a reservation. Please try again.",
|
|
324
|
+
});
|
|
325
|
+
return false;
|
|
284
326
|
}
|
|
285
327
|
const replacement = newNetworksFromSave.find((n) => n.agentNetworkName === currentAgentNetworkName);
|
|
286
328
|
if (replacement) {
|
|
@@ -289,12 +331,70 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
289
331
|
useAgentChatHistoryStore.getState().copyHistory(networkId, replacement.agentInfo.agent_name);
|
|
290
332
|
onNetworkReplaced(networkId, replacement.agentInfo.agent_name);
|
|
291
333
|
}
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
// Reservations came back but none matched the current network — surface this in the dock banner.
|
|
337
|
+
showDockBanner({
|
|
338
|
+
severity: "error",
|
|
339
|
+
title: "Failed to apply network change.",
|
|
340
|
+
detail: "A reservation was returned but did not match the current network. Please try again.",
|
|
341
|
+
});
|
|
342
|
+
return false;
|
|
343
|
+
}, [networkId, onNetworkReplaced, showDockBanner]);
|
|
344
|
+
const handleDockApply = useCallback(async () => {
|
|
345
|
+
if (!dockPrompt.trim() || !neuroSanURL || !currentUser)
|
|
346
|
+
return;
|
|
347
|
+
const currentTempNetwork = networkId
|
|
348
|
+
? tempNetworks.find((n) => n.agentInfo.agent_name === networkId)
|
|
349
|
+
: undefined;
|
|
350
|
+
const currentDefinition = currentTempNetwork?.agentNetworkDefinition ?? [];
|
|
351
|
+
setIsDockStreaming(true);
|
|
352
|
+
const controller = new AbortController();
|
|
353
|
+
dockAbortControllerRef.current = controller;
|
|
354
|
+
let hasTimedOut = false;
|
|
355
|
+
const timeoutId = setTimeout(() => {
|
|
356
|
+
hasTimedOut = true;
|
|
357
|
+
controller.abort();
|
|
358
|
+
}, 120_000); // 2 min timeout
|
|
359
|
+
try {
|
|
360
|
+
const newNetworks = await streamNetworkDesignerPrompt(neuroSanURL, controller.signal, dockPrompt, currentDefinition, currentTempNetwork?.agentNetworkName, currentUser);
|
|
361
|
+
const applied = applyNetworkSaveResult(newNetworks, currentTempNetwork?.agentNetworkName);
|
|
362
|
+
if (applied) {
|
|
363
|
+
setDockPrompt("");
|
|
364
|
+
showDockBanner({
|
|
365
|
+
severity: "success",
|
|
366
|
+
title: "Changes applied.",
|
|
367
|
+
detail: "Your network has been updated.",
|
|
368
|
+
});
|
|
369
|
+
}
|
|
292
370
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
371
|
+
catch (e) {
|
|
372
|
+
const isAbort = e instanceof DOMException && e.name === "AbortError";
|
|
373
|
+
if (!isAbort) {
|
|
374
|
+
showDockBanner({ severity: "error", title: "Failed to apply network change.", detail: String(e) });
|
|
375
|
+
}
|
|
376
|
+
else if (hasTimedOut) {
|
|
377
|
+
showDockBanner({
|
|
378
|
+
severity: "error",
|
|
379
|
+
title: "Failed to apply network change.",
|
|
380
|
+
detail: "The request timed out. Please try again.",
|
|
381
|
+
});
|
|
382
|
+
}
|
|
296
383
|
}
|
|
297
|
-
|
|
384
|
+
finally {
|
|
385
|
+
clearTimeout(timeoutId);
|
|
386
|
+
dockAbortControllerRef.current = null;
|
|
387
|
+
setIsDockStreaming(false);
|
|
388
|
+
}
|
|
389
|
+
}, [applyNetworkSaveResult, currentUser, dockPrompt, networkId, neuroSanURL, showDockBanner, tempNetworks]);
|
|
390
|
+
const handleExitEditMode = useCallback(() => {
|
|
391
|
+
if (isDockStreaming) {
|
|
392
|
+
dockAbortControllerRef.current?.abort();
|
|
393
|
+
dockAbortControllerRef.current = null;
|
|
394
|
+
setIsDockStreaming(false);
|
|
395
|
+
}
|
|
396
|
+
onExitEditMode?.();
|
|
397
|
+
}, [isDockStreaming, onExitEditMode]);
|
|
298
398
|
const handlePopupSave = useCallback(async (agentName, instructionsText, descriptionText) => {
|
|
299
399
|
if (!selectedAgent)
|
|
300
400
|
return;
|
|
@@ -310,33 +410,22 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
310
410
|
if (networkId) {
|
|
311
411
|
updateTempNetworkDefinition(networkId, updated);
|
|
312
412
|
}
|
|
313
|
-
|
|
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) {
|
|
413
|
+
if (!onSaveAgent) {
|
|
317
414
|
setIsPopupOpen(false);
|
|
318
415
|
return;
|
|
319
416
|
}
|
|
320
417
|
setIsSavingAgent(true);
|
|
321
418
|
const saveController = new AbortController();
|
|
322
419
|
saveAbortControllerRef.current = saveController;
|
|
323
|
-
|
|
324
|
-
|
|
420
|
+
const saveTimeoutId = setTimeout(() => saveController.abort(new DOMException("Save timed out", "TimeoutError")), 60_000 // 1 min timeout
|
|
421
|
+
);
|
|
325
422
|
try {
|
|
326
|
-
|
|
327
|
-
applyNetworkSaveResult(agentName, newNetworksFromSave, currentTempNetwork?.agentNetworkName);
|
|
423
|
+
await onSaveAgent(agentName, updated, currentTempNetwork?.agentNetworkName, saveController.signal);
|
|
328
424
|
}
|
|
329
425
|
catch (e) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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.
|
|
426
|
+
console.error(`Error saving network ${agentName}. See onSaveAgent implementation for details.`, e);
|
|
427
|
+
sendNotification(NotificationType.error, `Failed to save agent "${agentName}".`, String(e), undefined, null // show indefinitely until the user dismisses
|
|
428
|
+
);
|
|
340
429
|
}
|
|
341
430
|
finally {
|
|
342
431
|
clearTimeout(saveTimeoutId);
|
|
@@ -344,15 +433,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
344
433
|
setIsSavingAgent(false);
|
|
345
434
|
setIsPopupOpen(false);
|
|
346
435
|
}
|
|
347
|
-
}, [
|
|
348
|
-
selectedAgent,
|
|
349
|
-
tempNetworks,
|
|
350
|
-
updateTempNetworkDefinition,
|
|
351
|
-
neuroSanURL,
|
|
352
|
-
currentUser,
|
|
353
|
-
networkId,
|
|
354
|
-
applyNetworkSaveResult,
|
|
355
|
-
]);
|
|
436
|
+
}, [selectedAgent, tempNetworks, updateTempNetworkDefinition, networkId, onSaveAgent]);
|
|
356
437
|
const edges = layoutResult.edges;
|
|
357
438
|
// Make sure to extract only thought bubble edges for the overlay.
|
|
358
439
|
const thoughtBubbleEdgesForOverlay = useMemo(() => edges.filter((e) => e.type === "thoughtBubbleEdge"), [edges]);
|
|
@@ -399,11 +480,11 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
399
480
|
const length = isHeatmap ? palette.length : Math.min(maxDepth, palette.length);
|
|
400
481
|
return (_jsxs(Box, { id: `${id}-legend`, sx: {
|
|
401
482
|
position: "absolute",
|
|
402
|
-
top: "
|
|
483
|
+
top: "1.5rem",
|
|
403
484
|
right: "10px",
|
|
404
485
|
padding: "5px",
|
|
405
486
|
borderRadius: "5px",
|
|
406
|
-
boxShadow: `0 0 5px color-mix(in srgb, ${
|
|
487
|
+
boxShadow: `0 0 5px color-mix(in srgb, ${foregroundColor} 30%, transparent)`,
|
|
407
488
|
display: "flex",
|
|
408
489
|
alignItems: "center",
|
|
409
490
|
zIndex: getZIndex(2, theme),
|
|
@@ -444,7 +525,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
444
525
|
if (!isActive) {
|
|
445
526
|
return undefined;
|
|
446
527
|
}
|
|
447
|
-
return
|
|
528
|
+
return isDarkMode ? theme.palette.grey[800] : theme.palette.grey[200];
|
|
448
529
|
};
|
|
449
530
|
// Only show radial guides if radial layout is selected, radial guides are enabled, and it's not just Frontman
|
|
450
531
|
const shouldShowRadialGuides = enableRadialGuides && layout === "radial" && maxDepth > 1;
|
|
@@ -464,25 +545,144 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
|
|
|
464
545
|
backgroundColor: getControlButtonBackgroundColor(enableRadialGuides),
|
|
465
546
|
}, disabled: layout !== "radial", children: _jsx(AdjustRoundedIcon, { id: "radial-guides-icon" }) }) }) }), _jsx(Tooltip, { id: "thought-bubble-tooltip", title: `Toggle thought bubbles ${showThoughtBubbles ? "off" : "on"}`, placement: "right", children: _jsx("span", { id: "thought-bubble-span", children: _jsx(ControlButton, { id: "thought-bubble-button", onClick: () => setShowThoughtBubbles(!showThoughtBubbles), style: {
|
|
466
547
|
backgroundColor: getControlButtonBackgroundColor(showThoughtBubbles),
|
|
467
|
-
}, children: _jsx(
|
|
548
|
+
}, children: _jsx(ChatBubbleOutlinedIcon, { id: "thought-bubble-icon" }) }) }) })] }));
|
|
549
|
+
};
|
|
550
|
+
const titleBackgroundColor = alpha(theme.palette.background.paper, 0.75);
|
|
551
|
+
const getTitle = () => {
|
|
552
|
+
return (networkDisplayName && (_jsxs(Box, { id: `${id}-network-title-bar`, sx: {
|
|
553
|
+
alignItems: "center",
|
|
554
|
+
display: "flex",
|
|
555
|
+
gap: 1,
|
|
556
|
+
left: "50%",
|
|
557
|
+
pointerEvents: "none",
|
|
558
|
+
position: "absolute",
|
|
559
|
+
top: 0,
|
|
560
|
+
transform: "translateX(-50%)",
|
|
561
|
+
zIndex: getZIndex(2, theme),
|
|
562
|
+
}, children: [_jsx(Tooltip, { title: networkDisplayName, placement: "top", children: _jsx(Box, { sx: { pointerEvents: "auto" }, children: _jsx(Typography, { id: `${id}-network-title`, variant: "subtitle1", sx: {
|
|
563
|
+
backdropFilter: "blur(6px)",
|
|
564
|
+
backgroundColor: titleBackgroundColor,
|
|
565
|
+
border: `1px solid ${alpha(theme.palette.divider, 0.6)}`,
|
|
566
|
+
borderRadius: 2,
|
|
567
|
+
boxShadow: theme.palette.mode === "dark"
|
|
568
|
+
? `0 6px 20px ${alpha(theme.palette.common.black, 0.35)}`
|
|
569
|
+
: `0 6px 16px ${alpha(theme.palette.common.black, 0.12)}`,
|
|
570
|
+
color: theme.palette.getContrastText(alpha(titleBackgroundColor, 0.65)),
|
|
571
|
+
fontWeight: 600,
|
|
572
|
+
letterSpacing: "0.01em",
|
|
573
|
+
lineHeight: 1.35,
|
|
574
|
+
maxWidth: 400,
|
|
575
|
+
overflow: "hidden",
|
|
576
|
+
px: 2,
|
|
577
|
+
py: 0.45,
|
|
578
|
+
textOverflow: "ellipsis",
|
|
579
|
+
whiteSpace: "nowrap",
|
|
580
|
+
}, children: networkDisplayName }) }) }), isTemporaryNetwork && !isEditMode && !isAwaitingLlm && onEnterEditMode && (_jsx(Button, { id: `${id}-enter-edit-mode-btn`, variant: "contained", size: "small", onClick: onEnterEditMode, startIcon: _jsx(EditIcon, {}), sx: {
|
|
581
|
+
pointerEvents: "auto",
|
|
582
|
+
"&:hover": { backgroundColor: theme.palette.primary.main },
|
|
583
|
+
}, children: "Edit" }))] })));
|
|
468
584
|
};
|
|
469
585
|
return (_jsxs(Box, { id: `${id}-outer-box`, sx: {
|
|
586
|
+
display: "flex",
|
|
587
|
+
flexDirection: "column",
|
|
588
|
+
position: "relative",
|
|
470
589
|
height: "100%",
|
|
471
590
|
width: "100%",
|
|
472
591
|
backgroundColor: theme.palette.background.default,
|
|
473
592
|
"& .react-flow__node": {
|
|
474
|
-
border:
|
|
593
|
+
border: "1px solid divider",
|
|
475
594
|
},
|
|
476
595
|
"& .react-flow__panel": {
|
|
477
596
|
backgroundColor: theme.palette.background.paper,
|
|
478
|
-
border:
|
|
597
|
+
border: "1px solid divider",
|
|
479
598
|
color: theme.palette.text.primary,
|
|
480
599
|
},
|
|
481
600
|
"& .react-flow__controls-button": {
|
|
482
601
|
backgroundColor: theme.palette.background.paper,
|
|
483
|
-
borderBottom:
|
|
602
|
+
borderBottom: "1px solid divider",
|
|
484
603
|
color: theme.palette.text.primary,
|
|
485
604
|
fill: theme.palette.text.primary,
|
|
486
605
|
},
|
|
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 }),
|
|
606
|
+
}, children: [_jsxs(Box, { id: `${id}-react-flow-wrapper`, sx: { position: "relative", flex: 1, minHeight: 0 }, children: [networkDisplayName ? _jsx(Box, { sx: { marginBottom: "1rem" }, children: getTitle() }) : null, _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 && !isEditMode ? getLegend() : null, _jsx(Background, { id: `${id}-background` }), !isAgentNetworkDesignerMode && !isEditMode && getControls(), shouldShowRadialGuides ? getRadialGuides() : null] })) }), _jsx(ThoughtBubbleOverlay, { nodes: nodes, edges: thoughtBubbleEdgesForOverlay, showThoughtBubbles: showThoughtBubbles, isStreaming: isStreaming, onBubbleHoverChange: handleBubbleHoverChange })] }), isEditMode && isTemporaryNetwork && !isAwaitingLlm && (_jsxs(Box, { sx: {
|
|
607
|
+
borderTop: `2px solid ${theme.palette.primary.main}`,
|
|
608
|
+
backgroundColor: theme.palette.background.paper,
|
|
609
|
+
flexShrink: 0,
|
|
610
|
+
}, children: [dockBanner && (_jsx(MUIAlert, { closeable: true, id: `${id}-dock-banner`, onClose: handleDismissBanner, severity: dockBanner.severity, sx: {
|
|
611
|
+
borderRadius: 0,
|
|
612
|
+
// Override MUIAlert's default 1rem bottom margin so the banner sits flush
|
|
613
|
+
// against the dock header below it.
|
|
614
|
+
marginBottom: 0,
|
|
615
|
+
py: 0,
|
|
616
|
+
// Match the dock header's right padding so the banner's close X lines up
|
|
617
|
+
// vertically with the header's close X below it.
|
|
618
|
+
paddingRight: 0.5,
|
|
619
|
+
alignItems: "center",
|
|
620
|
+
// Frost the banner like the dock header so the graph doesn't show through the
|
|
621
|
+
// app's translucent paper background; keep a tinted, mostly-opaque severity wash.
|
|
622
|
+
backdropFilter: "blur(8px)",
|
|
623
|
+
backgroundColor: alpha(theme.palette[dockBanner.severity].main, isDarkMode ? 0.28 : 0.16),
|
|
624
|
+
"& .MuiAlert-action": {
|
|
625
|
+
alignItems: "center",
|
|
626
|
+
marginRight: 0,
|
|
627
|
+
paddingTop: 0,
|
|
628
|
+
},
|
|
629
|
+
}, children: _jsxs(Typography, { variant: "caption", component: "span", children: [_jsx("strong", { children: dockBanner.title }), ` ${dockBanner.detail}`] }) })), _jsxs(Box, { sx: {
|
|
630
|
+
backdropFilter: "blur(6px)",
|
|
631
|
+
borderBottom: "1px solid divider",
|
|
632
|
+
display: "flex",
|
|
633
|
+
alignItems: "center",
|
|
634
|
+
justifyContent: "space-between",
|
|
635
|
+
paddingLeft: 1.25,
|
|
636
|
+
paddingRight: 0.25,
|
|
637
|
+
}, children: [_jsx(Typography, { variant: "overline", sx: { fontWeight: "bold", letterSpacing: 1, lineHeight: 1.8 }, children: "Network Editor" }), _jsx(IconButton, { size: "small", "aria-label": "close edit mode", onClick: handleExitEditMode, children: _jsx(CloseIcon, { fontSize: "small" }) })] }), _jsxs(Box, { sx: {
|
|
638
|
+
display: "flex",
|
|
639
|
+
gap: 1,
|
|
640
|
+
px: 1,
|
|
641
|
+
py: 0.5,
|
|
642
|
+
alignItems: "center",
|
|
643
|
+
}, children: [_jsx(TextField, { fullWidth: true, placeholder: DOCK_PROMPT_PLACEHOLDER, variant: "outlined", size: "small", value: dockPrompt, onChange: (e) => setDockPrompt(e.target.value), onKeyDown: (e) => {
|
|
644
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
645
|
+
e.preventDefault();
|
|
646
|
+
void handleDockApply();
|
|
647
|
+
}
|
|
648
|
+
}, disabled: isDockStreaming, slotProps: { htmlInput: { style: { fontSize: "0.75rem" } } } }), _jsx(Button, { variant: "contained", onClick: () => void handleDockApply(), disabled: isDockStreaming || !dockPrompt.trim(), sx: {
|
|
649
|
+
fontSize: 16,
|
|
650
|
+
marginBottom: "1px",
|
|
651
|
+
marginRight: 0,
|
|
652
|
+
minWidth: 110,
|
|
653
|
+
paddingTop: 0.3,
|
|
654
|
+
paddingBottom: 0.3,
|
|
655
|
+
whiteSpace: "nowrap",
|
|
656
|
+
}, startIcon: isDockStreaming ? (_jsx(CircularProgress, { size: 16, color: "inherit" })) : undefined, children: isDockStreaming ? "Applying..." : "Apply" })] })] })), selectedAgent && !isAwaitingLlm && (_jsx(AgentNodePopup, { agentName: selectedAgent.agentName, initialInstructions: selectedAgent.initialInstructions, initialDescription: selectedAgent.initialDescription, isOpen: isPopupOpen, isSaving: isSavingAgent, onClose: handlePopupClose, onSave: handlePopupSave })), _jsx(Backdrop, { open: isDockStreaming, sx: { zIndex: (t) => t.zIndex.modal + 1 }, children: stopState === "confirming" ? (_jsxs(Paper, { elevation: 6, sx: {
|
|
657
|
+
display: "flex",
|
|
658
|
+
flexDirection: "column",
|
|
659
|
+
gap: 2,
|
|
660
|
+
px: 4,
|
|
661
|
+
py: 3,
|
|
662
|
+
borderRadius: 2,
|
|
663
|
+
maxWidth: 420,
|
|
664
|
+
}, children: [_jsx(Typography, { variant: "body1", sx: { fontWeight: "bold" }, children: "Abort changes?" }), _jsx(Typography, { variant: "body2", color: "text.secondary", children: "The in-progress update will be cancelled and discarded. Your network will not be modified." }), _jsxs(Box, { sx: {
|
|
665
|
+
display: "flex",
|
|
666
|
+
gap: 1.5,
|
|
667
|
+
justifyContent: "flex-end",
|
|
668
|
+
}, children: [_jsx(Button, { variant: "outlined", onClick: handleKeepApplying, children: "Keep applying" }), _jsx(Button, { variant: "contained", color: "error", startIcon: _jsx("span", { style: { fontSize: "0.7rem" }, children: "\u25A0" }), onClick: handleStopAndDiscard, children: "Stop & discard" })] })] })) : (_jsxs(Paper, { elevation: 6, sx: {
|
|
669
|
+
display: "flex",
|
|
670
|
+
alignItems: "center",
|
|
671
|
+
gap: 2,
|
|
672
|
+
px: 4,
|
|
673
|
+
py: 2.5,
|
|
674
|
+
borderRadius: 2,
|
|
675
|
+
maxWidth: 480,
|
|
676
|
+
}, children: [_jsx(CircularProgress, { size: 24 }), _jsxs(Box, { sx: { flex: 1 }, children: [_jsx(Typography, { variant: "body1", sx: { fontWeight: "bold" }, children: "Applying changes to network" }), dockPrompt && (_jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mt: 0.25 }, children: dockPrompt }))] }), _jsx(Button, { variant: "outlined", size: "small", startIcon: _jsx("span", { style: { fontSize: "0.65rem" }, children: "\u25A0" }), onClick: handleStopClick, sx: {
|
|
677
|
+
whiteSpace: "nowrap",
|
|
678
|
+
flexShrink: 0,
|
|
679
|
+
color: theme.palette.common.white,
|
|
680
|
+
borderColor: theme.palette.common.white,
|
|
681
|
+
fontWeight: "bold",
|
|
682
|
+
"&:hover": {
|
|
683
|
+
borderColor: theme.palette.error.main,
|
|
684
|
+
color: theme.palette.error.main,
|
|
685
|
+
backgroundColor: alpha(theme.palette.error.main, 0.08),
|
|
686
|
+
},
|
|
687
|
+
}, children: "Stop" })] })) })] }));
|
|
488
688
|
};
|
|
@@ -9,11 +9,13 @@ export interface AgentNodeProps extends Record<string, unknown> {
|
|
|
9
9
|
readonly displayAs?: string;
|
|
10
10
|
readonly getConversations: () => AgentConversation[] | null;
|
|
11
11
|
readonly isAwaitingLlm?: boolean;
|
|
12
|
+
readonly isEditMode?: boolean;
|
|
12
13
|
readonly agentIconSuggestion?: string;
|
|
13
|
-
readonly
|
|
14
|
+
readonly isEditable?: boolean;
|
|
14
15
|
}
|
|
15
16
|
export declare const NODE_HEIGHT = 100;
|
|
16
17
|
export declare const NODE_WIDTH = 100;
|
|
18
|
+
export declare const FRONTMAN_SIZE_MULTIPLIER = 1.25;
|
|
17
19
|
/**
|
|
18
20
|
* A node representing an agent in the network for use in react-flow.
|
|
19
21
|
* @param props See AgentNodeProps
|