@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.
Files changed (98) hide show
  1. package/README.md +3 -3
  2. package/dist/Theme/Palettes.d.ts +0 -9
  3. package/dist/Theme/Palettes.js +0 -18
  4. package/dist/components/AgentChat/ChatCommon/ChatCommon.d.ts +13 -4
  5. package/dist/components/AgentChat/ChatCommon/ChatCommon.js +131 -38
  6. package/dist/components/AgentChat/ChatCommon/ChatHistory.d.ts +7 -0
  7. package/dist/components/AgentChat/ChatCommon/ChatHistory.js +1 -1
  8. package/dist/components/AgentChat/ChatCommon/Const.d.ts +1 -0
  9. package/dist/components/AgentChat/ChatCommon/Const.js +2 -0
  10. package/dist/components/AgentChat/ChatCommon/ControlButtons.d.ts +5 -3
  11. package/dist/components/AgentChat/ChatCommon/ControlButtons.js +13 -4
  12. package/dist/components/AgentChat/ChatCommon/ConversationTurn.d.ts +5 -5
  13. package/dist/components/AgentChat/ChatCommon/ConversationTurn.js +1 -0
  14. package/dist/components/AgentChat/ChatCommon/FormattedMarkdown.js +3 -0
  15. package/dist/components/AgentChat/ChatCommon/SendButton.js +7 -6
  16. package/dist/components/AgentChat/ChatCommon/SyntaxHighlighterThemes.js +1 -1
  17. package/dist/components/AgentChat/ChatCommon/Thinking.d.ts +1 -0
  18. package/dist/components/AgentChat/ChatCommon/Thinking.js +10 -7
  19. package/dist/components/AgentChat/Common/LlmChatButton.d.ts +0 -2
  20. package/dist/components/AgentChat/Common/LlmChatButton.js +6 -9
  21. package/dist/components/AgentChat/Common/Utils.d.ts +4 -0
  22. package/dist/components/AgentChat/Common/Utils.js +5 -1
  23. package/dist/components/AgentChat/VoiceChat/MicrophoneButton.js +12 -10
  24. package/dist/components/Common/Breadcrumbs.js +1 -1
  25. package/dist/components/Common/Navbar.js +24 -22
  26. package/dist/components/Common/StatusLight.d.ts +9 -0
  27. package/dist/components/Common/StatusLight.js +21 -0
  28. package/dist/components/Common/notification.d.ts +4 -4
  29. package/dist/components/ErrorPage/ErrorBoundary.js +1 -1
  30. package/dist/components/ErrorPage/ErrorPage.d.ts +3 -3
  31. package/dist/components/ErrorPage/ErrorPage.js +4 -3
  32. package/dist/components/Logo/Common.d.ts +4 -0
  33. package/dist/components/Logo/Common.js +8 -0
  34. package/dist/components/Logo/CustomerLogo.d.ts +23 -0
  35. package/dist/components/Logo/CustomerLogo.js +41 -0
  36. package/dist/components/MultiAgentAccelerator/AgentConversations.js +0 -1
  37. package/dist/components/MultiAgentAccelerator/{AgentFlow.d.ts → AgentFlow/AgentFlow.d.ts} +15 -5
  38. package/dist/components/MultiAgentAccelerator/{AgentFlow.js → AgentFlow/AgentFlow.js} +125 -84
  39. package/dist/components/MultiAgentAccelerator/{AgentNode.d.ts → AgentFlow/AgentNode.d.ts} +1 -1
  40. package/dist/components/MultiAgentAccelerator/{AgentNode.js → AgentFlow/AgentNode.js} +26 -30
  41. package/dist/components/MultiAgentAccelerator/AgentFlow/GraphLayouts.d.ts +43 -0
  42. package/dist/components/MultiAgentAccelerator/{GraphLayouts.js → AgentFlow/GraphLayouts.js} +10 -41
  43. package/dist/components/MultiAgentAccelerator/AgentFlow/GraphStructure.d.ts +21 -0
  44. package/dist/components/MultiAgentAccelerator/AgentFlow/GraphStructure.js +27 -0
  45. package/dist/components/MultiAgentAccelerator/AgentFlow/PlasmaEdge.d.ts +7 -0
  46. package/dist/components/MultiAgentAccelerator/{PlasmaEdge.js → AgentFlow/PlasmaEdge.js} +13 -8
  47. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.d.ts +3 -9
  48. package/dist/components/MultiAgentAccelerator/MultiAgentAccelerator.js +137 -103
  49. package/dist/components/MultiAgentAccelerator/Schema/SlyData.d.ts +17 -0
  50. package/dist/components/MultiAgentAccelerator/Schema/SlyData.js +23 -0
  51. package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.d.ts +0 -5
  52. package/dist/components/MultiAgentAccelerator/Sidebar/AgentNetworkTreeItem.js +38 -28
  53. package/dist/components/MultiAgentAccelerator/Sidebar/ImportNetworkModal.d.ts +67 -0
  54. package/dist/components/MultiAgentAccelerator/Sidebar/ImportNetworkModal.js +585 -0
  55. package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.d.ts +5 -4
  56. package/dist/components/MultiAgentAccelerator/Sidebar/Sidebar.js +77 -168
  57. package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.d.ts +31 -13
  58. package/dist/components/MultiAgentAccelerator/Sidebar/TreeBuilder.js +177 -100
  59. package/dist/components/MultiAgentAccelerator/TemporaryNetworks.d.ts +22 -0
  60. package/dist/components/MultiAgentAccelerator/TemporaryNetworks.js +74 -3
  61. package/dist/components/MultiAgentAccelerator/{ThoughtBubbleEdge.d.ts → ThoughtBubbles/ThoughtBubbleEdge.d.ts} +1 -1
  62. package/dist/components/MultiAgentAccelerator/{ThoughtBubbleOverlay.js → ThoughtBubbles/ThoughtBubbleOverlay.js} +1 -1
  63. package/dist/components/MultiAgentAccelerator/Tour/MainTourSteps.js +15 -8
  64. package/dist/components/MultiAgentAccelerator/const.d.ts +3 -0
  65. package/dist/components/MultiAgentAccelerator/const.js +6 -0
  66. package/dist/components/Settings/ApiKeyInput.js +7 -8
  67. package/dist/components/Settings/InfoTip.d.ts +8 -0
  68. package/dist/components/Settings/InfoTip.js +14 -0
  69. package/dist/components/Settings/SettingsDialog.js +373 -183
  70. package/dist/components/Settings/SettingsRow.d.ts +18 -0
  71. package/dist/components/Settings/SettingsRow.js +35 -0
  72. package/dist/controller/agent/Agent.d.ts +16 -25
  73. package/dist/controller/agent/Agent.js +24 -54
  74. package/dist/controller/agent/IconSuggestions.d.ts +35 -0
  75. package/dist/controller/agent/IconSuggestions.js +85 -0
  76. package/dist/index.d.ts +1 -3
  77. package/dist/index.js +1 -3
  78. package/dist/state/IconSuggestions.d.ts +27 -0
  79. package/dist/state/IconSuggestions.js +50 -0
  80. package/dist/state/Settings.d.ts +97 -6
  81. package/dist/state/Settings.js +110 -8
  82. package/dist/tsconfig.build.tsbuildinfo +1 -1
  83. package/dist/utils/BrowserNavigation.js +2 -0
  84. package/dist/utils/File.d.ts +4 -1
  85. package/dist/utils/File.js +4 -9
  86. package/dist/utils/text.js +3 -7
  87. package/dist/utils/title.js +1 -1
  88. package/package.json +9 -6
  89. package/dist/components/Common/CustomerLogo.d.ts +0 -17
  90. package/dist/components/Common/CustomerLogo.js +0 -47
  91. package/dist/components/Common/MUIAccordion.d.ts +0 -17
  92. package/dist/components/Common/MUIAccordion.js +0 -66
  93. package/dist/components/MultiAgentAccelerator/GraphLayouts.d.ts +0 -36
  94. package/dist/components/MultiAgentAccelerator/PlasmaEdge.d.ts +0 -3
  95. package/dist/utils/useLocalStorage.d.ts +0 -1
  96. package/dist/utils/useLocalStorage.js +0 -55
  97. /package/dist/components/MultiAgentAccelerator/{ThoughtBubbleEdge.js → ThoughtBubbles/ThoughtBubbleEdge.js} +0 -0
  98. /package/dist/components/MultiAgentAccelerator/{ThoughtBubbleOverlay.d.ts → ThoughtBubbles/ThoughtBubbleOverlay.d.ts} +0 -0
@@ -1,27 +1,89 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Business from "@mui/icons-material/Business";
3
+ import ClearIcon from "@mui/icons-material/Clear";
2
4
  import RestoreIcon from "@mui/icons-material/SettingsBackupRestore";
3
5
  import Box from "@mui/material/Box";
4
6
  import Button from "@mui/material/Button";
5
7
  import Checkbox from "@mui/material/Checkbox";
6
- import Divider from "@mui/material/Divider";
7
8
  import FormLabel from "@mui/material/FormLabel";
8
- import { createTheme, ThemeProvider, useTheme } from "@mui/material/styles";
9
+ import IconButton from "@mui/material/IconButton";
10
+ import InputAdornment from "@mui/material/InputAdornment";
11
+ import MenuItem from "@mui/material/MenuItem";
12
+ import Select from "@mui/material/Select";
13
+ import { createTheme, styled, ThemeProvider, useTheme } from "@mui/material/styles";
9
14
  import TextField from "@mui/material/TextField";
10
15
  import ToggleButton from "@mui/material/ToggleButton";
11
16
  import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
12
17
  import Tooltip from "@mui/material/Tooltip";
13
18
  import Typography from "@mui/material/Typography";
14
- import { useEffect, useState } from "react";
19
+ import { useEffect, useMemo, useState, } from "react";
15
20
  import { ApiKeyInput } from "./ApiKeyInput.js";
16
- import { FadingCheckmark, useCheckmarkFade } from "./FadingCheckmark.js";
17
- import { getBrandingSuggestions } from "../../controller/agent/Agent.js";
21
+ import { useCheckmarkFade } from "./FadingCheckmark.js";
22
+ import { SettingsRow } from "./SettingsRow.js";
23
+ import { getBrandingSuggestions, testConnection } from "../../controller/agent/Agent.js";
18
24
  import { isAnthropicKeyValid, isOpenAIKeyValid } from "../../controller/llm/Providers.js";
19
- import { useSettingsStore } from "../../state/Settings.js";
25
+ import { useEnvironmentStore } from "../../state/Environment.js";
26
+ import { API_KEYS_TTL_MS, DEFAULT_SETTINGS, getApiKey, useSettingsStore, } from "../../state/Settings.js";
20
27
  import { PALETTES } from "../../Theme/Palettes.js";
21
28
  import { ConfirmationModal } from "../Common/ConfirmationModal.js";
22
- import { CustomerLogo } from "../Common/CustomerLogo.js";
23
29
  import { MUIDialog } from "../Common/MUIDialog.js";
24
30
  import { NotificationType, sendNotification } from "../Common/notification.js";
31
+ import { StatusLight } from "../Common/StatusLight.js";
32
+ import { CustomerLogo } from "../Logo/CustomerLogo.js";
33
+ //#region: Styled Components
34
+ const SettingsSectionTitleBase = (props) => (_jsx(Typography, { variant: "h6", ...props }));
35
+ const SettingsSectionTitle = styled(SettingsSectionTitleBase)(({ theme }) => ({
36
+ marginBottom: theme.spacing(0.5),
37
+ }));
38
+ const Section = styled(Box)(({ theme }) => ({
39
+ display: "flex",
40
+ flexDirection: "column",
41
+ gap: theme.spacing(2),
42
+ marginBottom: theme.spacing(2),
43
+ }));
44
+ const SubsectionTitle = styled(Typography)(({ theme }) => ({
45
+ borderBottom: `1px solid ${theme.palette.divider}`,
46
+ color: theme.palette.text.secondary,
47
+ fontWeight: 600,
48
+ paddingBottom: theme.spacing(0.5),
49
+ marginBottom: theme.spacing(1.5),
50
+ width: "100%",
51
+ }));
52
+ const SubSection = styled(Box)(({ theme }) => ({
53
+ alignItems: "start",
54
+ display: "flex",
55
+ flexDirection: "column",
56
+ gap: theme.spacing(2),
57
+ marginBottom: theme.spacing(4),
58
+ }));
59
+ const SubSectionBody = styled(Box)(({ theme }) => ({
60
+ alignContent: "center",
61
+ display: "flex",
62
+ flexDirection: "column",
63
+ gap: theme.spacing(2.5),
64
+ width: "100%",
65
+ }));
66
+ //#endregion: Types and Interfaces
67
+ // eslint-disable-next-line react/no-multi-comp -- only relevant to this module.
68
+ const SettingsSubsection = ({ title, children }) => (_jsxs(SubSection, { children: [_jsx(SubsectionTitle, { variant: "subtitle1", children: title }), _jsx(SubSectionBody, { children: children })] }));
69
+ const URL_PROTOCOL_REGEX = /^https?:\/\//iu;
70
+ const HTTP_PROTOCOL_REGEX = /^http:\/\//iu;
71
+ const stripProtocol = (value) => (value ?? "").replace(URL_PROTOCOL_REGEX, "");
72
+ /**
73
+ * Normalize a Neuro SAN URL input by stripping the protocol and determining the protocol to use.
74
+ * @param value The input URL value to normalize.
75
+ * @param fallbackProtocol The protocol to use if the input value does not specify one.
76
+ * @return An object containing the normalized host and protocol.
77
+ */
78
+ const normalizeNeuroSanUrlInput = (value, fallbackProtocol) => {
79
+ const trimmedValue = value.trim();
80
+ const hasProtocol = URL_PROTOCOL_REGEX.exec(trimmedValue);
81
+ return {
82
+ host: stripProtocol(trimmedValue),
83
+ protocol: hasProtocol ? (HTTP_PROTOCOL_REGEX.test(trimmedValue) ? "http" : "https") : fallbackProtocol,
84
+ };
85
+ };
86
+ // eslint-disable-next-line react/no-multi-comp -- styled component shim is only used in this module
25
87
  export const SettingsDialog = ({ id, isOpen, logoServiceToken, onClose }) => {
26
88
  // Settings store actions
27
89
  const updateSettings = useSettingsStore((state) => state.updateSettings);
@@ -46,14 +108,42 @@ export const SettingsDialog = ({ id, isOpen, logoServiceToken, onClose }) => {
46
108
  const customer = useSettingsStore((state) => state.settings.branding.customer);
47
109
  const brandingCheckmark = useCheckmarkFade();
48
110
  const logoCheckmark = useCheckmarkFade();
49
- const [customerInput, setCustomerInput] = useState(customer);
111
+ const [customerInput, setCustomerInput] = useState(customer ?? "");
50
112
  const [isBrandingApplying, setIsBrandingApplying] = useState(false);
51
113
  const logoSource = useSettingsStore((state) => state.settings.branding.logoSource);
114
+ const iconSuggestion = useSettingsStore((state) => state.settings.branding.iconSuggestion);
52
115
  // Zen mode
53
116
  const enableZenMode = useSettingsStore((state) => state.settings.behavior.enableZenMode);
54
117
  const enableZenModeCheckmark = useCheckmarkFade();
55
118
  // API keys
56
119
  const apiKeys = useSettingsStore((state) => state.settings.apiKeys);
120
+ const openAIKeyCheckmark = useCheckmarkFade();
121
+ const anthropicKeyCheckmark = useCheckmarkFade();
122
+ // Native names setting
123
+ const useNativeNames = useSettingsStore((state) => state.settings.appearance.useNativeNames);
124
+ const nativeNamesCheckmark = useCheckmarkFade();
125
+ /*
126
+ Neuro SAN URL
127
+ This can come from three sources:
128
+ 1) The default URL from server environment variables
129
+ 2) The persisted value in the Zustand store
130
+ 3) The current state value, from the user's input in the settings dialog
131
+ */
132
+ const defaultNeuroSanUrl = useEnvironmentStore((state) => state.backendNeuroSanApiUrl ?? "");
133
+ const persistedNeuroSanUrl = useSettingsStore((state) => state.settings.externalServices.neuroSanUrl);
134
+ const effectiveNeuroSanUrl = persistedNeuroSanUrl || defaultNeuroSanUrl;
135
+ const effectiveNeuroSanProtocol = effectiveNeuroSanUrl.startsWith("http://") ? "http" : "https";
136
+ const neuroSanUrlNoProtocol = stripProtocol(effectiveNeuroSanUrl);
137
+ const [neuroSanUrlInput, setNeuroSanUrlInput] = useState(neuroSanUrlNoProtocol);
138
+ const [neuroSanProtocol, setNeuroSanProtocol] = useState(effectiveNeuroSanProtocol);
139
+ const [neuroSanUrlValidated, setNeuroSanUrlValidated] = useState(null);
140
+ const neuroSanURLCheckmark = useCheckmarkFade();
141
+ const neuroSanHostChanged = neuroSanUrlInput.trim() !== neuroSanUrlNoProtocol;
142
+ const neuroSanProtocolChanged = neuroSanProtocol !== effectiveNeuroSanProtocol;
143
+ const neuroSanUrlSaveDisabled = neuroSanUrlInput.trim().length === 0 || (!neuroSanHostChanged && !neuroSanProtocolChanged);
144
+ const neuroSanUrlResetDisabled = neuroSanUrlInput === stripProtocol(defaultNeuroSanUrl) &&
145
+ neuroSanProtocol === (defaultNeuroSanUrl.startsWith("http://") ? "http" : "https");
146
+ const statusLightValue = neuroSanUrlValidated === null ? "unknown" : neuroSanUrlValidated ? "green" : "red";
57
147
  // Record user's current theme so at least the settings dialog (with default MUI theme) matches that
58
148
  const theme = useTheme();
59
149
  const paletteMode = theme.palette.mode;
@@ -67,94 +157,137 @@ export const SettingsDialog = ({ id, isOpen, logoServiceToken, onClose }) => {
67
157
  }
68
158
  rangePaletteCheckmark.trigger();
69
159
  };
160
+ const updateBranding = (brandingSuggestions) => {
161
+ const { plasma, nodeColor, primary, secondary, background, rangePalette, iconSuggestion: newIconSuggestion, } = brandingSuggestions;
162
+ // Update persisted settings with new branding suggestions
163
+ const updates = {
164
+ branding: {
165
+ customer: customerInput,
166
+ ...(primary ? { primary } : {}),
167
+ ...(secondary ? { secondary } : {}),
168
+ ...(background ? { background } : {}),
169
+ ...(Array.isArray(rangePalette) ? { rangePalette } : {}),
170
+ ...(newIconSuggestion
171
+ ? {
172
+ iconSuggestion: newIconSuggestion,
173
+ logoSource: logoServiceToken ? "auto" : "generic",
174
+ }
175
+ : {}),
176
+ },
177
+ appearance: {
178
+ rangePalette: "brand",
179
+ ...(plasma ? { plasmaColor: plasma } : {}),
180
+ ...(nodeColor ? { agentNodeColor: nodeColor } : {}),
181
+ },
182
+ };
183
+ updateSettings(updates);
184
+ // Trigger checkmarks for items we changed
185
+ brandingCheckmark.trigger();
186
+ rangePaletteCheckmark.trigger();
187
+ if (plasma) {
188
+ plasmaColorCheckmark.trigger();
189
+ }
190
+ if (nodeColor) {
191
+ agentNodeColorCheckmark.trigger();
192
+ }
193
+ if (newIconSuggestion) {
194
+ logoCheckmark.trigger();
195
+ }
196
+ };
70
197
  /**
71
198
  * Handle applying branding based on customer input. Calls backend to get colors then updates settings store.
72
199
  */
73
200
  const handleBrandingApply = async () => {
74
201
  setIsBrandingApplying(true);
75
- updateSettings({
76
- branding: {
77
- customer: customerInput,
78
- },
79
- });
80
- let brandingSuggestions;
81
202
  try {
82
- brandingSuggestions = await getBrandingSuggestions(customerInput);
203
+ const brandingSuggestions = await getBrandingSuggestions(customerInput);
204
+ if (brandingSuggestions) {
205
+ updateBranding(brandingSuggestions);
206
+ }
83
207
  }
84
208
  catch (e) {
85
- console.warn(`Failed to fetch branding suggestions for customer "${customerInput}"`, e);
209
+ console.error(`Failed to fetch branding suggestions for customer "${customerInput}"`, e);
86
210
  sendNotification(NotificationType.error, `Failed to fetch branding suggestions for "${customerInput}"`, "Please check the name and try again. If the problem persists, there may be an issue with the " +
87
211
  "branding service.");
88
- return;
89
212
  }
213
+ finally {
214
+ setIsBrandingApplying(false);
215
+ }
216
+ };
217
+ const handleBrandingClear = () => {
218
+ // Clear branding settings
90
219
  updateSettings({
91
220
  appearance: {
92
- rangePalette: "brand",
221
+ agentIconColor: DEFAULT_SETTINGS.appearance.agentIconColor,
222
+ agentNodeColor: DEFAULT_SETTINGS.appearance.agentNodeColor,
223
+ autoAgentIconColor: DEFAULT_SETTINGS.appearance.autoAgentIconColor,
224
+ plasmaColor: DEFAULT_SETTINGS.appearance.plasmaColor,
225
+ rangePalette: DEFAULT_SETTINGS.appearance.rangePalette,
93
226
  },
227
+ branding: { ...DEFAULT_SETTINGS.branding },
94
228
  });
95
- if (brandingSuggestions["plasma"]) {
96
- updateSettings({
97
- appearance: {
98
- plasmaColor: brandingSuggestions["plasma"],
99
- },
100
- });
101
- plasmaColorCheckmark.trigger();
102
- }
103
- if (brandingSuggestions["nodeColor"]) {
104
- updateSettings({
105
- appearance: {
106
- agentNodeColor: brandingSuggestions["nodeColor"],
107
- },
108
- });
109
- agentNodeColorCheckmark.trigger();
110
- }
111
- // primary
112
- if (brandingSuggestions["primary"]) {
113
- updateSettings({
114
- branding: {
115
- primary: brandingSuggestions["primary"],
116
- },
117
- });
118
- }
119
- // secondary
120
- if (brandingSuggestions["secondary"]) {
121
- updateSettings({
122
- branding: {
123
- secondary: brandingSuggestions["secondary"],
124
- },
125
- });
126
- }
127
- // background
128
- if (brandingSuggestions["background"]) {
129
- updateSettings({
130
- branding: {
131
- background: brandingSuggestions["background"],
132
- },
133
- });
134
- }
135
- if (Array.isArray(brandingSuggestions["rangePalette"])) {
136
- updateSettings({
137
- branding: {
138
- rangePalette: brandingSuggestions["rangePalette"],
139
- },
140
- });
141
- }
142
- if (brandingSuggestions["iconSuggestion"]) {
143
- updateSettings({
144
- branding: {
145
- iconSuggestion: brandingSuggestions["iconSuggestion"],
146
- },
147
- });
148
- }
229
+ // Trigger checkmarks for all the settings that are affected by branding
230
+ agentIconColorCheckmark.trigger();
231
+ agentNodeColorCheckmark.trigger();
232
+ plasmaColorCheckmark.trigger();
233
+ rangePaletteCheckmark.trigger();
149
234
  brandingCheckmark.trigger();
150
- setIsBrandingApplying(false);
235
+ logoCheckmark.trigger();
151
236
  };
152
- const persistKey = (vendor, key) => {
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) => {
153
245
  updateSettings({
154
246
  apiKeys: {
155
- [vendor]: key,
247
+ [vendor]: {
248
+ value: key,
249
+ expiresAt: key ? now + API_KEYS_TTL_MS : 0,
250
+ },
251
+ },
252
+ });
253
+ checkmark.trigger();
254
+ };
255
+ const handleTestConnection = async () => {
256
+ const normalized = normalizeNeuroSanUrlInput(neuroSanUrlInput, neuroSanProtocol);
257
+ setNeuroSanProtocol(normalized.protocol);
258
+ setNeuroSanUrlInput(normalized.host);
259
+ const result = await testConnection(`${normalized.protocol}://${normalized.host}`);
260
+ setNeuroSanUrlValidated(result.success);
261
+ };
262
+ const handleNeuroSanUrlChange = (e) => {
263
+ setNeuroSanUrlInput(e.target.value);
264
+ setNeuroSanUrlValidated(null);
265
+ };
266
+ const handleSaveNeuroSanUrl = () => {
267
+ const normalized = normalizeNeuroSanUrlInput(neuroSanUrlInput, neuroSanProtocol);
268
+ setNeuroSanProtocol(normalized.protocol);
269
+ setNeuroSanUrlInput(normalized.host);
270
+ updateSettings({
271
+ externalServices: {
272
+ neuroSanUrl: `${normalized.protocol}://${normalized.host}`,
156
273
  },
157
274
  });
275
+ neuroSanURLCheckmark.trigger();
276
+ };
277
+ const handleNeuroSanProtocolChange = (e) => {
278
+ const newProtocol = e.target.value;
279
+ setNeuroSanProtocol(newProtocol);
280
+ setNeuroSanUrlValidated(null);
281
+ };
282
+ const handleNeuroSanUrlBlur = () => {
283
+ const normalized = normalizeNeuroSanUrlInput(neuroSanUrlInput, neuroSanProtocol);
284
+ setNeuroSanProtocol(normalized.protocol);
285
+ setNeuroSanUrlInput(normalized.host);
286
+ };
287
+ const handleResetNeuroSanUrl = () => {
288
+ const normalized = normalizeNeuroSanUrlInput(defaultNeuroSanUrl, "https");
289
+ setNeuroSanUrlInput(normalized.host);
290
+ setNeuroSanProtocol(normalized.protocol);
158
291
  };
159
292
  // Effect to keep input in sync with state store
160
293
  useEffect(() => {
@@ -162,131 +295,188 @@ export const SettingsDialog = ({ id, isOpen, logoServiceToken, onClose }) => {
162
295
  }, [customer]);
163
296
  const availablePalettes = customer && brandingRangePalette?.length > 0 ? { brand: brandingRangePalette } : PALETTES;
164
297
  const paletteKeys = Object.keys(availablePalettes);
298
+ // Config for API key inputs, so we can easily add new providers in the future by just adding to this array
165
299
  const apiKeyConfigs = [
166
300
  {
167
- vendor: "OpenAI",
301
+ checkmark: openAIKeyCheckmark,
168
302
  idSuffix: "openai",
169
303
  logo: theme.palette.mode === "dark" ? "/OpenAI-white.png" : "/OpenAI-black.png",
170
304
  onTest: isOpenAIKeyValid,
171
305
  placeholder: "sk-...",
306
+ vendor: "OpenAI",
172
307
  },
173
308
  {
174
- vendor: "Anthropic",
309
+ checkmark: anthropicKeyCheckmark,
175
310
  idSuffix: "anthropic",
176
311
  logo: "/claude.png",
177
312
  onTest: isAnthropicKeyValid,
178
313
  placeholder: "sk-ant-...",
314
+ vendor: "Anthropic",
179
315
  },
180
316
  ];
181
- /* Dev note:
182
- Before you go removing the "useless" spans in code below that wrap MUI elements: they are required because
183
- MUI's disabled state on certain components sets pointer-events: none, which prevents tooltips from working.
184
- Wrapping in a span allows the tooltip to still function while the inner component is disabled.
185
- See: https://github.com/mui/material-ui/issues/8416
186
- */
317
+ const getConfirmationModal = () => (_jsx(ConfirmationModal, { id: `${id}-reset-to-default-settings-confirmation-modal`, content: "This will reset all settings to their default values and cannot be undone. " +
318
+ "Are you sure you want to proceed?", handleCancel: () => {
319
+ setResetToDefaultSettingsOpen(false);
320
+ }, handleOk: () => {
321
+ setResetToDefaultSettingsOpen(false);
322
+ resetSettings();
323
+ sendNotification(NotificationType.success, "Settings have been reset to default values.");
324
+ }, title: "Reset to default settings" }));
325
+ const getNeuroSanSubsection = () => {
326
+ return (_jsx(SettingsSubsection, { title: "Neuro SAN", children: _jsxs(SettingsRow, { id: `${id}-neuro-san-server-url-row`, checkmark: neuroSanURLCheckmark, label: "", tooltip: "URL for the Neuro SAN server.", children: [_jsxs(Select, { "aria-label": "protocol-select", onChange: handleNeuroSanProtocolChange, size: "small", sx: { minWidth: 100, flexShrink: 0 }, value: neuroSanProtocol, children: [_jsx(MenuItem, { value: "https", children: "https://" }), _jsx(MenuItem, { value: "http", children: "http://" })] }), _jsx(TextField, { "aria-label": "neuro-san-server-url-host-input", onBlur: handleNeuroSanUrlBlur, onChange: handleNeuroSanUrlChange, placeholder: "example.com:1234", size: "small", slotProps: {
327
+ input: {
328
+ endAdornment: (_jsx(InputAdornment, { position: "end", children: _jsx(IconButton, { "aria-label": "Clear input", edge: "end", size: "small", onClick: () => {
329
+ setNeuroSanUrlInput("");
330
+ setNeuroSanUrlValidated(null);
331
+ }, children: _jsx(ClearIcon, { fontSize: "small" }) }) })),
332
+ },
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`) }));
334
+ };
335
+ const getServicesSection = () => (_jsxs(Section, { children: [_jsx(SettingsSectionTitle, { children: "External Services" }), getNeuroSanSubsection()] }));
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}`))) }) })] }));
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 " +
348
+ "experience.", children: _jsx(Checkbox, { checked: enableZenMode, "data-testid": "zen-mode-checkbox", onChange: (_, checked) => {
349
+ updateSettings({ behavior: { enableZenMode: checked } });
350
+ enableZenModeCheckmark.trigger();
351
+ }, sx: { p: 0.0 } }) }) })] }));
352
+ const getNamingSubsection = () => (_jsx(SettingsSubsection, { title: "Agent names", children: _jsxs(SettingsRow, { label: "Display as:", checkmark: nativeNamesCheckmark, tooltip: "Choose how agent names are displayed in the network. " +
353
+ '"Native" shows the original agent name as provided by the system, while "Beautified" applies ' +
354
+ "formatting to make names more human-readable. This setting does not affect the actual names of " +
355
+ "agents, only how they are displayed in the UI.", children: [_jsxs(ToggleButtonGroup, { "aria-label": "agent-name-format-selection", exclusive: true, onChange: (_, value) => {
356
+ if (value !== null) {
357
+ updateSettings({
358
+ appearance: {
359
+ useNativeNames: value === "native",
360
+ },
361
+ });
362
+ nativeNamesCheckmark.trigger();
363
+ }
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: {
365
+ border: "1px solid",
366
+ borderRadius: 1,
367
+ backgroundColor: "background.paper",
368
+ ml: 0.5,
369
+ px: 1,
370
+ py: 0.25,
371
+ lineHeight: 0,
372
+ fontSize: "0.7rem",
373
+ maxWidth: "100%",
374
+ }, children: _jsx("pre", { children: `category/some_agent_name → ${useNativeNames ? "category/some_agent_name [unchanged]" : "Category Some Agent Name"}` }) })] }) }));
375
+ const getBrandingSubsection = () => (_jsxs(SettingsSubsection, { title: "Branding", children: [_jsxs(SettingsRow, { checkmark: brandingCheckmark, label: "Customer:", tooltip: "Set a customer or organization name to automatically apply a custom color palette and " +
376
+ "logo to the network", children: [_jsx(TextField, { "aria-label": "branding-input", onChange: (e) => setCustomerInput(e.target.value), onKeyDown: (e) => {
377
+ if (e.key === "Enter" && customerInput?.trim().length > 0) {
378
+ void handleBrandingApply();
379
+ }
380
+ }, value: customerInput, placeholder: "Company or organization name", size: "small", sx: { width: "100%" }, variant: "outlined" }), _jsx(Button, { disabled: customerInput?.trim().length === 0 || isBrandingApplying || customerInput === customer, variant: "contained", size: "small", onClick: handleBrandingApply, loading: isBrandingApplying, children: "Apply" }), _jsx(Button, { disabled: !customer || isBrandingApplying, variant: "contained", size: "small", onClick: handleBrandingClear, loading: isBrandingApplying, children: "Clear" })] }), _jsxs(SettingsRow, { checkmark: logoCheckmark, label: "Logo:", tooltip: "Choose a logo to display in the top-left corner of the network. " +
381
+ '"None" will display no logo, "Generic" will display a simple generic logo, and "Auto" will ' +
382
+ "attempt to find a suitable logo based on the customer name using an external service (if " +
383
+ "configured on the server). Logo is only displayed when customer branding is applied.", children: [_jsx(Tooltip, { title: customer ? undefined : "Set a customer name to enable logo options", children: _jsx("span", { children: _jsxs(ToggleButtonGroup, { "aria-label": "logo-selection", disabled: !customer, exclusive: true, onChange: (_, value) => {
384
+ if (value !== null) {
385
+ updateSettings({
386
+ branding: {
387
+ logoSource: value,
388
+ },
389
+ });
390
+ logoCheckmark.trigger();
391
+ }
392
+ }, size: "small", sx: { cursor: customer && logoServiceToken ? "pointer" : "not-allowed", marginRight: "1rem" }, value: logoSource || "none", children: [_jsx(Tooltip, { title: customer && "No logo will be displayed", children: _jsx("span", { children: _jsx(ToggleButton, { value: "none", children: "None" }) }) }), _jsx(Tooltip, { title: customer && "Display a simple, anonymous generic logo based on a generic brand", children: _jsx("span", { children: _jsx(ToggleButton, { value: "generic", children: "Generic" }) }) }), _jsx(Tooltip, { title: customer
393
+ ? logoServiceToken
394
+ ? "Use a service to attempt to automatically find a suitable logo based " +
395
+ "on the customer name."
396
+ : "Logo service not configured on the server. Cannot use Auto logo source"
397
+ : undefined, children: _jsx("span", { children: _jsx(ToggleButton, { disabled: !logoServiceToken, value: "auto", children: "Auto" }) }) })] }) }) }), _jsx(FormLabel, { children: "Preview:" }), _jsx(Box, { children: logoSource === "auto" || logoSource === "generic" ? (_jsx(CustomerLogo, { customer: customer ?? "", fallbackIcon: Business, iconSuggestion: iconSuggestion ?? undefined, logoServiceToken: logoServiceToken, logoSource: logoSource })) : ("(None)") })] })] }));
398
+ const getNetworkDisplaySubsection = () => (_jsx(SettingsSubsection, { title: "Network display", children: _jsx(SettingsRow, { checkmark: rangePaletteCheckmark, label: "Palette:", tooltip: "Choose the color palette to use for heatmaps and depth display in the network. ", children: _jsx(Tooltip, { title: customer ? "Palette is determined by customer branding and cannot be changed" : "", children: _jsx("span", { children: _jsx(ToggleButtonGroup, { "aria-label": "depth-heatmap-palette-selection", disabled: Boolean(customer), exclusive: true, onChange: handlePaletteChange, size: "small", sx: {
399
+ cursor: customer ? "not-allowed" : "pointer",
400
+ opacity: customer ? 0.5 : 1,
401
+ }, value: paletteKey, children: paletteKeys.map((key) => {
402
+ const palette = availablePalettes[key];
403
+ if (!palette || !Array.isArray(palette))
404
+ return null;
405
+ const paletteArray = palette;
406
+ return (_jsx(ToggleButton, { "aria-label": `${key}-palette-button`, value: key, children: _jsxs(Box, { sx: {
407
+ display: "flex",
408
+ flexDirection: "column",
409
+ gap: 0.5,
410
+ alignItems: "center",
411
+ }, children: [_jsx(Typography, { variant: "caption", sx: { textTransform: "capitalize" }, children: key }), _jsx(Box, { sx: { display: "flex", gap: 0.5 }, children: Array.from({ length: 5 }, (_, i) => {
412
+ const paletteLength = paletteArray.length;
413
+ const index = Math.floor((i * paletteLength) / 5);
414
+ return paletteArray[index];
415
+ }).map((color) => (_jsx(Box, { sx: {
416
+ width: "0.75rem",
417
+ height: "0.75rem",
418
+ backgroundColor: color,
419
+ border: "1px solid",
420
+ } }, color))) })] }) }, key));
421
+ }) }) }) }) }) }));
422
+ const getNetworkAnimationSubsection = () => (_jsxs(SettingsSubsection, { title: "Network animation", children: [_jsx(SettingsRow, { checkmark: plasmaColorCheckmark, label: "Plasma animation color:", tooltip: customer
423
+ ? "Plasma color is locked when branding is applied"
424
+ : "Select the color for the plasma animation.", children: _jsx("input", { "aria-label": "plasma-color-picker", disabled: Boolean(customer), onChange: (e) => {
425
+ updateSettings({
426
+ appearance: {
427
+ plasmaColor: e.target.value,
428
+ },
429
+ });
430
+ plasmaColorCheckmark.trigger();
431
+ }, style: { cursor: customer ? "not-allowed" : "pointer", opacity: customer ? 0.5 : 1 }, type: "color", value: plasmaColor }) }), _jsx(SettingsRow, { checkmark: agentNodeColorCheckmark, label: "Agent node color:", tooltip: customer
432
+ ? "Agent node color is locked when branding is applied"
433
+ : "Select the color for the agent node animation.", children: _jsx("input", { "aria-label": "agent-node-color-picker", disabled: Boolean(customer), onChange: (e) => {
434
+ updateSettings({
435
+ appearance: {
436
+ agentNodeColor: e.target.value,
437
+ },
438
+ });
439
+ agentNodeColorCheckmark.trigger();
440
+ }, style: { cursor: customer ? "not-allowed" : "pointer", opacity: customer ? 0.5 : 1 }, type: "color", value: agentNodeColor }) }), _jsxs(SettingsRow, { checkmark: agentIconColorCheckmark, label: "Agent icon color:", tooltip: customer
441
+ ? "Agent icon color is locked when branding is applied"
442
+ : "Select the color for the agent icon animation.", children: [_jsx("span", { children: _jsxs(ToggleButtonGroup, { disabled: Boolean(customer), exclusive: true, value: autoAgentIconColor ? "auto" : "custom", onChange: (_, value) => {
443
+ if (value !== null) {
444
+ updateSettings({
445
+ appearance: {
446
+ autoAgentIconColor: value === "auto",
447
+ },
448
+ });
449
+ agentIconColorCheckmark.trigger();
450
+ }
451
+ }, size: "small", style: {
452
+ cursor: customer ? "not-allowed" : "pointer",
453
+ opacity: customer ? 0.5 : 1,
454
+ }, children: [_jsx(ToggleButton, { "data-testid": "auto-agent-icon-color-button", value: "auto", children: "Auto" }), _jsx(ToggleButton, { value: "custom", children: "Custom" })] }) }), _jsx("input", { "aria-label": "agent-icon-color-picker", disabled: Boolean(customer) || autoAgentIconColor, onChange: (e) => {
455
+ updateSettings({
456
+ appearance: {
457
+ agentIconColor: e.target.value,
458
+ },
459
+ });
460
+ agentIconColorCheckmark.trigger();
461
+ }, style: { cursor: customer ? "not-allowed" : "pointer", opacity: customer ? 0.5 : 1 }, type: "color", value: agentIconColor })] })] }));
462
+ const getAppearanceSection = () => (_jsxs(Section, { children: [_jsx(SettingsSectionTitle, { children: "Appearance" }), getNamingSubsection(), getBrandingSubsection(), getNetworkDisplaySubsection(), getNetworkAnimationSubsection()] }));
463
+ const getResetSettingsSection = () => (_jsx(SubSection, { sx: { marginTop: 4, paddingTop: 2, borderTop: "4px solid var(--bs-border-color)" }, children: _jsx(SubSectionBody, { children: _jsx(Button, { variant: "text", startIcon: _jsx(RestoreIcon, {}), onClick: () => {
464
+ setResetToDefaultSettingsOpen(true);
465
+ }, sx: { color: "var(--bs-secondary)" }, children: "Reset to defaults" }) }) }));
466
+ const settingsTheme = useMemo(() => createTheme({
467
+ palette: {
468
+ mode: paletteMode,
469
+ },
470
+ typography: {
471
+ fontSize: 12,
472
+ },
473
+ }), [paletteMode]);
187
474
  return (
188
- // 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
189
476
  // certain custom themes the dialog would be unreadable.
190
- _jsxs(ThemeProvider, { theme: createTheme({
191
- palette: {
192
- mode: paletteMode,
193
- },
194
- }), children: [resetToDefaultSettingsOpen ? (_jsx(ConfirmationModal, { id: `${id}-reset-to-default-settings-confirmation-modal`, content: "This will reset all settings to their default values and cannot be undone. " +
195
- "Are you sure you want to proceed?", handleCancel: () => {
196
- setResetToDefaultSettingsOpen(false);
197
- }, handleOk: () => {
198
- setResetToDefaultSettingsOpen(false);
199
- resetSettings();
200
- sendNotification(NotificationType.success, "Settings have been reset to default values.");
201
- }, title: "Reset to default settings" })) : null, _jsxs(MUIDialog, { id: id, title: _jsx(Box, { sx: { fontSize: "1.5rem" }, children: "Settings" }), isOpen: isOpen, onClose: onClose, paperProps: {
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: {
202
478
  minWidth: "50%",
203
479
  minHeight: "50%",
204
480
  border: "1px solid",
205
- }, children: [_jsxs(Box, { sx: { marginBottom: 3 }, children: [_jsx(Typography, { variant: "h6", sx: { marginBottom: 1 }, children: "API Keys" }), _jsx(Box, { sx: { display: "flex", flexDirection: "column", gap: 1.5 }, children: apiKeyConfigs.map(({ vendor, idSuffix, logo, onTest, placeholder }) => (_jsx(ApiKeyInput, { forgetKey: () => persistKey(vendor, ""), id: `${id}-${idSuffix}`, logo: logo, onSave: (key) => persistKey(vendor, key), onTest: onTest, persistedValue: apiKeys[vendor], placeholder: placeholder, vendor: vendor }, idSuffix))) })] }), _jsxs(Box, { sx: { marginBottom: 3 }, children: [_jsx(Typography, { variant: "h6", sx: { marginBottom: 1 }, children: "Behavior" }), _jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [_jsx(FormLabel, { children: "Enable \"Zen\" mode:" }), _jsx(Tooltip, { title: "Hides most of the UI during agent network animations, " +
206
- "providing a more immersive experience.", children: _jsx(Checkbox, { checked: enableZenMode, "data-testid": "zen-mode-checkbox", onChange: (_, checked) => {
207
- updateSettings({ behavior: { enableZenMode: checked } });
208
- enableZenModeCheckmark.trigger();
209
- } }) }), _jsx(FadingCheckmark, { show: enableZenModeCheckmark.show })] })] }), _jsxs(Box, { sx: { marginBottom: 3 }, children: [_jsx(Typography, { variant: "h6", sx: { marginBottom: 1 }, children: "Appearance" }), _jsx(Typography, { variant: "subtitle1", sx: { marginBottom: 1, marginTop: 2, fontWeight: 600 }, children: "Branding" }), _jsx(Divider, { sx: { marginBottom: 2 } }), _jsx(Box, { sx: { display: "flex", alignItems: "center" }, children: _jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 2, marginBottom: "1rem", width: "100%" }, children: [_jsx(FormLabel, { children: "Customer:" }), _jsx(TextField, { "aria-label": "branding-input", onChange: (e) => setCustomerInput(e.target.value), onKeyDown: (e) => {
210
- if (e.key === "Enter" && customerInput?.trim().length > 0) {
211
- void handleBrandingApply();
212
- }
213
- }, value: customerInput ?? "", placeholder: "Company or organization name", size: "small", sx: { width: "100%" }, variant: "outlined" }), _jsx(Button, { disabled: customerInput?.trim().length === 0 ||
214
- isBrandingApplying ||
215
- customerInput === customer, variant: "contained", size: "small", onClick: handleBrandingApply, loading: isBrandingApplying, sx: { minWidth: "8rem" }, children: "Apply" }), _jsx(FadingCheckmark, { show: brandingCheckmark.show })] }) }), _jsx(Box, { sx: { display: "flex", alignItems: "center" }, children: _jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 2, marginBottom: "1rem", width: "100%" }, children: [_jsx(FormLabel, { children: "Logo:" }), _jsx(Tooltip, { title: customer ? null : "Set a customer name to enable logo options", children: _jsx("span", { children: _jsxs(ToggleButtonGroup, { "aria-label": "logo-selection", disabled: !customer, exclusive: true, onChange: (_, value) => {
216
- if (value !== null) {
217
- updateSettings({
218
- branding: {
219
- logoSource: value,
220
- },
221
- });
222
- logoCheckmark.trigger();
223
- }
224
- }, size: "small", sx: { marginRight: "1rem" }, value: logoSource || "none", children: [_jsx(Tooltip, { title: "No logo will be displayed",
225
- // Avoids having overlapping tooltips
226
- disableHoverListener: !customer, disableFocusListener: !customer, disableTouchListener: !customer, children: _jsx("span", { style: { cursor: customer ? "pointer" : "not-allowed" }, children: _jsx(ToggleButton, { value: "none", children: "None" }) }) }), _jsx(Tooltip, { title: "Display a simple, anonymous generic logo based on a generic brand",
227
- // Avoids having overlapping tooltips
228
- disableHoverListener: !customer, disableFocusListener: !customer, disableTouchListener: !customer, children: _jsx("span", { style: { cursor: customer ? "pointer" : "not-allowed" }, children: _jsx(ToggleButton, { value: "generic", children: "Generic" }) }) }), _jsx(Tooltip, { title: logoServiceToken
229
- ? "Use a service to attempt to automatically find a suitable " +
230
- "logo based on the customer name. Results may vary based " +
231
- "on the uniqueness of the name and availability of logos online."
232
- : "No Logo.dev token found, cannot use Auto logo source",
233
- // Avoids having overlapping tooltips
234
- disableHoverListener: !customer, disableFocusListener: !customer, disableTouchListener: !customer, children: _jsx("span", { style: {
235
- cursor: customer && logoServiceToken ? "pointer" : "not-allowed",
236
- }, children: _jsx(ToggleButton, { disabled: !logoServiceToken, value: "auto", children: "Auto" }) }) })] }) }) }), _jsx(FormLabel, { children: "Preview:" }), _jsx(CustomerLogo, { fallbackElement: "(None)", logoServiceToken: logoServiceToken }), _jsx(FadingCheckmark, { show: logoCheckmark.show })] }) }), _jsx(Typography, { variant: "subtitle1", sx: { marginBottom: 1, marginTop: 2, fontWeight: 600 }, children: "Network display" }), _jsx(Divider, { sx: { marginBottom: 2 } }), _jsxs(Box, { sx: { display: "flex", alignItems: "center" }, children: [_jsx(FormLabel, { children: "Palette (heatmap and depth):" }), _jsx(Tooltip, { title: customer ? "Palette is locked when branding is applied" : "", children: _jsx("span", { children: _jsx(ToggleButtonGroup, { "aria-label": "depth-heatmap-palette-selection", disabled: Boolean(customer), exclusive: true, onChange: handlePaletteChange, size: "small", sx: {
237
- cursor: customer ? "not-allowed" : "pointer",
238
- marginLeft: "1rem",
239
- marginRight: "1rem",
240
- opacity: customer ? 0.5 : 1,
241
- }, value: paletteKey, children: paletteKeys.map((key) => {
242
- const palette = availablePalettes[key];
243
- if (!palette || !Array.isArray(palette))
244
- return null;
245
- const paletteArray = palette;
246
- return (_jsx(ToggleButton, { "aria-label": `${key}-palette-button`, value: key, children: _jsxs(Box, { sx: {
247
- display: "flex",
248
- flexDirection: "column",
249
- gap: 0.5,
250
- alignItems: "center",
251
- }, children: [_jsx(Typography, { variant: "caption", sx: { textTransform: "capitalize" }, children: key }), _jsx(Box, { sx: { display: "flex", gap: 0.5 }, children: Array.from({ length: 5 }, (_, i) => {
252
- const paletteLength = paletteArray.length;
253
- const index = Math.floor((i * paletteLength) / 5);
254
- return paletteArray[index];
255
- }).map((color) => (_jsx(Box, { sx: {
256
- width: "0.75rem",
257
- height: "0.75rem",
258
- backgroundColor: color,
259
- border: "1px solid",
260
- } }, color))) })] }) }, key));
261
- }) }) }) }), _jsx(FadingCheckmark, { show: rangePaletteCheckmark.show })] }), _jsx(Typography, { variant: "subtitle1", sx: { marginBottom: 1, marginTop: 2, fontWeight: 600 }, children: "Network animation" }), _jsx(Divider, { sx: { marginBottom: 2 } }), _jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 2 }, children: [_jsx(FormLabel, { children: "Plasma animation color:" }), _jsx(Tooltip, { title: customer ? "Plasma color is locked when branding is applied" : "", children: _jsx("input", { "aria-label": "plasma-color-picker", disabled: Boolean(customer), onChange: (e) => updateSettings({
262
- appearance: {
263
- plasmaColor: e.target.value,
264
- },
265
- }), style: { cursor: customer ? "not-allowed" : "pointer", opacity: customer ? 0.5 : 1 }, type: "color", value: plasmaColor }) }), _jsx(FadingCheckmark, { show: plasmaColorCheckmark.show })] }), _jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 2, marginTop: "1rem" }, children: [_jsx(FormLabel, { children: "Agent node color:" }), _jsx(Tooltip, { title: customer ? "Agent node color is locked when branding is applied" : "", children: _jsx("input", { "aria-label": "agent-node-color-picker", disabled: Boolean(customer), onChange: (e) => updateSettings({
266
- appearance: {
267
- agentNodeColor: e.target.value,
268
- },
269
- }), style: { cursor: customer ? "not-allowed" : "pointer", opacity: customer ? 0.5 : 1 }, type: "color", value: agentNodeColor }) }), _jsx(FadingCheckmark, { show: agentNodeColorCheckmark.show })] }), _jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 2, marginTop: "1rem" }, children: [_jsx(FormLabel, { children: "Agent icon color:" }), _jsx(Tooltip, { title: customer ? "Agent icon color is locked when branding is applied" : "", children: _jsx("span", { children: _jsxs(ToggleButtonGroup, { disabled: Boolean(customer), exclusive: true, value: autoAgentIconColor ? "auto" : "custom", onChange: (_, value) => {
270
- if (value !== null) {
271
- updateSettings({
272
- appearance: {
273
- autoAgentIconColor: value === "auto",
274
- },
275
- });
276
- agentIconColorCheckmark.trigger();
277
- }
278
- }, size: "small", style: { cursor: customer ? "not-allowed" : "pointer", opacity: customer ? 0.5 : 1 }, children: [_jsx(ToggleButton, { "data-testid": "auto-agent-icon-color-button", value: "auto", children: "Auto" }), _jsx(ToggleButton, { value: "custom", children: "Custom" })] }) }) }), _jsx(Tooltip, { title: customer
279
- ? "Agent icon color is locked when branding is applied"
280
- : autoAgentIconColor
281
- ? "Disabled when Auto is selected"
282
- : "", children: _jsx("input", { "aria-label": "agent-icon-color-picker", disabled: Boolean(customer) || autoAgentIconColor, onChange: (e) => {
283
- updateSettings({
284
- appearance: {
285
- agentIconColor: e.target.value,
286
- },
287
- });
288
- agentIconColorCheckmark.trigger();
289
- }, style: { cursor: customer ? "not-allowed" : "pointer", opacity: customer ? 0.5 : 1 }, type: "color", value: agentIconColor }) }), _jsx(FadingCheckmark, { show: agentIconColorCheckmark.show })] })] }), _jsx(Box, { sx: { marginTop: 4, paddingTop: 2, borderTop: "1px solid var(--bs-border-color)" }, children: _jsx(Button, { variant: "text", startIcon: _jsx(RestoreIcon, {}), onClick: () => {
290
- setResetToDefaultSettingsOpen(true);
291
- }, sx: { color: "var(--bs-secondary)" }, children: "Reset to defaults" }) })] })] }));
481
+ }, children: [getServicesSection(), getApiKeysSection(), getBehaviorSection(), getAppearanceSection(), getResetSettingsSection()] })] }));
292
482
  };