@cognizant-ai-lab/ui-common 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/components/AgentChat/ChatCommon/ChatCommon.d.ts +16 -6
  2. package/dist/components/AgentChat/ChatCommon/ChatCommon.js +250 -166
  3. package/dist/components/AgentChat/ChatCommon/ChatHistory.d.ts +1 -7
  4. package/dist/components/AgentChat/ChatCommon/ChatHistory.js +33 -22
  5. package/dist/components/AgentChat/ChatCommon/Conversation.d.ts +3 -5
  6. package/dist/components/AgentChat/ChatCommon/Conversation.js +35 -57
  7. package/dist/components/AgentChat/ChatCommon/ConversationTurn.d.ts +9 -5
  8. package/dist/components/AgentChat/ChatCommon/ConversationTurn.js +3 -2
  9. package/dist/components/AgentChat/ChatCommon/FormattedMarkdown.js +5 -3
  10. package/dist/components/AgentChat/ChatCommon/SampleQueries.d.ts +3 -0
  11. package/dist/components/AgentChat/ChatCommon/SampleQueries.js +6 -3
  12. package/dist/components/AgentChat/ChatCommon/Thinking.d.ts +12 -0
  13. package/dist/components/AgentChat/ChatCommon/Thinking.js +51 -0
  14. package/dist/components/AgentChat/Common/LlmChatButton.d.ts +2 -2
  15. package/dist/components/AgentChat/Common/Types.d.ts +6 -5
  16. package/dist/components/AgentChat/Common/Types.js +5 -0
  17. package/dist/components/AgentChat/Common/Utils.d.ts +1 -1
  18. package/dist/components/AgentChat/Common/Utils.js +13 -7
  19. package/dist/components/ChatBot/ChatBot.d.ts +0 -4
  20. package/dist/components/ChatBot/ChatBot.js +2 -2
  21. package/dist/components/Common/AccordionLite.d.ts +14 -0
  22. package/dist/components/Common/AccordionLite.js +25 -0
  23. package/dist/components/Common/ConfirmationModal.d.ts +1 -1
  24. package/dist/components/Common/CustomerLogo.js +1 -3
  25. package/dist/components/Common/MUIAlert.d.ts +1 -0
  26. package/dist/components/Common/MUIAlert.js +3 -4
  27. package/dist/components/MultiAgentAccelerator/AgentFlow.d.ts +1 -0
  28. package/dist/components/MultiAgentAccelerator/AgentFlow.js +154 -59
  29. package/dist/components/MultiAgentAccelerator/AgentNode.d.ts +1 -0
  30. package/dist/components/MultiAgentAccelerator/AgentNode.js +46 -45
  31. package/dist/components/MultiAgentAccelerator/GraphLayouts.js +12 -4
  32. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +103 -24
  33. package/dist/components/Settings/ApiKeyInput.d.ts +16 -0
  34. package/dist/components/Settings/ApiKeyInput.js +70 -0
  35. package/dist/components/Settings/SettingsDialog.js +30 -3
  36. package/dist/controller/llm/Providers.d.ts +2 -0
  37. package/dist/controller/llm/Providers.js +41 -0
  38. package/dist/index.d.ts +0 -1
  39. package/dist/index.js +0 -1
  40. package/dist/state/Settings.d.ts +2 -0
  41. package/dist/state/Settings.js +1 -0
  42. package/dist/tsconfig.build.tsbuildinfo +1 -1
  43. package/package.json +2 -2
  44. package/dist/components/AgentChat/ChatCommon/AgentConnectivity.d.ts +0 -14
  45. package/dist/components/AgentChat/ChatCommon/AgentConnectivity.js +0 -23
  46. package/dist/components/AgentChat/ChatCommon/AgentIntro.d.ts +0 -12
  47. package/dist/components/AgentChat/ChatCommon/AgentIntro.js +0 -19
  48. package/dist/components/AgentChat/ChatCommon/AgentMetadata.d.ts +0 -14
  49. package/dist/components/AgentChat/ChatCommon/AgentMetadata.js +0 -43
  50. package/dist/components/AgentChat/ChatCommon/Const.d.ts +0 -1
  51. package/dist/components/AgentChat/ChatCommon/Const.js +0 -2
  52. package/dist/components/AgentChat/ChatCommon/Greetings.d.ts +0 -1
  53. package/dist/components/AgentChat/ChatCommon/Greetings.js +0 -38
  54. package/dist/components/AgentChat/ChatCommon/UserQueryDisplay.d.ts +0 -7
  55. package/dist/components/AgentChat/ChatCommon/UserQueryDisplay.js +0 -32
  56. package/dist/components/Common/LlmChatOptionsButton.d.ts +0 -6
  57. package/dist/components/Common/LlmChatOptionsButton.js +0 -31
@@ -30,14 +30,13 @@ const StyledAlert = styled(Alert)({
30
30
  },
31
31
  });
32
32
  // #endregion: Types
33
- export const MUIAlert = ({ children, closeable = false, id, severity, sx }) => {
33
+ export const MUIAlert = ({ children, closeable = false, id, onClose, severity, sx }) => {
34
34
  const [alertOpen, setAlertOpen] = useState(true);
35
35
  const handleClose = () => {
36
36
  setAlertOpen(false);
37
+ onClose?.();
37
38
  };
38
- return (_jsx(Collapse, { id: `${id}-collapse`, in: alertOpen, children: _jsx(StyledAlert, { action: closeable && (_jsx(IconButton, { "aria-label": "close", color: "inherit", id: `${id}-icon-button`, onClick: () => {
39
- setAlertOpen(false);
40
- }, size: "small", sx: {
39
+ return (_jsx(Collapse, { id: `${id}-collapse`, in: alertOpen, children: _jsx(StyledAlert, { action: closeable && (_jsx(IconButton, { "aria-label": "close", color: "inherit", id: `${id}-icon-button`, onClick: handleClose, size: "small", sx: {
41
40
  bottom: "2px",
42
41
  position: "relative",
43
42
  }, children: _jsx(CloseIcon, { fontSize: "inherit", id: `${id}-close-icon` }) })), id: id, onClose: closeable ? handleClose : undefined, severity: severity, sx: sx, children: children }) }));
@@ -39,4 +39,5 @@ export interface AgentFlowProps {
39
39
  timestamp: number;
40
40
  }>>>;
41
41
  }
42
+ export declare const DOCK_BANNER_AUTO_DISMISS_MS = 5000;
42
43
  export declare const AgentFlow: FC<AgentFlowProps>;
@@ -20,6 +20,7 @@ import CloseIcon from "@mui/icons-material/Close";
20
20
  import EditIcon from "@mui/icons-material/Edit";
21
21
  import HubOutlinedIcon from "@mui/icons-material/HubOutlined";
22
22
  import ScatterPlotOutlinedIcon from "@mui/icons-material/ScatterPlotOutlined";
23
+ import Backdrop from "@mui/material/Backdrop";
23
24
  import Box from "@mui/material/Box";
24
25
  import Button from "@mui/material/Button";
25
26
  import CircularProgress from "@mui/material/CircularProgress";
@@ -48,11 +49,15 @@ import { useTempNetworksStore } from "../../state/TemporaryNetworks.js";
48
49
  import { usePalette } from "../../Theme/Palettes.js";
49
50
  import { getZIndex } from "../../utils/zIndexLayers.js";
50
51
  import { chatMessageFromChunk } from "../AgentChat/Common/Utils.js";
52
+ import { MUIAlert } from "../Common/MUIAlert.js";
51
53
  import { NotificationType, sendNotification } from "../Common/notification.js";
52
54
  // #endregion: Types
53
55
  // #region: Constants
54
56
  // Timeout for thought bubbles is set to 10 seconds
55
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;
56
61
  // #endregion: Constants
57
62
  const DOCK_PROMPT_PLACEHOLDER = "Describe a change to the network";
58
63
  // #region: Helpers
@@ -240,6 +245,45 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
240
245
  const [dockPrompt, setDockPrompt] = useState("");
241
246
  const [isDockStreaming, setIsDockStreaming] = useState(false);
242
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]);
243
287
  const handleNodeClick = useCallback((_event, node) => {
244
288
  // Popup is only available for temporary networks.
245
289
  if (!isTemporaryNetwork)
@@ -267,11 +311,18 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
267
311
  setIsPopupOpen(false);
268
312
  setIsSavingAgent(false);
269
313
  }, []);
270
- /** Applies the networks returned by the designer: upserts them and triggers navigation if needed. */
271
- const applyNetworkSaveResult = useCallback((agentName, newNetworksFromSave, currentAgentNetworkName) => {
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) => {
272
319
  if (newNetworksFromSave.length === 0) {
273
- sendNotification(NotificationType.error, `Failed to update network "${agentName}".`, "The network designer did not return a reservation. Please try again.");
274
- return;
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;
275
326
  }
276
327
  const replacement = newNetworksFromSave.find((n) => n.agentNetworkName === currentAgentNetworkName);
277
328
  if (replacement) {
@@ -280,12 +331,16 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
280
331
  useAgentChatHistoryStore.getState().copyHistory(networkId, replacement.agentInfo.agent_name);
281
332
  onNetworkReplaced(networkId, replacement.agentInfo.agent_name);
282
333
  }
334
+ return true;
283
335
  }
284
- else {
285
- // Reservations came back but none matched the current network — surface this to the user.
286
- sendNotification(NotificationType.error, `Failed to update network "${agentName}".`, "A reservation was returned but did not match the current network. Please try again.");
287
- }
288
- }, [networkId, onNetworkReplaced]);
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]);
289
344
  const handleDockApply = useCallback(async () => {
290
345
  if (!dockPrompt.trim() || !neuroSanURL || !currentUser)
291
346
  return;
@@ -303,17 +358,27 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
303
358
  }, 120_000); // 2 min timeout
304
359
  try {
305
360
  const newNetworks = await streamNetworkDesignerPrompt(neuroSanURL, controller.signal, dockPrompt, currentDefinition, currentTempNetwork?.agentNetworkName, currentUser);
306
- applyNetworkSaveResult(dockPrompt, newNetworks, currentTempNetwork?.agentNetworkName);
307
- setDockPrompt("");
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
+ }
308
370
  }
309
371
  catch (e) {
310
372
  const isAbort = e instanceof DOMException && e.name === "AbortError";
311
373
  if (!isAbort) {
312
- sendNotification(NotificationType.error, "Failed to apply network change.", String(e), undefined, null);
374
+ showDockBanner({ severity: "error", title: "Failed to apply network change.", detail: String(e) });
313
375
  }
314
376
  else if (hasTimedOut) {
315
- sendNotification(NotificationType.error, "Failed to apply network change.", "The request timed out. Please try again.", undefined, null // show indefinitely until the user dismisses
316
- );
377
+ showDockBanner({
378
+ severity: "error",
379
+ title: "Failed to apply network change.",
380
+ detail: "The request timed out. Please try again.",
381
+ });
317
382
  }
318
383
  }
319
384
  finally {
@@ -321,7 +386,7 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
321
386
  dockAbortControllerRef.current = null;
322
387
  setIsDockStreaming(false);
323
388
  }
324
- }, [applyNetworkSaveResult, currentUser, dockPrompt, networkId, neuroSanURL, tempNetworks]);
389
+ }, [applyNetworkSaveResult, currentUser, dockPrompt, networkId, neuroSanURL, showDockBanner, tempNetworks]);
325
390
  const handleExitEditMode = useCallback(() => {
326
391
  if (isDockStreaming) {
327
392
  dockAbortControllerRef.current?.abort();
@@ -508,10 +573,8 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
508
573
  lineHeight: 1.35,
509
574
  maxWidth: 400,
510
575
  overflow: "hidden",
511
- paddingLeft: 2,
512
- paddingRight: 2,
513
- paddingBottom: 0.45,
514
- paddingTop: 0.45,
576
+ px: 2,
577
+ py: 0.45,
515
578
  textOverflow: "ellipsis",
516
579
  whiteSpace: "nowrap",
517
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: {
@@ -527,67 +590,99 @@ export const AgentFlow = ({ agentCounts, agentIconSuggestions, agentsInNetwork,
527
590
  width: "100%",
528
591
  backgroundColor: theme.palette.background.default,
529
592
  "& .react-flow__node": {
530
- border: `1px solid ${theme.palette.divider}`,
593
+ border: "1px solid divider",
531
594
  },
532
595
  "& .react-flow__panel": {
533
596
  backgroundColor: theme.palette.background.paper,
534
- border: `1px solid ${theme.palette.divider}`,
597
+ border: "1px solid divider",
535
598
  color: theme.palette.text.primary,
536
599
  },
537
600
  "& .react-flow__controls-button": {
538
601
  backgroundColor: theme.palette.background.paper,
539
- borderBottom: `1px solid ${theme.palette.divider}`,
602
+ borderBottom: "1px solid divider",
540
603
  color: theme.palette.text.primary,
541
604
  fill: theme.palette.text.primary,
542
605
  },
543
- }, 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] })) }), isDockStreaming && (_jsx(Box, { id: `${id}-dock-applying-overlay`, sx: {
544
- position: "absolute",
545
- inset: 0,
546
- zIndex: getZIndex(2, theme),
547
- backgroundColor: alpha(theme.palette.background.default, 0.65),
548
- display: "flex",
549
- alignItems: "center",
550
- justifyContent: "center",
551
- }, children: _jsxs(Paper, { elevation: 6, sx: {
552
- display: "flex",
553
- alignItems: "center",
554
- gap: 2,
555
- px: 4,
556
- py: 2.5,
557
- borderRadius: 2,
558
- maxWidth: 480,
559
- }, children: [_jsx(CircularProgress, { id: `${id}-dock-applying-spinner`, size: 24 }), _jsxs(Box, { children: [_jsx(Typography, { id: `${id}-dock-applying-title`, variant: "body1", sx: {
560
- fontWeight: "bold",
561
- }, children: "Applying changes to network" }), dockPrompt && (_jsx(Typography, { id: `${id}-dock-applying-prompt`, variant: "body2", sx: {
562
- color: "text.secondary",
563
- mt: 0.25,
564
- }, children: dockPrompt }))] })] }) })), _jsx(ThoughtBubbleOverlay, { nodes: nodes, edges: thoughtBubbleEdgesForOverlay, showThoughtBubbles: showThoughtBubbles, isStreaming: isStreaming, onBubbleHoverChange: handleBubbleHoverChange })] }), isEditMode && isTemporaryNetwork && !isAwaitingLlm && (_jsxs(Box, { id: `${id}-topology-editor-dock`, sx: {
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: {
565
607
  borderTop: `2px solid ${theme.palette.primary.main}`,
566
608
  backgroundColor: theme.palette.background.paper,
567
609
  flexShrink: 0,
568
- }, children: [_jsxs(Box, { sx: {
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",
569
632
  display: "flex",
570
633
  alignItems: "center",
571
634
  justifyContent: "space-between",
572
- px: 2,
573
- py: 0.5,
574
- borderBottom: `1px solid ${theme.palette.divider}`,
575
- }, 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" }) })] }), _jsx(Typography, { variant: "caption", sx: {
576
- px: 2,
577
- pt: 0.5,
578
- pb: 0,
579
- color: theme.palette.text.secondary,
580
- display: "block",
581
- }, children: "Changes apply only to this Temporary network" }), _jsxs(Box, { sx: {
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: {
582
638
  display: "flex",
583
639
  gap: 1,
584
- px: 2,
585
- py: 1.5,
640
+ px: 1,
641
+ py: 0.5,
586
642
  alignItems: "center",
587
- }, children: [_jsx(TextField, { fullWidth: true, id: `${id}-dock-prompt-input`, placeholder: DOCK_PROMPT_PLACEHOLDER, variant: "outlined", size: "small", value: dockPrompt, onChange: (e) => setDockPrompt(e.target.value), onKeyDown: (e) => {
643
+ }, children: [_jsx(TextField, { fullWidth: true, placeholder: DOCK_PROMPT_PLACEHOLDER, variant: "outlined", size: "small", value: dockPrompt, onChange: (e) => setDockPrompt(e.target.value), onKeyDown: (e) => {
588
644
  if (e.key === "Enter" && !e.shiftKey) {
589
645
  e.preventDefault();
590
646
  void handleDockApply();
591
647
  }
592
- }, disabled: isDockStreaming, slotProps: { htmlInput: { style: { fontSize: "0.85rem" } } } }), _jsx(Button, { id: `${id}-dock-apply-button`, variant: "contained", onClick: () => void handleDockApply(), disabled: isDockStreaming || !dockPrompt.trim(), sx: { whiteSpace: "nowrap", minWidth: 120 }, 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 }))] }));
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" })] })) })] }));
593
688
  };
@@ -15,6 +15,7 @@ export interface AgentNodeProps extends Record<string, unknown> {
15
15
  }
16
16
  export declare const NODE_HEIGHT = 100;
17
17
  export declare const NODE_WIDTH = 100;
18
+ export declare const FRONTMAN_SIZE_MULTIPLIER = 1.25;
18
19
  /**
19
20
  * A node representing an agent in the network for use in react-flow.
20
21
  * @param props See AgentNodeProps
@@ -38,6 +38,7 @@ const ZERO_WIDTH_SPACE = "\u200B";
38
38
  // Node dimensions
39
39
  export const NODE_HEIGHT = 100;
40
40
  export const NODE_WIDTH = 100;
41
+ export const FRONTMAN_SIZE_MULTIPLIER = 1.25;
41
42
  // Icon sizes
42
43
  // These are used to set the size of the icons displayed in the agent nodes.
43
44
  const AGENT_ICON_SIZE = "2.25rem";
@@ -158,55 +159,55 @@ export const AgentNode = (props) => {
158
159
  const glowColor = isLightColor(theme.palette.background.default)
159
160
  ? theme.palette.common.black
160
161
  : theme.palette.common.white;
161
- const isClickableNode = isEditable;
162
162
  const [isHovered, setIsHovered] = useState(false);
163
163
  const wrappedAgentName = agentName
164
164
  // Allow wrap after underscore
165
165
  .replaceAll("_", `_${ZERO_WIDTH_SPACE}`)
166
166
  // Allow wrap before capitals in camel/PascalCase (e.g., CustomerSupport -> Customer|Support)
167
167
  .replaceAll(/(?<lower>[\da-z])(?<upper>[A-Z])/gu, `$<lower>${ZERO_WIDTH_SPACE}$<upper>`)
168
- // Also split acronym->word boundary (e.g., HTTPServer -> HTTP|Server), but not H|T|T|P
169
- .replaceAll(/(?<acronym>[A-Z])(?<word>[A-Z][a-z])/gu, `$<acronym>${ZERO_WIDTH_SPACE}$<word>`);
170
- return (_jsxs(_Fragment, { children: [_jsxs(AnimatedNode, { id: agentId, "data-testid": agentId, glowColor: glowColor, isActive: isActiveAgent, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), sx: {
171
- backgroundColor,
172
- color,
173
- cursor: isEditable ? "pointer" : "grab",
174
- height: NODE_HEIGHT * (isFrontman ? 1.25 : 1.0),
175
- width: NODE_WIDTH * (isFrontman ? 1.25 : 1.0),
176
- zIndex: getZIndex(1, theme),
177
- }, children: [getDisplayAsIcon(), isHovered && isClickableNode && (_jsx(Tooltip, { title: "Edit", placement: "top", disableInteractive: true, children: _jsx(IconButton, { sx: {
178
- position: "absolute",
179
- top: 0,
180
- right: 0,
181
- backgroundColor: "rgba(0,0,0,0.45)",
182
- borderRadius: "50%",
183
- padding: "3px",
184
- zIndex: 2,
185
- cursor: "pointer",
186
- "&:hover": { backgroundColor: "rgba(0,0,0,0.65)" },
187
- }, children: _jsx(EditIcon, { id: `${agentId}-edit-icon`, sx: { fontSize: "1rem", color: "white" } }) }) })), _jsx(Handle, { id: `${agentId}-left-handle`, position: Position.Left, type: "source", style: { visibility: handleVisibility } }), _jsx(Handle, { id: `${agentId}-right-handle`, position: Position.Right, type: "source", style: { visibility: handleVisibility } }), _jsx(Handle, { id: `${agentId}-top-handle`, position: Position.Top, type: "source", style: { visibility: handleVisibility } }), _jsx(Handle, { id: `${agentId}-bottom-handle`, position: Position.Bottom, type: "source", style: { visibility: handleVisibility } })] }), _jsx(Tooltip, { id: `${agentId}-tooltip`, title: agentName, placement: "top", disableInteractive: true, children: _jsx(Typography, { id: `${agentId}-name`, sx: {
188
- WebkitBoxOrient: "vertical",
189
- WebkitLineClamp: 4,
190
- backgroundColor: theme.palette.background.paper,
191
- border: `1px solid ${theme.palette.divider}`,
192
- borderRadius: 1,
193
- color: theme.palette.text.primary,
194
- display: "-webkit-box",
195
- fontSize: "17px",
196
- fontWeight: 700,
197
- lineHeight: 1.3,
198
- maxHeight: "4.4em",
199
- mx: "auto",
200
- my: "auto",
201
- overflow: "hidden",
202
- overflowWrap: "anywhere",
203
- px: 0.75,
204
- py: 0.45,
205
- textAlign: "center",
206
- textOverflow: "ellipsis",
207
- whiteSpace: "normal",
208
- width: `${NODE_WIDTH}px`,
209
- wordBreak: "break-word",
210
- zIndex: 10,
211
- }, children: wrappedAgentName }) })] }));
168
+ // Also split abbreviation->word boundary (e.g., HTTPServer -> HTTP|Server), but not H|T|T|P
169
+ .replaceAll(/(?<abbr>[A-Z])(?<word>[A-Z][a-z])/gu, `$<abbr>${ZERO_WIDTH_SPACE}$<word>`);
170
+ const height = NODE_HEIGHT * (isFrontman ? FRONTMAN_SIZE_MULTIPLIER : 1.0);
171
+ const width = height;
172
+ const getEditButton = () => (_jsx(Tooltip, { title: "Edit", placement: "top", disableInteractive: true, children: _jsx(IconButton, { sx: {
173
+ position: "absolute",
174
+ top: 0,
175
+ right: 0,
176
+ backgroundColor: "rgba(0,0,0,0.45)",
177
+ borderRadius: "50%",
178
+ padding: "3px",
179
+ zIndex: 2,
180
+ cursor: "pointer",
181
+ "&:hover": { backgroundColor: "rgba(0,0,0,0.65)" },
182
+ }, children: _jsx(EditIcon, { id: `${agentId}-edit-icon`, sx: { fontSize: "1rem", color: "white" } }) }) }));
183
+ const getNodeName = () => (_jsx(Tooltip, { id: `${agentId}-tooltip`, title: agentName, placement: "top", disableInteractive: true, children: _jsx(Typography, { id: `${agentId}-name`, sx: {
184
+ backgroundColor: theme.palette.background.paper,
185
+ border: `1px solid ${theme.palette.divider}`,
186
+ borderRadius: 1,
187
+ color: theme.palette.text.primary,
188
+ display: "-webkit-box",
189
+ fontSize: "16px",
190
+ fontWeight: "700",
191
+ marginTop: "0.5rem",
192
+ overflow: "hidden",
193
+ overflowWrap: "anywhere",
194
+ paddingLeft: 0.75,
195
+ paddingRight: 0.75,
196
+ paddingBottom: 0.45,
197
+ paddingTop: 0.45,
198
+ textAlign: "center",
199
+ WebkitBoxOrient: "vertical",
200
+ WebkitLineClamp: 4,
201
+ width,
202
+ zIndex: getZIndex(1, theme),
203
+ }, children: wrappedAgentName }) }));
204
+ const getNode = () => (_jsxs(AnimatedNode, { id: agentId, "data-testid": agentId, glowColor: glowColor, isActive: isActiveAgent, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), sx: {
205
+ backgroundColor,
206
+ color,
207
+ cursor: isEditable ? "pointer" : "grab",
208
+ height,
209
+ width,
210
+ zIndex: getZIndex(1, theme),
211
+ }, children: [getDisplayAsIcon(), isHovered && isEditable && getEditButton(), _jsx(Handle, { id: `${agentId}-left-handle`, position: Position.Left, type: "source", style: { visibility: handleVisibility } }), _jsx(Handle, { id: `${agentId}-right-handle`, position: Position.Right, type: "source", style: { visibility: handleVisibility } }), _jsx(Handle, { id: `${agentId}-top-handle`, position: Position.Top, type: "source", style: { visibility: handleVisibility } }), _jsx(Handle, { id: `${agentId}-bottom-handle`, position: Position.Bottom, type: "source", style: { visibility: handleVisibility } })] }));
212
+ return (_jsxs(_Fragment, { children: [getNode(), getNodeName()] }));
212
213
  };
@@ -19,11 +19,13 @@ limitations under the License.
19
19
  import dagre from "@dagrejs/dagre";
20
20
  import { MarkerType } from "@xyflow/react";
21
21
  import cloneDeep from "lodash-es/cloneDeep.js";
22
- import { NODE_HEIGHT, NODE_WIDTH } from "./AgentNode.js";
22
+ import { FRONTMAN_SIZE_MULTIPLIER, 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
24
  import { isEditableAgent } from "./TemporaryNetworks.js";
25
25
  import { cleanUpAgentName, KNOWN_MESSAGE_TYPES_FOR_PLASMA } from "../AgentChat/Common/Utils.js";
26
26
  export const MAX_GLOBAL_THOUGHT_BUBBLES = 5;
27
+ const FRONTMAN_OFFSET_X = (NODE_WIDTH * (FRONTMAN_SIZE_MULTIPLIER - 1)) / 2;
28
+ const FRONTMAN_OFFSET_Y = (NODE_HEIGHT * (FRONTMAN_SIZE_MULTIPLIER - 1)) / 2;
27
29
  export const addThoughtBubbleEdge = (thoughtBubbleEdges, conversationId, edge) => {
28
30
  // Add with timestamp for age-based cleanup
29
31
  thoughtBubbleEdges.set(conversationId, {
@@ -194,7 +196,10 @@ isAwaitingLlm, isAgentNetworkDesignerMode, thoughtBubbleEdges, agentIconSuggesti
194
196
  isEditable: isTemporaryNetwork &&
195
197
  isEditableAgent(agentsInNetwork.find((a) => a.origin === nodeId)?.display_as),
196
198
  },
197
- position: isFrontman ? { x: DEFAULT_FRONTMAN_X_POS, y: DEFAULT_FRONTMAN_Y_POS } : { x, y },
199
+ // position: allow for Frontman being larger
200
+ position: isFrontman
201
+ ? { x: DEFAULT_FRONTMAN_X_POS - FRONTMAN_OFFSET_X, y: DEFAULT_FRONTMAN_Y_POS - FRONTMAN_OFFSET_Y }
202
+ : { x, y },
198
203
  style: {
199
204
  border: "none",
200
205
  background: "transparent",
@@ -279,11 +284,14 @@ isAwaitingLlm, isAgentNetworkDesignerMode, thoughtBubbleEdges, agentIconSuggesti
279
284
  // Convert dagre's layout to what our flow graph needs
280
285
  nodesTmp.forEach((node) => {
281
286
  const nodeWithPosition = dagreGraph.node(node.id);
287
+ const isFrontman = frontman?.origin === node.id;
282
288
  // We are shifting the dagre node position (anchor=center) to the top left
283
289
  // so it matches the React Flow node anchor point (top left).
290
+ const x = nodeWithPosition.x - NODE_WIDTH / 2 - (isFrontman ? FRONTMAN_OFFSET_X : 0);
291
+ const y = nodeWithPosition.y - NODE_HEIGHT / 2 - (isFrontman ? FRONTMAN_OFFSET_Y : 0);
284
292
  node.position = {
285
- x: nodeWithPosition.x - NODE_WIDTH / 2,
286
- y: nodeWithPosition.y - NODE_HEIGHT / 2,
293
+ x,
294
+ y,
287
295
  };
288
296
  // Depth is index of x position in xPositions array
289
297
  // Create a new data object with updated depth