Skip to content

Commit

Permalink
Fix for minor bugs in instructions page and prefs modal (#584)
Browse files Browse the repository at this point in the history
Fix for minor bugs in instructions page and prefs modal
  • Loading branch information
kliu57 authored Apr 12, 2024
1 parent 7cc1e2f commit 62cd016
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 66 deletions.
110 changes: 60 additions & 50 deletions src/components/Message/AppMessage/Instructions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FormEvent, memo, useState, useCallback, ChangeEvent } from "react";
import { FormEvent, memo, useState, useEffect } from "react";
import {
Button,
Box,
Expand All @@ -13,11 +13,12 @@ import {

import MessageBase, { type MessageBaseProps } from "../MessageBase";
import { ChatCraftAppMessage } from "../../../lib/ChatCraftMessage";
import { nameToUrlMap, providerFromUrl, supportedProviders } from "../../../lib/providers";
import { providerFromUrl, supportedProviders } from "../../../lib/providers";
import { OpenRouterProvider } from "../../../lib/providers/OpenRouterProvider";
import PasswordInput from "../../PasswordInput";
import { useSettings } from "../../../hooks/use-settings";
import { FreeModelProvider } from "../../../lib/providers/DefaultProvider/FreeModelProvider";
import { ProviderData } from "../../../lib/ChatCraftProvider";

const ApiKeyInstructionsText = `## Getting Started with ChatCraft
Expand Down Expand Up @@ -56,20 +57,50 @@ function Instructions(props: MessageBaseProps) {
const { settings, setSettings } = useSettings();
const [isValidating, setIsValidating] = useState(false);
const [isInvalid, setIsInvalid] = useState(false);
const [selectedProvider, setSelectedProvider] = useState(settings.currentProvider);

useEffect(() => {
setSelectedProvider(settings.currentProvider);
}, [settings.currentProvider]);

const providersList: ProviderData = {
...supportedProviders,
...settings.providers,
};

// Override the text of the message
const message = new ChatCraftAppMessage({ ...props.message, text: ApiKeyInstructionsText });

const handleApiKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const apiKey = e.target.value;
const newProvider = providerFromUrl(
selectedProvider.apiUrl,
apiKey,
selectedProvider.name,
selectedProvider.defaultModel
);

setSelectedProvider(newProvider);
providersList[newProvider.name] = newProvider;

// Save key to settings.providers
setSettings({
...settings,
providers: {
...settings.providers,
[newProvider.name]: newProvider,
},
});
};

const handleApiKeySubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const data = new FormData(e.target as HTMLFormElement);
const apiKey = data.get("openai-api-key");

if (typeof apiKey !== "string") {
if (typeof selectedProvider.apiKey !== "string") {
return;
}

if (settings.currentProvider instanceof FreeModelProvider) {
if (selectedProvider instanceof FreeModelProvider) {
// If user chooses the free provider, no need for validation
setSettings({
...settings,
Expand All @@ -78,21 +109,15 @@ function Instructions(props: MessageBaseProps) {
} else {
// See if this API Key is valid
setIsValidating(true);
settings.currentProvider
.validateApiKey(apiKey)
selectedProvider
.validateApiKey(selectedProvider.apiKey)
.then((valid) => {
if (valid) {
setIsInvalid(false);

const newProvider = providerFromUrl(settings.currentProvider.apiUrl, apiKey.trim());

setSettings({
...settings,
currentProvider: newProvider,
providers: {
...settings.providers,
[newProvider.name]: newProvider,
},
currentProvider: selectedProvider,
});
} else {
setIsInvalid(true);
Expand All @@ -106,44 +131,25 @@ function Instructions(props: MessageBaseProps) {
}
};

const handleProviderChange = useCallback(
(e: ChangeEvent<HTMLSelectElement>) => {
const apiUrl = nameToUrlMap[e.target.value];

// Get stored data from settings.providers array if exists
const newProvider = settings.providers[e.target.value]
? settings.providers[e.target.value]
: providerFromUrl(apiUrl);

if (newProvider instanceof FreeModelProvider) {
// If user chooses the free provider, set the key automatically
setSettings({
...settings,
currentProvider: new FreeModelProvider(),
});
} else {
setSettings({
...settings,
currentProvider: newProvider,
});
}
},
[setSettings, settings]
);

// Provide a form to enter and process the api key when entered
const apiKeyForm = (
<Container pb={6}>
{!supportedProviders ? (
{!providersList ? (
<Box>Loading providers...</Box>
) : (
<form onSubmit={handleApiKeySubmit}>
<VStack gap={4}>
<FormControl>
<FormLabel>Provider API URL</FormLabel>

<Select value={settings.currentProvider.name} onChange={handleProviderChange}>
{Object.values(supportedProviders).map((provider) => (
<Select
value={selectedProvider.name}
onChange={(e) => {
setSelectedProvider(providersList[e.target.value]);
setIsInvalid(false);
}}
>
{Object.values(providersList).map((provider) => (
<option key={provider.name} value={provider.name}>
{provider.name} ({provider.apiUrl})
</option>
Expand All @@ -152,28 +158,32 @@ function Instructions(props: MessageBaseProps) {
</FormControl>

<FormControl isInvalid={isInvalid}>
<FormLabel>{settings.currentProvider.name} API Key </FormLabel>
<FormLabel>{selectedProvider.name} API Key </FormLabel>
<Flex gap={4} align="center">
<PasswordInput
flex="1"
size={"md"}
type="password"
name="openai-api-key"
bg="white"
_dark={{ bg: "gray.700" }}
required={!(settings.currentProvider instanceof FreeModelProvider)}
style={{
color:
selectedProvider instanceof FreeModelProvider ? "transparent" : "initial",
}}
isDisabled={selectedProvider instanceof FreeModelProvider}
value={selectedProvider.apiKey || ""}
onChange={handleApiKeyChange}
/>
<Button type="submit" size="sm" isLoading={isValidating}>
Save
</Button>
</Flex>
{settings.currentProvider instanceof OpenRouterProvider && (
<Button mt="3" size="sm" onClick={settings.currentProvider.openRouterPkceRedirect}>
{selectedProvider instanceof OpenRouterProvider && (
<Button mt="3" size="sm" onClick={selectedProvider.openRouterPkceRedirect}>
Get API key from OpenRouter{" "}
</Button>
)}
<FormErrorMessage>
Unable to verify API Key with {settings.currentProvider.name}.
Unable to verify API Key with {selectedProvider.name}.
</FormErrorMessage>
</FormControl>
</VStack>
Expand Down
5 changes: 4 additions & 1 deletion src/components/PasswordInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ function PasswordInput({
paddingRight={paddingRight}
isInvalid={isInvalid}
{...props}
type={show ? "text" : "password"}
type="text"
sx={{
WebkitTextSecurity: show ? "none" : "disc",
}}
/>
<InputRightElement width={paddingRight}>
<IconButton
Expand Down
3 changes: 1 addition & 2 deletions src/components/PreferencesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ function PreferencesModal({ isOpen, onClose, finalFocusRef }: PreferencesModalPr
useEffect(() => {
if (!isOpen) {
setNewCustomProvider(null);
setIsApiKeyInvalid(false);
}
}, [isOpen]);

Expand Down Expand Up @@ -669,7 +670,6 @@ function PreferencesModal({ isOpen, onClose, finalFocusRef }: PreferencesModalPr
paddingRight={"2.5rem"}
paddingLeft={"0.5rem"}
fontSize="xs"
type="password"
placeholder="API Key"
value={newCustomProvider.apiKey || ""}
onChange={(e) => {
Expand Down Expand Up @@ -746,7 +746,6 @@ function PreferencesModal({ isOpen, onClose, finalFocusRef }: PreferencesModalPr
paddingRight={"2.5rem"}
paddingLeft={"0.5rem"}
fontSize="xs"
type="password"
value={provider.apiKey || ""}
onChange={(e) => handleApiKeyChange(provider, e.target.value)}
onFocus={() => setFocusedProvider(provider)}
Expand Down
16 changes: 3 additions & 13 deletions src/lib/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { ChatCraftProvider, ProviderData, SerializedChatCraftProvider } from "../ChatCraftProvider";
import { getSettings } from "../settings";
import { OPENAI_API_URL, OPENAI_NAME, OpenAiProvider } from "./OpenAiProvider";
import { OPENROUTER_API_URL, OPENROUTER_NAME, OpenRouterProvider } from "./OpenRouterProvider";
import {
FREEMODELPROVIDER_API_URL,
FREEMODELPROVIDER_NAME,
FreeModelProvider,
} from "./DefaultProvider/FreeModelProvider";
import { OPENAI_API_URL, OpenAiProvider } from "./OpenAiProvider";
import { OPENROUTER_API_URL, OpenRouterProvider } from "./OpenRouterProvider";
import { FREEMODELPROVIDER_API_URL, FreeModelProvider } from "./DefaultProvider/FreeModelProvider";
import { CustomProvider } from "./CustomProvider";

export const usingOfficialOpenAI = () => getSettings().currentProvider.apiUrl === OPENAI_API_URL;
Expand Down Expand Up @@ -71,9 +67,3 @@ export const supportedProviders: ProviderData = (() => {
[openAi.name]: openAi,
};
})();

export const nameToUrlMap: { [key: string]: string } = {
[OPENAI_NAME]: OPENAI_API_URL,
[OPENROUTER_NAME]: OPENROUTER_API_URL,
[FREEMODELPROVIDER_NAME]: FREEMODELPROVIDER_API_URL,
};

0 comments on commit 62cd016

Please sign in to comment.