@cognizant-ai-lab/ui-common 1.6.0 → 1.7.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.
@@ -3,7 +3,6 @@ import RestoreIcon from "@mui/icons-material/SettingsBackupRestore";
3
3
  import Box from "@mui/material/Box";
4
4
  import Button from "@mui/material/Button";
5
5
  import Checkbox from "@mui/material/Checkbox";
6
- import CircularProgress from "@mui/material/CircularProgress";
7
6
  import Divider from "@mui/material/Divider";
8
7
  import FormLabel from "@mui/material/FormLabel";
9
8
  import { createTheme, ThemeProvider, useTheme } from "@mui/material/styles";
@@ -13,8 +12,10 @@ import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
13
12
  import Tooltip from "@mui/material/Tooltip";
14
13
  import Typography from "@mui/material/Typography";
15
14
  import { useEffect, useState } from "react";
15
+ import { ApiKeyInput } from "./ApiKeyInput.js";
16
16
  import { FadingCheckmark, useCheckmarkFade } from "./FadingCheckmark.js";
17
17
  import { getBrandingSuggestions } from "../../controller/agent/Agent.js";
18
+ import { isAnthropicKeyValid, isOpenAIKeyValid } from "../../controller/llm/Providers.js";
18
19
  import { useSettingsStore } from "../../state/Settings.js";
19
20
  import { PALETTES } from "../../Theme/Palettes.js";
20
21
  import { ConfirmationModal } from "../Common/ConfirmationModal.js";
@@ -51,6 +52,8 @@ export const SettingsDialog = ({ id, isOpen, logoServiceToken, onClose }) => {
51
52
  // Zen mode
52
53
  const enableZenMode = useSettingsStore((state) => state.settings.behavior.enableZenMode);
53
54
  const enableZenModeCheckmark = useCheckmarkFade();
55
+ // API keys
56
+ const apiKeys = useSettingsStore((state) => state.settings.apiKeys);
54
57
  // Record user's current theme so at least the settings dialog (with default MUI theme) matches that
55
58
  const theme = useTheme();
56
59
  const paletteMode = theme.palette.mode;
@@ -146,12 +149,35 @@ export const SettingsDialog = ({ id, isOpen, logoServiceToken, onClose }) => {
146
149
  brandingCheckmark.trigger();
147
150
  setIsBrandingApplying(false);
148
151
  };
152
+ const persistKey = (vendor, key) => {
153
+ updateSettings({
154
+ apiKeys: {
155
+ [vendor]: key,
156
+ },
157
+ });
158
+ };
149
159
  // Effect to keep input in sync with state store
150
160
  useEffect(() => {
151
161
  setCustomerInput(customer ?? "");
152
162
  }, [customer]);
153
163
  const availablePalettes = customer && brandingRangePalette?.length > 0 ? { brand: brandingRangePalette } : PALETTES;
154
164
  const paletteKeys = Object.keys(availablePalettes);
165
+ const apiKeyConfigs = [
166
+ {
167
+ vendor: "OpenAI",
168
+ idSuffix: "openai",
169
+ logo: theme.palette.mode === "dark" ? "/OpenAI-white.png" : "/OpenAI-black.png",
170
+ onTest: isOpenAIKeyValid,
171
+ placeholder: "sk-...",
172
+ },
173
+ {
174
+ vendor: "Anthropic",
175
+ idSuffix: "anthropic",
176
+ logo: "/claude.png",
177
+ onTest: isAnthropicKeyValid,
178
+ placeholder: "sk-ant-...",
179
+ },
180
+ ];
155
181
  /* Dev note:
156
182
  Before you go removing the "useless" spans in code below that wrap MUI elements: they are required because
157
183
  MUI's disabled state on certain components sets pointer-events: none, which prevents tooltips from working.
@@ -176,7 +202,8 @@ export const SettingsDialog = ({ id, isOpen, logoServiceToken, onClose }) => {
176
202
  minWidth: "50%",
177
203
  minHeight: "50%",
178
204
  border: "1px solid",
179
- }, children: [_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, providing a more immersive experience.", children: _jsx(Checkbox, { checked: enableZenMode, "data-testid": "zen-mode-checkbox", onChange: (_, checked) => {
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) => {
180
207
  updateSettings({ behavior: { enableZenMode: checked } });
181
208
  enableZenModeCheckmark.trigger();
182
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) => {
@@ -185,7 +212,7 @@ export const SettingsDialog = ({ id, isOpen, logoServiceToken, onClose }) => {
185
212
  }
186
213
  }, value: customerInput ?? "", placeholder: "Company or organization name", size: "small", sx: { width: "100%" }, variant: "outlined" }), _jsx(Button, { disabled: customerInput?.trim().length === 0 ||
187
214
  isBrandingApplying ||
188
- customerInput === customer, variant: "contained", size: "small", onClick: handleBrandingApply, startIcon: isBrandingApplying ? (_jsx(CircularProgress, { size: 16, color: "inherit" })) : undefined, sx: { minWidth: "8rem" }, children: isBrandingApplying ? "Applying..." : "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) => {
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) => {
189
216
  if (value !== null) {
190
217
  updateSettings({
191
218
  branding: {
@@ -0,0 +1,2 @@
1
+ export declare const isOpenAIKeyValid: (key: string) => Promise<boolean>;
2
+ export declare const isAnthropicKeyValid: (key: string) => Promise<boolean>;
@@ -0,0 +1,41 @@
1
+ export const isOpenAIKeyValid = async (key) => {
2
+ try {
3
+ // Just call the "list models" API with the supplied key.
4
+ // We don't care about the result, just whether it succeeds or not
5
+ const res = await fetch("https://api.openai.com/v1/models", {
6
+ method: "GET",
7
+ headers: {
8
+ Accept: "application/json",
9
+ Authorization: `Bearer ${key}`,
10
+ },
11
+ });
12
+ return res.ok;
13
+ }
14
+ catch (e) {
15
+ console.error("Error validating API key:", e);
16
+ return false;
17
+ }
18
+ };
19
+ export const isAnthropicKeyValid = async (key) => {
20
+ try {
21
+ // Just call the "list models" API with the supplied key.
22
+ // We don't care about the result, just whether it succeeds or not
23
+ const res = await fetch("https://api.anthropic.com/v1/models", {
24
+ method: "GET",
25
+ headers: {
26
+ Accept: "application/json",
27
+ "anthropic-version": "2023-06-01",
28
+ "X-Api-Key": key,
29
+ // Request vendor to allow CORS for this endpoint
30
+ // Reference: https://simonwillison.net/2024/Aug/23/anthropic-dangerous-direct-browser-access/
31
+ // The request will be rejected without this.
32
+ "anthropic-dangerous-direct-browser-access": "true",
33
+ },
34
+ });
35
+ return res.ok;
36
+ }
37
+ catch (e) {
38
+ console.error("Error validating API key:", e);
39
+ return false;
40
+ }
41
+ };
@@ -9,6 +9,7 @@ import { PaletteKey } from "../Theme/Palettes.js";
9
9
  type DeepPartial<T> = {
10
10
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
11
11
  };
12
+ export type LLMProvider = "OpenAI" | "Anthropic";
12
13
  /**
13
14
  * User preference settings
14
15
  */
@@ -32,6 +33,7 @@ interface Settings {
32
33
  readonly behavior: {
33
34
  readonly enableZenMode: boolean;
34
35
  };
36
+ readonly apiKeys: Partial<Record<LLMProvider, string>>;
35
37
  }
36
38
  /**
37
39
  * Zustand state store for user preferences/Settings
@@ -40,6 +40,7 @@ export const DEFAULT_SETTINGS = {
40
40
  behavior: {
41
41
  enableZenMode: true,
42
42
  },
43
+ apiKeys: {},
43
44
  };
44
45
  /**
45
46
  * The hook that lets apps use the store