feat: feat: new endpoint-style endpoint select

fix: a wrong use of bingai params
This commit is contained in:
Wentao Lyu 2023-03-31 04:22:16 +08:00
parent adcc021c9e
commit e8e3903b78
18 changed files with 7891 additions and 233 deletions

View file

@ -32,13 +32,15 @@ const askBing = async ({
jailbreakConversationId: jailbreakConversationId || jailbreak,
parentMessageId,
conversationId,
conversationSignature,
clientId,
invocationId,
toneStyle,
onProgress
};
if (conversationSignature) options.conversationSignature = conversationSignature;
if (conversationSignature) options.clientId = clientId;
if (conversationSignature) options.invocationId = invocationId;
if (conversationSignature) options.toneStyle = toneStyle;
if (options?.jailbreakConversationId == 'false') {
options.jailbreakConversationId = false;
}

View file

@ -90,6 +90,9 @@ const migrateToSupportBetterCustomization = async () => {
convo.endpoint = 'chatGPTBrowser';
convo.model = 'text-davinci-002-render-sha';
convo.jailbreak = true;
} else {
convo.endpoint = 'openAI';
convo.model = 'gpt-3.5-turbo';
}
promises.push(convo.save());

View file

@ -79,8 +79,8 @@ const convoSchema = mongoose.Schema(
default: null
},
invocationId: {
type: String,
default: null
type: Number,
default: 1
},
toneStyle: {
type: String,

View file

@ -50,7 +50,12 @@ router.post('/', async (req, res) => {
if (!overrideParentMessageId) {
await saveBingMessage(userMessage);
await saveConvo(req?.session?.user?.username, { ...userMessage, ...endpointOption, conversationId });
await saveConvo(req?.session?.user?.username, {
...userMessage,
...endpointOption,
conversationId,
endpoint
});
}
return await ask({

View file

@ -42,7 +42,12 @@ router.post('/', async (req, res) => {
if (!overrideParentMessageId) {
await saveMessage(userMessage);
await saveConvo(req?.session?.user?.username, { ...userMessage, ...endpointOption, conversationId });
await saveConvo(req?.session?.user?.username, {
...userMessage,
...endpointOption,
conversationId,
endpoint
});
}
return await ask({

View file

@ -47,7 +47,12 @@ router.post('/', async (req, res) => {
if (!overrideParentMessageId) {
await saveMessage(userMessage);
await saveConvo(req?.session?.user?.username, { ...userMessage, ...endpointOption, conversationId });
await saveConvo(req?.session?.user?.username, {
...userMessage,
...endpointOption,
conversationId,
endpoint
});
}
return await ask({

View file

@ -0,0 +1,24 @@
import React from 'react';
import { DropdownMenuRadioItem } from '../../ui/DropdownMenu.tsx';
import getIcon from '~/utils/getIcon';
export default function ModelItem({ endpoint, value, onSelect }) {
const icon = getIcon({
size: 20,
endpoint,
error: false,
className: 'mr-2'
});
// regular model
return (
<DropdownMenuRadioItem
value={value}
className="dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
>
{icon}
{endpoint}
{endpoint in ['azureOpenAI', 'openAI'] && <sup>$</sup>}
</DropdownMenuRadioItem>
);
}

View file

@ -0,0 +1,17 @@
import React from 'react';
import EndpointItem from './EndpointItem';
export default function EndpointItems({ endpoints, onSelect }) {
return (
<>
{endpoints.map(endpoint => (
<EndpointItem
key={endpoint}
value={endpoint}
onSelect={onSelect}
endpoint={endpoint}
/>
))}
</>
);
}

View file

@ -0,0 +1,188 @@
import React, { useState, useEffect } from 'react';
import { useRecoilValue } from 'recoil';
// import ModelDialog from './ModelDialog';
import EndpointItems from './EndpointItems';
import { swr } from '~/utils/fetchers';
import getIcon from '~/utils/getIcon';
import { Button } from '../../ui/Button.tsx';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '../../ui/DropdownMenu.tsx';
import { Dialog } from '../../ui/Dialog.tsx';
import store from '~/store';
export default function EndpointMenu() {
// const [modelSave, setModelSave] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
// const models = useRecoilValue(store.models);
const availableEndpoints = useRecoilValue(store.availableEndpoints);
// const setCustomGPTModels = useSetRecoilState(store.customGPTModels);
const conversation = useRecoilValue(store.conversation) || {};
const { endpoint, conversationId } = conversation;
// const { model, promptPrefix, chatGptLabel, conversationId } = conversation;
const { newConversation } = store.useConversation();
// fetch the list of saved chatgptCustom
// const { data, isLoading, mutate } = swr(`/api/customGpts`, res => {
// const fetchedModels = res.map(modelItem => ({
// ...modelItem,
// name: modelItem.chatGptLabel,
// model: 'chatgptCustom'
// }));
// setCustomGPTModels(fetchedModels);
// });
// update the default model when availableModels changes
// typically, availableModels changes => modelsFilter or customGPTModels changes
useEffect(() => {
if (conversationId == 'new') {
newConversation();
}
}, [availableEndpoints]);
// save selected model to localstoreage
useEffect(() => {
if (endpoint) localStorage.setItem('lastConversationSetup', JSON.stringify(conversation));
}, [conversation]);
// set the current model
const onChange = (newEndpoint, value = null) => {
setMenuOpen(false);
if (!newEndpoint) return;
else if (newEndpoint === endpoint) return;
else {
newConversation({}, newEndpoint);
}
// } else if (newModel === model && value === chatGptLabel) {
// // bypass if not changed
// return;
// } else if (newModel === 'chatgptCustom' && value === null) {
// // return;
// } else if (newModel !== 'chatgptCustom') {
// newConversation({
// model: newModel,
// chatGptLabel: null,
// promptPrefix: null
// });
// } else if (newModel === 'chatgptCustom') {
// const targetModel = models.find(element => element.value == value);
// if (targetModel) {
// const chatGptLabel = targetModel?.chatGptLabel;
// const promptPrefix = targetModel?.promptPrefix;
// newConversation({
// model: newModel,
// chatGptLabel,
// promptPrefix
// });
// }
// }
};
// const onOpenChange = open => {
// mutate();
// if (!open) {
// setModelSave(false);
// }
// };
// const handleSaveState = value => {
// if (!modelSave) {
// return;
// }
// setCustomGPTModels(value);
// setModelSave(false);
// };
// const defaultColorProps = [
// 'text-gray-500',
// 'hover:bg-gray-100',
// 'hover:bg-opacity-20',
// 'disabled:hover:bg-transparent',
// 'dark:data-[state=open]:bg-gray-800',
// 'dark:hover:bg-opacity-20',
// 'dark:hover:bg-gray-900',
// 'dark:hover:text-gray-400',
// 'dark:disabled:hover:bg-transparent'
// ];
// const chatgptColorProps = [
// 'text-green-700',
// 'data-[state=open]:bg-green-100',
// 'dark:text-emerald-300',
// 'hover:bg-green-100',
// 'disabled:hover:bg-transparent',
// 'dark:data-[state=open]:bg-green-900',
// 'dark:hover:bg-opacity-50',
// 'dark:hover:bg-green-900',
// 'dark:hover:text-gray-100',
// 'dark:disabled:hover:bg-transparent'
// ];
// const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
const icon = getIcon({
size: 32,
...conversation,
isCreatedByUser: false,
error: false,
button: true
});
return (
<Dialog
// onOpenChange={onOpenChange}
>
<DropdownMenu
open={menuOpen}
onOpenChange={setMenuOpen}
>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
// style={{backgroundColor: 'rgb(16, 163, 127)'}}
className={`absolute top-[0.25px] mb-0 ml-1 items-center rounded-md border-0 p-1 outline-none focus:ring-0 focus:ring-offset-0 disabled:top-[0.25px] dark:data-[state=open]:bg-opacity-50 md:top-1 md:left-1 md:ml-0 md:pl-1 md:disabled:top-1`}
>
{icon}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-56 dark:bg-gray-700"
onCloseAutoFocus={event => event.preventDefault()}
>
<DropdownMenuLabel className="dark:text-gray-300">Select an AI Endpoint</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup
value={endpoint}
onValueChange={onChange}
className="overflow-y-auto"
>
{availableEndpoints.length ? (
<EndpointItems
endpoints={availableEndpoints}
onSelect={onChange}
/>
) : (
<DropdownMenuLabel className="dark:text-gray-300">No endpoint available.</DropdownMenuLabel>
)}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
{/* <ModelDialog
mutate={mutate}
setModelSave={setModelSave}
handleSaveState={handleSaveState}
/> */}
</Dialog>
);
}

View file

@ -1,205 +0,0 @@
import React, { useState, useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import axios from 'axios';
import ModelDialog from './ModelDialog';
import MenuItems from './MenuItems';
import { swr } from '~/utils/fetchers';
import getIcon from '~/utils/getIcon';
import { Button } from '../../ui/Button.tsx';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '../../ui/DropdownMenu.tsx';
import { Dialog } from '../../ui/Dialog.tsx';
import store from '~/store';
export default function ModelMenu() {
const [modelSave, setModelSave] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const models = useRecoilValue(store.models);
const availableModels = useRecoilValue(store.availableModels);
const setCustomGPTModels = useSetRecoilState(store.customGPTModels);
const conversation = useRecoilValue(store.conversation) || {};
const { model, promptPrefix, chatGptLabel, conversationId } = conversation;
const { newConversation } = store.useConversation();
// fetch the list of saved chatgptCustom
const { data, isLoading, mutate } = swr(`/api/customGpts`, res => {
const fetchedModels = res.map(modelItem => ({
...modelItem,
name: modelItem.chatGptLabel,
model: 'chatgptCustom'
}));
setCustomGPTModels(fetchedModels);
});
// useEffect(() => {
// mutate();
// try {
// const lastSelected = JSON.parse(localStorage.getItem('model'));
// if (lastSelected === 'chatgptCustom') {
// return;
// } else if (initial[lastSelected]) {
// dispatch(setModel(lastSelected));
// }
// } catch (err) {
// console.log(err);
// }
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, []);
// update the default model when availableModels changes
// typically, availableModels changes => modelsFilter or customGPTModels changes
useEffect(() => {
if (conversationId == 'new') {
newConversation();
}
}, [availableModels]);
// save selected model to localstoreage
useEffect(() => {
if (model) localStorage.setItem('model', JSON.stringify({ model, chatGptLabel, promptPrefix }));
}, [model]);
// set the current model
const onChange = (newModel, value = null) => {
setMenuOpen(false);
if (!newModel) {
return;
} else if (newModel === model && value === chatGptLabel) {
// bypass if not changed
return;
} else if (newModel === 'chatgptCustom' && value === null) {
// return;
} else if (newModel !== 'chatgptCustom') {
newConversation({
model: newModel,
chatGptLabel: null,
promptPrefix: null
});
} else if (newModel === 'chatgptCustom') {
const targetModel = models.find(element => element.value == value);
if (targetModel) {
const chatGptLabel = targetModel?.chatGptLabel;
const promptPrefix = targetModel?.promptPrefix;
newConversation({
model: newModel,
chatGptLabel,
promptPrefix
});
}
}
};
const onOpenChange = open => {
mutate();
if (!open) {
setModelSave(false);
}
};
const handleSaveState = value => {
if (!modelSave) {
return;
}
setCustomGPTModels(value);
setModelSave(false);
};
const defaultColorProps = [
'text-gray-500',
'hover:bg-gray-100',
'hover:bg-opacity-20',
'disabled:hover:bg-transparent',
'dark:data-[state=open]:bg-gray-800',
'dark:hover:bg-opacity-20',
'dark:hover:bg-gray-900',
'dark:hover:text-gray-400',
'dark:disabled:hover:bg-transparent'
];
const chatgptColorProps = [
'text-green-700',
'data-[state=open]:bg-green-100',
'dark:text-emerald-300',
'hover:bg-green-100',
'disabled:hover:bg-transparent',
'dark:data-[state=open]:bg-green-900',
'dark:hover:bg-opacity-50',
'dark:hover:bg-green-900',
'dark:hover:text-gray-100',
'dark:disabled:hover:bg-transparent'
];
const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
const icon = getIcon({
size: 32,
sender: chatGptLabel || model,
isCreatedByUser: false,
model,
chatGptLabel,
promptPrefix,
error: false,
button: true
});
return (
<Dialog onOpenChange={onOpenChange}>
<DropdownMenu
open={menuOpen}
onOpenChange={setMenuOpen}
>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
// style={{backgroundColor: 'rgb(16, 163, 127)'}}
className={`absolute top-[0.25px] mb-0 ml-1 items-center rounded-md border-0 p-1 outline-none md:ml-0 ${colorProps.join(
' '
)} focus:ring-0 focus:ring-offset-0 disabled:top-[0.25px] dark:data-[state=open]:bg-opacity-50 md:top-1 md:left-1 md:pl-1 md:disabled:top-1`}
>
{icon}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-56 dark:bg-gray-700"
onCloseAutoFocus={event => event.preventDefault()}
>
<DropdownMenuLabel className="dark:text-gray-300">Select a Model</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup
value={chatGptLabel || model}
onValueChange={onChange}
className="overflow-y-auto"
>
{availableModels.length ? (
<MenuItems
models={availableModels}
onSelect={onChange}
/>
) : (
<DropdownMenuLabel className="dark:text-gray-300">No model available.</DropdownMenuLabel>
)}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<ModelDialog
mutate={mutate}
setModelSave={setModelSave}
handleSaveState={handleSaveState}
/>
</Dialog>
);
}

View file

@ -3,7 +3,7 @@ import { useRecoilValue, useRecoilState } from 'recoil';
import SubmitButton from './SubmitButton';
import AdjustToneButton from './AdjustToneButton';
import BingStyles from './BingStyles';
// import ModelMenu from './Models/ModelMenu';
import EndpointMenu from './Endpoints/EndpointMenu';
import Footer from './Footer';
import TextareaAutosize from 'react-textarea-autosize';
import RegenerateIcon from '../svg/RegenerateIcon';
@ -167,7 +167,7 @@ export default function TextChat({ isSearchView = false }) {
disabled ? 'dark:bg-gray-900' : 'dark:bg-gray-700'
} dark:text-white dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] md:py-3 md:pl-4`}
>
{/* <ModelMenu /> */}
<EndpointMenu />
<TextareaAutosize
tabIndex="0"
autoFocus

View file

@ -24,7 +24,7 @@ import getDefaultConversation from '~/utils/getDefaultConversation';
// jailbreakConversationId: null,
// conversationSignature: null,
// clientId: null,
// invocationId: null,
// invocationId: 1,
// toneStyle: null,
// suggestions: []
// };
@ -61,10 +61,13 @@ const useConversation = () => {
const switchToConversation = useRecoilCallback(
({ snapshot }) =>
async (_conversation, messages = null) => {
async (_conversation, messages = null, targetEndpoint = null) => {
const prevConversation = await snapshot.getPromise(conversation);
const availableEndpoints = await snapshot.getPromise(endpoints.availableEndpoints);
_switchToConversation(_conversation, messages, { availableEndpoints, prevConversation });
const endpointsFilter = await snapshot.getPromise(endpoints.endpointsFilter);
_switchToConversation(_conversation, messages, targetEndpoint, {
endpointsFilter,
prevConversation
});
},
[]
);
@ -72,27 +75,34 @@ const useConversation = () => {
const _switchToConversation = (
conversation,
messages = null,
{ availableEndpoints = [], prevConversation = {} }
targetEndpoint = null,
{ endpointsFilter = {}, prevConversation = {} }
) => {
let { endpoint = null } = conversation;
if (endpoint === null)
// get the default model
conversation = getDefaultConversation({ conversation, availableEndpoints, prevConversation });
console.log(conversation);
conversation = getDefaultConversation({
conversation,
endpointsFilter,
prevConversation,
targetEndpoint
});
setConversation(conversation);
setMessages(messages);
resetLatestMessage();
};
const newConversation = (template = {}) => {
const newConversation = (template = {}, targetEndpoint = null) => {
switchToConversation(
{
conversationId: 'new',
title: 'New Chat',
...template
},
[]
[],
targetEndpoint
);
};

View file

@ -16,11 +16,11 @@ const buildDefaultConversation = ({ conversation, endpoint, lastConversationSetu
endpoint,
jailbreak: lastConversationSetup?.jailbreak || false,
jailbreakConversationId: lastConversationSetup?.jailbreakConversationId || null,
conversationSignature: lastConversationSetup?.conversationSignature || null,
clientId: lastConversationSetup?.clientId || null,
invocationId: lastConversationSetup?.invocationId || null,
conversationSignature: null,
clientId: null,
invocationId: 1,
toneStyle: lastConversationSetup?.toneStyle || 'fast',
suggestions: lastConversationSetup?.suggestions || []
suggestions: []
};
} else if (endpoint === 'chatGPTBrowser') {
conversation = {
@ -44,11 +44,27 @@ const buildDefaultConversation = ({ conversation, endpoint, lastConversationSetu
return conversation;
};
const getDefaultConversation = ({ conversation, prevConversation, availableEndpoints }) => {
const getDefaultConversation = ({ conversation, prevConversation, endpointsFilter, targetEndpoint }) => {
if (targetEndpoint) {
// try to use current model
const endpoint = targetEndpoint;
if (endpointsFilter?.[endpoint]) {
conversation = buildDefaultConversation({
conversation,
endpoint,
lastConversationSetup: {}
});
return conversation;
} else {
console.log(endpoint);
console.warn(`Illegal target endpoint ${targetEndpoint} ${endpointsFilter}`);
}
}
try {
// try to use current model
const { endpoint = null } = prevConversation || {};
if (endpoint in availableEndpoints) {
if (endpointsFilter?.[endpoint]) {
conversation = buildDefaultConversation({
conversation,
endpoint,
@ -63,14 +79,15 @@ const getDefaultConversation = ({ conversation, prevConversation, availableEndpo
const lastConversationSetup = JSON.parse(localStorage.getItem('lastConversationSetup'));
const { endpoint = null } = lastConversationSetup;
if (endpoint in availableEndpoints) {
if (endpointsFilter?.[endpoint]) {
conversation = buildDefaultConversation({ conversation, endpoint, lastConversationSetup });
return conversation;
}
} catch (error) {}
// if anything happens, reset to default model
const endpoint = availableEndpoints?.[0];
const endpoint = ['openAI', 'azureOpenAI', 'bingAI', 'chatGPTBrowser'].find(e => endpointsFilter?.[e]);
if (endpoint) {
conversation = buildDefaultConversation({ conversation, endpoint });
return conversation;

View file

@ -42,7 +42,7 @@ const useMessageHandler = () => {
jailbreakConversationId: currentConversation?.jailbreakConversationId || null,
conversationSignature: currentConversation?.conversationSignature || null,
clientId: currentConversation?.clientId || null,
invocationId: currentConversation?.invocationId || null,
invocationId: currentConversation?.invocationId || 1,
toneStyle: currentConversation?.toneStyle || 'fast',
suggestions: currentConversation?.suggestions || []
};

7587
client/yarn.lock Normal file

File diff suppressed because it is too large Load diff