Skip to content

Commit 14104b2

Browse files
Added functionality to allow users to set custom api keys (#276)
* Added functionality to allow users to set custom api keys * Added error handling * Changed token to apiKey * Changed apiKey to oaiApiKey * added azure openai ui * Removed logging * Changed configure to Use * Made checked position more rounded * Made setting api key optional if it is openai * Modified error handling * Add support for insufficient_quota errors * Fixed faulty error detection * removed logging
1 parent 08f3a77 commit 14104b2

File tree

11 files changed

+207
-32
lines changed

11 files changed

+207
-32
lines changed

api/app/clients/chatgpt-client.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const askClient = async ({
77
parentMessageId,
88
conversationId,
99
model,
10+
oaiApiKey,
1011
chatGptLabel,
1112
promptPrefix,
1213
temperature,
@@ -41,10 +42,10 @@ const askClient = async ({
4142
// debug: true
4243
};
4344

44-
let apiKey = process.env.OPENAI_KEY;
45+
let apiKey = oaiApiKey ? oaiApiKey : process.env.OPENAI_KEY || null;
4546

4647
if (azure) {
47-
apiKey = process.env.AZURE_OPENAI_API_KEY;
48+
apiKey = oaiApiKey ? oaiApiKey : process.env.AZURE_OPENAI_API_KEY || null;
4849
clientOptions.reverseProxyUrl = genAzureEndpoint({
4950
azureOpenAIApiInstanceName: process.env.AZURE_OPENAI_API_INSTANCE_NAME,
5051
azureOpenAIApiDeploymentName: process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME,

api/server/routes/ask/askOpenAI.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ const ask = async ({
174174
text,
175175
parentMessageId: userParentMessageId,
176176
conversationId,
177+
oaiApiKey: req.body?.token ?? null,
177178
...endpointOption,
178179
onProgress: progressCallback.call(null, {
179180
res,

api/server/routes/endpoints.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ router.get('/', async function (req, res) {
4040
const azureOpenAI = !!process.env.AZURE_OPENAI_KEY;
4141
const openAI =
4242
process.env.OPENAI_KEY || process.env.AZURE_OPENAI_API_KEY
43-
? { availableModels: getOpenAIModels() }
43+
? { availableModels: getOpenAIModels(), userProvide: true }
4444
: false;
4545
const bingAI = process.env.BINGAI_TOKEN
4646
? { userProvide: process.env.BINGAI_TOKEN == 'user_provided' }

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@radix-ui/react-dialog": "^1.0.2",
3232
"@radix-ui/react-dropdown-menu": "^2.0.2",
3333
"@radix-ui/react-hover-card": "^1.0.5",
34+
"@radix-ui/react-icons": "^1.3.0",
3435
"@radix-ui/react-label": "^2.0.0",
3536
"@radix-ui/react-slider": "^1.1.1",
3637
"@radix-ui/react-tabs": "^1.0.3",

client/src/components/Input/NewConversationMenu/EndpointItem.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default function ModelItem({ endpoint, value, onSelect }) {
2626
className: 'mr-2'
2727
});
2828

29-
const isuserProvide = endpointsConfig?.[endpoint]?.userProvide;
29+
const isUserProvided = endpointsConfig?.[endpoint]?.userProvide;
3030

3131
// regular model
3232
return (
@@ -39,7 +39,7 @@ export default function ModelItem({ endpoint, value, onSelect }) {
3939
{alternateName[endpoint] || endpoint}
4040
{!!['azureOpenAI', 'openAI'].find(e => e === endpoint) && <sup>$</sup>}
4141
<div className="flex w-4 flex-1" />
42-
{isuserProvide ? (
42+
{isUserProvided ? (
4343
<button
4444
className="invisible m-0 mr-1 flex-initial rounded-md p-0 text-xs font-medium text-gray-400 hover:text-gray-700 group-hover:visible dark:font-normal dark:text-gray-400 dark:hover:text-gray-200"
4545
onClick={e => {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from 'react';
2+
import { Input } from '../../ui/Input.tsx';
3+
import { Label } from '../../ui/Label.tsx';
4+
import { cn } from '~/utils/';
5+
6+
function InputWithLabel({ value, onChange, label, id }) {
7+
const defaultTextProps =
8+
'rounded-md border border-gray-300 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-400 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0';
9+
10+
return (
11+
<>
12+
<Label
13+
htmlFor={id}
14+
className="text-left text-sm font-medium"
15+
>
16+
{label}
17+
<br />
18+
</Label>
19+
20+
<Input
21+
id={id}
22+
value={value || ''}
23+
onChange={onChange}
24+
placeholder={`Enter ${label}`}
25+
className={cn(
26+
defaultTextProps,
27+
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0'
28+
)}
29+
/>
30+
</>
31+
);
32+
}
33+
34+
export default InputWithLabel;

client/src/components/Input/SetTokenDialog/index.jsx

Lines changed: 114 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,24 @@ import { Dialog } from '../../ui/Dialog.tsx';
44
import { Input } from '../../ui/Input.tsx';
55
import { Label } from '../../ui/Label.tsx';
66
import { cn } from '~/utils/';
7+
import * as Checkbox from '@radix-ui/react-checkbox';
8+
import { CheckIcon } from '@radix-ui/react-icons';
79
import FileUpload from '../NewConversationMenu/FileUpload';
810
import store from '~/store';
11+
import InputWithLabel from './InputWithLabel';
12+
13+
function isJson(str) {
14+
try {
15+
JSON.parse(str);
16+
} catch (e) {
17+
return false;
18+
}
19+
return true;
20+
}
921

1022
const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
1123
const [token, setToken] = useState('');
24+
const [showPanel, setShowPanel] = useState(false);
1225
const { getToken, saveToken } = store.useToken(endpoint);
1326

1427
const defaultTextProps =
@@ -20,9 +33,19 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
2033
};
2134

2235
useEffect(() => {
23-
setToken(getToken() ?? '');
36+
let oldToken = getToken();
37+
if (isJson(token)) {
38+
setShowPanel(true);
39+
}
40+
setToken(oldToken ?? '');
2441
}, [open]);
2542

43+
useEffect(() => {
44+
if (!showPanel && isJson(token)) {
45+
setToken('');
46+
}
47+
}, [showPanel]);
48+
2649
const helpText = {
2750
bingAI: (
2851
<small className="break-all text-gray-600">
@@ -79,6 +102,25 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
79102
)
80103
};
81104

105+
function getAzure(name) {
106+
if (isJson(token)) {
107+
let newToken = JSON.parse(token);
108+
return newToken[name];
109+
} else {
110+
return '';
111+
}
112+
}
113+
114+
function setAzure(name, value) {
115+
let newToken = {};
116+
if (isJson(token)) {
117+
newToken = JSON.parse(token);
118+
}
119+
newToken[name] = value;
120+
121+
setToken(JSON.stringify(newToken));
122+
}
123+
82124
return (
83125
<Dialog
84126
open={open}
@@ -88,16 +130,9 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
88130
title={`Set Token of ${endpoint}`}
89131
main={
90132
<div className="grid w-full items-center gap-2">
91-
<Label
92-
htmlFor="chatGptLabel"
93-
className="text-left text-sm font-medium"
94-
>
95-
Token Name
96-
<br />
97-
</Label>
98133
{endpoint === 'google' ? (
99134
<FileUpload
100-
id="googleKey"
135+
id="googleKey"
101136
className="w-full"
102137
text="Import Service Account JSON Key"
103138
successText="Successfully Imported Service Account JSON Key"
@@ -137,17 +172,77 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
137172
setToken(JSON.stringify(data));
138173
}}
139174
/>
140-
) : (
141-
<Input
142-
id="chatGptLabel"
143-
value={token || ''}
144-
onChange={e => setToken(e.target.value || '')}
145-
placeholder="Set the token."
146-
className={cn(
147-
defaultTextProps,
148-
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0'
175+
) : endpoint === 'openAI' ? (
176+
<>
177+
{!showPanel ? (
178+
<>
179+
<InputWithLabel
180+
id={'chatGPTLabel'}
181+
value={token || ''}
182+
onChange={e => setToken(e.target.value || '')}
183+
label={'OpenAI API Key'}
184+
/>
185+
</>
186+
) : (
187+
<>
188+
<InputWithLabel
189+
id={'instanceNameLabel'}
190+
value={getAzure('instanceName') || ''}
191+
onChange={e => setAzure('instanceName', e.target.value || '')}
192+
label={'Azure OpenAI Instance Name'}
193+
/>
194+
195+
<InputWithLabel
196+
id={'deploymentNameLabel'}
197+
value={getAzure('deploymentName') || ''}
198+
onChange={e => setAzure('deploymentName', e.target.value || '')}
199+
label={'Azure OpenAI Deployment Name'}
200+
/>
201+
202+
<InputWithLabel
203+
id={'versionLabel'}
204+
value={getAzure('version') || ''}
205+
onChange={e => setAzure('version', e.target.value || '')}
206+
label={'Azure OpenAI API Version'}
207+
/>
208+
209+
<InputWithLabel
210+
id={'apiKeyLabel'}
211+
value={getAzure('apiKey') || ''}
212+
onChange={e => setAzure('apiKey', e.target.value || '')}
213+
label={'Azure OpenAI API Key'}
214+
/>
215+
</>
149216
)}
150-
/>
217+
<div className="flex items-center">
218+
<Checkbox.Root
219+
className="flex h-[20px] w-[20px] appearance-none items-center justify-center rounded-[4px] bg-gray-100 text-white outline-none hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-900"
220+
id="azureOpenAI"
221+
checked={showPanel}
222+
onCheckedChange={() => setShowPanel(!showPanel)}
223+
>
224+
<Checkbox.Indicator className="flex h-[20px] w-[20px] items-center justify-center rounded-[3.5px] bg-green-600">
225+
<CheckIcon />
226+
</Checkbox.Indicator>
227+
</Checkbox.Root>
228+
229+
<label
230+
className="pl-[8px] text-[15px] leading-none dark:text-white"
231+
htmlFor="azureOpenAI"
232+
>
233+
Use Azure OpenAI.
234+
</label>
235+
</div>
236+
</>
237+
) : (
238+
<>
239+
<InputWithLabel
240+
id={'chatGPTLabel'}
241+
value={token || ''}
242+
onChange={e => setToken(e.target.value || '')}
243+
label={'Token Name'}
244+
/>
245+
</>
151246
)}
152247
<small className="text-red-600">Your token will be sent to the server, but not saved.</small>
153248
{helpText?.[endpoint]}

client/src/components/Input/SubmitButton.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default function SubmitButton({
3333
type="button"
3434
className="group absolute bottom-0 right-0 flex h-[100%] w-[50px] items-center justify-center bg-transparent p-1 text-gray-500"
3535
>
36-
<div className="m-1 mr-0 rounded-md p-2 pt-[10px] pb-[10px] group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
36+
<div className="m-1 mr-0 rounded-md p-2 pb-[10px] pt-[10px] group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
3737
<StopGeneratingIcon />
3838
</div>
3939
</button>
@@ -61,15 +61,15 @@ export default function SubmitButton({
6161
// </div>
6262
// </button>
6363
// );
64-
else if (!isTokenProvided) {
64+
else if (!isTokenProvided && endpoint !== 'openAI') {
6565
return (
6666
<>
6767
<button
6868
onClick={setToken}
6969
type="button"
7070
className="group absolute bottom-0 right-0 flex h-[100%] w-auto items-center justify-center bg-transparent p-1 text-gray-500"
7171
>
72-
<div className="m-1 mr-0 rounded-md p-2 pt-[10px] pb-[10px] align-middle text-xs group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
72+
<div className="m-1 mr-0 rounded-md p-2 pb-[10px] pt-[10px] align-middle text-xs group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
7373
<Settings className="mr-1 inline-block w-[18px]" />
7474
Set Token First
7575
</div>
@@ -88,7 +88,7 @@ export default function SubmitButton({
8888
disabled={disabled}
8989
className="group absolute bottom-0 right-0 flex h-[100%] w-[50px] items-center justify-center bg-transparent p-1 text-gray-500"
9090
>
91-
<div className="m-1 mr-0 rounded-md p-2 pt-[10px] pb-[10px] group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
91+
<div className="m-1 mr-0 rounded-md p-2 pb-[10px] pt-[10px] group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
9292
<svg
9393
stroke="currentColor"
9494
fill="none"

client/src/components/Messages/Message.jsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ import { useGetConversationByIdQuery } from '~/data-provider';
1212
import { cn } from '~/utils/';
1313
import store from '~/store';
1414

15+
function isJson(str) {
16+
try {
17+
JSON.parse(str);
18+
} catch (e) {
19+
return false;
20+
}
21+
return true;
22+
}
23+
1524
export default function Message({
1625
conversation,
1726
message,
@@ -62,6 +71,23 @@ export default function Message({
6271
}
6372
};
6473

74+
const getError = text => {
75+
const match = text.match(/\{[^{}]*\}/);
76+
var json = match ? match[0] : ''
77+
if (isJson(json)) {
78+
json = JSON.parse(json);
79+
if (json.code === 'invalid_api_key') {
80+
return 'Invalid API key. Please check your API key and try again. You can access your API key by clicking on the model logo in the top-left corner of the textbox.';
81+
} else if (json.type === 'insufficient_quota') {
82+
return "We're sorry, but the default API key has reached its limit. To continue using this service, please set up your own API key. You can do this by clicking on the model logo in the top-left corner of the textbox.";
83+
} else {
84+
return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${text}`;
85+
}
86+
} else {
87+
return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${text}`;
88+
}
89+
};
90+
6591
const props = {
6692
className:
6793
'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 bg-white dark:text-gray-100 group dark:bg-gray-800'
@@ -98,7 +124,7 @@ export default function Message({
98124
if (!isSubmitting && !message?.isCreatedByUser) regenerate(message);
99125
};
100126

101-
const copyToClipboard = (setIsCopied) => {
127+
const copyToClipboard = setIsCopied => {
102128
setIsCopied(true);
103129
copy(message?.text);
104130

@@ -149,7 +175,7 @@ export default function Message({
149175
{error ? (
150176
<div className="flex flex min-h-[20px] flex-grow flex-col items-start gap-2 gap-4 text-red-500">
151177
<div className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-100">
152-
{`An error occurred. Please try again in a few moments.\n\nError message: ${text}`}
178+
{getError(text)}
153179
</div>
154180
</div>
155181
) : edit ? (

client/src/utils/handleSubmit.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ const useMessageHandler = () => {
3737
temperature: currentConversation?.temperature ?? 1,
3838
top_p: currentConversation?.top_p ?? 1,
3939
presence_penalty: currentConversation?.presence_penalty ?? 0,
40-
frequency_penalty: currentConversation?.frequency_penalty ?? 0
40+
frequency_penalty: currentConversation?.frequency_penalty ?? 0,
41+
token: endpointsConfig[endpoint]?.userProvide ? getToken() : null
4142
};
4243
responseSender = endpointOption.chatGptLabel ?? 'ChatGPT';
4344
} else if (endpoint === 'google') {
@@ -47,7 +48,7 @@ const useMessageHandler = () => {
4748
currentConversation?.model ?? endpointsConfig[endpoint]?.availableModels?.[0] ?? 'chat-bison',
4849
chatGptLabel: currentConversation?.chatGptLabel ?? null,
4950
promptPrefix: currentConversation?.promptPrefix ?? null,
50-
examples: currentConversation?.examples ?? [{ input: { content: '' }, output: { content: '' }}],
51+
examples: currentConversation?.examples ?? [{ input: { content: '' }, output: { content: '' } }],
5152
temperature: currentConversation?.temperature ?? 0.2,
5253
maxOutputTokens: currentConversation?.maxOutputTokens ?? 1024,
5354
topP: currentConversation?.topP ?? 0.95,

0 commit comments

Comments
 (0)