@cognizant-ai-lab/ui-common 1.8.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/components/AgentChat/ChatCommon/ChatCommon.d.ts +9 -3
- package/dist/components/AgentChat/ChatCommon/ChatCommon.js +89 -23
- package/dist/components/AgentChat/ChatCommon/ChatHistory.d.ts +7 -0
- package/dist/components/AgentChat/ChatCommon/ChatHistory.js +1 -1
- package/dist/components/AgentChat/ChatCommon/ControlButtons.d.ts +4 -2
- package/dist/components/AgentChat/ChatCommon/ControlButtons.js +8 -2
- package/dist/components/AgentChat/ChatCommon/ConversationTurn.d.ts +5 -5
- package/dist/components/AgentChat/ChatCommon/ConversationTurn.js +1 -0
- package/dist/components/AgentChat/Common/Utils.d.ts +4 -0
- package/dist/components/AgentChat/Common/Utils.js +5 -1
- package/dist/components/Common/Breadcrumbs.js +1 -1
- package/dist/components/Common/notification.d.ts +4 -4
- package/dist/components/MultiAgentAccelerator/{AgentFlow.d.ts → AgentFlow/AgentFlow.d.ts} +15 -5
- package/dist/components/MultiAgentAccelerator/{AgentFlow.js → AgentFlow/AgentFlow.js} +44 -35
- package/dist/components/MultiAgentAccelerator/{AgentNode.d.ts → AgentFlow/AgentNode.d.ts} +1 -1
- package/dist/components/MultiAgentAccelerator/{AgentNode.js → AgentFlow/AgentNode.js} +4 -4
- package/dist/components/MultiAgentAccelerator/{GraphLayouts.d.ts → AgentFlow/GraphLayouts.d.ts} +4 -4
- package/dist/components/MultiAgentAccelerator/{GraphLayouts.js → AgentFlow/GraphLayouts.js} +6 -35
- 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.js +116 -78
- 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.js +9 -4
- 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 +2 -0
- package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.js +19 -26
- package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.d.ts +2 -1
- package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +24 -4
- 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 +5 -0
- package/dist/components/MultiAgentAccelerator/const.d.ts +1 -0
- package/dist/components/MultiAgentAccelerator/const.js +2 -0
- package/dist/components/Settings/SettingsDialog.js +26 -10
- package/dist/controller/agent/Agent.d.ts +14 -6
- package/dist/controller/agent/Agent.js +18 -14
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/state/Settings.d.ts +81 -4
- package/dist/state/Settings.js +87 -7
- 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/package.json +9 -10
- package/dist/components/MultiAgentAccelerator/PlasmaEdge.d.ts +0 -3
- /package/dist/components/MultiAgentAccelerator/{ThoughtBubbleEdge.js → ThoughtBubbles/ThoughtBubbleEdge.js} +0 -0
- /package/dist/components/MultiAgentAccelerator/{ThoughtBubbleOverlay.d.ts → ThoughtBubbles/ThoughtBubbleOverlay.d.ts} +0 -0
|
@@ -6,12 +6,14 @@ export declare const SPARKLE_HIGHLIGHT_CLASS = "sparkle-highlight";
|
|
|
6
6
|
export interface SidebarProps {
|
|
7
7
|
readonly id: string;
|
|
8
8
|
readonly isAwaitingLlm: boolean;
|
|
9
|
+
readonly isImporting: boolean;
|
|
9
10
|
readonly networkIconSuggestions?: NetworkIconSuggestions;
|
|
10
11
|
readonly networks: readonly AgentInfo[];
|
|
11
12
|
readonly neuroSanServerURL: string;
|
|
12
13
|
readonly newlyAddedTemporaryNetworks?: Set<string>;
|
|
13
14
|
readonly onDeleteNetwork?: (network: string, isExpired: boolean) => void;
|
|
14
15
|
readonly onEditNetwork?: (network: string) => void;
|
|
16
|
+
readonly onImportClick?: () => void;
|
|
15
17
|
readonly setSelectedNetwork: (network: string) => void;
|
|
16
18
|
readonly temporaryNetworks?: readonly TemporaryNetwork[];
|
|
17
19
|
}
|
|
@@ -15,6 +15,7 @@ See the License for the specific language governing permissions and
|
|
|
15
15
|
limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
import AddBoxRounded from "@mui/icons-material/AddBoxRounded";
|
|
18
|
+
import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined";
|
|
18
19
|
import Box from "@mui/material/Box";
|
|
19
20
|
import Button from "@mui/material/Button";
|
|
20
21
|
import { keyframes, styled } from "@mui/material/styles";
|
|
@@ -115,31 +116,22 @@ const ServerStatusTooltip = styled(({ className, ...props }) => (_jsx(Tooltip, {
|
|
|
115
116
|
},
|
|
116
117
|
});
|
|
117
118
|
//#endregion: Types
|
|
118
|
-
export const Sidebar = ({ id, isAwaitingLlm, networkIconSuggestions, networks, neuroSanServerURL, newlyAddedTemporaryNetworks, onDeleteNetwork, onEditNetwork, setSelectedNetwork, temporaryNetworks = EMPTY_ARRAY, }) => {
|
|
119
|
+
export const Sidebar = ({ id, isAwaitingLlm, isImporting, networkIconSuggestions, networks, neuroSanServerURL, newlyAddedTemporaryNetworks, onDeleteNetwork, onEditNetwork, onImportClick, setSelectedNetwork, temporaryNetworks = EMPTY_ARRAY, }) => {
|
|
119
120
|
const [expandedItems, setExpandedItems] = useState([]);
|
|
120
121
|
const [selectedItem, setSelectedItem] = useState(null);
|
|
121
122
|
// Display option for agent/network names
|
|
122
123
|
const useNativeNames = useSettingsStore((state) => state.settings.appearance.useNativeNames);
|
|
123
|
-
const [neuroSanServerStatus, setNeuroSanServerStatus] = useState(
|
|
124
|
+
const [neuroSanServerStatus, setNeuroSanServerStatus] = useState(null);
|
|
124
125
|
useEffect(() => {
|
|
126
|
+
// Track whether the current request is still valid. If the component unmounts or the URL changes,
|
|
127
|
+
// we want to ignore any results from previous requests.
|
|
125
128
|
let isCurrentRequest = true;
|
|
126
129
|
const checkStatus = async () => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
setNeuroSanServerStatus(result.success
|
|
133
|
-
? { onlineStatus: "online", version: result.version }
|
|
134
|
-
: { error: result.status, httpStatus: result.httpStatus, onlineStatus: "offline", version: null });
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
if (!isCurrentRequest) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const errorString = error instanceof Error ? error.message : String(error);
|
|
141
|
-
setNeuroSanServerStatus({ error: errorString, onlineStatus: "offline", version: null });
|
|
130
|
+
const result = await testConnection(neuroSanServerURL);
|
|
131
|
+
if (!isCurrentRequest) {
|
|
132
|
+
return;
|
|
142
133
|
}
|
|
134
|
+
setNeuroSanServerStatus(result);
|
|
143
135
|
};
|
|
144
136
|
void checkStatus();
|
|
145
137
|
const intervalId = setInterval(checkStatus, NEURO_SAN_PING_INTERVAL_MS);
|
|
@@ -207,25 +199,26 @@ export const Sidebar = ({ id, isAwaitingLlm, networkIconSuggestions, networks, n
|
|
|
207
199
|
};
|
|
208
200
|
}, [newlyAddedTemporaryNetworks]);
|
|
209
201
|
const toStatusColor = () => {
|
|
210
|
-
switch (neuroSanServerStatus
|
|
211
|
-
case
|
|
202
|
+
switch (neuroSanServerStatus?.success) {
|
|
203
|
+
case true:
|
|
212
204
|
return "green";
|
|
213
|
-
case
|
|
205
|
+
case false:
|
|
214
206
|
return "red";
|
|
215
|
-
case
|
|
207
|
+
case undefined:
|
|
216
208
|
default:
|
|
217
209
|
return "unknown";
|
|
218
210
|
}
|
|
219
211
|
};
|
|
212
|
+
const mapStatus = (statusText) => (statusText === "ok" ? "online" : (statusText ?? "unknown"));
|
|
220
213
|
const getStatusLight = () => (_jsx(ServerStatusTooltip, { slotProps: {
|
|
221
214
|
popper: {
|
|
222
215
|
"aria-label": "Neuro-san server status",
|
|
223
216
|
},
|
|
224
|
-
}, title: _jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 0.5 }, children: [_jsxs(Typography, { variant: "body2", children: [_jsx("strong", { children: "Status:" }), " ", neuroSanServerStatus
|
|
225
|
-
const getAddNetworkButton = () => (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
217
|
+
}, title: _jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 0.5 }, children: [_jsxs(Typography, { variant: "body2", children: [_jsx("strong", { children: "Status:" }), " ", mapStatus(neuroSanServerStatus?.statusText)] }), _jsxs(Typography, { variant: "body2", children: [_jsx("strong", { children: "URL:" }), " ", neuroSanServerURL || "unknown"] }), _jsxs(Typography, { variant: "body2", children: [_jsx("strong", { children: "Service:" }), " ", neuroSanServerStatus?.healthCheckResponse?.service || "unknown"] }), _jsx(Typography, { sx: { fontWeight: "bold" }, variant: "body2", children: "Versions:" }), _jsxs(Box, { sx: { ml: 1 }, children: [_jsxs(Typography, { variant: "body2", children: [_jsx("strong", { children: "Neuro SAN:" }), " ", neuroSanServerStatus?.healthCheckResponse?.versions?.["neuro-san"] ?? "unknown"] }), _jsxs(Typography, { variant: "body2", children: [_jsx("strong", { children: "Neuro SAN Studio:" }), " ", neuroSanServerStatus?.healthCheckResponse?.versions?.["neuro-san-studio"] ?? "unknown"] })] }), !neuroSanServerStatus?.success && neuroSanServerStatus?.httpStatus && (_jsx(_Fragment, { children: _jsxs(Typography, { variant: "body2", children: [_jsx("strong", { children: "HTTP status:" }), " ", `${neuroSanServerStatus.httpStatus} (${httpStatus[neuroSanServerStatus.httpStatus] ?? "Unknown status"})`] }) }))] }), placement: "right", children: _jsx("span", { children: _jsx(StatusLight, { id: `${id}-agent-network-status-light`, statusValue: toStatusColor() }) }) }));
|
|
218
|
+
const getAddNetworkButton = () => (_jsxs(Box, { sx: { display: "flex" }, children: [_jsx(Button, { "aria-label": "Add New Network", disabled: isAwaitingLlm, id: "add-network-btn", onClick: () => {
|
|
219
|
+
setSelectedItem(AGENT_NETWORK_DESIGNER_ID);
|
|
220
|
+
setSelectedNetwork(AGENT_NETWORK_DESIGNER_ID);
|
|
221
|
+
}, sx: { display: "inline-block", minWidth: "40px" }, children: _jsx(Tooltip, { title: "Create your own agent network", placement: "top", children: _jsx(AddBoxRounded, { id: "add-network-icon", sx: { color: "var(--bs-secondary)" } }) }) }), _jsx(Button, { "aria-label": "Import Network Definition", disabled: isAwaitingLlm || isImporting, id: "import-network-btn", onClick: onImportClick, sx: { display: "inline-block", minWidth: "40px" }, children: _jsx(Tooltip, { title: "Import a network definition file", placement: "top", children: _jsx(FileUploadOutlinedIcon, { id: "import-network-icon", sx: { color: isAwaitingLlm || isImporting ? "rgba(0, 0, 0, 0.12)" : "var(--bs-secondary)" } }) }) })] }));
|
|
229
222
|
const getSidebarHeading = () => (_jsxs(SidebarHeading, { id: `${id}-heading`, children: [_jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: (theme) => theme.spacing(1.5) }, children: [getStatusLight(), "Agent Networks"] }), getAddNetworkButton()] }));
|
|
230
223
|
const getSidebarNetworksTree = () => (_jsx(RichTreeView, { disableSelection: isAwaitingLlm, expandedItems: expandedItems, items: treeViewItems, multiSelect: false, onExpandedItemsChange: (_event, itemIds) => setExpandedItems(itemIds), onSelectedItemsChange: handleSelectedItemsChange, selectedItems: selectedItem, slotProps: {
|
|
231
224
|
item: { onDeleteNetwork, onEditNetwork: handleEditNetworkWithSelect },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TreeViewDefaultItemModelProperties } from "@mui/x-tree-view/models";
|
|
2
2
|
import { AgentInfo } from "../../../generated/neuro-san/NeuroSanClient.js";
|
|
3
3
|
import { TemporaryNetwork } from "../../../state/TemporaryNetworks.js";
|
|
4
|
+
import { AgentNetworkDefinitionEntry } from "../const.js";
|
|
4
5
|
/**
|
|
5
6
|
* Represents either a category (folder) or a network (leaf) in the tree view.
|
|
6
7
|
* Note that we omit the `children` property from the parent as we want to use read-only semantics
|
|
@@ -12,7 +13,7 @@ export interface AgentNetworkTreeItemModel extends Omit<TreeViewDefaultItemModel
|
|
|
12
13
|
readonly isNetwork: boolean;
|
|
13
14
|
readonly tags?: readonly string[];
|
|
14
15
|
readonly temporaryNetworkExpirationTime?: Date;
|
|
15
|
-
readonly
|
|
16
|
+
readonly temporaryNetworkDefinition?: readonly AgentNetworkDefinitionEntry[];
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* Recursively searches for a tree item with the specified ID within the given list of tree items.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cleanUpAgentName, removeTrailingUuid } from "../../AgentChat/Common/Utils.js";
|
|
2
|
-
//#endregion
|
|
2
|
+
//#endregion Types and Interfaces
|
|
3
3
|
/**
|
|
4
4
|
* Recursively searches for a tree item with the specified ID within the given list of tree items.
|
|
5
5
|
* @param items - The list of tree items to search through
|
|
@@ -28,6 +28,23 @@ export const findTreeItemById = (items, itemId) => {
|
|
|
28
28
|
* @returns The display name to show in the tree view
|
|
29
29
|
*/
|
|
30
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
|
+
};
|
|
31
48
|
/**
|
|
32
49
|
* Converts an AgentInfo object into a tree item model representing a network (leaf node).
|
|
33
50
|
* @param network - The AgentInfo object containing details about the network
|
|
@@ -39,12 +56,12 @@ const toDisplayName = (itemName, useNativeNames) => useNativeNames ? itemName :
|
|
|
39
56
|
const toNetworkLeaf = (network, label, useNativeNames, metadata = {}) => ({
|
|
40
57
|
id: network.agent_name,
|
|
41
58
|
label,
|
|
42
|
-
displayName:
|
|
59
|
+
displayName: toLeafDisplayName(label, useNativeNames, metadata.displayNameOverride),
|
|
43
60
|
iconSuggestion: metadata.iconSuggestion,
|
|
44
61
|
isNetwork: true,
|
|
45
62
|
tags: network.tags,
|
|
46
63
|
temporaryNetworkExpirationTime: metadata.temporaryNetworkExpirationTime,
|
|
47
|
-
|
|
64
|
+
temporaryNetworkDefinition: metadata.temporaryNetworkDefinition,
|
|
48
65
|
});
|
|
49
66
|
/**
|
|
50
67
|
* Recursively sort tree nodes and their children by display name
|
|
@@ -157,7 +174,10 @@ export const buildTreeViewItems = (useNativeNames, regularNetworks = [], tempora
|
|
|
157
174
|
tree = withNetworkAdded(tree, temporaryNetwork.agentInfo, useNativeNames, {
|
|
158
175
|
iconSuggestion: "HourglassTop",
|
|
159
176
|
temporaryNetworkExpirationTime: new Date(temporaryNetwork.reservation.expiration_time_in_seconds * 1000),
|
|
160
|
-
|
|
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,
|
|
161
181
|
});
|
|
162
182
|
}
|
|
163
183
|
const treeViewItems = tree.uncategorizedItems.length > 0
|
|
@@ -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
|
|
@@ -67,6 +67,11 @@ export const MAIN_TOUR_STEPS = [
|
|
|
67
67
|
target: () => document.querySelector("#agent-network-ui-options-menu-button-container"),
|
|
68
68
|
placement: "bottom",
|
|
69
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"),
|
|
73
|
+
placement: "bottom",
|
|
74
|
+
},
|
|
70
75
|
{
|
|
71
76
|
content: "Access Settings from this icon. Here you can change the look and feel of the application, including " +
|
|
72
77
|
"auto branding for a particular customer, enter API keys for providers like OpenAI and Anthropic, and " +
|
|
@@ -49,3 +49,5 @@ export var DisplayAs;
|
|
|
49
49
|
export const GRACE_PERIOD_MS = 5 * 60 * 1000; // 5 minutes
|
|
50
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
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;
|
|
@@ -19,12 +19,11 @@ import Typography from "@mui/material/Typography";
|
|
|
19
19
|
import { useEffect, useMemo, useState, } from "react";
|
|
20
20
|
import { ApiKeyInput } from "./ApiKeyInput.js";
|
|
21
21
|
import { useCheckmarkFade } from "./FadingCheckmark.js";
|
|
22
|
-
import InfoTip from "./InfoTip.js";
|
|
23
22
|
import { SettingsRow } from "./SettingsRow.js";
|
|
24
23
|
import { getBrandingSuggestions, testConnection } from "../../controller/agent/Agent.js";
|
|
25
24
|
import { isAnthropicKeyValid, isOpenAIKeyValid } from "../../controller/llm/Providers.js";
|
|
26
25
|
import { useEnvironmentStore } from "../../state/Environment.js";
|
|
27
|
-
import { DEFAULT_SETTINGS, useSettingsStore } from "../../state/Settings.js";
|
|
26
|
+
import { API_KEYS_TTL_MS, DEFAULT_SETTINGS, getApiKey, useSettingsStore, } from "../../state/Settings.js";
|
|
28
27
|
import { PALETTES } from "../../Theme/Palettes.js";
|
|
29
28
|
import { ConfirmationModal } from "../Common/ConfirmationModal.js";
|
|
30
29
|
import { MUIDialog } from "../Common/MUIDialog.js";
|
|
@@ -40,7 +39,7 @@ const Section = styled(Box)(({ theme }) => ({
|
|
|
40
39
|
display: "flex",
|
|
41
40
|
flexDirection: "column",
|
|
42
41
|
gap: theme.spacing(2),
|
|
43
|
-
marginBottom: theme.spacing(
|
|
42
|
+
marginBottom: theme.spacing(2),
|
|
44
43
|
}));
|
|
45
44
|
const SubsectionTitle = styled(Typography)(({ theme }) => ({
|
|
46
45
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
@@ -235,10 +234,20 @@ export const SettingsDialog = ({ id, isOpen, logoServiceToken, onClose }) => {
|
|
|
235
234
|
brandingCheckmark.trigger();
|
|
236
235
|
logoCheckmark.trigger();
|
|
237
236
|
};
|
|
238
|
-
|
|
237
|
+
/**
|
|
238
|
+
* Used for both saving and removing keys.
|
|
239
|
+
* @param vendor Vendor for this key: see the list in LLMProvider type
|
|
240
|
+
* @param key Value of the key
|
|
241
|
+
* @param checkmark Checkmark to trigger on save
|
|
242
|
+
* @param now Timestamp for "now"
|
|
243
|
+
*/
|
|
244
|
+
const persistKey = (vendor, key, checkmark, now) => {
|
|
239
245
|
updateSettings({
|
|
240
246
|
apiKeys: {
|
|
241
|
-
[vendor]:
|
|
247
|
+
[vendor]: {
|
|
248
|
+
value: key,
|
|
249
|
+
expiresAt: key ? now + API_KEYS_TTL_MS : 0,
|
|
250
|
+
},
|
|
242
251
|
},
|
|
243
252
|
});
|
|
244
253
|
checkmark.trigger();
|
|
@@ -324,9 +333,17 @@ export const SettingsDialog = ({ id, isOpen, logoServiceToken, onClose }) => {
|
|
|
324
333
|
}, sx: { flex: 1 }, value: neuroSanUrlInput }), _jsx(StatusLight, { id: `${id}-status-light`, statusValue: statusLightValue }), _jsx(Button, { disabled: neuroSanUrlInput.trim().length === 0, onClick: handleTestConnection, size: "small", variant: "contained", children: "Test" }), _jsx(Button, { disabled: neuroSanUrlSaveDisabled, onClick: handleSaveNeuroSanUrl, size: "small", variant: "contained", children: "Save" }), _jsx(Button, { disabled: neuroSanUrlResetDisabled, onClick: handleResetNeuroSanUrl, size: "small", variant: "contained", children: "Default" })] }, `${id}-neuro-san-server-url`) }));
|
|
325
334
|
};
|
|
326
335
|
const getServicesSection = () => (_jsxs(Section, { children: [_jsx(SettingsSectionTitle, { children: "External Services" }), getNeuroSanSubsection()] }));
|
|
327
|
-
const getApiKeysSection = () => (_jsxs(Section, { children: [_jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: theme.spacing(
|
|
328
|
-
|
|
329
|
-
|
|
336
|
+
const getApiKeysSection = () => (_jsxs(Section, { children: [_jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: theme.spacing(1) }, children: [_jsx(SettingsSectionTitle, { children: "API Keys" }), _jsx(Tooltip, { title: "API keys are stored locally in your browser's memory for the duration of your session only\n and are only sent to the Neuro SAN service for use with the associated LLM provider when you use\n this application to interact with networks.\n Your keys are never sent to any other servers or services and are not stored on our servers.\n When you close this tab or your browser, your keys will be permanently cleared from memory and\n you will need to enter them again to use services that require them.", children: _jsxs(Typography, { sx: {
|
|
337
|
+
alignSelf: "flex-start",
|
|
338
|
+
color: "var(--bs-secondary)",
|
|
339
|
+
cursor: "help",
|
|
340
|
+
fontSize: "0.7rem",
|
|
341
|
+
lineHeight: 1,
|
|
342
|
+
mt: 0.5,
|
|
343
|
+
}, variant: "caption", children: [_jsx(Box, { component: "span", sx: { mr: 0.5 }, children: "\u24D8" }), _jsx(Box, { component: "span", sx: {
|
|
344
|
+
borderBottom: "1px dashed",
|
|
345
|
+
pb: 0.25,
|
|
346
|
+
}, children: "How are my keys stored and used?" })] }) })] }), _jsx(SettingsSubsection, { title: "LLM Providers", children: _jsx(Box, { sx: { display: "flex", flexDirection: "column", alignItems: "center", gap: 1.5 }, children: apiKeyConfigs.map(({ checkmark, vendor, idSuffix, logo, onTest, placeholder }) => (_jsx(SettingsRow, { checkmark: checkmark, label: "", tooltip: `API key for ${vendor}.`, children: _jsx(ApiKeyInput, { forgetKey: () => persistKey(vendor, "", checkmark, 0), id: `${id}-${idSuffix}`, logo: logo, onSave: (key) => persistKey(vendor, key, checkmark, Date.now()), onTest: onTest, persistedValue: getApiKey(apiKeys, vendor), placeholder: placeholder, vendor: vendor }) }, `${id}-${idSuffix}`))) }) })] }));
|
|
330
347
|
const getBehaviorSection = () => (_jsxs(Section, { children: [_jsx(SettingsSectionTitle, { children: "Behavior" }), _jsx(SettingsSubsection, { title: "Zen mode", children: _jsx(SettingsRow, { label: 'Enable "Zen" mode:', checkmark: enableZenModeCheckmark, tooltip: "Hides most of the UI during agent network animations providing a more immersive " +
|
|
331
348
|
"experience.", children: _jsx(Checkbox, { checked: enableZenMode, "data-testid": "zen-mode-checkbox", onChange: (_, checked) => {
|
|
332
349
|
updateSettings({ behavior: { enableZenMode: checked } });
|
|
@@ -345,7 +362,6 @@ export const SettingsDialog = ({ id, isOpen, logoServiceToken, onClose }) => {
|
|
|
345
362
|
nativeNamesCheckmark.trigger();
|
|
346
363
|
}
|
|
347
364
|
}, size: "small", value: useNativeNames ? "native" : "beautified", children: [_jsx(ToggleButton, { value: "native", children: "Native" }), _jsx(ToggleButton, { value: "beautified", children: "Beautified" })] }), _jsx(FormLabel, { children: "Preview: " }), _jsx(FormLabel, { sx: {
|
|
348
|
-
marginBottom: 0,
|
|
349
365
|
border: "1px solid",
|
|
350
366
|
borderRadius: 1,
|
|
351
367
|
backgroundColor: "background.paper",
|
|
@@ -456,7 +472,7 @@ export const SettingsDialog = ({ id, isOpen, logoServiceToken, onClose }) => {
|
|
|
456
472
|
},
|
|
457
473
|
}), [paletteMode]);
|
|
458
474
|
return (
|
|
459
|
-
// Always use default theme for settings dialog so user can always see to reset. It's possible that with
|
|
475
|
+
// Always use the default theme for settings dialog so user can always see to reset. It's possible that with
|
|
460
476
|
// certain custom themes the dialog would be unreadable.
|
|
461
477
|
_jsxs(ThemeProvider, { theme: settingsTheme, children: [resetToDefaultSettingsOpen ? getConfirmationModal() : null, _jsxs(MUIDialog, { id: id, title: _jsx(Box, { sx: { fontSize: "1.5rem" }, children: "Settings" }), isOpen: isOpen, onClose: onClose, paperProps: {
|
|
462
478
|
minWidth: "50%",
|
|
@@ -2,11 +2,19 @@ import { AgentNetworkDefinitionEntry } from "../../components/MultiAgentAccelera
|
|
|
2
2
|
import { AgentInfo, ChatContext, ChatResponse, ConnectivityResponse, FunctionResponse } from "../../generated/neuro-san/NeuroSanClient.js";
|
|
3
3
|
import { StreamingUnit } from "../llm/LlmChat.js";
|
|
4
4
|
import { BrandingSuggestions } from "../Types/Branding.js";
|
|
5
|
+
export interface HealthCheckResponse {
|
|
6
|
+
readonly service?: string;
|
|
7
|
+
readonly status?: string;
|
|
8
|
+
readonly versions?: {
|
|
9
|
+
readonly "neuro-san-studio"?: string;
|
|
10
|
+
readonly "neuro-san"?: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
5
13
|
export interface TestConnectionResult {
|
|
14
|
+
readonly healthCheckResponse?: HealthCheckResponse;
|
|
6
15
|
readonly httpStatus?: number;
|
|
7
|
-
readonly
|
|
16
|
+
readonly statusText?: string;
|
|
8
17
|
readonly success: boolean;
|
|
9
|
-
readonly version?: string;
|
|
10
18
|
}
|
|
11
19
|
/**
|
|
12
20
|
* Test connection for a neuro-san server.
|
|
@@ -64,8 +72,8 @@ export declare const getConnectivity: (url: string, network: string, userId: str
|
|
|
64
72
|
*/
|
|
65
73
|
export declare const getAgentFunction: (url: string, agent: string, userId: string) => Promise<FunctionResponse>;
|
|
66
74
|
/**
|
|
67
|
-
* Streams the Agent Network Designer endpoint with an
|
|
68
|
-
* Calls `onChunk` for each line received; callers are
|
|
69
|
-
* reservations / networks out of each chunk.
|
|
75
|
+
* Streams the Agent Network Designer endpoint with an agent definition, either updating an
|
|
76
|
+
* existing network or adding a fresh one. Calls `onChunk` for each line received; callers are
|
|
77
|
+
* responsible for parsing reservations / networks out of each chunk.
|
|
70
78
|
*/
|
|
71
|
-
export declare const
|
|
79
|
+
export declare const sendNetworkDesignerRequest: (url: string, signal: AbortSignal, agentName: string, updated: AgentNetworkDefinitionEntry[], agentNetworkName: string | undefined, currentUser: string, onChunk: (chunk: string) => void) => Promise<void>;
|
|
@@ -50,23 +50,27 @@ export const testConnection = async (url) => {
|
|
|
50
50
|
const response = await fetch(url, { signal: controller.signal });
|
|
51
51
|
if (!response.ok) {
|
|
52
52
|
return {
|
|
53
|
-
success: false,
|
|
54
53
|
httpStatus: response.status,
|
|
55
|
-
|
|
54
|
+
statusText: response.statusText,
|
|
55
|
+
success: false,
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
const jsonResponse = await response.json();
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
const statusText = jsonResponse?.status;
|
|
60
|
+
const success = statusText === "ok";
|
|
61
|
+
return {
|
|
62
|
+
healthCheckResponse: jsonResponse,
|
|
63
|
+
httpStatus: response.status,
|
|
64
|
+
statusText,
|
|
65
|
+
success,
|
|
66
|
+
};
|
|
66
67
|
}
|
|
67
68
|
catch (error) {
|
|
68
69
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
69
|
-
return {
|
|
70
|
+
return {
|
|
71
|
+
statusText: errorMessage,
|
|
72
|
+
success: false,
|
|
73
|
+
};
|
|
70
74
|
}
|
|
71
75
|
finally {
|
|
72
76
|
clearTimeout(timeout);
|
|
@@ -180,11 +184,11 @@ export const getAgentFunction = async (url, agent, userId) => {
|
|
|
180
184
|
return response.json();
|
|
181
185
|
};
|
|
182
186
|
/**
|
|
183
|
-
* Streams the Agent Network Designer endpoint with an
|
|
184
|
-
* Calls `onChunk` for each line received; callers are
|
|
185
|
-
* reservations / networks out of each chunk.
|
|
187
|
+
* Streams the Agent Network Designer endpoint with an agent definition, either updating an
|
|
188
|
+
* existing network or adding a fresh one. Calls `onChunk` for each line received; callers are
|
|
189
|
+
* responsible for parsing reservations / networks out of each chunk.
|
|
186
190
|
*/
|
|
187
|
-
export const
|
|
191
|
+
export const sendNetworkDesignerRequest = async (url, signal, agentName, updated, agentNetworkName, currentUser, onChunk) => {
|
|
188
192
|
await sendChatQuery(url, signal,
|
|
189
193
|
// Shouldn't have to pass a user message, but API behaves differently without it
|
|
190
194
|
`Update instructions for agent "${agentName}"`, AGENT_NETWORK_DESIGNER_ID, onChunk, null, {
|
package/dist/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export * from "./components/Common/notification.js";
|
|
|
23
23
|
export * from "./components/ErrorPage/ErrorBoundary.js";
|
|
24
24
|
export * from "./components/MultiAgentAccelerator/AgentConversations.js";
|
|
25
25
|
export * from "./components/MultiAgentAccelerator/AgentCounts.js";
|
|
26
|
-
export * from "./components/MultiAgentAccelerator/AgentFlow.js";
|
|
26
|
+
export * from "./components/MultiAgentAccelerator/AgentFlow/AgentFlow.js";
|
|
27
27
|
export * from "./components/MultiAgentAccelerator/MultiAgentAccelerator.js";
|
|
28
28
|
export * from "./components/MultiAgentAccelerator/Sidebar/Sidebar.js";
|
|
29
29
|
export * from "./components/MultiAgentAccelerator/TemporaryNetworks.js";
|
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ export * from "./components/Common/notification.js";
|
|
|
38
38
|
export * from "./components/ErrorPage/ErrorBoundary.js";
|
|
39
39
|
export * from "./components/MultiAgentAccelerator/AgentConversations.js";
|
|
40
40
|
export * from "./components/MultiAgentAccelerator/AgentCounts.js";
|
|
41
|
-
export * from "./components/MultiAgentAccelerator/AgentFlow.js";
|
|
41
|
+
export * from "./components/MultiAgentAccelerator/AgentFlow/AgentFlow.js";
|
|
42
42
|
export * from "./components/MultiAgentAccelerator/MultiAgentAccelerator.js";
|
|
43
43
|
export * from "./components/MultiAgentAccelerator/Sidebar/Sidebar.js";
|
|
44
44
|
export * from "./components/MultiAgentAccelerator/TemporaryNetworks.js";
|