feat: customise free api endpoint

This commit is contained in:
Jing Hua 2023-03-07 08:15:28 +08:00
parent 98729351e0
commit 8ba2e473cd
5 changed files with 91 additions and 38 deletions

View file

@ -1,8 +1,7 @@
import { ConfigInterface, MessageInterface } from '@type/chat'; import { ConfigInterface, MessageInterface } from '@type/chat';
export const endpoint = 'https://chatgpt-api.shn.hk/v1/';
export const getChatCompletion = async ( export const getChatCompletion = async (
endpoint: string,
messages: MessageInterface[], messages: MessageInterface[],
config: ConfigInterface config: ConfigInterface
) => { ) => {
@ -24,6 +23,7 @@ export const getChatCompletion = async (
}; };
export const getChatCompletionStream = async ( export const getChatCompletionStream = async (
endpoint: string,
messages: MessageInterface[], messages: MessageInterface[],
config: ConfigInterface config: ConfigInterface
) => { ) => {
@ -39,6 +39,11 @@ export const getChatCompletionStream = async (
stream: true, stream: true,
}), }),
}); });
if (response.status === 404)
throw new Error(
'Message from freechatgpt.chat:\nInvalid API endpoint! We recommend you to check your free API endpoint.'
);
const text = await response.text(); const text = await response.text();
if (response.status === 429 && text.includes('insufficient_quota')) if (response.status === 429 && text.includes('insufficient_quota'))
throw new Error( throw new Error(

View file

@ -15,26 +15,34 @@ const ApiMenu = ({
const setApiKey = useStore((state) => state.setApiKey); const setApiKey = useStore((state) => state.setApiKey);
const apiFree = useStore((state) => state.apiFree); const apiFree = useStore((state) => state.apiFree);
const setApiFree = useStore((state) => state.setApiFree); const setApiFree = useStore((state) => state.setApiFree);
const apiFreeEndpoint = useStore((state) => state.apiFreeEndpoint);
const setApiFreeEndpoint = useStore((state) => state.setApiFreeEndpoint);
const [_apiFree, _setApiFree] = useState<boolean>(apiFree); const [_apiFree, _setApiFree] = useState<boolean>(apiFree);
const [_apiKey, _setApiKey] = useState<string>(apiKey || ''); const [_apiKey, _setApiKey] = useState<string>(apiKey || '');
const [error, setError] = useState<boolean>(false); const [_apiFreeEndpoint, _setApiFreeEndpoint] =
useState<string>(apiFreeEndpoint);
const [error, setError] = useState<string>('');
const handleSave = async () => { const handleSave = async () => {
if (_apiFree === true) { if (_apiFree === true) {
setApiFreeEndpoint(_apiFreeEndpoint);
setApiFree(true); setApiFree(true);
setError('');
setIsModalOpen(false); setIsModalOpen(false);
} else { } else {
const valid = await validateApiKey(_apiKey); const valid = await validateApiKey(_apiKey);
if (valid) { if (valid) {
setApiKey(_apiKey); setApiKey(_apiKey);
setApiFree(false); setApiFree(false);
setError(false); setError('');
setIsModalOpen(false); setIsModalOpen(false);
} else { } else {
setError(true); setError(
'Error: Invalid API key or network blocked. Please check your API key and network settings for OpenAI API.'
);
setTimeout(() => { setTimeout(() => {
setError(false); setError('');
}, 10000); }, 10000);
} }
} }
@ -42,17 +50,24 @@ const ApiMenu = ({
useEffect(() => { useEffect(() => {
if (apiKey) { if (apiKey) {
setApiFree(false); setApiFree(false);
_setApiFree(false); _setApiFree(false);
_setApiKey(apiKey); _setApiKey(apiKey);
} }
}, []); }, []);
const handleClose = () => {
_setApiFree(apiFree);
_setApiFreeEndpoint(apiFreeEndpoint);
apiKey && _setApiKey(apiKey);
};
return isModalOpen ? ( return isModalOpen ? (
<PopupModal <PopupModal
title='API' title='API'
setIsModalOpen={setIsModalOpen} setIsModalOpen={setIsModalOpen}
handleConfirm={handleSave} handleConfirm={handleSave}
handleClose={handleClose}
> >
<div className='p-6 border-b border-gray-200 dark:border-gray-600'> <div className='p-6 border-b border-gray-200 dark:border-gray-600'>
<div className='flex items-center mb-2'> <div className='flex items-center mb-2'>
@ -63,16 +78,40 @@ const ApiMenu = ({
onChange={() => _setApiFree(true)} onChange={() => _setApiFree(true)}
/> />
<label className='ml-2 text-sm font-medium text-gray-900 dark:text-gray-300'> <label className='ml-2 text-sm font-medium text-gray-900 dark:text-gray-300'>
Use free API from{' '} Use free API endpoint
<a
href='https://github.com/ayaka14732/ChatGPTAPIFree'
className='underline dark:hover:text-white hover:text-black'
target='_blank'
>
Ayaka
</a>
</label> </label>
</div> </div>
{_apiFree && (
<div className='mt-2 mb-6'>
<div className='text-sm font-medium text-gray-900 dark:text-gray-300 mb-2'>
Use free API endpoint from{' '}
<a
href='https://github.com/ayaka14732/ChatGPTAPIFree'
className='underline dark:hover:text-white hover:text-black'
target='_blank'
>
Ayaka
</a>
: https://chatgpt-api.shn.hk/v1/ or enter your own API endpoint
</div>
<div className='flex gap-2 items-center justify-center'>
<div className='min-w-fit text-gray-900 dark:text-gray-300 text-sm'>
Free API Endpoint
</div>
<input
type='text'
className='text-gray-800 dark:text-white p-3 text-sm border-none bg-gray-200 dark:bg-gray-600 rounded-md m-0 w-full mr-0 h-8 focus:outline-none'
value={_apiFreeEndpoint}
placeholder='https://chatgpt-api.shn.hk/v1/'
onChange={(e) => {
_setApiFreeEndpoint(e.target.value);
}}
/>
</div>
</div>
)}
<div className='flex items-center'> <div className='flex items-center'>
<input <input
type='radio' type='radio'
@ -86,27 +125,19 @@ const ApiMenu = ({
</div> </div>
{_apiFree === false && ( {_apiFree === false && (
<> <div className='flex gap-2 items-center justify-center mt-2'>
<div className='flex gap-2 items-center justify-center mt-2'> <div className='min-w-fit text-gray-900 dark:text-gray-300 text-sm'>
<div className='min-w-fit text-gray-900 dark:text-gray-300 text-sm'> API Key
API Key
</div>
<input
type='text'
className='text-gray-800 dark:text-white p-3 text-sm border-none bg-gray-200 dark:bg-gray-600 rounded-md m-0 w-full mr-0 h-8 focus:outline-none'
value={_apiKey}
onChange={(e) => {
_setApiKey(e.target.value);
}}
/>
</div> </div>
{error && ( <input
<div className='bg-red-600/50 p-2 rounded-sm mt-3 text-gray-900 dark:text-gray-300 text-sm'> type='text'
Error: Invalid API key or network blocked. Please check your API className='text-gray-800 dark:text-white p-3 text-sm border-none bg-gray-200 dark:bg-gray-600 rounded-md m-0 w-full mr-0 h-8 focus:outline-none'
key and network settings for OpenAI API. value={_apiKey}
</div> onChange={(e) => {
)} _setApiKey(e.target.value);
</> }}
/>
</div>
)} )}
<div className='min-w-fit text-gray-900 dark:text-gray-300 text-sm mt-4 text-center'> <div className='min-w-fit text-gray-900 dark:text-gray-300 text-sm mt-4 text-center'>
@ -126,6 +157,11 @@ const ApiMenu = ({
purpose of accessing the OpenAI API and not for any other unauthorised purpose of accessing the OpenAI API and not for any other unauthorised
use. use.
</div> </div>
{error !== '' && (
<div className='bg-red-600/50 p-2 rounded-sm mt-3 text-gray-900 dark:text-gray-300 text-sm'>
{error}
</div>
)}
</div> </div>
</PopupModal> </PopupModal>
) : ( ) : (

View file

@ -40,6 +40,7 @@ const useSubmit = () => {
if (apiFree) { if (apiFree) {
stream = await getChatCompletionStreamFree( stream = await getChatCompletionStreamFree(
useStore.getState().apiFreeEndpoint,
messages, messages,
chats[currentChatIndex].config chats[currentChatIndex].config
); );

View file

@ -3,12 +3,15 @@ import { StoreSlice } from './store';
export interface AuthSlice { export interface AuthSlice {
apiKey?: string; apiKey?: string;
apiFree: boolean; apiFree: boolean;
apiFreeEndpoint: string;
setApiKey: (apiKey: string) => void; setApiKey: (apiKey: string) => void;
setApiFree: (apiFree: boolean) => void; setApiFree: (apiFree: boolean) => void;
setApiFreeEndpoint: (apiFreeEndpoint: string) => void;
} }
export const createAuthSlice: StoreSlice<AuthSlice> = (set, get) => ({ export const createAuthSlice: StoreSlice<AuthSlice> = (set, get) => ({
apiFree: true, apiFree: true,
apiFreeEndpoint: 'https://chatgpt-api.shn.hk/v1/',
setApiKey: (apiKey: string) => { setApiKey: (apiKey: string) => {
set((prev: AuthSlice) => ({ set((prev: AuthSlice) => ({
...prev, ...prev,
@ -21,4 +24,10 @@ export const createAuthSlice: StoreSlice<AuthSlice> = (set, get) => ({
apiFree: apiFree, apiFree: apiFree,
})); }));
}, },
setApiFreeEndpoint: (apiFreeEndpoint: string) => {
set((prev: AuthSlice) => ({
...prev,
apiFreeEndpoint: apiFreeEndpoint,
}));
},
}); });

View file

@ -26,6 +26,8 @@ const useStore = create<StoreState>()(
chats: state.chats, chats: state.chats,
currentChatIndex: state.currentChatIndex, currentChatIndex: state.currentChatIndex,
apiKey: state.apiKey, apiKey: state.apiKey,
apiFree: state.apiFree,
apiFreeEndpoint: state.apiFreeEndpoint,
theme: state.theme, theme: state.theme,
}), }),
} }