mirror of
https://github.com/NovaOSS/nova-betterchat.git
synced 2024-11-25 21:43:59 +01:00
parent
670c26774a
commit
a009be9986
|
@ -1,4 +1,4 @@
|
|||
import { MessageInterface } from '@type/chat';
|
||||
import { ConfigInterface, MessageInterface } from '@type/chat';
|
||||
|
||||
export const endpoint = 'https://api.openai.com/v1/chat/completions';
|
||||
|
||||
|
@ -23,7 +23,8 @@ export const validateApiKey = async (apiKey: string) => {
|
|||
|
||||
export const getChatCompletion = async (
|
||||
apiKey: string,
|
||||
messages: MessageInterface[]
|
||||
messages: MessageInterface[],
|
||||
config: ConfigInterface
|
||||
) => {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
|
@ -34,6 +35,7 @@ export const getChatCompletion = async (
|
|||
body: JSON.stringify({
|
||||
model: 'gpt-3.5-turbo',
|
||||
messages,
|
||||
...config,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) throw new Error(await response.text());
|
||||
|
@ -44,7 +46,8 @@ export const getChatCompletion = async (
|
|||
|
||||
export const getChatCompletionStream = async (
|
||||
apiKey: string,
|
||||
messages: MessageInterface[]
|
||||
messages: MessageInterface[],
|
||||
config: ConfigInterface
|
||||
) => {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
|
@ -55,6 +58,7 @@ export const getChatCompletionStream = async (
|
|||
body: JSON.stringify({
|
||||
model: 'gpt-3.5-turbo',
|
||||
messages,
|
||||
...config,
|
||||
stream: true,
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { MessageInterface } from '@type/chat';
|
||||
import { ConfigInterface, MessageInterface } from '@type/chat';
|
||||
|
||||
export const endpoint = 'https://chatgpt-api.shn.hk/v1/';
|
||||
|
||||
export const getChatCompletion = async (messages: MessageInterface[]) => {
|
||||
export const getChatCompletion = async (
|
||||
messages: MessageInterface[],
|
||||
config: ConfigInterface
|
||||
) => {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -11,6 +14,7 @@ export const getChatCompletion = async (messages: MessageInterface[]) => {
|
|||
body: JSON.stringify({
|
||||
model: 'gpt-3.5-turbo',
|
||||
messages,
|
||||
...config,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) throw new Error(await response.text());
|
||||
|
@ -19,7 +23,10 @@ export const getChatCompletion = async (messages: MessageInterface[]) => {
|
|||
return data;
|
||||
};
|
||||
|
||||
export const getChatCompletionStream = async (messages: MessageInterface[]) => {
|
||||
export const getChatCompletionStream = async (
|
||||
messages: MessageInterface[],
|
||||
config: ConfigInterface
|
||||
) => {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -28,6 +35,7 @@ export const getChatCompletionStream = async (messages: MessageInterface[]) => {
|
|||
body: JSON.stringify({
|
||||
model: 'gpt-3.5-turbo',
|
||||
messages,
|
||||
...config,
|
||||
stream: true,
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -1,11 +1,73 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import useStore from '@store/store';
|
||||
import ConfigMenu from '@components/ConfigMenu';
|
||||
import { ChatInterface, ConfigInterface } from '@type/chat';
|
||||
import { defaultChatConfig } from '@constants/chat';
|
||||
|
||||
const ChatTitle = () => {
|
||||
return (
|
||||
<div className='flex w-full items-center justify-center gap-1 border-b border-black/10 bg-gray-50 p-3 text-gray-500 dark:border-gray-900/50 dark:bg-gray-700 dark:text-gray-300'>
|
||||
Model: Default
|
||||
</div>
|
||||
const ChatTitle = React.memo(() => {
|
||||
const config = useStore(
|
||||
(state) =>
|
||||
state.chats &&
|
||||
state.chats.length > 0 &&
|
||||
state.currentChatIndex >= 0 &&
|
||||
state.currentChatIndex < state.chats.length
|
||||
? state.chats[state.currentChatIndex].config
|
||||
: undefined,
|
||||
shallow
|
||||
);
|
||||
};
|
||||
const setChats = useStore((state) => state.setChats);
|
||||
const currentChatIndex = useStore((state) => state.currentChatIndex);
|
||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||
|
||||
const setConfig = (config: ConfigInterface) => {
|
||||
const updatedChats: ChatInterface[] = JSON.parse(
|
||||
JSON.stringify(useStore.getState().chats)
|
||||
);
|
||||
updatedChats[currentChatIndex].config = config;
|
||||
setChats(updatedChats);
|
||||
};
|
||||
|
||||
// for migrating from old ChatInterface to new ChatInterface (with config)
|
||||
useEffect(() => {
|
||||
if (!config) {
|
||||
const updatedChats: ChatInterface[] = JSON.parse(
|
||||
JSON.stringify(useStore.getState().chats)
|
||||
);
|
||||
updatedChats[currentChatIndex].config = { ...defaultChatConfig };
|
||||
setChats(updatedChats);
|
||||
}
|
||||
}, [currentChatIndex]);
|
||||
|
||||
return config ? (
|
||||
<>
|
||||
<div
|
||||
className='flex gap-4 flex-wrap w-full items-center justify-center gap-1 border-b border-black/10 bg-gray-50 p-3 text-gray-500 dark:border-gray-900/50 dark:bg-gray-700 dark:text-gray-300 cursor-pointer'
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<div className='text-center p-1 rounded-md bg-gray-900/10 hover:bg-gray-900/50'>
|
||||
Model: Default
|
||||
</div>
|
||||
<div className='text-center p-1 rounded-md bg-gray-900/10 hover:bg-gray-900/50'>
|
||||
Temperature: {config.temperature}
|
||||
</div>
|
||||
<div className='text-center p-1 rounded-md bg-gray-900/10 hover:bg-gray-900/50'>
|
||||
PresencePenalty: {config.presence_penalty}
|
||||
</div>
|
||||
</div>
|
||||
{isModalOpen && (
|
||||
<ConfigMenu
|
||||
setIsModalOpen={setIsModalOpen}
|
||||
config={config}
|
||||
setConfig={setConfig}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
});
|
||||
|
||||
export default ChatTitle;
|
||||
|
|
|
@ -4,7 +4,7 @@ import useStore from '@store/store';
|
|||
import PlusIcon from '@icon/PlusIcon';
|
||||
|
||||
import { ChatInterface } from '@type/chat';
|
||||
import { defaultSystemMessage } from '@constants/chat';
|
||||
import { defaultChatConfig, defaultSystemMessage } from '@constants/chat';
|
||||
|
||||
const NewMessageButton = React.memo(
|
||||
({ messageIndex }: { messageIndex: number }) => {
|
||||
|
@ -26,7 +26,13 @@ const NewMessageButton = React.memo(
|
|||
|
||||
updatedChats.unshift({
|
||||
title,
|
||||
messages: [{ role: 'system', content: defaultSystemMessage }],
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: defaultSystemMessage,
|
||||
},
|
||||
],
|
||||
config: { ...defaultChatConfig },
|
||||
});
|
||||
setChats(updatedChats);
|
||||
setCurrentChatIndex(0);
|
||||
|
|
83
src/components/ConfigMenu/ConfigMenu.tsx
Normal file
83
src/components/ConfigMenu/ConfigMenu.tsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
import React, { useState } from 'react';
|
||||
import PopupModal from '@components/PopupModal';
|
||||
import { ConfigInterface } from '@type/chat';
|
||||
|
||||
const ConfigMenu = ({
|
||||
setIsModalOpen,
|
||||
config,
|
||||
setConfig,
|
||||
}: {
|
||||
setIsModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
config: ConfigInterface;
|
||||
setConfig: (config: ConfigInterface) => void;
|
||||
}) => {
|
||||
const [_temperature, _setTemperature] = useState<number>(config.temperature);
|
||||
const [_presencePenalty, _setPresencePenalty] = useState<number>(
|
||||
config.presence_penalty
|
||||
);
|
||||
|
||||
const handleConfirm = () => {
|
||||
setConfig({
|
||||
temperature: _temperature,
|
||||
presence_penalty: _presencePenalty,
|
||||
});
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<PopupModal
|
||||
title='Configuration'
|
||||
setIsModalOpen={setIsModalOpen}
|
||||
handleConfirm={handleConfirm}
|
||||
>
|
||||
<div className='p-6 border-b border-gray-200 dark:border-gray-600'>
|
||||
<div>
|
||||
<label className='block text-sm font-medium text-gray-900 dark:text-white'>
|
||||
Temperature: {_temperature}
|
||||
</label>
|
||||
<input
|
||||
id='default-range'
|
||||
type='range'
|
||||
value={_temperature}
|
||||
onChange={(e) => {
|
||||
_setTemperature(Number(e.target.value));
|
||||
}}
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.1}
|
||||
className='w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer'
|
||||
/>
|
||||
<div className='min-w-fit text-gray-500 dark:text-gray-300 text-sm mt-2'>
|
||||
What sampling temperature to use, between 0 and 2. Higher values
|
||||
like 0.8 will make the output more random, while lower values like
|
||||
0.2 will make it more focused and deterministic.
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-5 pt-5 border-t border-gray-500'>
|
||||
<label className='block text-sm font-medium text-gray-900 dark:text-white'>
|
||||
Presence Penalty: {_presencePenalty}
|
||||
</label>
|
||||
<input
|
||||
id='default-range'
|
||||
type='range'
|
||||
value={_presencePenalty}
|
||||
onChange={(e) => {
|
||||
_setPresencePenalty(Number(e.target.value));
|
||||
}}
|
||||
min={-2}
|
||||
max={2}
|
||||
step={0.1}
|
||||
className='w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer'
|
||||
/>
|
||||
<div className='min-w-fit text-gray-500 dark:text-gray-300 text-sm mt-2'>
|
||||
Number between -2.0 and 2.0. Positive values penalize new tokens
|
||||
based on whether they appear in the text so far, increasing the
|
||||
model's likelihood to talk about new topics.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopupModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigMenu;
|
1
src/components/ConfigMenu/index.ts
Normal file
1
src/components/ConfigMenu/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from './ConfigMenu';
|
|
@ -31,7 +31,7 @@ const PopupModal = ({
|
|||
return ReactDOM.createPortal(
|
||||
<div className='fixed top-0 left-0 z-[999] w-full p-4 overflow-x-hidden overflow-y-auto h-full flex justify-center items-center'>
|
||||
<div className='relative z-2 max-w-2xl md:h-auto flex justify-center items-center'>
|
||||
<div className='relative bg-white rounded-lg shadow dark:bg-gray-700'>
|
||||
<div className='relative bg-gray-50 rounded-lg shadow dark:bg-gray-700'>
|
||||
<div className='flex items-center justify-between p-4 border-b rounded-t dark:border-gray-600'>
|
||||
<h3 className='ml-2 text-lg font-semibold text-gray-900 dark:text-white'>
|
||||
{title}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { ConfigInterface } from '@type/chat';
|
||||
|
||||
const date = new Date();
|
||||
const dateString =
|
||||
date.getFullYear() +
|
||||
|
@ -10,3 +12,8 @@ const dateString =
|
|||
export const defaultSystemMessage = `You are ChatGPT, a large language model trained by OpenAI.
|
||||
Knowledge cutoff: 2021-09
|
||||
Current date: ${dateString}`;
|
||||
|
||||
export const defaultChatConfig: ConfigInterface = {
|
||||
temperature: 1,
|
||||
presence_penalty: 0,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import useStore from '@store/store';
|
||||
import { defaultSystemMessage } from '@constants/chat';
|
||||
import { defaultChatConfig, defaultSystemMessage } from '@constants/chat';
|
||||
import { ChatInterface } from '@type/chat';
|
||||
|
||||
const useAddChat = () => {
|
||||
|
@ -22,6 +22,7 @@ const useAddChat = () => {
|
|||
updatedChats.unshift({
|
||||
title,
|
||||
messages: [{ role: 'system', content: defaultSystemMessage }],
|
||||
config: { ...defaultChatConfig },
|
||||
});
|
||||
setChats(updatedChats);
|
||||
setCurrentChatIndex(0);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import useStore from '@store/store';
|
||||
import { MessageInterface } from '@type/chat';
|
||||
import { defaultSystemMessage } from '@constants/chat';
|
||||
import { defaultChatConfig, defaultSystemMessage } from '@constants/chat';
|
||||
|
||||
const useInitialiseNewChat = () => {
|
||||
const setChats = useStore((state) => state.setChats);
|
||||
|
@ -16,6 +16,7 @@ const useInitialiseNewChat = () => {
|
|||
{
|
||||
title: 'New Chat',
|
||||
messages: [message],
|
||||
config: { ...defaultChatConfig },
|
||||
},
|
||||
]);
|
||||
setCurrentChatIndex(0);
|
||||
|
|
|
@ -33,12 +33,14 @@ const useSubmit = () => {
|
|||
try {
|
||||
if (apiFree) {
|
||||
stream = await getChatCompletionStreamFree(
|
||||
chats[currentChatIndex].messages
|
||||
chats[currentChatIndex].messages,
|
||||
chats[currentChatIndex].config
|
||||
);
|
||||
} else if (apiKey) {
|
||||
stream = await getChatCompletionStreamCustom(
|
||||
apiKey,
|
||||
chats[currentChatIndex].messages
|
||||
chats[currentChatIndex].messages,
|
||||
chats[currentChatIndex].config
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,14 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
@apply w-4;
|
||||
@apply h-4;
|
||||
@apply rounded-full;
|
||||
background: rgba(16, 163, 127);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
height: 1rem;
|
||||
width: 0.5rem;
|
||||
|
|
|
@ -9,4 +9,10 @@ export interface MessageInterface {
|
|||
export interface ChatInterface {
|
||||
title: string;
|
||||
messages: MessageInterface[];
|
||||
config: ConfigInterface;
|
||||
}
|
||||
|
||||
export interface ConfigInterface {
|
||||
temperature: number;
|
||||
presence_penalty: number;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue