@cognizant-ai-lab/ui-common 1.8.0 → 1.10.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 (59) hide show
  1. package/README.md +3 -3
  2. package/dist/components/AgentChat/ChatCommon/ChatCommon.d.ts +9 -3
  3. package/dist/components/AgentChat/ChatCommon/ChatCommon.js +94 -27
  4. package/dist/components/AgentChat/ChatCommon/ChatHistory.d.ts +7 -0
  5. package/dist/components/AgentChat/ChatCommon/ChatHistory.js +1 -1
  6. package/dist/components/AgentChat/ChatCommon/ControlButtons.d.ts +4 -2
  7. package/dist/components/AgentChat/ChatCommon/ControlButtons.js +8 -2
  8. package/dist/components/AgentChat/ChatCommon/ConversationTurn.d.ts +5 -5
  9. package/dist/components/AgentChat/ChatCommon/ConversationTurn.js +1 -0
  10. package/dist/components/AgentChat/ChatCommon/Thinking.js +2 -2
  11. package/dist/components/AgentChat/Common/Utils.d.ts +0 -7
  12. package/dist/components/AgentChat/Common/Utils.js +0 -12
  13. package/dist/components/Common/Breadcrumbs.js +1 -1
  14. package/dist/components/Common/Footer.js +4 -0
  15. package/dist/components/Common/notification.d.ts +4 -4
  16. package/dist/components/MultiAgentAccelerator/{AgentFlow.d.ts → AgentFlow/AgentFlow.d.ts} +15 -5
  17. package/dist/components/MultiAgentAccelerator/{AgentFlow.js → AgentFlow/AgentFlow.js} +99 -58
  18. package/dist/components/MultiAgentAccelerator/{AgentNode.d.ts → AgentFlow/AgentNode.d.ts} +1 -1
  19. package/dist/components/MultiAgentAccelerator/{AgentNode.js → AgentFlow/AgentNode.js} +11 -9
  20. package/dist/components/MultiAgentAccelerator/{GraphLayouts.d.ts → AgentFlow/GraphLayouts.d.ts} +4 -4
  21. package/dist/components/MultiAgentAccelerator/{GraphLayouts.js → AgentFlow/GraphLayouts.js} +15 -41
  22. package/dist/components/MultiAgentAccelerator/AgentFlow/GraphStructure.d.ts +21 -0
  23. package/dist/components/MultiAgentAccelerator/AgentFlow/GraphStructure.js +27 -0
  24. package/dist/components/MultiAgentAccelerator/AgentFlow/PlasmaEdge.d.ts +7 -0
  25. package/dist/components/MultiAgentAccelerator/{PlasmaEdge.js → AgentFlow/PlasmaEdge.js} +13 -8
  26. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +74 -83
  27. package/dist/components/MultiAgentAccelerator/Schema/SlyData.d.ts +17 -0
  28. package/dist/components/MultiAgentAccelerator/Schema/SlyData.js +23 -0
  29. package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.d.ts +2 -0
  30. package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.js +22 -25
  31. package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.js +16 -22
  32. package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.d.ts +1 -0
  33. package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +27 -7
  34. package/dist/components/MultiAgentAccelerator/TemporaryNetworks.d.ts +8 -0
  35. package/dist/components/MultiAgentAccelerator/TemporaryNetworks.js +61 -13
  36. package/dist/components/MultiAgentAccelerator/{ThoughtBubbleEdge.d.ts → ThoughtBubbles/ThoughtBubbleEdge.d.ts} +1 -1
  37. package/dist/components/MultiAgentAccelerator/{ThoughtBubbleOverlay.js → ThoughtBubbles/ThoughtBubbleOverlay.js} +1 -1
  38. package/dist/components/MultiAgentAccelerator/Tour/MainTourSteps.js +7 -2
  39. package/dist/components/MultiAgentAccelerator/const.d.ts +11 -10
  40. package/dist/components/MultiAgentAccelerator/const.js +22 -20
  41. package/dist/components/Settings/SettingsDialog.js +26 -10
  42. package/dist/controller/agent/Agent.d.ts +14 -6
  43. package/dist/controller/agent/Agent.js +18 -14
  44. package/dist/index.d.ts +1 -1
  45. package/dist/index.js +1 -1
  46. package/dist/state/Settings.d.ts +81 -4
  47. package/dist/state/Settings.js +87 -7
  48. package/dist/state/TemporaryNetworks.js +12 -21
  49. package/dist/tsconfig.build.tsbuildinfo +1 -1
  50. package/dist/utils/AgentName.d.ts +17 -0
  51. package/dist/utils/AgentName.js +46 -0
  52. package/dist/utils/BrowserNavigation.js +2 -0
  53. package/dist/utils/File.d.ts +4 -1
  54. package/dist/utils/File.js +4 -9
  55. package/dist/utils/text.js +3 -7
  56. package/package.json +9 -10
  57. package/dist/components/MultiAgentAccelerator/PlasmaEdge.d.ts +0 -3
  58. /package/dist/components/MultiAgentAccelerator/{ThoughtBubbleEdge.js → ThoughtBubbles/ThoughtBubbleEdge.js} +0 -0
  59. /package/dist/components/MultiAgentAccelerator/{ThoughtBubbleOverlay.d.ts → ThoughtBubbles/ThoughtBubbleOverlay.d.ts} +0 -0
package/README.md CHANGED
@@ -72,7 +72,7 @@ via the chat interface, and visualize the agent network flow.
72
72
  Among the technologies and libraries used in this package are:
73
73
 
74
74
  - ESLint and Prettier (for code quality)
75
- - Jest and React Testing Library (for testing)
75
+ - Vitest and React Testing Library (for testing)
76
76
  - MUI
77
77
  - next-auth (for authentication)
78
78
  - React
@@ -160,9 +160,9 @@ async function checkServer() {
160
160
  const result = await testConnection("https://api.example.com")
161
161
 
162
162
  if (result.success) {
163
- console.log(`Connected! Version: ${result.version}`)
163
+ console.log(`Connected!`)
164
164
  } else {
165
- console.error(`Connection failed: ${result.status}`)
165
+ console.error(`Connection failed: ${result.statusText}`)
166
166
  }
167
167
  }
168
168
  ```
@@ -1,5 +1,5 @@
1
1
  import { Dispatch, Ref, SetStateAction } from "react";
2
- import { LLMProvider } from "../../../state/Settings.js";
2
+ import { MessageRole } from "./ConversationTurn.js";
3
3
  import { CombinedAgentType } from "../Common/Types.js";
4
4
  export interface ChatCommonProps {
5
5
  /**
@@ -96,14 +96,20 @@ export interface ChatCommonProps {
96
96
  */
97
97
  readonly sampleQueries?: string[];
98
98
  /**
99
- * Array of LLM providers for which API keys are required but missing. Only applies to BYOK networks.
99
+ * If true, indicates that the user is missing API keys for one or more LLM providers.
100
100
  */
101
- readonly missingApiKeys?: LLMProvider[];
101
+ readonly hasMissingApiKeys?: boolean;
102
102
  }
103
103
  export type ChatCommonHandle = {
104
104
  handleStop: () => void;
105
105
  handleClearChat: () => void;
106
106
  };
107
+ /**
108
+ * Helper function to convert a message role to a label for exporting chat history.
109
+ * @param role The message role to convert
110
+ * @returns The label for the message role
111
+ */
112
+ export declare const roleToExportLabel: (role: MessageRole) => string;
107
113
  /**
108
114
  * Common chat component for agent chat. This component is used by all agent chat components to provide a consistent
109
115
  * experience for users when chatting with agents. It handles user input as well as displaying and nicely formatting
@@ -37,7 +37,7 @@ import Typography from "@mui/material/Typography";
37
37
  import { isEmpty } from "lodash-es";
38
38
  import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
39
39
  import { v4 as uuid } from "uuid";
40
- import { ChatHistory } from "./ChatHistory.js";
40
+ import { ChatHistory, toTurns } from "./ChatHistory.js";
41
41
  import { MAX_TURNS } from "./Const.js";
42
42
  import { ControlButtons } from "./ControlButtons.js";
43
43
  import { Conversation } from "./Conversation.js";
@@ -50,14 +50,17 @@ import { sendLlmRequest, StreamingUnit } from "../../../controller/llm/LlmChat.j
50
50
  import { ChatMessageType } from "../../../generated/neuro-san/NeuroSanClient.js";
51
51
  import { useAgentChatHistoryStore } from "../../../state/ChatHistory.js";
52
52
  import { useSettingsStore } from "../../../state/Settings.js";
53
+ import { toDisplayName } from "../../../utils/AgentName.js";
54
+ import { downloadFile, toSafeFilename } from "../../../utils/File.js";
53
55
  import { hasOnlyWhitespace } from "../../../utils/text.js";
54
56
  import { getZIndex } from "../../../utils/zIndexLayers.js";
57
+ import { ConfirmationModal } from "../../Common/ConfirmationModal.js";
55
58
  import { AGENT_NETWORK_DESIGNER_ID } from "../../MultiAgentAccelerator/const.js";
56
59
  import { givesFinalAnswer, isLegacyAgentType } from "../Common/Types.js";
57
- import { chatMessageFromChunk, checkError, cleanUpAgentName, removeTrailingUuid } from "../Common/Utils.js";
60
+ import { chatMessageFromChunk, checkError } from "../Common/Utils.js";
58
61
  import { MicrophoneButton } from "../VoiceChat/MicrophoneButton.js";
59
62
  import { cleanupAndStopSpeechRecognition, setupSpeechRecognition } from "../VoiceChat/VoiceChat.js";
60
- //#endregion
63
+ //#endregion Types and Interfaces
61
64
  //#region Constants
62
65
  // Define fancy EMPTY constant to avoid linter error about using object literals as default props
63
66
  const EMPTY = {};
@@ -70,14 +73,29 @@ const MAX_AGENT_RETRIES = 3;
70
73
  * @returns The final answer from the agent, if it exists or undefined if it doesn't
71
74
  */
72
75
  const extractFinalAnswer = (response) => /Final Answer: (?<finalAnswerText>.*)/su.exec(response)?.groups?.["finalAnswerText"];
73
- //#endregion
76
+ // List of message roles that are exported when saving chat history.
77
+ const EXPORTED_MESSAGE_TYPES = [MessageRole.User, MessageRole.FinalAnswer, MessageRole.Warning, MessageRole.Error];
78
+ const EXPORT_ROLE_LABELS = {
79
+ [MessageRole.User]: "User",
80
+ [MessageRole.Agent]: "Assistant",
81
+ [MessageRole.FinalAnswer]: "Assistant",
82
+ [MessageRole.Warning]: "Warning",
83
+ [MessageRole.Error]: "Error",
84
+ };
85
+ //#endregion Constants
86
+ /**
87
+ * Helper function to convert a message role to a label for exporting chat history.
88
+ * @param role The message role to convert
89
+ * @returns The label for the message role
90
+ */
91
+ export const roleToExportLabel = (role) => EXPORT_ROLE_LABELS[role];
74
92
  /**
75
93
  * Common chat component for agent chat. This component is used by all agent chat components to provide a consistent
76
94
  * experience for users when chatting with agents. It handles user input as well as displaying and nicely formatting
77
95
  * agent responses. Customization for inputs and outputs is provided via event handlers-like props.
78
96
  */
79
97
  export const ChatCommon = ({ ref, ...props }) => {
80
- const { agentPlaceholders = EMPTY, backgroundColor, currentUser, customAgentGreetings = EMPTY, extraParams, extraSlyData, id, isAwaitingLlm, legacyAgentEndpoint, missingApiKeys = [], networkDescription, neuroSanURL, onChunkReceived, onClose, onSend, onStreamingComplete, onStreamingStarted, sampleQueries, selectedNetwork, setIsAwaitingLlm, setPreviousResponse, setSelectedNetwork, title, } = props;
98
+ const { agentPlaceholders = EMPTY, backgroundColor, currentUser, customAgentGreetings = EMPTY, extraParams, extraSlyData, id, isAwaitingLlm, legacyAgentEndpoint, hasMissingApiKeys = false, networkDescription, neuroSanURL, onChunkReceived, onClose, onSend, onStreamingComplete, onStreamingStarted, sampleQueries, selectedNetwork, setIsAwaitingLlm, setPreviousResponse, setSelectedNetwork, title, } = props;
81
99
  // MUI theme
82
100
  const theme = useTheme();
83
101
  // User LLM chat input
@@ -106,6 +124,10 @@ export const ChatCommon = ({ ref, ...props }) => {
106
124
  // (see store implementation for details)
107
125
  const storedChatHistory = useAgentChatHistoryStore((state) => selectedNetwork ? state?.history?.[selectedNetwork] : undefined);
108
126
  const agentChatHistory = useMemo(() => storedChatHistory ?? { chatHistory: [], chatContext: null, slyData: {} }, [storedChatHistory]);
127
+ // For saving chat history. Specifically, exclude "Agent" messages as they are verbose and not useful in a saved
128
+ // export.
129
+ const exportableTurns = turns.filter((turn) => EXPORTED_MESSAGE_TYPES.includes(turn.role));
130
+ const enableSaveChatButton = turns.length > 0 || agentChatHistory?.chatHistory?.length > 0;
109
131
  // Display option for agent/network names
110
132
  const useNativeNames = useSettingsStore((state) => state.settings.appearance.useNativeNames);
111
133
  // Access store for context items
@@ -142,7 +164,7 @@ export const ChatCommon = ({ ref, ...props }) => {
142
164
  };
143
165
  // Keeps track of whether the agent completed its task
144
166
  const succeeded = useRef(false);
145
- const networkDisplayName = useMemo(() => (useNativeNames ? selectedNetwork : cleanUpAgentName(removeTrailingUuid(selectedNetwork))), [selectedNetwork, useNativeNames]);
167
+ const networkDisplayName = useMemo(() => toDisplayName(selectedNetwork, useNativeNames), [selectedNetwork, useNativeNames]);
146
168
  useEffect(() => {
147
169
  // Set up speech recognition
148
170
  const handlers = setupSpeechRecognition(setChatInput, setVoiceInputState, speechRecognitionRef);
@@ -475,6 +497,44 @@ export const ChatCommon = ({ ref, ...props }) => {
475
497
  resetState();
476
498
  }
477
499
  }, [addTurn, resetState]);
500
+ const formatTurns = (items) => items.map((turn) => `${roleToExportLabel(turn.role)}: ${turn.text}`).join("\n\n");
501
+ /**
502
+ * Save the current chat history and session to a file
503
+ */
504
+ const handleSaveChat = () => {
505
+ const chatHistory = agentChatHistory?.chatHistory;
506
+ const chatHistoryTurns = toTurns(chatHistory);
507
+ // Get the current date and time in a human-readable format, including the local time zone
508
+ const exportedAt = new Date();
509
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
510
+ const exportedAtText = exportedAt.toLocaleString(undefined, {
511
+ year: "numeric",
512
+ month: "short",
513
+ day: "numeric",
514
+ hour: "numeric",
515
+ minute: "2-digit",
516
+ second: "2-digit",
517
+ timeZoneName: "short",
518
+ });
519
+ const allHistoryText = [
520
+ "Chat export",
521
+ `Network: ${selectedNetwork}`,
522
+ `Exported: ${exportedAtText} (${timeZone})`,
523
+ "",
524
+ "====================",
525
+ "Previous history",
526
+ "====================",
527
+ formatTurns(chatHistoryTurns) || "No persisted chat history.",
528
+ "",
529
+ "====================",
530
+ "Current session",
531
+ "====================",
532
+ formatTurns(exportableTurns) || "No current session turns.",
533
+ ].join("\n");
534
+ // Download the file
535
+ const filename = `${toSafeFilename(`${selectedNetwork}-history`)}.txt`;
536
+ downloadFile(allHistoryText, filename, "text/plain");
537
+ };
478
538
  // Regex to check if user has typed anything besides whitespace
479
539
  const userInputEmpty = !chatInput || chatInput.length === 0 || hasOnlyWhitespace(chatInput);
480
540
  // Enable Send button when there is user input and not awaiting a response
@@ -483,7 +543,8 @@ export const ChatCommon = ({ ref, ...props }) => {
483
543
  const shouldEnableRegenerateButton = previousUserQuery && !isAwaitingLlm;
484
544
  // Enable Clear Chat button if not awaiting response and there is chat output to clear
485
545
  const enableClearChatButton = !isAwaitingLlm && (turns.length > 0 || agentChatHistory?.chatHistory?.length > 0);
486
- const getPlaceholder = () => selectedNetwork ? agentPlaceholders[selectedNetwork] || `Chat with ${networkDisplayName}` : null;
546
+ const [clearChatConfirmationDialogOpen, setClearChatConfirmationDialogOpen] = useState(false);
547
+ const getPlaceholder = () => agentPlaceholders[selectedNetwork] || `Chat with ${networkDisplayName}`;
487
548
  const handleClearChat = useCallback(() => {
488
549
  setTurns([]);
489
550
  resetHistory(selectedNetwork);
@@ -563,14 +624,14 @@ export const ChatCommon = ({ ref, ...props }) => {
563
624
  right: theme.spacing(3),
564
625
  position: "absolute",
565
626
  zIndex: getZIndex(2, theme),
566
- }, children: _jsx(ControlButtons, { enableClearChatButton: enableClearChatButton, handleClearChat: handleClearChat, handleSend: handleSend, handleStop: handleStop, isAwaitingLlm: isAwaitingLlm, previousUserQuery: previousUserQuery, shouldEnableRegenerateButton: shouldEnableRegenerateButton }) }));
627
+ }, children: _jsx(ControlButtons, { enableClearChatButton: enableClearChatButton, enableSaveChatButton: enableSaveChatButton, handleClearChat: () => setClearChatConfirmationDialogOpen(true), handleSave: handleSaveChat, handleSend: handleSend, handleStop: handleStop, isAwaitingLlm: isAwaitingLlm, previousUserQuery: previousUserQuery, shouldEnableRegenerateButton: shouldEnableRegenerateButton }) }));
567
628
  const getWorkingSpinner = () => (_jsx(_Fragment, { children: isAwaitingLlm && (_jsxs(Box, { id: "awaitingOutputContainer", sx: { display: "flex", alignItems: "center", fontSize: "smaller" }, children: [_jsx("span", { id: "working-span", style: { marginRight: "1rem" }, children: "Working..." }), _jsx(CircularProgress, { id: "awaitingOutputSpinner", sx: {
568
629
  color: "var(--bs-primary)",
569
630
  }, size: "1rem" })] })) }));
570
631
  const getThinking = () => (_jsx(_Fragment, { children: !isAwaitingLlm && turns.length > 0 && (
571
632
  // Only show thinking once streaming is complete
572
633
  _jsx(Thinking, { id: id, turns: turns, useNativeNames: useNativeNames })) }));
573
- const getResponseBox = () => (_jsx(Box, { id: "llm-response-div", sx: {
634
+ const getResponseBox = () => (_jsxs(Box, { id: "llm-response-div", sx: {
574
635
  ...divStyle,
575
636
  border: "var(--bs-border-width) var(--bs-border-style)",
576
637
  borderRadius: "var(--bs-border-radius)",
@@ -579,18 +640,27 @@ export const ChatCommon = ({ ref, ...props }) => {
579
640
  marginLeft: "10px",
580
641
  position: "relative",
581
642
  overflowY: "auto",
582
- }, children: _jsxs(Box, { id: "llm-responses", ref: chatOutputRef, sx: {
583
- backgroundColor,
584
- borderRadius: "0.5rem",
585
- fontSize: "16px",
586
- overflowY: "auto",
587
- paddingBottom: "60px",
588
- paddingLeft: "15px",
589
- paddingRight: "15px",
590
- paddingTop: "7.5px",
591
- scrollbarGutter: "stable",
592
- width: "100%",
593
- }, tabIndex: -1, children: [getOptionsMenu(), getOptionsMenuButton(), getChatHistory(), getNetworkIntro(), getAgentGreeting(), _jsx(SampleQueries, { disabled: isAwaitingLlm, handleSend: handleSend, sampleQueries: sampleQueries }), _jsx(Conversation, { id: id, includeAgentMessages: !givesFinalAnswer(selectedNetwork), shouldWrapOutput: shouldWrapOutput, turns: turns }), getThinking(), getWorkingSpinner(), getControlButtons()] }) }));
643
+ }, children: [clearChatConfirmationDialogOpen ? (_jsx(ConfirmationModal, { id: `${id}-clear-chat-confirmation-modal`, content: "This will clear the current chat session and any persisted chat history for this " +
644
+ "network. This operation cannot be undone. Are you sure you wish to continue?", handleCancel: () => {
645
+ setClearChatConfirmationDialogOpen(false);
646
+ }, handleOk: () => {
647
+ setClearChatConfirmationDialogOpen(false);
648
+ setTurns([]);
649
+ resetHistory(selectedNetwork);
650
+ setPreviousUserQuery("");
651
+ currentResponse.current = "";
652
+ }, okBtnLabel: "Yes, clear chat", title: `Clear all chat including history for ${networkDisplayName}?` })) : null, _jsxs(Box, { id: "llm-responses", ref: chatOutputRef, sx: {
653
+ backgroundColor,
654
+ borderRadius: "0.5rem",
655
+ fontSize: "16px",
656
+ overflowY: "auto",
657
+ paddingBottom: "60px",
658
+ paddingLeft: "15px",
659
+ paddingRight: "15px",
660
+ paddingTop: "7.5px",
661
+ scrollbarGutter: "stable",
662
+ width: "100%",
663
+ }, tabIndex: -1, children: [getOptionsMenu(), getOptionsMenuButton(), getChatHistory(), getNetworkIntro(), getAgentGreeting(), _jsx(SampleQueries, { disabled: isAwaitingLlm, handleSend: handleSend, sampleQueries: sampleQueries }), _jsx(Conversation, { id: id, includeAgentMessages: !givesFinalAnswer(selectedNetwork), shouldWrapOutput: shouldWrapOutput, turns: turns }), getThinking(), getWorkingSpinner(), getControlButtons()] })] }));
594
664
  const getUserInputBox = () => (_jsxs(Box, { id: "user-input-div", sx: {
595
665
  ...divStyle,
596
666
  alignItems: "center",
@@ -642,15 +712,12 @@ export const ChatCommon = ({ ref, ...props }) => {
642
712
  color: "var(--bs-secondary)",
643
713
  fontSize: "1rem",
644
714
  } }) }), "to design your own network!"] }));
645
- const allApiKeysPresent = missingApiKeys?.length === 0;
646
- const getMissingApiKeysOverlayBody = () => (_jsx(Typography, { component: "span", children: `API key(s) required for: ${missingApiKeys.join(", ")}. Please add the required key(s) in
647
- "Settings" to use this Network.` }));
648
715
  return (_jsx(Box, { id: `llm-chat-${id}`, sx: {
649
716
  height: "100%",
650
717
  position: "relative",
651
718
  }, children: selectedNetwork
652
- ? allApiKeysPresent
653
- ? getChatBox()
654
- : getErrorOverlay(getMissingApiKeysOverlayBody())
719
+ ? hasMissingApiKeys
720
+ ? getErrorOverlay("")
721
+ : getChatBox() // Parent will display error for this case
655
722
  : getErrorOverlay(getSelectNetworkOverlayBody()) }));
656
723
  };
@@ -1,9 +1,16 @@
1
1
  import { BaseMessage } from "@langchain/core/messages";
2
2
  import { FC } from "react";
3
+ import { ConversationTurn } from "./ConversationTurn.js";
3
4
  interface ChatHistoryProps {
4
5
  readonly messages: BaseMessage[];
5
6
  readonly id: string;
6
7
  }
8
+ /**
9
+ * Helper function to convert from BaseMessage format used in persisted chat history to ConversationTurn format used for
10
+ * rendering the conversation.
11
+ * @param chatHistory
12
+ */
13
+ export declare const toTurns: (chatHistory: BaseMessage[]) => ConversationTurn[];
7
14
  /**
8
15
  * Component for displaying chat history from previous interactions with the agent.
9
16
  */
@@ -10,7 +10,7 @@ import { AccordionLite } from "../../Common/AccordionLite.js";
10
10
  * rendering the conversation.
11
11
  * @param chatHistory
12
12
  */
13
- const toTurns = (chatHistory) => chatHistory
13
+ export const toTurns = (chatHistory) => chatHistory
14
14
  .filter((message) => message.type === "human" || message.type === "ai")
15
15
  .map((message) => {
16
16
  const role = message.type === "human" ? MessageRole.User : MessageRole.Agent;
@@ -1,10 +1,12 @@
1
1
  import { FC } from "react";
2
2
  interface ControlButtonsProps {
3
- handleClearChat: () => void;
4
3
  enableClearChatButton: boolean;
5
- isAwaitingLlm: boolean;
4
+ enableSaveChatButton: boolean;
5
+ handleClearChat: () => void;
6
+ handleSave: () => void;
6
7
  handleSend: (query: string) => void;
7
8
  handleStop: () => void;
9
+ isAwaitingLlm: boolean;
8
10
  previousUserQuery: string;
9
11
  shouldEnableRegenerateButton: boolean;
10
12
  }
@@ -16,14 +16,20 @@ limitations under the License.
16
16
  */
17
17
  import DeleteOutlined from "@mui/icons-material/DeleteOutlined";
18
18
  import Loop from "@mui/icons-material/Loop";
19
+ import SaveIcon from "@mui/icons-material/Save";
19
20
  import StopCircle from "@mui/icons-material/StopCircle";
20
21
  import Box from "@mui/material/Box";
22
+ import Tooltip from "@mui/material/Tooltip";
21
23
  import { SmallLlmChatButton } from "../Common/LlmChatButton.js";
22
24
  //#endregion: Types
23
25
  /**
24
26
  * Generate the Control Buttons for a chat window (resend, clear chat, stop)
25
27
  * @returns A fragment containing the Control Buttons.
26
28
  */
27
- export const ControlButtons = ({ handleClearChat, enableClearChatButton, isAwaitingLlm, handleSend, handleStop, previousUserQuery, shouldEnableRegenerateButton, }) => (_jsx(_Fragment, { children: isAwaitingLlm ? (
29
+ export const ControlButtons = ({ enableClearChatButton, enableSaveChatButton, handleClearChat, handleSave, handleSend, handleStop, isAwaitingLlm, previousUserQuery, shouldEnableRegenerateButton, }) => (_jsx(_Fragment, { children: isAwaitingLlm ? (
28
30
  // Stop Button
29
- _jsx(SmallLlmChatButton, { "aria-label": "Stop", disabled: !isAwaitingLlm, id: "stop-output-button", onClick: () => handleStop(), children: _jsx(StopCircle, { fontSize: "small", id: "stop-button-icon" }) })) : (_jsxs(Box, { sx: { display: "flex", gap: 1 }, children: [_jsx(SmallLlmChatButton, { "aria-label": "Clear Chat", disabled: !enableClearChatButton, id: "clear-chat-button", onClick: handleClearChat, children: _jsx(DeleteOutlined, { fontSize: "small", id: "stop-button-icon" }) }), _jsx(SmallLlmChatButton, { "aria-label": "Regenerate", disabled: !shouldEnableRegenerateButton, id: "regenerate-output-button", onClick: () => handleSend(previousUserQuery), children: _jsx(Loop, { fontSize: "small", id: "generate-icon" }) })] })) }));
31
+ _jsx(Tooltip, { title: "Terminate request", children: _jsx(SmallLlmChatButton, { "aria-label": "Stop", disabled: !isAwaitingLlm, id: "stop-output-button", onClick: () => handleStop(), children: _jsx(StopCircle, { fontSize: "small", id: "stop-button-icon" }) }) })) : (_jsxs(Box, { sx: { display: "flex", gap: 1 }, children: [_jsx(Tooltip, { title: "Save Chat", children: _jsx("span", { children: _jsx(SmallLlmChatButton, { "aria-label": "Save Chat", disabled: !enableSaveChatButton, id: "save-chat-button", onClick: handleSave, children: _jsx(SaveIcon, { fontSize: "small", id: "save-button-icon" }) }) }) }), _jsx(Tooltip, { title: "Clear Chat", children: _jsx("span", { children: _jsx(SmallLlmChatButton, { "aria-label": "Clear Chat", disabled: !enableClearChatButton, id: "clear-chat-button", onClick: handleClearChat, children: _jsx(DeleteOutlined, { fontSize: "small", id: "stop-button-icon", sx: {
32
+ "&:hover": {
33
+ color: "error.main",
34
+ },
35
+ } }) }) }) }), _jsx(Tooltip, { title: "Resend last query", children: _jsx("span", { children: _jsx(SmallLlmChatButton, { "aria-label": "Regenerate", disabled: !shouldEnableRegenerateButton, id: "regenerate-output-button", onClick: () => handleSend(previousUserQuery), children: _jsx(Loop, { fontSize: "small", id: "generate-icon" }) }) }) })] })) }));
@@ -3,11 +3,11 @@ import { ChatMessageType } from "../../../generated/neuro-san/NeuroSanClient.js"
3
3
  * The various messages roles in a conversation turn.
4
4
  */
5
5
  export declare enum MessageRole {
6
- "Agent" = "Agent",
7
- "Error" = "Error",
8
- "FinalAnswer" = "FinalAnswer",
9
- "User" = "User",
10
- "Warning" = "Warning"
6
+ Agent = "Agent",
7
+ Error = "Error",
8
+ FinalAnswer = "FinalAnswer",
9
+ User = "User",
10
+ Warning = "Warning"
11
11
  }
12
12
  /**
13
13
  * Represents a single turn in a conversation, which may include a message from the user or agent, or an error
@@ -4,6 +4,7 @@
4
4
  export var MessageRole;
5
5
  (function (MessageRole) {
6
6
  MessageRole["Agent"] = "Agent";
7
+ // eslint-disable-next-line no-shadow
7
8
  MessageRole["Error"] = "Error";
8
9
  MessageRole["FinalAnswer"] = "FinalAnswer";
9
10
  MessageRole["User"] = "User";
@@ -5,8 +5,8 @@ import { useMemo } from "react";
5
5
  import ReactMarkdown from "react-markdown";
6
6
  import { MessageRole } from "./ConversationTurn.js";
7
7
  import { ChatMessageType } from "../../../generated/neuro-san/NeuroSanClient.js";
8
+ import { toDisplayName } from "../../../utils/AgentName.js";
8
9
  import { AccordionLite } from "../../Common/AccordionLite.js";
9
- import { cleanUpAgentName, removeTrailingUuid } from "../Common/Utils.js";
10
10
  /**
11
11
  * Set of message types that should be included in the "thinking" section.
12
12
  */
@@ -19,7 +19,7 @@ const THINKING_MESSAGE_TYPES = new Set([
19
19
  const DEFAULT_AGENT_NAME = "agent";
20
20
  const formatTurn = (turn, useNativeNames) => {
21
21
  const agentName = turn.agentName ?? DEFAULT_AGENT_NAME;
22
- const displayName = useNativeNames ? agentName : cleanUpAgentName(removeTrailingUuid(agentName));
22
+ const displayName = toDisplayName(agentName, useNativeNames);
23
23
  const headerLine = `**${displayName}**: ${turn.text || ""}`;
24
24
  const structureLine = turn.structure == null ? "" : `\n\`${JSON.stringify(turn.structure, null, 2)}\``;
25
25
  return `${headerLine}${structureLine}`;
@@ -3,10 +3,3 @@ export declare const KNOWN_MESSAGE_TYPES: ChatMessageType[];
3
3
  export declare const KNOWN_MESSAGE_TYPES_FOR_PLASMA: ChatMessageType[];
4
4
  export declare const chatMessageFromChunk: (chunk: string) => ChatMessage | null;
5
5
  export declare const checkError: (chatMessageJson: Record<string, unknown>) => string | null;
6
- export declare const removeTrailingUuid: (agentName: string) => string;
7
- /**
8
- * Convert FOO_BAR to more human "Foo Bar".
9
- * @param agentName Agent name in SNAKE_CASE format.
10
- * @returns User-friendly agent name.
11
- */
12
- export declare const cleanUpAgentName: (agentName: string) => string;
@@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
16
  import { isEmpty } from "lodash-es";
17
- import startCase from "lodash-es/startCase.js";
18
17
  import { ChatMessageType } from "../../../generated/neuro-san/NeuroSanClient.js";
19
18
  // We ignore any messages that are not of these types
20
19
  export const KNOWN_MESSAGE_TYPES = [
@@ -60,14 +59,3 @@ export const checkError = (chatMessageJson) => {
60
59
  const { error, traceback, tool } = chatMessageJson;
61
60
  return `Error occurred. Error: "${error}", traceback: "${traceback}", tool: "${tool}"`;
62
61
  };
63
- export const removeTrailingUuid = (agentName) => {
64
- return agentName?.replace(/-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/u, "");
65
- };
66
- /**
67
- * Convert FOO_BAR to more human "Foo Bar".
68
- * @param agentName Agent name in SNAKE_CASE format.
69
- * @returns User-friendly agent name.
70
- */
71
- export const cleanUpAgentName = (agentName) => {
72
- return startCase(agentName);
73
- };
@@ -18,7 +18,7 @@ import ChevronRightIcon from "@mui/icons-material/ChevronRight";
18
18
  import Breadcrumbs from "@mui/material/Breadcrumbs";
19
19
  import Grid from "@mui/material/Grid";
20
20
  import Typography from "@mui/material/Typography";
21
- import startCase from "lodash-es/startCase.js";
21
+ import startCase from "lodash-es/startCase";
22
22
  export const NeuroAIBreadcrumbs = ({ pathname }) => {
23
23
  const urlPaths = pathname?.split("/").filter((path) => path !== "");
24
24
  const pageName = startCase(urlPaths?.at(-1));
@@ -7,6 +7,8 @@ import { navigateToUrl } from "../../utils/BrowserNavigation.js";
7
7
  const HeaderLine = styled("h6")({
8
8
  fontWeight: "bold",
9
9
  fontSize: "smaller",
10
+ margin: 0,
11
+ marginBottom: "0.9rem",
10
12
  });
11
13
  const LinkDivider = styled("div")({
12
14
  border: "1px solid var(--bs-accent3-medium)",
@@ -16,6 +18,7 @@ const LinkDivider = styled("div")({
16
18
  const FooterSectionHeader = styled("div")({
17
19
  display: "inline-flex",
18
20
  flexDirection: "column",
21
+ alignItems: "flex-start",
19
22
  marginTop: "0.1rem",
20
23
  });
21
24
  const FooterItemLink = styled("a")(({ theme }) => ({
@@ -31,6 +34,7 @@ const FooterItemLink = styled("a")(({ theme }) => ({
31
34
  const FooterContainer = styled("footer")({
32
35
  backgroundColor: "var(--bs-primary)",
33
36
  borderTop: "var(--bs-border-width) var(--bs-border-style) var(--bs-gray-lighter)",
37
+ alignItems: "flex-start",
34
38
  columnGap: "60px",
35
39
  display: "flex",
36
40
  justifyContent: "center",
@@ -1,10 +1,10 @@
1
1
  import { SnackbarOrigin } from "notistack";
2
2
  import { JSX as ReactJSX } from "react";
3
3
  export declare enum NotificationType {
4
- "success" = 0,
5
- "info" = 1,
6
- "warning" = 2,
7
- "error" = 3
4
+ success = 0,
5
+ info = 1,
6
+ warning = 2,
7
+ error = 3
8
8
  }
9
9
  export declare const closeNotification: () => void;
10
10
  /**
@@ -1,9 +1,11 @@
1
+ import { NodeChange, Node as RFNode } from "@xyflow/react";
1
2
  import { Dispatch, FC, SetStateAction } from "react";
2
- import { AgentConversation } from "./AgentConversations.js";
3
- import { AgentNetworkDefinitionEntry } from "./const.js";
4
- import { ThoughtBubbleEdgeShape } from "./ThoughtBubbleEdge.js";
5
- import { AgentIconSuggestions } from "../../controller/Types/AgentIconSuggestions.js";
6
- import { ConnectivityInfo } from "../../generated/neuro-san/NeuroSanClient.js";
3
+ import { AgentConversation } from "../AgentConversations.js";
4
+ import { AgentNodeProps } from "./AgentNode.js";
5
+ import { AgentNetworkDefinitionEntry } from "../const.js";
6
+ import { AgentIconSuggestions } from "../../../controller/Types/AgentIconSuggestions.js";
7
+ import { ConnectivityInfo } from "../../../generated/neuro-san/NeuroSanClient.js";
8
+ import { ThoughtBubbleEdgeShape } from "../ThoughtBubbles/ThoughtBubbleEdge.js";
7
9
  export interface AgentFlowProps {
8
10
  readonly agentCounts?: Map<string, number>;
9
11
  readonly agentIconSuggestions?: AgentIconSuggestions;
@@ -40,4 +42,12 @@ export interface AgentFlowProps {
40
42
  }>>>;
41
43
  }
42
44
  export declare const DOCK_BANNER_AUTO_DISMISS_MS = 5000;
45
+ /**
46
+ * Filters node events based on the current mode. Don't allow any topological-modifying events (adding, deleting nodes)
47
+ * and in Agent Network Designer mode, don't allow dragging nodes (position changes) either.
48
+ * @param change The node change event to filter.
49
+ * @param isAgentNetworkDesignerMode Whether the flow is in Agent Network Designer mode (read-only preview).
50
+ * @return True if the event should be allowed, false if it should be filtered out.
51
+ */
52
+ export declare const filterNodeEvents: (change: NodeChange<RFNode<AgentNodeProps>>, isAgentNetworkDesignerMode: boolean) => boolean;
43
53
  export declare const AgentFlow: FC<AgentFlowProps>;