mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 18:00:15 +01:00
refactor(SetTokenDialog): refactor to TS, modularize config logic into separate components
This commit is contained in:
parent
b6f21af69b
commit
8b91145953
15 changed files with 385 additions and 306 deletions
|
|
@ -31,7 +31,7 @@ router.get('/', async function (req, res) {
|
||||||
key = require('../../data/auth.json');
|
key = require('../../data/auth.json');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
console.log("No 'auth.json' file (service account key) found in /api/data/ for PaLM models");
|
console.log('No \'auth.json\' file (service account key) found in /api/data/ for PaLM models');
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,6 +50,7 @@ router.get('/', async function (req, res) {
|
||||||
: false;
|
: false;
|
||||||
const openAIApiKey = process.env.OPENAI_API_KEY;
|
const openAIApiKey = process.env.OPENAI_API_KEY;
|
||||||
const azureOpenAIApiKey = process.env.AZURE_API_KEY;
|
const azureOpenAIApiKey = process.env.AZURE_API_KEY;
|
||||||
|
const userProvidedOpenAI = openAIApiKey ? openAIApiKey === 'user_provided' : azureOpenAIApiKey === 'user_provided';
|
||||||
const openAI = openAIApiKey
|
const openAI = openAIApiKey
|
||||||
? { availableModels: getOpenAIModels(), userProvide: openAIApiKey === 'user_provided' }
|
? { availableModels: getOpenAIModels(), userProvide: openAIApiKey === 'user_provided' }
|
||||||
: false;
|
: false;
|
||||||
|
|
@ -57,7 +58,7 @@ router.get('/', async function (req, res) {
|
||||||
? { availableModels: getOpenAIModels({ azure: true}), userProvide: azureOpenAIApiKey === 'user_provided' }
|
? { availableModels: getOpenAIModels({ azure: true}), userProvide: azureOpenAIApiKey === 'user_provided' }
|
||||||
: false;
|
: false;
|
||||||
const gptPlugins = openAIApiKey || azureOpenAIApiKey
|
const gptPlugins = openAIApiKey || azureOpenAIApiKey
|
||||||
? { availableModels: getPluginModels(), availableTools, availableAgents: ['classic', 'functions'] }
|
? { availableModels: getPluginModels(), availableTools, availableAgents: ['classic', 'functions'], userProvide: userProvidedOpenAI }
|
||||||
: false;
|
: false;
|
||||||
const bingAI = process.env.BINGAI_TOKEN
|
const bingAI = process.env.BINGAI_TOKEN
|
||||||
? { userProvide: process.env.BINGAI_TOKEN == 'user_provided' }
|
? { userProvide: process.env.BINGAI_TOKEN == 'user_provided' }
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,12 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { DropdownMenuRadioItem } from '../../ui/DropdownMenu.tsx';
|
import { DropdownMenuRadioItem } from '~/components';
|
||||||
import { Settings } from 'lucide-react';
|
import { Settings } from 'lucide-react';
|
||||||
import getIcon from '~/utils/getIcon';
|
import getIcon from '~/utils/getIcon';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import SetTokenDialog from '../SetTokenDialog';
|
import { SetTokenDialog } from '../SetTokenDialog';
|
||||||
|
|
||||||
import store from '../../../store';
|
import store from '~/store';
|
||||||
import { cn } from '~/utils/index.jsx';
|
import { cn, alternateName } from '~/utils';
|
||||||
|
|
||||||
const alternateName = {
|
|
||||||
openAI: 'OpenAI',
|
|
||||||
azureOpenAI: 'Azure OpenAI',
|
|
||||||
bingAI: 'Bing',
|
|
||||||
chatGPTBrowser: 'ChatGPT',
|
|
||||||
gptPlugins: 'Plugins',
|
|
||||||
google: 'PaLM'
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ModelItem({ endpoint, value, isSelected }) {
|
export default function ModelItem({ endpoint, value, isSelected }) {
|
||||||
const [setTokenDialogOpen, setSetTokenDialogOpen] = useState(false);
|
const [setTokenDialogOpen, setSetTokenDialogOpen] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,36 @@
|
||||||
import { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { FileUp } from 'lucide-react';
|
import { FileUp } from 'lucide-react';
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
const FileUpload = ({
|
type FileUploadProps = {
|
||||||
|
onFileSelected: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
className?: string;
|
||||||
|
successText?: string;
|
||||||
|
invalidText?: string;
|
||||||
|
validator?: ((data: any) => boolean) | null;
|
||||||
|
text?: string;
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FileUpload: React.FC<FileUploadProps> = ({
|
||||||
onFileSelected,
|
onFileSelected,
|
||||||
|
className = '',
|
||||||
successText = null,
|
successText = null,
|
||||||
invalidText = null,
|
invalidText = null,
|
||||||
validator = null,
|
validator = null,
|
||||||
text = null,
|
text = null,
|
||||||
id = '1'
|
id = '1'
|
||||||
}) => {
|
}) => {
|
||||||
const [statusColor, setStatusColor] = useState('text-gray-600');
|
const [statusColor, setStatusColor] = useState<string>('text-gray-600');
|
||||||
const [status, setStatus] = useState(null);
|
const [status, setStatus] = useState<null | string>(null);
|
||||||
|
|
||||||
const handleFileChange = (event) => {
|
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
const file = event.target.files[0];
|
const file = event.target.files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const jsonData = JSON.parse(e.target.result);
|
const jsonData = JSON.parse(e.target?.result as string);
|
||||||
if (validator && !validator(jsonData)) {
|
if (validator && !validator(jsonData)) {
|
||||||
setStatus('invalid');
|
setStatus('invalid');
|
||||||
setStatusColor('text-red-600');
|
setStatusColor('text-red-600');
|
||||||
|
|
@ -52,7 +63,7 @@ const FileUpload = ({
|
||||||
id={`file-upload-${id}`}
|
id={`file-upload-${id}`}
|
||||||
value=""
|
value=""
|
||||||
type="file"
|
type="file"
|
||||||
className="hidden "
|
className={cn('hidden ', className)}
|
||||||
accept=".json"
|
accept=".json"
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
/>
|
/>
|
||||||
50
client/src/components/Input/SetTokenDialog/GoogleConfig.tsx
Normal file
50
client/src/components/Input/SetTokenDialog/GoogleConfig.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from 'react';
|
||||||
|
import FileUpload from '../NewConversationMenu/FileUpload';
|
||||||
|
|
||||||
|
const GoogleConfig = ({ setToken } : { setToken: React.Dispatch<React.SetStateAction<string>> }) => {
|
||||||
|
return (
|
||||||
|
<FileUpload
|
||||||
|
id="googleKey"
|
||||||
|
className="w-full"
|
||||||
|
text="Import Service Account JSON Key"
|
||||||
|
successText="Successfully Imported Service Account JSON Key"
|
||||||
|
invalidText="Invalid Service Account JSON Key, Did you import the correct file?"
|
||||||
|
validator={(credentials) => {
|
||||||
|
if (!credentials) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!credentials.client_email ||
|
||||||
|
typeof credentials.client_email !== 'string' ||
|
||||||
|
credentials.client_email.length <= 2
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!credentials.project_id ||
|
||||||
|
typeof credentials.project_id !== 'string' ||
|
||||||
|
credentials.project_id.length <= 2
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!credentials.private_key ||
|
||||||
|
typeof credentials.private_key !== 'string' ||
|
||||||
|
credentials.private_key.length <= 600
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
onFileSelected={(data) => {
|
||||||
|
setToken(JSON.stringify(data));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GoogleConfig;
|
||||||
82
client/src/components/Input/SetTokenDialog/HelpText.tsx
Normal file
82
client/src/components/Input/SetTokenDialog/HelpText.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function HelpText({ endpoint } : { endpoint: string }) {
|
||||||
|
const textMap = {
|
||||||
|
bingAI: (
|
||||||
|
<small className="break-all text-gray-600">
|
||||||
|
{'To get your Access token for Bing, login to '}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://www.bing.com"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-600 underline"
|
||||||
|
>
|
||||||
|
https://www.bing.com
|
||||||
|
</a>
|
||||||
|
{`. Use dev tools or an extension while logged into the site to copy the content of the _U cookie.
|
||||||
|
If this fails, follow these `}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/waylaidwanderer/node-chatgpt-api/issues/378#issuecomment-1559868368"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-600 underline"
|
||||||
|
>
|
||||||
|
instructions
|
||||||
|
</a>
|
||||||
|
{' to provide the full cookie strings.'}
|
||||||
|
</small>
|
||||||
|
),
|
||||||
|
chatGPTBrowser: (
|
||||||
|
<small className="break-all text-gray-600">
|
||||||
|
{'To get your Access token For ChatGPT \'Free Version\', login to '}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://chat.openai.com"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-600 underline"
|
||||||
|
>
|
||||||
|
https://chat.openai.com
|
||||||
|
</a>
|
||||||
|
, then visit{' '}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://chat.openai.com/api/auth/session"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-600 underline"
|
||||||
|
>
|
||||||
|
https://chat.openai.com/api/auth/session
|
||||||
|
</a>
|
||||||
|
. Copy access token.
|
||||||
|
</small>
|
||||||
|
),
|
||||||
|
google: (
|
||||||
|
<small className="break-all text-gray-600">
|
||||||
|
You need to{' '}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://console.cloud.google.com/vertex-ai"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-600 underline"
|
||||||
|
>
|
||||||
|
Enable Vertex AI
|
||||||
|
</a>{' '}
|
||||||
|
API on Google Cloud, then{' '}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account#step_index=1"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-600 underline"
|
||||||
|
>
|
||||||
|
Create a Service Account
|
||||||
|
</a>
|
||||||
|
{`. Make sure to click 'Create and Continue' to give at least the 'Vertex AI User' role.
|
||||||
|
Lastly, create a JSON key to import here.`}
|
||||||
|
</small>
|
||||||
|
)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return textMap[endpoint] || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(HelpText);
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
import React from 'react';
|
import React, { ChangeEvent, FC } from 'react';
|
||||||
import { Input } from '../../ui/Input.tsx';
|
import { Input, Label } from '~/components';
|
||||||
import { Label } from '../../ui/Label.tsx';
|
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
function InputWithLabel({ value, onChange, label, id }) {
|
interface InputWithLabelProps {
|
||||||
|
value: string;
|
||||||
|
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
label: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputWithLabel: FC<InputWithLabelProps> = ({ value, onChange, label, id }) => {
|
||||||
const defaultTextProps =
|
const defaultTextProps =
|
||||||
'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';
|
'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';
|
||||||
|
|
||||||
126
client/src/components/Input/SetTokenDialog/OpenAIConfig.tsx
Normal file
126
client/src/components/Input/SetTokenDialog/OpenAIConfig.tsx
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import * as Checkbox from '@radix-ui/react-checkbox';
|
||||||
|
import { CheckIcon } from '@radix-ui/react-icons';
|
||||||
|
import InputWithLabel from './InputWithLabel';
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
function isJson(str: string) {
|
||||||
|
try {
|
||||||
|
JSON.parse(str);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpenAIConfigProps = {
|
||||||
|
token: string;
|
||||||
|
setToken: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
endpoint: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const OpenAIConfig = ({ token, setToken, endpoint } : OpenAIConfigProps) => {
|
||||||
|
const [showPanel, setShowPanel] = useState(endpoint === 'azureOpenAI');
|
||||||
|
const { getToken } = store.useToken(endpoint);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let oldToken = getToken();
|
||||||
|
if (isJson(token)) {
|
||||||
|
setShowPanel(true);
|
||||||
|
}
|
||||||
|
setToken(oldToken ?? '');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showPanel && isJson(token)) {
|
||||||
|
setToken('');
|
||||||
|
}
|
||||||
|
}, [showPanel]);
|
||||||
|
|
||||||
|
function getAzure(name: string) {
|
||||||
|
if (isJson(token)) {
|
||||||
|
let newToken = JSON.parse(token);
|
||||||
|
return newToken[name];
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAzure(name: string, value: any) {
|
||||||
|
let newToken = {};
|
||||||
|
if (isJson(token)) {
|
||||||
|
newToken = JSON.parse(token);
|
||||||
|
}
|
||||||
|
newToken[name] = value;
|
||||||
|
|
||||||
|
setToken(JSON.stringify(newToken));
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!showPanel ? (
|
||||||
|
<>
|
||||||
|
<InputWithLabel
|
||||||
|
id={'chatGPTLabel'}
|
||||||
|
value={token || ''}
|
||||||
|
onChange={(e: { target: { value: any; }; }) => setToken(e.target.value || '')}
|
||||||
|
label={'OpenAI API Key'}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<InputWithLabel
|
||||||
|
id={'instanceNameLabel'}
|
||||||
|
value={getAzure('azureOpenAIApiInstanceName') || ''}
|
||||||
|
onChange={(e: { target: { value: any; }; }) => setAzure('azureOpenAIApiInstanceName', e.target.value || '')}
|
||||||
|
label={'Azure OpenAI Instance Name'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputWithLabel
|
||||||
|
id={'deploymentNameLabel'}
|
||||||
|
value={getAzure('azureOpenAIApiDeploymentName') || ''}
|
||||||
|
onChange={(e: { target: { value: any; }; }) => setAzure('azureOpenAIApiDeploymentName', e.target.value || '')}
|
||||||
|
label={'Azure OpenAI Deployment Name'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputWithLabel
|
||||||
|
id={'versionLabel'}
|
||||||
|
value={getAzure('azureOpenAIApiVersion') || ''}
|
||||||
|
onChange={(e: { target: { value: any; }; }) => setAzure('azureOpenAIApiVersion', e.target.value || '')}
|
||||||
|
label={'Azure OpenAI API Version'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputWithLabel
|
||||||
|
id={'apiKeyLabel'}
|
||||||
|
value={getAzure('azureOpenAIApiKey') || ''}
|
||||||
|
onChange={(e: { target: { value: any; }; }) => setAzure('azureOpenAIApiKey', e.target.value || '')}
|
||||||
|
label={'Azure OpenAI API Key'}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{ endpoint === 'gptPlugins' && (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Checkbox.Root
|
||||||
|
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"
|
||||||
|
id="azureOpenAI"
|
||||||
|
checked={showPanel}
|
||||||
|
onCheckedChange={() => setShowPanel(!showPanel)}
|
||||||
|
>
|
||||||
|
<Checkbox.Indicator className="flex h-[20px] w-[20px] items-center justify-center rounded-[3.5px] bg-green-600">
|
||||||
|
<CheckIcon />
|
||||||
|
</Checkbox.Indicator>
|
||||||
|
</Checkbox.Root>
|
||||||
|
|
||||||
|
<label
|
||||||
|
className="pl-[8px] text-[15px] leading-none dark:text-white"
|
||||||
|
htmlFor="azureOpenAI"
|
||||||
|
>
|
||||||
|
Use Azure OpenAI.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OpenAIConfig;
|
||||||
20
client/src/components/Input/SetTokenDialog/OtherConfig.tsx
Normal file
20
client/src/components/Input/SetTokenDialog/OtherConfig.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
import InputWithLabel from './InputWithLabel';
|
||||||
|
|
||||||
|
type ConfigProps = {
|
||||||
|
token: string;
|
||||||
|
setToken: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const OtherConfig = ({ token, setToken } : ConfigProps) => {
|
||||||
|
return (
|
||||||
|
<InputWithLabel
|
||||||
|
id={'chatGPTLabel'}
|
||||||
|
value={token || ''}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setToken(e.target.value || '')}
|
||||||
|
label={'Token Name'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OtherConfig;
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import React, { useState }from 'react';
|
||||||
|
import HelpText from './HelpText';
|
||||||
|
import GoogleConfig from './GoogleConfig';
|
||||||
|
import OpenAIConfig from './OpenAIConfig';
|
||||||
|
import OtherConfig from './OtherConfig';
|
||||||
|
import { Dialog, DialogTemplate } from '~/components';
|
||||||
|
import { alternateName } from '~/utils';
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
|
||||||
|
const [token, setToken] = useState('');
|
||||||
|
const { saveToken } = store.useToken(endpoint);
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
saveToken(token);
|
||||||
|
onOpenChange(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const endpointComponents = {
|
||||||
|
'google': GoogleConfig,
|
||||||
|
'openAI': OpenAIConfig,
|
||||||
|
'azureOpenAI': OpenAIConfig,
|
||||||
|
'gptPlugins': OpenAIConfig,
|
||||||
|
'default': OtherConfig
|
||||||
|
};
|
||||||
|
|
||||||
|
const EndpointComponent = endpointComponents[endpoint] || endpointComponents['default'];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogTemplate
|
||||||
|
title={`Set Token for ${alternateName[endpoint] ?? endpoint}`}
|
||||||
|
main={
|
||||||
|
<div className="grid w-full items-center gap-2">
|
||||||
|
<EndpointComponent token={token} setToken={setToken} endpoint={endpoint}/>
|
||||||
|
<small className="text-red-600">
|
||||||
|
Your token will be sent to the server, but not saved.
|
||||||
|
</small>
|
||||||
|
<HelpText endpoint={endpoint}/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
selection={{
|
||||||
|
selectHandler: submit,
|
||||||
|
selectClasses: 'bg-green-600 hover:bg-green-700 dark:hover:bg-green-800 text-white',
|
||||||
|
selectText: 'Submit'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SetTokenDialog;
|
||||||
|
|
@ -1,272 +0,0 @@
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { Dialog, DialogTemplate } from '../../ui';
|
|
||||||
import * as Checkbox from '@radix-ui/react-checkbox';
|
|
||||||
import { CheckIcon } from '@radix-ui/react-icons';
|
|
||||||
import FileUpload from '../NewConversationMenu/FileUpload';
|
|
||||||
import store from '~/store';
|
|
||||||
import InputWithLabel from './InputWithLabel';
|
|
||||||
|
|
||||||
function isJson(str) {
|
|
||||||
try {
|
|
||||||
JSON.parse(str);
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
|
|
||||||
const [token, setToken] = useState('');
|
|
||||||
const [showPanel, setShowPanel] = useState(false);
|
|
||||||
const { getToken, saveToken } = store.useToken(endpoint);
|
|
||||||
|
|
||||||
const submit = () => {
|
|
||||||
saveToken(token);
|
|
||||||
onOpenChange(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let oldToken = getToken();
|
|
||||||
if (isJson(token)) {
|
|
||||||
setShowPanel(true);
|
|
||||||
}
|
|
||||||
setToken(oldToken ?? '');
|
|
||||||
}, [open]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!showPanel && isJson(token)) {
|
|
||||||
setToken('');
|
|
||||||
}
|
|
||||||
}, [showPanel]);
|
|
||||||
|
|
||||||
const helpText = {
|
|
||||||
bingAI: (
|
|
||||||
<small className="break-all text-gray-600">
|
|
||||||
{`To get your Access token for Bing, login to `}
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://www.bing.com"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="text-blue-600 underline"
|
|
||||||
>
|
|
||||||
https://www.bing.com
|
|
||||||
</a>
|
|
||||||
{`. Use dev tools or an extension while logged into the site to copy the content of the _U cookie.
|
|
||||||
If this fails, follow these `}
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://github.com/waylaidwanderer/node-chatgpt-api/issues/378#issuecomment-1559868368"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="text-blue-600 underline"
|
|
||||||
>
|
|
||||||
instructions
|
|
||||||
</a>
|
|
||||||
{` to provide the full cookie strings.`}
|
|
||||||
</small>
|
|
||||||
),
|
|
||||||
chatGPTBrowser: (
|
|
||||||
<small className="break-all text-gray-600">
|
|
||||||
{`To get your Access token For ChatGPT 'Free Version', login to `}
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://chat.openai.com"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="text-blue-600 underline"
|
|
||||||
>
|
|
||||||
https://chat.openai.com
|
|
||||||
</a>
|
|
||||||
, then visit{' '}
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://chat.openai.com/api/auth/session"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="text-blue-600 underline"
|
|
||||||
>
|
|
||||||
https://chat.openai.com/api/auth/session
|
|
||||||
</a>
|
|
||||||
. Copy access token.
|
|
||||||
</small>
|
|
||||||
),
|
|
||||||
google: (
|
|
||||||
<small className="break-all text-gray-600">
|
|
||||||
You need to{' '}
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://console.cloud.google.com/vertex-ai"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="text-blue-600 underline"
|
|
||||||
>
|
|
||||||
Enable Vertex AI
|
|
||||||
</a>{' '}
|
|
||||||
API on Google Cloud, then{' '}
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create?walkthrough_id=iam--create-service-account#step_index=1"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="text-blue-600 underline"
|
|
||||||
>
|
|
||||||
Create a Service Account
|
|
||||||
</a>
|
|
||||||
{`. Make sure to click 'Create and Continue' to give at least the 'Vertex AI User' role.
|
|
||||||
Lastly, create a JSON key to import here.`}
|
|
||||||
</small>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
function getAzure(name) {
|
|
||||||
if (isJson(token)) {
|
|
||||||
let newToken = JSON.parse(token);
|
|
||||||
return newToken[name];
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAzure(name, value) {
|
|
||||||
let newToken = {};
|
|
||||||
if (isJson(token)) {
|
|
||||||
newToken = JSON.parse(token);
|
|
||||||
}
|
|
||||||
newToken[name] = value;
|
|
||||||
|
|
||||||
setToken(JSON.stringify(newToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
||||||
<DialogTemplate
|
|
||||||
title={`Set Token of ${endpoint}`}
|
|
||||||
main={
|
|
||||||
<div className="grid w-full items-center gap-2">
|
|
||||||
{endpoint === 'google' ? (
|
|
||||||
<FileUpload
|
|
||||||
id="googleKey"
|
|
||||||
className="w-full"
|
|
||||||
text="Import Service Account JSON Key"
|
|
||||||
successText="Successfully Imported Service Account JSON Key"
|
|
||||||
invalidText="Invalid Service Account JSON Key, Did you import the correct file?"
|
|
||||||
validator={(credentials) => {
|
|
||||||
if (!credentials) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!credentials.client_email ||
|
|
||||||
typeof credentials.client_email !== 'string' ||
|
|
||||||
credentials.client_email.length <= 2
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!credentials.project_id ||
|
|
||||||
typeof credentials.project_id !== 'string' ||
|
|
||||||
credentials.project_id.length <= 2
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!credentials.private_key ||
|
|
||||||
typeof credentials.private_key !== 'string' ||
|
|
||||||
credentials.private_key.length <= 600
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}}
|
|
||||||
onFileSelected={(data) => {
|
|
||||||
setToken(JSON.stringify(data));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : endpoint === 'openAI' || endpoint === 'azureOpenAI' ? (
|
|
||||||
<>
|
|
||||||
{!showPanel ? (
|
|
||||||
<>
|
|
||||||
<InputWithLabel
|
|
||||||
id={'chatGPTLabel'}
|
|
||||||
value={token || ''}
|
|
||||||
onChange={(e) => setToken(e.target.value || '')}
|
|
||||||
label={'OpenAI API Key'}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<InputWithLabel
|
|
||||||
id={'instanceNameLabel'}
|
|
||||||
value={getAzure('azureOpenAIApiInstanceName') || ''}
|
|
||||||
onChange={(e) => setAzure('azureOpenAIApiInstanceName', e.target.value || '')}
|
|
||||||
label={'Azure OpenAI Instance Name'}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputWithLabel
|
|
||||||
id={'deploymentNameLabel'}
|
|
||||||
value={getAzure('azureOpenAIApiDeploymentName') || ''}
|
|
||||||
onChange={(e) => setAzure('azureOpenAIApiDeploymentName', e.target.value || '')}
|
|
||||||
label={'Azure OpenAI Deployment Name'}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputWithLabel
|
|
||||||
id={'versionLabel'}
|
|
||||||
value={getAzure('azureOpenAIApiVersion') || ''}
|
|
||||||
onChange={(e) => setAzure('azureOpenAIApiVersion', e.target.value || '')}
|
|
||||||
label={'Azure OpenAI API Version'}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputWithLabel
|
|
||||||
id={'apiKeyLabel'}
|
|
||||||
value={getAzure('azureOpenAIApiKey') || ''}
|
|
||||||
onChange={(e) => setAzure('azureOpenAIApiKey', e.target.value || '')}
|
|
||||||
label={'Azure OpenAI API Key'}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Checkbox.Root
|
|
||||||
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"
|
|
||||||
id="azureOpenAI"
|
|
||||||
checked={showPanel && endpoint === 'azureOpenAI'}
|
|
||||||
onCheckedChange={() => setShowPanel(!showPanel)}
|
|
||||||
>
|
|
||||||
<Checkbox.Indicator className="flex h-[20px] w-[20px] items-center justify-center rounded-[3.5px] bg-green-600">
|
|
||||||
<CheckIcon />
|
|
||||||
</Checkbox.Indicator>
|
|
||||||
</Checkbox.Root>
|
|
||||||
|
|
||||||
<label
|
|
||||||
className="pl-[8px] text-[15px] leading-none dark:text-white"
|
|
||||||
htmlFor="azureOpenAI"
|
|
||||||
>
|
|
||||||
Use Azure OpenAI.
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<InputWithLabel
|
|
||||||
id={'chatGPTLabel'}
|
|
||||||
value={token || ''}
|
|
||||||
onChange={(e) => setToken(e.target.value || '')}
|
|
||||||
label={'Token Name'}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<small className="text-red-600">
|
|
||||||
Your token will be sent to the server, but not saved.
|
|
||||||
</small>
|
|
||||||
{helpText?.[endpoint]}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
selection={{
|
|
||||||
selectHandler: submit,
|
|
||||||
selectClasses: 'bg-green-600 hover:bg-green-700 dark:hover:bg-green-800 text-white',
|
|
||||||
selectText: 'Submit'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SetTokenDialog;
|
|
||||||
1
client/src/components/Input/SetTokenDialog/index.ts
Normal file
1
client/src/components/Input/SetTokenDialog/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as SetTokenDialog } from './SetTokenDialog';
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import StopGeneratingIcon from '../svg/StopGeneratingIcon';
|
import { StopGeneratingIcon } from '~/components';
|
||||||
import { Settings } from 'lucide-react';
|
import { Settings } from 'lucide-react';
|
||||||
import SetTokenDialog from './SetTokenDialog';
|
import { SetTokenDialog } from './SetTokenDialog';
|
||||||
import store from '../../store';
|
import store from '~/store';
|
||||||
|
|
||||||
export default function SubmitButton({
|
export default function SubmitButton({
|
||||||
endpoint,
|
endpoint,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { useState } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { Plugin } from '~/components/svg';
|
import { Plugin } from '~/components/svg';
|
||||||
import EndpointOptionsDialog from '../Endpoints/EndpointOptionsDialog';
|
import EndpointOptionsDialog from '../Endpoints/EndpointOptionsDialog';
|
||||||
import { cn } from '~/utils/';
|
import { cn, alternateName } from '~/utils/';
|
||||||
|
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ const MessageHeader = ({ isSearchView = false }) => {
|
||||||
const getConversationTitle = () => {
|
const getConversationTitle = () => {
|
||||||
if (isSearchView) return `Search: ${searchQuery}`;
|
if (isSearchView) return `Search: ${searchQuery}`;
|
||||||
else {
|
else {
|
||||||
let _title = `${endpoint}`;
|
let _title = `${alternateName[endpoint] ?? endpoint}`;
|
||||||
|
|
||||||
if (endpoint === 'azureOpenAI' || endpoint === 'openAI') {
|
if (endpoint === 'azureOpenAI' || endpoint === 'openAI') {
|
||||||
const { chatGptLabel } = conversation;
|
const { chatGptLabel } = conversation;
|
||||||
|
|
@ -42,7 +42,7 @@ const MessageHeader = ({ isSearchView = false }) => {
|
||||||
} else if (endpoint === 'bingAI') {
|
} else if (endpoint === 'bingAI') {
|
||||||
const { jailbreak, toneStyle } = conversation;
|
const { jailbreak, toneStyle } = conversation;
|
||||||
if (toneStyle) _title += `: ${toneStyle}`;
|
if (toneStyle) _title += `: ${toneStyle}`;
|
||||||
if (jailbreak) _title += ` as Sydney`;
|
if (jailbreak) _title += ' as Sydney';
|
||||||
} else if (endpoint === 'chatGPTBrowser') {
|
} else if (endpoint === 'chatGPTBrowser') {
|
||||||
if (model) _title += `: ${model}`;
|
if (model) _title += `: ${model}`;
|
||||||
} else if (endpoint === 'gptPlugins') {
|
} else if (endpoint === 'gptPlugins') {
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,4 @@ export { default as BingIcon } from './BingIcon';
|
||||||
export { default as CogIcon } from './CogIcon';
|
export { default as CogIcon } from './CogIcon';
|
||||||
export { default as Spinner } from './Spinner';
|
export { default as Spinner } from './Spinner';
|
||||||
export { default as MessagesSquared } from './MessagesSquared';
|
export { default as MessagesSquared } from './MessagesSquared';
|
||||||
|
export { default as StopGeneratingIcon } from './StopGeneratingIcon';
|
||||||
|
|
@ -34,3 +34,13 @@ export const languages = [
|
||||||
'perl',
|
'perl',
|
||||||
'pascal'
|
'pascal'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const alternateName = {
|
||||||
|
openAI: 'OpenAI',
|
||||||
|
azureOpenAI: 'Azure OpenAI',
|
||||||
|
bingAI: 'Bing',
|
||||||
|
chatGPTBrowser: 'ChatGPT',
|
||||||
|
gptPlugins: 'Plugins',
|
||||||
|
google: 'PaLM'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue