@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.
- package/dist/Theme/Palettes.d.ts +0 -9
- package/dist/Theme/Palettes.js +0 -18
- package/dist/components/AgentChat/ChatCommon/ChatCommon.d.ts +11 -7
- package/dist/components/AgentChat/ChatCommon/ChatCommon.js +119 -90
- package/dist/components/AgentChat/ChatCommon/Const.d.ts +1 -0
- package/dist/components/AgentChat/ChatCommon/Const.js +2 -0
- package/dist/components/AgentChat/ChatCommon/ControlButtons.d.ts +1 -1
- package/dist/components/AgentChat/ChatCommon/ControlButtons.js +7 -4
- package/dist/components/AgentChat/ChatCommon/FormattedMarkdown.js +3 -0
- package/dist/components/AgentChat/ChatCommon/SendButton.js +7 -6
- package/dist/components/AgentChat/ChatCommon/SyntaxHighlighterThemes.js +1 -1
- package/dist/components/AgentChat/ChatCommon/Thinking.d.ts +1 -0
- package/dist/components/AgentChat/ChatCommon/Thinking.js +10 -7
- package/dist/components/AgentChat/Common/LlmChatButton.d.ts +0 -2
- package/dist/components/AgentChat/Common/LlmChatButton.js +6 -9
- package/dist/components/AgentChat/VoiceChat/MicrophoneButton.js +12 -10
- package/dist/components/ChatBot/ChatBot.d.ts +0 -4
- package/dist/components/ChatBot/ChatBot.js +2 -2
- package/dist/components/Common/Navbar.js +24 -22
- package/dist/components/Common/StatusLight.d.ts +9 -0
- package/dist/components/Common/StatusLight.js +21 -0
- package/dist/components/ErrorPage/ErrorBoundary.js +1 -1
- package/dist/components/ErrorPage/ErrorPage.d.ts +3 -3
- package/dist/components/ErrorPage/ErrorPage.js +4 -3
- package/dist/components/Logo/Common.d.ts +4 -0
- package/dist/components/Logo/Common.js +8 -0
- package/dist/components/Logo/CustomerLogo.d.ts +23 -0
- package/dist/components/Logo/CustomerLogo.js +41 -0
- package/dist/components/MultiAgentAccelerator/AgentConversations.js +0 -1
- package/dist/components/MultiAgentAccelerator/AgentFlow.js +85 -53
- package/dist/components/MultiAgentAccelerator/AgentNode.js +23 -27
- package/dist/components/MultiAgentAccelerator/GraphLayouts.d.ts +17 -10
- package/dist/components/MultiAgentAccelerator/GraphLayouts.js +4 -6
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +3 -9
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +102 -47
- package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.d.ts +0 -5
- package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.js +30 -25
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.d.ts +3 -4
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.js +84 -168
- package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.d.ts +30 -13
- package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +157 -100
- package/dist/components/MultiAgentAccelerator/Tour/MainTourSteps.js +10 -8
- package/dist/components/MultiAgentAccelerator/const.d.ts +2 -0
- package/dist/components/MultiAgentAccelerator/const.js +4 -0
- package/dist/components/Settings/ApiKeyInput.d.ts +16 -0
- package/dist/components/Settings/ApiKeyInput.js +69 -0
- package/dist/components/Settings/InfoTip.d.ts +8 -0
- package/dist/components/Settings/InfoTip.js +14 -0
- package/dist/components/Settings/SettingsDialog.js +379 -178
- package/dist/components/Settings/SettingsRow.d.ts +18 -0
- package/dist/components/Settings/SettingsRow.js +35 -0
- package/dist/controller/agent/Agent.d.ts +3 -20
- package/dist/controller/agent/Agent.js +9 -43
- package/dist/controller/agent/IconSuggestions.d.ts +35 -0
- package/dist/controller/agent/IconSuggestions.js +85 -0
- package/dist/controller/llm/Providers.d.ts +2 -0
- package/dist/controller/llm/Providers.js +41 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/state/IconSuggestions.d.ts +27 -0
- package/dist/state/IconSuggestions.js +50 -0
- package/dist/state/Settings.d.ts +19 -3
- package/dist/state/Settings.js +24 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils/title.js +1 -1
- package/package.json +5 -1
- package/dist/components/Common/CustomerLogo.d.ts +0 -17
- package/dist/components/Common/CustomerLogo.js +0 -49
- package/dist/components/Common/MUIAccordion.d.ts +0 -17
- package/dist/components/Common/MUIAccordion.js +0 -66
- package/dist/utils/useLocalStorage.d.ts +0 -1
- 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
|
-
*
|
|
4
|
-
* @param
|
|
5
|
-
* @param
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
*
|
|
88
|
-
* @param
|
|
89
|
-
* @
|
|
90
|
-
*
|
|
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 = (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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: "
|
|
28
|
-
"
|
|
29
|
-
target: () => document.querySelector("#agent-network-
|
|
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: "
|
|
65
|
-
|
|
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
|
},
|
|
@@ -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
|
+
}
|