@cognizant-ai-lab/ui-common 1.7.0 → 1.9.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/README.md +3 -3
- package/dist/Theme/Palettes.d.ts +0 -9
- package/dist/Theme/Palettes.js +0 -18
- package/dist/components/AgentChat/ChatCommon/ChatCommon.d.ts +13 -4
- package/dist/components/AgentChat/ChatCommon/ChatCommon.js +131 -38
- package/dist/components/AgentChat/ChatCommon/ChatHistory.d.ts +7 -0
- package/dist/components/AgentChat/ChatCommon/ChatHistory.js +1 -1
- 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 +5 -3
- package/dist/components/AgentChat/ChatCommon/ControlButtons.js +13 -4
- package/dist/components/AgentChat/ChatCommon/ConversationTurn.d.ts +5 -5
- package/dist/components/AgentChat/ChatCommon/ConversationTurn.js +1 -0
- 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/Common/Utils.d.ts +4 -0
- package/dist/components/AgentChat/Common/Utils.js +5 -1
- package/dist/components/AgentChat/VoiceChat/MicrophoneButton.js +12 -10
- package/dist/components/Common/Breadcrumbs.js +1 -1
- 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/Common/notification.d.ts +4 -4
- 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.d.ts → AgentFlow/AgentFlow.d.ts} +15 -5
- package/dist/components/MultiAgentAccelerator/{AgentFlow.js → AgentFlow/AgentFlow.js} +125 -84
- package/dist/components/MultiAgentAccelerator/{AgentNode.d.ts → AgentFlow/AgentNode.d.ts} +1 -1
- package/dist/components/MultiAgentAccelerator/{AgentNode.js → AgentFlow/AgentNode.js} +26 -30
- package/dist/components/MultiAgentAccelerator/AgentFlow/GraphLayouts.d.ts +43 -0
- package/dist/components/MultiAgentAccelerator/{GraphLayouts.js → AgentFlow/GraphLayouts.js} +10 -41
- package/dist/components/MultiAgentAccelerator/AgentFlow/GraphStructure.d.ts +21 -0
- package/dist/components/MultiAgentAccelerator/AgentFlow/GraphStructure.js +27 -0
- package/dist/components/MultiAgentAccelerator/AgentFlow/PlasmaEdge.d.ts +7 -0
- package/dist/components/MultiAgentAccelerator/{PlasmaEdge.js → AgentFlow/PlasmaEdge.js} +13 -8
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +3 -9
- package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +137 -103
- package/dist/components/MultiAgentAccelerator/Schema/SlyData.d.ts +17 -0
- package/dist/components/MultiAgentAccelerator/Schema/SlyData.js +23 -0
- package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.d.ts +0 -5
- package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.js +38 -28
- package/dist/components/MultiAgentAccelerator/Sidebar/ImportNetworkModal.d.ts +67 -0
- package/dist/components/MultiAgentAccelerator/Sidebar/ImportNetworkModal.js +585 -0
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.d.ts +5 -4
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.js +77 -168
- package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.d.ts +31 -13
- package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +177 -100
- package/dist/components/MultiAgentAccelerator/TemporaryNetworks.d.ts +22 -0
- package/dist/components/MultiAgentAccelerator/TemporaryNetworks.js +74 -3
- package/dist/components/MultiAgentAccelerator/{ThoughtBubbleEdge.d.ts → ThoughtBubbles/ThoughtBubbleEdge.d.ts} +1 -1
- package/dist/components/MultiAgentAccelerator/{ThoughtBubbleOverlay.js → ThoughtBubbles/ThoughtBubbleOverlay.js} +1 -1
- package/dist/components/MultiAgentAccelerator/Tour/MainTourSteps.js +15 -8
- package/dist/components/MultiAgentAccelerator/const.d.ts +3 -0
- package/dist/components/MultiAgentAccelerator/const.js +6 -0
- package/dist/components/Settings/ApiKeyInput.js +7 -8
- package/dist/components/Settings/InfoTip.d.ts +8 -0
- package/dist/components/Settings/InfoTip.js +14 -0
- package/dist/components/Settings/SettingsDialog.js +373 -183
- package/dist/components/Settings/SettingsRow.d.ts +18 -0
- package/dist/components/Settings/SettingsRow.js +35 -0
- package/dist/controller/agent/Agent.d.ts +16 -25
- package/dist/controller/agent/Agent.js +24 -54
- package/dist/controller/agent/IconSuggestions.d.ts +35 -0
- package/dist/controller/agent/IconSuggestions.js +85 -0
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -3
- package/dist/state/IconSuggestions.d.ts +27 -0
- package/dist/state/IconSuggestions.js +50 -0
- package/dist/state/Settings.d.ts +97 -6
- package/dist/state/Settings.js +110 -8
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils/BrowserNavigation.js +2 -0
- package/dist/utils/File.d.ts +4 -1
- package/dist/utils/File.js +4 -9
- package/dist/utils/text.js +3 -7
- package/dist/utils/title.js +1 -1
- package/package.json +9 -6
- package/dist/components/Common/CustomerLogo.d.ts +0 -17
- package/dist/components/Common/CustomerLogo.js +0 -47
- package/dist/components/Common/MUIAccordion.d.ts +0 -17
- package/dist/components/Common/MUIAccordion.js +0 -66
- package/dist/components/MultiAgentAccelerator/GraphLayouts.d.ts +0 -36
- package/dist/components/MultiAgentAccelerator/PlasmaEdge.d.ts +0 -3
- package/dist/utils/useLocalStorage.d.ts +0 -1
- package/dist/utils/useLocalStorage.js +0 -55
- /package/dist/components/MultiAgentAccelerator/{ThoughtBubbleEdge.js → ThoughtBubbles/ThoughtBubbleEdge.js} +0 -0
- /package/dist/components/MultiAgentAccelerator/{ThoughtBubbleOverlay.d.ts → ThoughtBubbles/ThoughtBubbleOverlay.d.ts} +0 -0
|
@@ -1,119 +1,196 @@
|
|
|
1
1
|
import { cleanUpAgentName, removeTrailingUuid } from "../../AgentChat/Common/Utils.js";
|
|
2
|
+
//#endregion Types and Interfaces
|
|
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
|
+
* Computes the display name for a network (leaf) node.
|
|
33
|
+
* @param label - The label to use for the tree item (usually derived from the agent name)
|
|
34
|
+
* @param useNativeNames - Whether to use native names or cleaned-up names for display
|
|
35
|
+
* @param displayNameOverride - For temporary networks, `label` is the raw reservation_id, which is canonicalized and
|
|
36
|
+
* may have lost the word separators (_/-) that cleanUpAgentName relies on. When provided (the temp network's
|
|
37
|
+
* agentNetworkName), it is used as the basis for the cleaned name instead of the path part.
|
|
38
|
+
* @returns The display name to show in the tree view
|
|
39
|
+
*/
|
|
40
|
+
const toLeafDisplayName = (label, useNativeNames, displayNameOverride) => {
|
|
41
|
+
// Native mode shows the raw agent name part, unmodified.
|
|
42
|
+
if (useNativeNames) {
|
|
43
|
+
return label;
|
|
44
|
+
}
|
|
45
|
+
const cleanedName = cleanUpAgentName(displayNameOverride ?? removeTrailingUuid(label));
|
|
46
|
+
return cleanedName;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Converts an AgentInfo object into a tree item model representing a network (leaf node).
|
|
50
|
+
* @param network - The AgentInfo object containing details about the network
|
|
51
|
+
* @param label - The label to use for the tree item (usually derived from the agent name)
|
|
52
|
+
* @param useNativeNames - Whether to use native names or cleaned-up names for display
|
|
53
|
+
* @param metadata - Additional metadata for the network tree item, such as icon suggestions and temporary network info
|
|
54
|
+
* @returns An AgentNetworkTreeItemModel representing the network as a leaf node in the tree
|
|
55
|
+
*/
|
|
56
|
+
const toNetworkLeaf = (network, label, useNativeNames, metadata = {}) => ({
|
|
57
|
+
id: network.agent_name,
|
|
58
|
+
label,
|
|
59
|
+
displayName: toLeafDisplayName(label, useNativeNames, metadata.displayNameOverride),
|
|
60
|
+
iconSuggestion: metadata.iconSuggestion,
|
|
61
|
+
isNetwork: true,
|
|
62
|
+
tags: network.tags,
|
|
63
|
+
temporaryNetworkExpirationTime: metadata.temporaryNetworkExpirationTime,
|
|
64
|
+
temporaryNetworkDefinition: metadata.temporaryNetworkDefinition,
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Recursively sort tree nodes and their children by display name
|
|
68
|
+
* @param nodes - Array of tree nodes to sort
|
|
69
|
+
* @returns New array of tree nodes sorted by display name, with children also sorted
|
|
70
|
+
*/
|
|
71
|
+
const toSortedTreeNodes = (nodes) => [...nodes]
|
|
72
|
+
.sort((a, b) => a.displayName.localeCompare(b.displayName))
|
|
73
|
+
.map((node) => ({
|
|
74
|
+
...node,
|
|
75
|
+
children: node.children ? toSortedTreeNodes(node.children) : undefined,
|
|
76
|
+
}));
|
|
77
|
+
/**
|
|
78
|
+
* Creates a tree item model representing a category (folder node) in the tree view.
|
|
79
|
+
* @param id - The unique ID for the folder node, typically derived from the category path
|
|
80
|
+
* @param label - The label to use for the tree item (usually derived from the category name)
|
|
81
|
+
* @param useNativeNames - Whether to use native names or cleaned-up names for display
|
|
82
|
+
* @returns An AgentNetworkTreeItemModel representing the category as a folder node in the tree
|
|
33
83
|
*/
|
|
34
|
-
const
|
|
84
|
+
const toFolderNode = (id, label, useNativeNames) => ({
|
|
85
|
+
id,
|
|
86
|
+
label,
|
|
87
|
+
displayName: toDisplayName(label, useNativeNames),
|
|
88
|
+
isNetwork: false,
|
|
89
|
+
children: [],
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
92
|
+
* Recursively adds a network to the categorized tree structure based on its agent name parts.
|
|
93
|
+
* @param nodes - The current list of tree nodes at this level of the hierarchy
|
|
94
|
+
* @param network - The AgentInfo object representing the network to add
|
|
95
|
+
* @param useNativeNames - Whether to use native names or cleaned-up names for display
|
|
96
|
+
* @param metadata - Additional metadata for the network tree item, such as icon suggestions and temporary network info
|
|
97
|
+
* @param parts - The parts of the agent name split by "/", used to determine the category hierarchy
|
|
98
|
+
* @param depth - The current depth in the category hierarchy, used to determine which part of the agent name to use
|
|
99
|
+
* for this level. For example, if the agent name is "category/subcategory/network", then at depth 0 we use "category",
|
|
100
|
+
* at depth 1 we use "subcategory", and at depth 2 we use "network".
|
|
101
|
+
*/
|
|
102
|
+
const withCategorizedNetworkAdded = (nodes, network, useNativeNames, metadata, parts, depth = 0) => {
|
|
103
|
+
const label = parts[depth];
|
|
104
|
+
// The node ID is constructed from the parts up to the current depth, analogous to a file path.
|
|
105
|
+
const nodeId = parts.slice(0, depth + 1).join("/");
|
|
106
|
+
// It's a network (leaf node) if we're at the last part of the agent name, otherwise it's a category (folder node)
|
|
107
|
+
const isNetwork = depth === parts.length - 1;
|
|
108
|
+
// Check if a node with this ID already exists at the current level
|
|
109
|
+
const existingIndex = nodes.findIndex((node) => node.id === nodeId);
|
|
110
|
+
// If it's a network, we create a leaf node. If it's a category, we either create a new folder node or
|
|
111
|
+
// update the existing one with the new child.
|
|
112
|
+
const nextNode = isNetwork
|
|
113
|
+
? toNetworkLeaf(network, label, useNativeNames, metadata)
|
|
114
|
+
: {
|
|
115
|
+
...(existingIndex >= 0 ? nodes[existingIndex] : toFolderNode(nodeId, label, useNativeNames)),
|
|
116
|
+
children: withCategorizedNetworkAdded(existingIndex >= 0 ? (nodes[existingIndex].children ?? []) : [], network, useNativeNames, metadata, parts, depth + 1),
|
|
117
|
+
};
|
|
118
|
+
// If the node doesn't already exist, we add it to the list. If it does exist, we replace it with the updated node.
|
|
119
|
+
if (existingIndex < 0) {
|
|
120
|
+
return [...nodes, nextNode];
|
|
121
|
+
}
|
|
122
|
+
// Return a new array with the existing node replaced by the updated node
|
|
123
|
+
return nodes.map((node, index) => (index === existingIndex ? nextNode : node));
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Adds a network to the tree build state, either as an uncategorized item if its agent name has no "/"
|
|
127
|
+
* or as a categorized item
|
|
128
|
+
* @param state - The current state of the tree build, containing categorized and uncategorized items
|
|
129
|
+
* @param network - The AgentInfo object representing the network to add to the tree
|
|
130
|
+
* @param useNativeNames - Whether to use native names or cleaned-up names for display
|
|
131
|
+
* @param metadata - Additional metadata for the network tree item, such as icon suggestions and temporary network info
|
|
132
|
+
*/
|
|
133
|
+
const withNetworkAdded = (state, network, useNativeNames, metadata) => {
|
|
134
|
+
// Split the agent name into parts based on "/", which indicates category hierarchy. For example, an agent name
|
|
35
135
|
const parts = network.agent_name.split("/");
|
|
36
|
-
// If there
|
|
136
|
+
// If there are no "/" in the agent name, we consider it uncategorized and add it to the uncategorized items list.
|
|
37
137
|
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
|
-
});
|
|
138
|
+
return {
|
|
139
|
+
...state,
|
|
140
|
+
uncategorizedItems: [
|
|
141
|
+
...state.uncategorizedItems,
|
|
142
|
+
toNetworkLeaf(network, network.agent_name, useNativeNames, metadata),
|
|
143
|
+
],
|
|
144
|
+
};
|
|
81
145
|
}
|
|
146
|
+
// Return the updated state with the network added to the categorized items
|
|
147
|
+
return {
|
|
148
|
+
...state,
|
|
149
|
+
categorizedItems: withCategorizedNetworkAdded(state.categorizedItems, network, useNativeNames, metadata, parts),
|
|
150
|
+
};
|
|
82
151
|
};
|
|
83
152
|
/**
|
|
84
153
|
* Build a tree view structure from a flat list of networks.
|
|
85
154
|
* 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
|
-
*
|
|
155
|
+
* The tree structure is used by the RichTreeView component to display the networks.
|
|
156
|
+
*
|
|
157
|
+
* @param useNativeNames - Whether to use the raw agent names from the API or to clean them up for display.
|
|
158
|
+
* @param regularNetworks - Array of networks from the Neuro-san /list API
|
|
159
|
+
* @param temporaryNetworks - Array of temporary networks (e.g., ones recently created by the user)
|
|
160
|
+
* @param iconSuggestions
|
|
161
|
+
* @returns Array of {@linkcode AgentNetworkTreeItemModel} objects representing the tree structure
|
|
91
162
|
*/
|
|
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: [],
|
|
163
|
+
export const buildTreeViewItems = (useNativeNames, regularNetworks = [], temporaryNetworks = [], iconSuggestions = {}) => {
|
|
164
|
+
let tree = {
|
|
165
|
+
categorizedItems: [],
|
|
166
|
+
uncategorizedItems: [],
|
|
104
167
|
};
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
168
|
+
for (const network of regularNetworks) {
|
|
169
|
+
tree = withNetworkAdded(tree, network, useNativeNames, {
|
|
170
|
+
iconSuggestion: iconSuggestions[network.agent_name],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
for (const temporaryNetwork of temporaryNetworks) {
|
|
174
|
+
tree = withNetworkAdded(tree, temporaryNetwork.agentInfo, useNativeNames, {
|
|
175
|
+
iconSuggestion: "HourglassTop",
|
|
176
|
+
temporaryNetworkExpirationTime: new Date(temporaryNetwork.reservation.expiration_time_in_seconds * 1000),
|
|
177
|
+
// The structured definition is carried through and serialized at download time (it's the same shape
|
|
178
|
+
// the import modal reads back in).
|
|
179
|
+
temporaryNetworkDefinition: temporaryNetwork.agentNetworkDefinition,
|
|
180
|
+
displayNameOverride: temporaryNetwork.agentNetworkName,
|
|
181
|
+
});
|
|
115
182
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
183
|
+
const treeViewItems = tree.uncategorizedItems.length > 0
|
|
184
|
+
? [
|
|
185
|
+
...tree.categorizedItems,
|
|
186
|
+
{
|
|
187
|
+
id: "uncategorized",
|
|
188
|
+
label: "uncategorized",
|
|
189
|
+
displayName: toDisplayName("uncategorized", useNativeNames),
|
|
190
|
+
isNetwork: false,
|
|
191
|
+
children: tree.uncategorizedItems,
|
|
192
|
+
},
|
|
193
|
+
]
|
|
194
|
+
: tree.categorizedItems;
|
|
195
|
+
return toSortedTreeNodes(treeViewItems);
|
|
119
196
|
};
|
|
@@ -62,3 +62,25 @@ export declare const isTemporaryNetwork: (agentName: string | null, networks: Te
|
|
|
62
62
|
* Returns a new array; does not mutate either argument.
|
|
63
63
|
*/
|
|
64
64
|
export declare const mergeNetworks: (target: TemporaryNetwork[], incoming: TemporaryNetwork[]) => TemporaryNetwork[];
|
|
65
|
+
/** Logs and notifies about a save error. Suppresses AbortError (user-canceled). */
|
|
66
|
+
export declare const notifySaveError: (agentName: string, e: unknown) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Streams a network definition through the network designer, collecting the
|
|
69
|
+
* reservations returned across every chunk. Returns the accumulated networks
|
|
70
|
+
* (empty if the designer returned no reservation).
|
|
71
|
+
*/
|
|
72
|
+
export declare const streamNetworkDesignerUpsert: (neuroSanURL: string, signal: AbortSignal, frontman: string, networkDef: AgentNetworkDefinitionEntry[], agentNetworkName: string | undefined, username: string) => Promise<TemporaryNetwork[]>;
|
|
73
|
+
/** Reasons an import can fail before its networks reach the store. */
|
|
74
|
+
export type ImportFailureReason = "invalid-definition" | "no-reservation";
|
|
75
|
+
export type ImportNetworkResult = {
|
|
76
|
+
networks: TemporaryNetwork[];
|
|
77
|
+
} | {
|
|
78
|
+
failure: ImportFailureReason;
|
|
79
|
+
};
|
|
80
|
+
export declare const IMPORT_FAILURE_DETAIL: Record<ImportFailureReason, string>;
|
|
81
|
+
/**
|
|
82
|
+
* Converts imported JSON into a network definition and streams it through the
|
|
83
|
+
* network designer to obtain reservations. Returns the resulting networks, or a
|
|
84
|
+
* failure reason if the content is not a valid definition or no reservation is returned.
|
|
85
|
+
*/
|
|
86
|
+
export declare const importNetworkFromJson: (content: string, agentNetworkName: string, neuroSanURL: string, username: string) => Promise<ImportNetworkResult>;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { getFrontman } from "./AgentFlow/GraphStructure.js";
|
|
1
2
|
import { AGENT_NETWORK_DEFINITION_KEY, AGENT_NETWORK_HOCON, AGENT_RESERVATIONS_KEY, DisplayAs, TEMPORARY_NETWORK_FOLDER, } from "./const.js";
|
|
3
|
+
import { jsonToNetworkDefinition } from "./Sidebar/ImportNetworkModal.js";
|
|
4
|
+
import { sendNetworkDesignerRequest } from "../../controller/agent/Agent.js";
|
|
2
5
|
import { ChatMessageType } from "../../generated/neuro-san/NeuroSanClient.js";
|
|
3
|
-
import { removeTrailingUuid } from "../AgentChat/Common/Utils.js";
|
|
6
|
+
import { chatMessageFromChunk, removeTrailingUuid } from "../AgentChat/Common/Utils.js";
|
|
7
|
+
import { NotificationType, sendNotification } from "../Common/notification.js";
|
|
4
8
|
/**
|
|
5
9
|
* Extracts agent reservations from a chat message, if they exist.
|
|
6
10
|
* @param message The chat message to extract reservations from. We expect reservations to be present in messages of
|
|
@@ -44,7 +48,11 @@ export const extractNetworkHocon = (message) => {
|
|
|
44
48
|
* @returns List of TemporaryNetwork objects ready for the store.
|
|
45
49
|
*/
|
|
46
50
|
export const convertReservationsToNetworks = (agentReservations, networkHocon, agentNetworkDefinition, agentNetworkName) => {
|
|
47
|
-
return agentReservations
|
|
51
|
+
return (agentReservations
|
|
52
|
+
// reservation_id is typed as required but is echoed from un-vetted backend data; skip entries
|
|
53
|
+
// missing it rather than build a "temporary/undefined" network.
|
|
54
|
+
.filter((reservation) => Boolean(reservation?.reservation_id))
|
|
55
|
+
.map((reservation) => ({
|
|
48
56
|
reservation,
|
|
49
57
|
agentInfo: {
|
|
50
58
|
agent_name: `${TEMPORARY_NETWORK_FOLDER}/${reservation.reservation_id}`,
|
|
@@ -54,7 +62,7 @@ export const convertReservationsToNetworks = (agentReservations, networkHocon, a
|
|
|
54
62
|
agentNetworkName: agentNetworkName ?? removeTrailingUuid(reservation.reservation_id),
|
|
55
63
|
networkHocon,
|
|
56
64
|
agentNetworkDefinition,
|
|
57
|
-
}));
|
|
65
|
+
})));
|
|
58
66
|
};
|
|
59
67
|
export const isEditableAgent = (displayAs) => displayAs === DisplayAs.LLM_AGENT;
|
|
60
68
|
/**
|
|
@@ -93,3 +101,66 @@ export const mergeNetworks = (target, incoming) => incoming.reduce((result, n) =
|
|
|
93
101
|
}
|
|
94
102
|
return result;
|
|
95
103
|
}, [...target]);
|
|
104
|
+
/**
|
|
105
|
+
* Extracts TemporaryNetworks from a single streamed chunk, merging into `accumulated`.
|
|
106
|
+
* Returns `accumulated` unchanged if the chunk yields no reservations or on parse error.
|
|
107
|
+
*/
|
|
108
|
+
const collectNetworksFromChunk = (chunk, updated, accumulated) => {
|
|
109
|
+
try {
|
|
110
|
+
const chatMessage = chatMessageFromChunk(chunk);
|
|
111
|
+
if (!chatMessage)
|
|
112
|
+
return accumulated;
|
|
113
|
+
// Always use the user's edited definition as the authoritative value.
|
|
114
|
+
const converted = extractTemporaryNetworksFromMessage(chatMessage, updated);
|
|
115
|
+
if (converted.length === 0)
|
|
116
|
+
return accumulated;
|
|
117
|
+
return mergeNetworks(accumulated, converted);
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
console.warn("Failed to process chunk from network designer:", e);
|
|
121
|
+
return accumulated;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
/** Logs and notifies about a save error. Suppresses AbortError (user-canceled). */
|
|
125
|
+
export const notifySaveError = (agentName, e) => {
|
|
126
|
+
if (e instanceof DOMException && e.name === "AbortError")
|
|
127
|
+
return;
|
|
128
|
+
console.error("Failed to submit agent network update:", e);
|
|
129
|
+
const detail = e instanceof DOMException && e.name === "TimeoutError"
|
|
130
|
+
? "The request timed out waiting for the server. Please try again."
|
|
131
|
+
: String(e);
|
|
132
|
+
sendNotification(NotificationType.error, `Failed to update network "${agentName}".`, detail);
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Streams a network definition through the network designer, collecting the
|
|
136
|
+
* reservations returned across every chunk. Returns the accumulated networks
|
|
137
|
+
* (empty if the designer returned no reservation).
|
|
138
|
+
*/
|
|
139
|
+
export const streamNetworkDesignerUpsert = async (neuroSanURL, signal, frontman, networkDef, agentNetworkName, username) => {
|
|
140
|
+
let newNetworks = [];
|
|
141
|
+
await sendNetworkDesignerRequest(neuroSanURL, signal, frontman, networkDef, agentNetworkName, username, (chunk) => {
|
|
142
|
+
newNetworks = collectNetworksFromChunk(chunk, networkDef, newNetworks);
|
|
143
|
+
});
|
|
144
|
+
return newNetworks;
|
|
145
|
+
};
|
|
146
|
+
export const IMPORT_FAILURE_DETAIL = {
|
|
147
|
+
"invalid-definition": "The file does not contain a valid network definition.",
|
|
148
|
+
"no-reservation": "The network designer did not return a reservation. Please try again.",
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Converts imported JSON into a network definition and streams it through the
|
|
152
|
+
* network designer to obtain reservations. Returns the resulting networks, or a
|
|
153
|
+
* failure reason if the content is not a valid definition or no reservation is returned.
|
|
154
|
+
*/
|
|
155
|
+
export const importNetworkFromJson = async (content, agentNetworkName, neuroSanURL, username) => {
|
|
156
|
+
const networkDef = jsonToNetworkDefinition(JSON.parse(content));
|
|
157
|
+
if (networkDef.length === 0) {
|
|
158
|
+
return { failure: "invalid-definition" };
|
|
159
|
+
}
|
|
160
|
+
const frontman = getFrontman(networkDef)?.origin ?? networkDef[0]?.origin ?? "agent";
|
|
161
|
+
const networks = await streamNetworkDesignerUpsert(neuroSanURL, new AbortController().signal, frontman, networkDef, agentNetworkName, username);
|
|
162
|
+
if (networks.length === 0) {
|
|
163
|
+
return { failure: "no-reservation" };
|
|
164
|
+
}
|
|
165
|
+
return { networks };
|
|
166
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Edge, EdgeProps } from "@xyflow/react";
|
|
2
2
|
import { FC } from "react";
|
|
3
|
-
import { ChatMessageType } from "
|
|
3
|
+
import { ChatMessageType } from "../../../generated/neuro-san/NeuroSanClient.js";
|
|
4
4
|
interface ThoughtBubbleEdgeData extends Record<string, unknown> {
|
|
5
5
|
text?: string;
|
|
6
6
|
showAlways?: boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { styled } from "@mui/material/styles";
|
|
3
3
|
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { ChatMessageType } from "
|
|
4
|
+
import { ChatMessageType } from "../../../generated/neuro-san/NeuroSanClient.js";
|
|
5
5
|
// #endregion: Types
|
|
6
6
|
// #region: Constants
|
|
7
7
|
const BUBBLE_DISTANCE_FROM_RIGHT_EDGE = 20; // Fixed distance from right edge
|
|
@@ -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,19 @@ export const MAIN_TOUR_STEPS = [
|
|
|
61
63
|
placement: "top",
|
|
62
64
|
},
|
|
63
65
|
{
|
|
64
|
-
content: "
|
|
65
|
-
|
|
66
|
-
|
|
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"),
|
|
68
|
+
placement: "bottom",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
content: "This button allows you to download the chat with this agent, including history, as a plain text file.",
|
|
72
|
+
target: () => document.querySelector("#save-chat-button"),
|
|
67
73
|
placement: "bottom",
|
|
68
74
|
},
|
|
69
75
|
{
|
|
70
76
|
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
|
|
77
|
+
"auto branding for a particular customer, enter API keys for providers like OpenAI and Anthropic, and " +
|
|
78
|
+
"configure the URL used to access Neuro SAN services!",
|
|
72
79
|
target: () => document.querySelector("#settings-icon"),
|
|
73
80
|
placement: "top",
|
|
74
81
|
},
|
|
@@ -26,3 +26,6 @@ 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;
|
|
31
|
+
export declare const EXPIRED_NETWORKS_CHECK_INTERVAL_MS: number;
|
|
@@ -45,3 +45,9 @@ 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;
|
|
52
|
+
// Check for expired networks every this many milliseconds
|
|
53
|
+
export const EXPIRED_NETWORKS_CHECK_INTERVAL_MS = 10 * 1000;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import CheckIcon from "@mui/icons-material/Check";
|
|
3
2
|
import ClearIcon from "@mui/icons-material/Clear";
|
|
4
|
-
import ErrorIcon from "@mui/icons-material/Error";
|
|
5
3
|
import Visibility from "@mui/icons-material/Visibility";
|
|
6
4
|
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
|
7
5
|
import Box from "@mui/material/Box";
|
|
@@ -13,6 +11,7 @@ import TextField from "@mui/material/TextField";
|
|
|
13
11
|
import Tooltip from "@mui/material/Tooltip";
|
|
14
12
|
import { useEffect, useState } from "react";
|
|
15
13
|
import { ConfirmationModal } from "../Common/ConfirmationModal.js";
|
|
14
|
+
import { StatusLight } from "../Common/StatusLight.js";
|
|
16
15
|
/**
|
|
17
16
|
* Component for inputting an API key for a given vendor, with the ability to test the key and forget the saved key.
|
|
18
17
|
*/
|
|
@@ -43,6 +42,10 @@ export const ApiKeyInput = ({ forgetKey, id, logo, onSave, onTest, persistedValu
|
|
|
43
42
|
setInputValue(persistedValue ?? "");
|
|
44
43
|
setKeyValidated(null);
|
|
45
44
|
}, [persistedValue]);
|
|
45
|
+
const handleClearInput = () => {
|
|
46
|
+
setInputValue("");
|
|
47
|
+
setKeyValidated(null);
|
|
48
|
+
};
|
|
46
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 ` +
|
|
47
50
|
" the key again to use networks that require it. Are you sure you want to continue?", handleCancel: () => {
|
|
48
51
|
setConfirmationDialogOpen(false);
|
|
@@ -58,13 +61,9 @@ export const ApiKeyInput = ({ forgetKey, id, logo, onSave, onTest, persistedValu
|
|
|
58
61
|
justifyContent: "center",
|
|
59
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: {
|
|
60
63
|
input: {
|
|
61
|
-
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:
|
|
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" }) })] })),
|
|
62
65
|
},
|
|
63
66
|
}, sx: { flex: 1 },
|
|
64
67
|
// Type depends on whether we're showing or hiding the key
|
|
65
|
-
type: showKey ? "text" : "password", value: inputValue, variant: "outlined" }), _jsx(
|
|
66
|
-
width: 24,
|
|
67
|
-
height: 24,
|
|
68
|
-
color: (theme) => (keyValidated ? theme.palette.success.main : theme.palette.error.main),
|
|
69
|
-
}, children: keyValidated === null ? null : keyValidated ? (_jsx(CheckIcon, { fontSize: "small" })) : (_jsx(ErrorIcon, { fontSize: "small" })) }), _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" })] }));
|
|
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" })] }));
|
|
70
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
|
+
}
|