Skip to content

Commit

Permalink
Bring enhancement from previous PreferencesModal
Browse files Browse the repository at this point in the history
  • Loading branch information
menghif committed Apr 20, 2024
1 parent e7dcc0f commit f911324
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 1,190 deletions.
218 changes: 113 additions & 105 deletions src/components/Preferences/ModelsSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
VStack,
} from "@chakra-ui/react";
import { ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useDebounce } from "react-use";

import { capitalize } from "lodash-es";
import { FaCheck } from "react-icons/fa";
Expand Down Expand Up @@ -65,6 +64,10 @@ async function isStoragePersisted() {
return false;
}

interface ModelsSettingsProps {
isOpen: boolean;
}

function ModelsSettings(isOpen: ModelsSettingsProps) {
const { settings, setSettings } = useSettings();
const { models } = useModels();
Expand All @@ -87,39 +90,6 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {
settings.currentProvider
);

// Get the list of providers to display in providers table
// Combination of default list of supported providers and settings.providers from localStorage
useEffect(() => {
setTableProviders({ ...supportedProviders, ...settings.providers });
}, [settings.providers]);

// Check the API Key, but debounce requests if user is typing
useDebounce(
() => {
setIsApiKeyInvalid(false);
if (focusedProvider) {
setIsValidating(true);
if (!focusedProvider.apiKey) {
setIsApiKeyInvalid(true);
setIsValidating(false);
} else {
focusedProvider
.validateApiKey(focusedProvider.apiKey)
.then((result: boolean) => {
setIsApiKeyInvalid(!result);
})
.catch((err: any) => {
console.warn("Error validating API key", err.message);
setIsApiKeyInvalid(true);
})
.finally(() => setIsValidating(false));
}
}
},
500,
[focusedProvider]
);

useEffect(() => {
isStoragePersisted()
.then((value) => setIsPersisted(value))
Expand Down Expand Up @@ -215,39 +185,53 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {
}
}, [addToAudioQueue, clearAudioQueue, error, settings.textToSpeech]);

const handleApiKeyChange = (provider: ChatCraftProvider, apiKey: string) => {
const handleApiKeyChange = async (provider: ChatCraftProvider, apiKey: string) => {
const newProvider = providerFromUrl(
provider.apiUrl,
apiKey,
provider.name,
provider.defaultModel
);

// Set as focused provider to trigger key validation
setFocusedProvider(newProvider);

if (newProvider.name === settings.currentProvider.name) {
// Save key to settings.currentProvider and settings.providers
setSettings({
...settings,
currentProvider: newProvider,
providers: {
...settings.providers,
[newProvider.name]: newProvider,
},
});
} else {
// Save key to settings.providers
setSettings({
...settings,
providers: {
...settings.providers,
[newProvider.name]: newProvider,
},
});
const newProviders = { ...settings.providers };

// Update api key in table
tableProviders[newProvider.name] = newProvider;

// Api key validation
try {
setIsApiKeyInvalid(false);
setIsValidating(true);

const result = await newProvider.validateApiKey(newProvider.apiKey!);

setIsApiKeyInvalid(!result);
setIsValidating(false);

// Valid key, update in settings.providers
if (result) {
// Valid key, update in settings.providers
newProviders[newProvider.name] = newProvider;
} else {
// Invalid key, remove from settings.providers
delete newProviders[newProvider.name];
}
} catch (err: any) {
setIsApiKeyInvalid(true);
setIsValidating(false);

// Invalid key, remove from settings.providers
delete newProviders[newProvider.name];
}

// If the key that was changed is the selected provider, update the selected provider
setSettings({
...settings,
...(newProvider.name === settings.currentProvider.name && { currentProvider: newProvider }),
providers: newProviders,
});

if (newProvider.name === selectedProvider?.name) {
setSelectedProvider(newProvider);
}
Expand All @@ -273,41 +257,53 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {

setFocusedProvider(selectedProvider);

// Validate api key
setIsApiKeyInvalid(false);
setIsValidating(true);
if (!selectedProvider.apiKey) {
setIsApiKeyInvalid(true);
// Api key validation
try {
setIsApiKeyInvalid(false);
setIsValidating(true);

const result = await selectedProvider.validateApiKey(selectedProvider.apiKey!);

setIsApiKeyInvalid(!result);
setIsValidating(false);
} else {
selectedProvider
.validateApiKey(selectedProvider.apiKey)
.then((result: boolean) => {
setIsApiKeyInvalid(!result);

// Set as current provider
setSettings({ ...settings, currentProvider: selectedProvider });
setApiKeySaved(true);
setSelectedProvider(null);

success({
title: "Current provider changed",
message: `${selectedProvider.name} set as current provider`,
});

// Uncheck checkbox
setSelectedProvider(null);
})
.catch((err: any) => {
console.warn("Error validating API key", err);
setIsApiKeyInvalid(true);

warning({
title: "Provider not set",
message: err.message,
});
})
.finally(() => setIsValidating(false));

// Valid key
if (result) {
// Set as current provider
setSettings({ ...settings, currentProvider: selectedProvider });
setApiKeySaved(true);
setSelectedProvider(null);

success({
title: "Current provider changed",
message: `${selectedProvider.name} set as current provider`,
});

setSettings({ ...settings, currentProvider: selectedProvider });
setApiKeySaved(true);

// Sync table
tableProviders[selectedProvider.name] = selectedProvider;

// Uncheck checkbox
setSelectedProvider(null);
} else {
console.warn("Error validating API key");
error({
title: "Provider not set",
message: "Invalid API key",
});
}
} catch (err: any) {
setIsValidating(false);

console.warn("Error validating API key", err);
setIsApiKeyInvalid(true);

error({
title: "Provider not set",
message: err.message,
});
}
};

Expand Down Expand Up @@ -352,12 +348,15 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {
return;
}

const newSettingsProviders = { ...settings.providers };
delete newSettingsProviders[selectedProvider.name];
setSettings({ ...settings, providers: newSettingsProviders });
const newProviders = { ...settings.providers };
delete newProviders[selectedProvider.name];
setSettings({ ...settings, providers: newProviders });

// Uncheck checkbox
setSelectedProvider(null);

// Sync table
setTableProviders({ ...supportedProviders, ...newProviders });
};

const handleSaveNewCustomProvider = async () => {
Expand Down Expand Up @@ -400,12 +399,16 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {
return;
}

// Validate api key
// Api key validation
try {
setIsApiKeyInvalid(false);
setIsValidating(true);

const result = await newProvider.validateApiKey(newProvider.apiKey!);

setIsApiKeyInvalid(!result);
setIsValidating(false);

if (!result) {
return;
}
Expand All @@ -417,7 +420,7 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {
} catch (err: any) {
console.warn("Error querying models for custom provider:", err);
setFocusedProvider(null);
warning({
error({
title: "Provider not added",
message: err.message,
});
Expand All @@ -427,7 +430,7 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {
if (models.length === 0) {
console.warn("No models available for custom provider");
setFocusedProvider(null);
warning({
error({
title: "Provider not added",
message: "Provider is not Open AI compatible.",
});
Expand All @@ -442,16 +445,18 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {
models[0]
);

// Save the new custom provider
const newProviders = { ...settings.providers };
newProviders[newProviderWithModel.name] = newProviderWithModel;

setSettings({
...settings,
providers: {
...settings.providers,
[newProviderWithModel.name]: newProviderWithModel,
},
providers: newProviders,
});
setApiKeySaved(true);

// Sync table
setTableProviders({ ...supportedProviders, ...newProviders });

success({
title: `New provider added`,
message: `${newProviderWithModel.name}`,
Expand All @@ -463,7 +468,7 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {
} catch (err: any) {
console.warn("Error validating API key", err.message);
setFocusedProvider(null);
warning({
error({
title: "Provider not added",
message: err.message,
});
Expand All @@ -486,10 +491,14 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {

// Clean up actions when modal closes
useEffect(() => {
// Sync table
setTableProviders({ ...supportedProviders, ...settings.providers });

if (!isOpen) {
setNewCustomProvider(null);
setIsApiKeyInvalid(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen]);

const isTtsSupported = useMemo(() => {
Expand All @@ -501,7 +510,7 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {
{!tableProviders ? (
<Box>Loading providers...</Box>
) : (
<VStack gap={6} mt={3}>
<VStack gap={4}>
<FormControl>
<FormLabel>
Providers
Expand Down Expand Up @@ -653,7 +662,6 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {
paddingRight={"2.5rem"}
paddingLeft={"0.5rem"}
fontSize="xs"
placeholder="API Key"
value={newCustomProvider.apiKey || ""}
onChange={(e) => {
setNewCustomProvider(
Expand Down Expand Up @@ -750,7 +758,7 @@ function ModelsSettings(isOpen: ModelsSettingsProps) {
</Flex>
) : (
<FormErrorMessage fontSize="xs">
{focusedProvider?.apiKey
{provider.apiKey
? "Unable to verify key."
: "API Key is required."}
</FormErrorMessage>
Expand Down
Loading

0 comments on commit f911324

Please sign in to comment.