@cognizant-ai-lab/ui-common 1.6.0 → 1.8.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 (72) hide show
  1. package/dist/Theme/Palettes.d.ts +0 -9
  2. package/dist/Theme/Palettes.js +0 -18
  3. package/dist/components/AgentChat/ChatCommon/ChatCommon.d.ts +11 -7
  4. package/dist/components/AgentChat/ChatCommon/ChatCommon.js +119 -90
  5. package/dist/components/AgentChat/ChatCommon/Const.d.ts +1 -0
  6. package/dist/components/AgentChat/ChatCommon/Const.js +2 -0
  7. package/dist/components/AgentChat/ChatCommon/ControlButtons.d.ts +1 -1
  8. package/dist/components/AgentChat/ChatCommon/ControlButtons.js +7 -4
  9. package/dist/components/AgentChat/ChatCommon/FormattedMarkdown.js +3 -0
  10. package/dist/components/AgentChat/ChatCommon/SendButton.js +7 -6
  11. package/dist/components/AgentChat/ChatCommon/SyntaxHighlighterThemes.js +1 -1
  12. package/dist/components/AgentChat/ChatCommon/Thinking.d.ts +1 -0
  13. package/dist/components/AgentChat/ChatCommon/Thinking.js +10 -7
  14. package/dist/components/AgentChat/Common/LlmChatButton.d.ts +0 -2
  15. package/dist/components/AgentChat/Common/LlmChatButton.js +6 -9
  16. package/dist/components/AgentChat/VoiceChat/MicrophoneButton.js +12 -10
  17. package/dist/components/ChatBot/ChatBot.d.ts +0 -4
  18. package/dist/components/ChatBot/ChatBot.js +2 -2
  19. package/dist/components/Common/Navbar.js +24 -22
  20. package/dist/components/Common/StatusLight.d.ts +9 -0
  21. package/dist/components/Common/StatusLight.js +21 -0
  22. package/dist/components/ErrorPage/ErrorBoundary.js +1 -1
  23. package/dist/components/ErrorPage/ErrorPage.d.ts +3 -3
  24. package/dist/components/ErrorPage/ErrorPage.js +4 -3
  25. package/dist/components/Logo/Common.d.ts +4 -0
  26. package/dist/components/Logo/Common.js +8 -0
  27. package/dist/components/Logo/CustomerLogo.d.ts +23 -0
  28. package/dist/components/Logo/CustomerLogo.js +41 -0
  29. package/dist/components/MultiAgentAccelerator/AgentConversations.js +0 -1
  30. package/dist/components/MultiAgentAccelerator/AgentFlow.js +85 -53
  31. package/dist/components/MultiAgentAccelerator/AgentNode.js +23 -27
  32. package/dist/components/MultiAgentAccelerator/GraphLayouts.d.ts +17 -10
  33. package/dist/components/MultiAgentAccelerator/GraphLayouts.js +4 -6
  34. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +3 -9
  35. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +102 -47
  36. package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.d.ts +0 -5
  37. package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.js +30 -25
  38. package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.d.ts +3 -4
  39. package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.js +84 -168
  40. package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.d.ts +30 -13
  41. package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +157 -100
  42. package/dist/components/MultiAgentAccelerator/Tour/MainTourSteps.js +10 -8
  43. package/dist/components/MultiAgentAccelerator/const.d.ts +2 -0
  44. package/dist/components/MultiAgentAccelerator/const.js +4 -0
  45. package/dist/components/Settings/ApiKeyInput.d.ts +16 -0
  46. package/dist/components/Settings/ApiKeyInput.js +69 -0
  47. package/dist/components/Settings/InfoTip.d.ts +8 -0
  48. package/dist/components/Settings/InfoTip.js +14 -0
  49. package/dist/components/Settings/SettingsDialog.js +379 -178
  50. package/dist/components/Settings/SettingsRow.d.ts +18 -0
  51. package/dist/components/Settings/SettingsRow.js +35 -0
  52. package/dist/controller/agent/Agent.d.ts +3 -20
  53. package/dist/controller/agent/Agent.js +9 -43
  54. package/dist/controller/agent/IconSuggestions.d.ts +35 -0
  55. package/dist/controller/agent/IconSuggestions.js +85 -0
  56. package/dist/controller/llm/Providers.d.ts +2 -0
  57. package/dist/controller/llm/Providers.js +41 -0
  58. package/dist/index.d.ts +0 -2
  59. package/dist/index.js +0 -2
  60. package/dist/state/IconSuggestions.d.ts +27 -0
  61. package/dist/state/IconSuggestions.js +50 -0
  62. package/dist/state/Settings.d.ts +19 -3
  63. package/dist/state/Settings.js +24 -1
  64. package/dist/tsconfig.build.tsbuildinfo +1 -1
  65. package/dist/utils/title.js +1 -1
  66. package/package.json +5 -1
  67. package/dist/components/Common/CustomerLogo.d.ts +0 -17
  68. package/dist/components/Common/CustomerLogo.js +0 -49
  69. package/dist/components/Common/MUIAccordion.d.ts +0 -17
  70. package/dist/components/Common/MUIAccordion.js +0 -66
  71. package/dist/utils/useLocalStorage.d.ts +0 -1
  72. package/dist/utils/useLocalStorage.js +0 -55
@@ -1,119 +1,176 @@
1
1
  import { cleanUpAgentName, removeTrailingUuid } from "../../AgentChat/Common/Utils.js";
2
+ //#endregion
2
3
  /**
3
- * Iteratively sort all children of tree nodes using a queue-based approach
4
- * @param nodes - Array of tree nodes to sort
5
- * @param nodeIndex - Index mapping node IDs to their AgentInfo and display names, used for sorting by display name
4
+ * Recursively searches for a tree item with the specified ID within the given list of tree items.
5
+ * @param items - The list of tree items to search through
6
+ * @param itemId - The ID of the tree item to find
7
+ * @returns The tree item with the matching ID, or undefined if not found
8
+ *
9
+ * @note Short-circuiting is used to avoid unnecessary searches once a match is found (compared to e.g., `flatMap()`)
6
10
  */
7
- const sortTreeNodes = (nodes, nodeIndex) => {
8
- // Sort the top level nodes first. We sort by displayName because that's what the user sees
9
- nodes.sort((a, b) => {
10
- const aDisplayName = nodeIndex.get(a.id)?.displayName ?? a.label;
11
- const bDisplayName = nodeIndex.get(b.id)?.displayName ?? b.label;
12
- return aDisplayName.localeCompare(bDisplayName);
13
- });
14
- // Use a queue for breadth-first traversal to avoid recursion
15
- const queue = [...nodes];
16
- let index = 0;
17
- // For each node in the queue, sort its children and add them to the end of the queue
18
- while (index < queue.length) {
19
- const node = queue[index];
20
- index += 1;
21
- if (node.children && node.children.length > 0) {
22
- node.children.sort((a, b) => {
23
- const aDisplayName = nodeIndex.get(a.id)?.displayName ?? a.label;
24
- const bDisplayName = nodeIndex.get(b.id)?.displayName ?? b.label;
25
- return aDisplayName.localeCompare(bDisplayName);
26
- });
27
- queue.push(...node.children);
11
+ export const findTreeItemById = (items, itemId) => {
12
+ for (const item of items) {
13
+ if (item.id === itemId) {
14
+ return item;
15
+ }
16
+ const childMatch = item.children ? findTreeItemById(item.children, itemId) : undefined;
17
+ if (childMatch) {
18
+ return childMatch;
28
19
  }
29
20
  }
21
+ return undefined;
30
22
  };
31
23
  /**
32
- * Add a single AgentInfo entry into the tree structure
24
+ * Converts a raw agent name into a display name for the tree view, respecting the user's preference for native
25
+ * vs cleaned names.
26
+ * @param itemName - The raw agent name from the API
27
+ * @param useNativeNames - Whether to use native names or cleaned-up names for display
28
+ * @returns The display name to show in the tree view
29
+ */
30
+ const toDisplayName = (itemName, useNativeNames) => useNativeNames ? itemName : cleanUpAgentName(removeTrailingUuid(itemName));
31
+ /**
32
+ * Converts an AgentInfo object into a tree item model representing a network (leaf node).
33
+ * @param network - The AgentInfo object containing details about the network
34
+ * @param label - The label to use for the tree item (usually derived from the agent name)
35
+ * @param useNativeNames - Whether to use native names or cleaned-up names for display
36
+ * @param metadata - Additional metadata for the network tree item, such as icon suggestions and temporary network info
37
+ * @returns An AgentNetworkTreeItemModel representing the network as a leaf node in the tree
38
+ */
39
+ const toNetworkLeaf = (network, label, useNativeNames, metadata = {}) => ({
40
+ id: network.agent_name,
41
+ label,
42
+ displayName: toDisplayName(label, useNativeNames),
43
+ iconSuggestion: metadata.iconSuggestion,
44
+ isNetwork: true,
45
+ tags: network.tags,
46
+ temporaryNetworkExpirationTime: metadata.temporaryNetworkExpirationTime,
47
+ temporaryNetworkHocon: metadata.temporaryNetworkHocon,
48
+ });
49
+ /**
50
+ * Recursively sort tree nodes and their children by display name
51
+ * @param nodes - Array of tree nodes to sort
52
+ * @returns New array of tree nodes sorted by display name, with children also sorted
53
+ */
54
+ const toSortedTreeNodes = (nodes) => [...nodes]
55
+ .sort((a, b) => a.displayName.localeCompare(b.displayName))
56
+ .map((node) => ({
57
+ ...node,
58
+ children: node.children ? toSortedTreeNodes(node.children) : undefined,
59
+ }));
60
+ /**
61
+ * Creates a tree item model representing a category (folder node) in the tree view.
62
+ * @param id - The unique ID for the folder node, typically derived from the category path
63
+ * @param label - The label to use for the tree item (usually derived from the category name)
64
+ * @param useNativeNames - Whether to use native names or cleaned-up names for display
65
+ * @returns An AgentNetworkTreeItemModel representing the category as a folder node in the tree
66
+ */
67
+ const toFolderNode = (id, label, useNativeNames) => ({
68
+ id,
69
+ label,
70
+ displayName: toDisplayName(label, useNativeNames),
71
+ isNetwork: false,
72
+ children: [],
73
+ });
74
+ /**
75
+ * Recursively adds a network to the categorized tree structure based on its agent name parts.
76
+ * @param nodes - The current list of tree nodes at this level of the hierarchy
77
+ * @param network - The AgentInfo object representing the network to add
78
+ * @param useNativeNames - Whether to use native names or cleaned-up names for display
79
+ * @param metadata - Additional metadata for the network tree item, such as icon suggestions and temporary network info
80
+ * @param parts - The parts of the agent name split by "/", used to determine the category hierarchy
81
+ * @param depth - The current depth in the category hierarchy, used to determine which part of the agent name to use
82
+ * for this level. For example, if the agent name is "category/subcategory/network", then at depth 0 we use "category",
83
+ * at depth 1 we use "subcategory", and at depth 2 we use "network".
33
84
  */
34
- const addNetworkToTree = (network, result, uncategorized, map, nodeIndex, displayNameCounts) => {
85
+ const withCategorizedNetworkAdded = (nodes, network, useNativeNames, metadata, parts, depth = 0) => {
86
+ const label = parts[depth];
87
+ // The node ID is constructed from the parts up to the current depth, analogous to a file path.
88
+ const nodeId = parts.slice(0, depth + 1).join("/");
89
+ // It's a network (leaf node) if we're at the last part of the agent name, otherwise it's a category (folder node)
90
+ const isNetwork = depth === parts.length - 1;
91
+ // Check if a node with this ID already exists at the current level
92
+ const existingIndex = nodes.findIndex((node) => node.id === nodeId);
93
+ // If it's a network, we create a leaf node. If it's a category, we either create a new folder node or
94
+ // update the existing one with the new child.
95
+ const nextNode = isNetwork
96
+ ? toNetworkLeaf(network, label, useNativeNames, metadata)
97
+ : {
98
+ ...(existingIndex >= 0 ? nodes[existingIndex] : toFolderNode(nodeId, label, useNativeNames)),
99
+ children: withCategorizedNetworkAdded(existingIndex >= 0 ? (nodes[existingIndex].children ?? []) : [], network, useNativeNames, metadata, parts, depth + 1),
100
+ };
101
+ // If the node doesn't already exist, we add it to the list. If it does exist, we replace it with the updated node.
102
+ if (existingIndex < 0) {
103
+ return [...nodes, nextNode];
104
+ }
105
+ // Return a new array with the existing node replaced by the updated node
106
+ return nodes.map((node, index) => (index === existingIndex ? nextNode : node));
107
+ };
108
+ /**
109
+ * Adds a network to the tree build state, either as an uncategorized item if its agent name has no "/"
110
+ * or as a categorized item
111
+ * @param state - The current state of the tree build, containing categorized and uncategorized items
112
+ * @param network - The AgentInfo object representing the network to add to the tree
113
+ * @param useNativeNames - Whether to use native names or cleaned-up names for display
114
+ * @param metadata - Additional metadata for the network tree item, such as icon suggestions and temporary network info
115
+ */
116
+ const withNetworkAdded = (state, network, useNativeNames, metadata) => {
117
+ // Split the agent name into parts based on "/", which indicates category hierarchy. For example, an agent name
35
118
  const parts = network.agent_name.split("/");
36
- // If there's only one part, it means this network isn't in any folder, so we add it directly under "Uncategorized"
119
+ // If there are no "/" in the agent name, we consider it uncategorized and add it to the uncategorized items list.
37
120
  if (parts.length === 1) {
38
- uncategorized.children.push({ id: network.agent_name, label: network.agent_name, children: [] });
39
- nodeIndex.set(network.agent_name, { agentInfo: network, displayName: cleanUpAgentName(network.agent_name) });
40
- }
41
- else {
42
- // Otherwise, we need to build out the tree structure based on the parts of the agent_name. Some paths might
43
- // already exist if we've processed another network that shares the same parent folders,
44
- // so we check the map to avoid duplicating nodes.
45
- let currentLevel = result;
46
- parts.forEach((part, index) => {
47
- // Build the full path ID by joining all parts up to the current position
48
- const nodeId = parts.slice(0, index + 1).join("/");
49
- let node = map.get(nodeId);
50
- if (!node) {
51
- // If we haven't created a node for this path yet, create it and add it to the map
52
- node = { id: nodeId, label: part, children: [] };
53
- map.set(nodeId, node);
54
- if (index === parts.length - 1) {
55
- const cleanedName = cleanUpAgentName(removeTrailingUuid(part));
56
- // Handle duplicate display names by appending a number (e.g. "macys", "macys 2", "macys 3", etc.)
57
- const count = displayNameCounts.get(cleanedName) || 0;
58
- displayNameCounts.set(cleanedName, count + 1);
59
- const displayName = count > 0 ? `${cleanedName} ${count + 1}` : cleanedName;
60
- // Add the AgentInfo to the nodeIndex for quick lookup later, using the full path as the key
61
- nodeIndex.set(nodeId, { agentInfo: network, displayName });
62
- }
63
- // If this is a top-level node (index 0), add it directly to the result.
64
- // Otherwise, find its parent and add it there.
65
- if (index === 0) {
66
- // Top-level node, add directly to result
67
- currentLevel.push(node);
68
- }
69
- else {
70
- // Not a top-level node, find parent and add to its children
71
- const parentId = parts.slice(0, index).join("/");
72
- const parentNode = map.get(parentId);
73
- if (parentNode) {
74
- parentNode.children.push(node);
75
- }
76
- }
77
- }
78
- // Move down to the next level of the tree for the next iteration
79
- currentLevel = node.children;
80
- });
121
+ return {
122
+ ...state,
123
+ uncategorizedItems: [
124
+ ...state.uncategorizedItems,
125
+ toNetworkLeaf(network, network.agent_name, useNativeNames, metadata),
126
+ ],
127
+ };
81
128
  }
129
+ // Return the updated state with the network added to the categorized items
130
+ return {
131
+ ...state,
132
+ categorizedItems: withCategorizedNetworkAdded(state.categorizedItems, network, useNativeNames, metadata, parts),
133
+ };
82
134
  };
83
135
  /**
84
136
  * Build a tree view structure from a flat list of networks.
85
137
  * The list of networks comes from a call to the Neuro-san /list API
86
- * The tree structure is used by the RichTreeView component to display the networks
87
- * @param networks - Array of networks from the Neuro-san /list API
88
- * @param temporaryNetworks - Array of temporary networks (e.g. ones recently created by the user)
89
- * @returns Array of {@linkcode TreeViewDefaultItemModelProperties} objects representing the tree structure and an
90
- * index for rapid access
138
+ * The tree structure is used by the RichTreeView component to display the networks.
139
+ *
140
+ * @param useNativeNames - Whether to use the raw agent names from the API or to clean them up for display.
141
+ * @param regularNetworks - Array of networks from the Neuro-san /list API
142
+ * @param temporaryNetworks - Array of temporary networks (e.g., ones recently created by the user)
143
+ * @param iconSuggestions
144
+ * @returns Array of {@linkcode AgentNetworkTreeItemModel} objects representing the tree structure
91
145
  */
92
- export const buildTreeViewItems = (networks, temporaryNetworks) => {
93
- // Map to keep track of created nodes in a tree structure
94
- const treeBuilderMap = new Map();
95
- // Index to quickly look up AgentInfo by node ID without having to traverse the tree
96
- const nodeIndex = new Map();
97
- // Resulting tree view items, ready for consumption by RichTreeView
98
- const treeViewItems = [];
99
- // Special parent node for networks that aren't in any folder
100
- const uncategorized = {
101
- id: "uncategorized",
102
- label: "Uncategorized",
103
- children: [],
146
+ export const buildTreeViewItems = (useNativeNames, regularNetworks = [], temporaryNetworks = [], iconSuggestions = {}) => {
147
+ let tree = {
148
+ categorizedItems: [],
149
+ uncategorizedItems: [],
104
150
  };
105
- const displayNameCounts = new Map();
106
- // Build a tree structure from the flat list of networks.
107
- // The networks come in as a series of "paths" like "industry/retail/macys" and we need to build a tree
108
- // structure from that.
109
- networks.forEach((network) => addNetworkToTree(network, treeViewItems, uncategorized, treeBuilderMap, nodeIndex, displayNameCounts));
110
- // Now handle temporary networks
111
- temporaryNetworks?.forEach((temporaryNetwork) => addNetworkToTree(temporaryNetwork.agentInfo, treeViewItems, uncategorized, treeBuilderMap, nodeIndex, displayNameCounts));
112
- // Add "Uncategorized" to the result if there are any such networks
113
- if (uncategorized.children.length > 0) {
114
- treeViewItems.push(uncategorized);
151
+ for (const network of regularNetworks) {
152
+ tree = withNetworkAdded(tree, network, useNativeNames, {
153
+ iconSuggestion: iconSuggestions[network.agent_name],
154
+ });
155
+ }
156
+ for (const temporaryNetwork of temporaryNetworks) {
157
+ tree = withNetworkAdded(tree, temporaryNetwork.agentInfo, useNativeNames, {
158
+ iconSuggestion: "HourglassTop",
159
+ temporaryNetworkExpirationTime: new Date(temporaryNetwork.reservation.expiration_time_in_seconds * 1000),
160
+ temporaryNetworkHocon: temporaryNetwork.networkHocon,
161
+ });
115
162
  }
116
- // Sort all nodes in the tree
117
- sortTreeNodes(treeViewItems, nodeIndex);
118
- return { treeViewItems, nodeIndex };
163
+ const treeViewItems = tree.uncategorizedItems.length > 0
164
+ ? [
165
+ ...tree.categorizedItems,
166
+ {
167
+ id: "uncategorized",
168
+ label: "uncategorized",
169
+ displayName: toDisplayName("uncategorized", useNativeNames),
170
+ isNetwork: false,
171
+ children: tree.uncategorizedItems,
172
+ },
173
+ ]
174
+ : tree.categorizedItems;
175
+ return toSortedTreeNodes(treeViewItems);
119
176
  };
@@ -24,13 +24,15 @@ export const MAIN_TOUR_STEPS = [
24
24
  placement: "bottom",
25
25
  },
26
26
  {
27
- content: "Click this icon to connect to any Neuro SAN backend service. Mouse over it to see the current " +
28
- "server you are connected to including the version.",
29
- target: () => document.querySelector("#agent-network-settings-icon"),
27
+ content: "This indicator shows the online status of the Neuro SAN backend service. " +
28
+ "Mouse over it to see the current server URL, status, version, and any error details.",
29
+ target: () => document.querySelector("#multi-agent-accelerator-sidebar-agent-network-status-light"),
30
30
  placement: "bottom",
31
31
  },
32
32
  {
33
- content: "These are the agents within the current network.",
33
+ content: "These are the agents within the current network. The arrows show the flow of information between " +
34
+ "them and the tools used by each agent. For networks you have created, you can click on an agent " +
35
+ "to edit it",
34
36
  target: () => document.querySelector("#multi-agent-accelerator-grid-agent-flow"),
35
37
  placement: "auto",
36
38
  },
@@ -61,14 +63,14 @@ export const MAIN_TOUR_STEPS = [
61
63
  placement: "top",
62
64
  },
63
65
  {
64
- content: "These buttons allow you to control what is shown in the Chat window. " +
65
- "Try mousing over them to see what they do!",
66
- target: () => document.querySelector("#show-thinking-button"),
66
+ content: "Click this button to display a menu that allows you to choose how content is displayed in the chat window",
67
+ target: () => document.querySelector("#agent-network-ui-options-menu-button-container"),
67
68
  placement: "bottom",
68
69
  },
69
70
  {
70
71
  content: "Access Settings from this icon. Here you can change the look and feel of the application, including " +
71
- "auto branding for a particular customer!",
72
+ "auto branding for a particular customer, enter API keys for providers like OpenAI and Anthropic, and " +
73
+ "configure the URL used to access Neuro SAN services!",
72
74
  target: () => document.querySelector("#settings-icon"),
73
75
  placement: "top",
74
76
  },
@@ -26,3 +26,5 @@ export declare enum DisplayAs {
26
26
  LANGCHAIN_TOOL = "langchain_tool",
27
27
  EXTERNAL_AGENT = "external_agent"
28
28
  }
29
+ export declare const GRACE_PERIOD_MS: number;
30
+ export declare const SHOW_TOUR_DELAY_MS = 5000;
@@ -45,3 +45,7 @@ export var DisplayAs;
45
45
  DisplayAs["LANGCHAIN_TOOL"] = "langchain_tool";
46
46
  DisplayAs["EXTERNAL_AGENT"] = "external_agent";
47
47
  })(DisplayAs || (DisplayAs = {}));
48
+ // Display expired temporary networks for this amount of time after they expire so users can see what happened
49
+ export const GRACE_PERIOD_MS = 5 * 60 * 1000; // 5 minutes
50
+ // We show the tour modal after this amount of time so as not to "pounce" on the user when they first open the app
51
+ export const SHOW_TOUR_DELAY_MS = 5000;
@@ -0,0 +1,16 @@
1
+ import { FC } from "react";
2
+ interface ApiKeyInputProps {
3
+ readonly forgetKey: () => void;
4
+ readonly id: string;
5
+ readonly logo: string;
6
+ readonly onSave: (key: string) => void;
7
+ readonly onTest: (key: string) => Promise<boolean>;
8
+ readonly persistedValue: string;
9
+ readonly placeholder: string;
10
+ readonly vendor: string;
11
+ }
12
+ /**
13
+ * Component for inputting an API key for a given vendor, with the ability to test the key and forget the saved key.
14
+ */
15
+ export declare const ApiKeyInput: FC<ApiKeyInputProps>;
16
+ export {};
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import ClearIcon from "@mui/icons-material/Clear";
3
+ import Visibility from "@mui/icons-material/Visibility";
4
+ import VisibilityOff from "@mui/icons-material/VisibilityOff";
5
+ import Box from "@mui/material/Box";
6
+ import Button from "@mui/material/Button";
7
+ import FormLabel from "@mui/material/FormLabel";
8
+ import IconButton from "@mui/material/IconButton";
9
+ import InputAdornment from "@mui/material/InputAdornment";
10
+ import TextField from "@mui/material/TextField";
11
+ import Tooltip from "@mui/material/Tooltip";
12
+ import { useEffect, useState } from "react";
13
+ import { ConfirmationModal } from "../Common/ConfirmationModal.js";
14
+ import { StatusLight } from "../Common/StatusLight.js";
15
+ /**
16
+ * Component for inputting an API key for a given vendor, with the ability to test the key and forget the saved key.
17
+ */
18
+ export const ApiKeyInput = ({ forgetKey, id, logo, onSave, onTest, persistedValue, placeholder, vendor, }) => {
19
+ const [inputValue, setInputValue] = useState(persistedValue ?? "");
20
+ const [keyValidated, setKeyValidated] = useState(null);
21
+ const [isValidating, setIsValidating] = useState(false);
22
+ const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
23
+ const [showKey, setShowKey] = useState(false);
24
+ const handleValueChange = (e) => {
25
+ setKeyValidated(null);
26
+ setInputValue(e.target.value);
27
+ };
28
+ const handleOnTest = async () => {
29
+ setIsValidating(true);
30
+ setKeyValidated(null);
31
+ try {
32
+ const isValid = await onTest(inputValue);
33
+ setKeyValidated(isValid);
34
+ }
35
+ finally {
36
+ setIsValidating(false);
37
+ }
38
+ };
39
+ const disableActions = !inputValue || inputValue === persistedValue || isValidating;
40
+ // Sync with persisted value changes - if the persisted value changes from outside
41
+ useEffect(() => {
42
+ setInputValue(persistedValue ?? "");
43
+ setKeyValidated(null);
44
+ }, [persistedValue]);
45
+ const handleClearInput = () => {
46
+ setInputValue("");
47
+ setKeyValidated(null);
48
+ };
49
+ return (_jsxs(Box, { "data-testid": `${id}-input`, sx: { display: "flex", alignItems: "center", width: "100%", gap: 2 }, children: [confirmationDialogOpen ? (_jsx(ConfirmationModal, { id: `${id}-forget-key-confirmation-modal`, content: `This will forget the currently saved API key for ${vendor} and you will need to enter ` +
50
+ " the key again to use networks that require it. Are you sure you want to continue?", handleCancel: () => {
51
+ setConfirmationDialogOpen(false);
52
+ }, handleOk: () => {
53
+ setConfirmationDialogOpen(false);
54
+ setInputValue("");
55
+ forgetKey();
56
+ }, okBtnLabel: "Yes, forget key", title: `Forget ${vendor} API key?` })) : null, _jsx(Box, { sx: {
57
+ width: 25,
58
+ height: 25,
59
+ display: "flex",
60
+ alignItems: "center",
61
+ justifyContent: "center",
62
+ }, children: _jsx("img", { src: logo, alt: `${vendor} logo`, style: { maxWidth: "100%", maxHeight: "100%", objectFit: "contain" } }) }), _jsx(FormLabel, { id: `${id}-label`, sx: { width: 90, flexShrink: 0 }, children: vendor }), _jsx(TextField, { "aria-labelledby": `${id}-label`, autoComplete: "off", onChange: handleValueChange, placeholder: placeholder, size: "small", slotProps: {
63
+ input: {
64
+ endAdornment: (_jsxs(InputAdornment, { position: "end", children: [_jsx(Tooltip, { title: showKey ? "Hide API key" : "Show API key", children: _jsx("span", { children: _jsx(IconButton, { "aria-label": "toggle key visibility", disabled: !inputValue, onClick: () => setShowKey(!showKey), onMouseDown: (e) => e.preventDefault(), size: "small", children: showKey ? (_jsx(VisibilityOff, { fontSize: "small" })) : (_jsx(Visibility, { fontSize: "small" })) }) }) }), _jsx(IconButton, { "aria-label": "Clear input", edge: "end", onClick: handleClearInput, size: "small", children: _jsx(ClearIcon, { fontSize: "small" }) })] })),
65
+ },
66
+ }, sx: { flex: 1 },
67
+ // Type depends on whether we're showing or hiding the key
68
+ type: showKey ? "text" : "password", value: inputValue, variant: "outlined" }), _jsx(StatusLight, { id: `${id}-status-light`, statusValue: keyValidated === null ? "unknown" : keyValidated ? "green" : "red" }), _jsx(Button, { disabled: disableActions, loading: isValidating, onClick: handleOnTest, size: "small", variant: "contained", children: "Test" }), _jsx(Button, { onClick: () => onSave(inputValue), size: "small", variant: "contained", disabled: disableActions, children: "Save" }), _jsx(Button, { onClick: () => setConfirmationDialogOpen(true), size: "small", variant: "contained", disabled: !persistedValue || isValidating, children: "Forget" })] }));
69
+ };
@@ -0,0 +1,8 @@
1
+ import { ReactNode } from "react";
2
+ /**
3
+ * Simple wrapper around MUI's Tooltip that displays an info icon which, when hovered, shows the provided title.
4
+ * @param props Object containing the title to be displayed in the tooltip.
5
+ */
6
+ export default function InfoTip(props: {
7
+ title: ReactNode;
8
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
3
+ import Tooltip from "@mui/material/Tooltip";
4
+ /**
5
+ * Simple wrapper around MUI's Tooltip that displays an info icon which, when hovered, shows the provided title.
6
+ * @param props Object containing the title to be displayed in the tooltip.
7
+ */
8
+ export default function InfoTip(props) {
9
+ return (_jsx(Tooltip, { title: props.title, children: _jsx(InfoOutlinedIcon, { "aria-label": "setting information", fontSize: "small", sx: {
10
+ color: "primary.main",
11
+ cursor: "help",
12
+ fontSize: "0.9rem",
13
+ } }) }));
14
+ }