Merge branch 'main' into dano/react-query-typescript

This commit is contained in:
Daniel Avila 2023-04-07 22:11:15 -04:00
commit 285351bb53
13 changed files with 153 additions and 125 deletions

View file

@ -41,6 +41,11 @@ OPENAI_KEY=
# Leave it and BINGAI_USER_TOKEN blank to disable this endpoint.
BINGAI_TOKEN=
# BingAI Host:
# Necessary for some people in different countries, e.g. China (https://cn.bing.com)
# Leave it blank to use default server.
# BINGAI_HOST="https://cn.bing.com"
# BingAI User defined Token
# Allow user to set their own token by client
# Uncomment this to enable this feature.

View file

@ -27,6 +27,7 @@ const askBing = async ({
// cookies: '',
debug: false,
cache: store,
host: process.env.BINGAI_HOST || null,
proxy: process.env.PROXY || null
});

View file

@ -18,49 +18,38 @@ const proxyEnvToAxiosProxy = proxyString => {
const titleConvo = async ({ endpoint, text, response }) => {
let title = 'New Chat';
const messages = [
{
const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default;
try {
const instructionsPayload = {
role: 'system',
content:
// `You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user, using the same language. The requirement are: 1. If possible, generate in 5 words or less, 2. Using title case, 3. must give the title using the language as the user said. 4. Don't refer to the participants of the conversation. 5. Do not include punctuation or quotation marks. 6. Your response should be in title case, exclusively containing the title. 7. don't say anything except the title.
`Detect user language and write in the same language an extremely concise title for this conversation, which you must accurately detect. Write in the detected language. Title in 5 Words or Less. No Punctuation/Quotation. All first letters of every word should be capitalized and complete only the title in User Language only.
content: `Detect user language and write in the same language an extremely concise title for this conversation, which you must accurately detect. Write in the detected language. Title in 5 Words or Less. No Punctuation or Quotation. All first letters of every word should be capitalized and complete only the title in User Language only.
||>User:
"${text}"
||>Response:
"${JSON.stringify(response?.text)}"
||>User:
"${text}"
||>Response:
"${JSON.stringify(response?.text)}"
||>Title:`
}
// {
// role: 'user',
// content: `User:\n "${text}"\n\n${model}: \n"${JSON.stringify(response?.text)}"\n\n`
// }
];
||>Title:`
};
// console.log('Title Prompt', messages[0]);
const options = {
reverseProxyUrl: process.env.OPENAI_REVERSE_PROXY || null,
proxy: process.env.PROXY || null
};
const request = {
const titleGenClientOptions = JSON.parse(JSON.stringify(options));
titleGenClientOptions.modelOptions = {
model: 'gpt-3.5-turbo',
messages,
temperature: 0,
presence_penalty: 0,
frequency_penalty: 0
};
// console.log('REQUEST', request);
try {
const configuration = new Configuration({
apiKey: process.env.OPENAI_KEY
});
const openai = new OpenAIApi(configuration);
const completion = await openai.createChatCompletion(request, {
proxy: proxyEnvToAxiosProxy(process.env.PROXY || null)
});
//eslint-disable-next-line
title = completion.data.choices[0].message.content.replace(/["\.]/g, '');
const titleGenClient = new ChatGPTClient(process.env.OPENAI_KEY, titleGenClientOptions);
const result = await titleGenClient.getCompletion([instructionsPayload], null);
title = result.choices[0].message.content.replace(/\s+/g, ' ').trim();
} catch (e) {
console.error(e);
console.log('There was an issue generating title, see error above');

View file

@ -35,21 +35,21 @@ router.post('/', async (req, res) => {
let endpointOption = {};
if (req.body?.jailbreak)
endpointOption = {
jailbreak: req.body?.jailbreak || false,
jailbreakConversationId: req.body?.jailbreakConversationId || null,
systemMessage: req.body?.systemMessage || null,
context: req.body?.context || null,
toneStyle: req.body?.toneStyle || 'fast'
jailbreak: req.body?.jailbreak ?? false,
jailbreakConversationId: req.body?.jailbreakConversationId ?? null,
systemMessage: req.body?.systemMessage ?? null,
context: req.body?.context ?? null,
toneStyle: req.body?.toneStyle ?? 'fast'
};
else
endpointOption = {
jailbreak: req.body?.jailbreak || false,
systemMessage: req.body?.systemMessage || null,
context: req.body?.context || null,
conversationSignature: req.body?.conversationSignature || null,
clientId: req.body?.clientId || null,
invocationId: req.body?.invocationId || null,
toneStyle: req.body?.toneStyle || 'fast'
jailbreak: req.body?.jailbreak ?? false,
systemMessage: req.body?.systemMessage ?? null,
context: req.body?.context ?? null,
conversationSignature: req.body?.conversationSignature ?? null,
clientId: req.body?.clientId ?? null,
invocationId: req.body?.invocationId ?? null,
toneStyle: req.body?.toneStyle ?? 'fast'
};
console.log('ask log', {
@ -122,31 +122,23 @@ const ask = async ({
console.log('BING RESPONSE', response);
const newConversationId = endpointOption?.jailbreak
? response.jailbreakConversationId
: response.conversationId || conversationId;
const newUserMassageId = response.parentMessageId || response.details.requestId || userMessageId;
const newResponseMessageId = response.messageId || response.details.messageId;
// STEP1 generate response message
response.text = response.response || response.details.spokenText || '**Bing refused to answer.**';
let responseMessage = {
conversationId: newConversationId,
messageId: newResponseMessageId,
parentMessageId: overrideParentMessageId || newUserMassageId,
sender: endpointOption?.jailbreak ? 'Sydney' : 'BingAI',
text: await handleText(response, true),
suggestions:
response.details.suggestedResponses && response.details.suggestedResponses.map(s => s.text),
jailbreak: endpointOption?.jailbreak
suggestions: response.details.suggestedResponses && response.details.suggestedResponses.map(s => s.text)
};
// // response.text = await handleText(response, true);
// response.suggestions =
// response.details.suggestedResponses && response.details.suggestedResponses.map(s => s.text);
if (endpointOption?.jailbreak) {
responseMessage.conversationId = response.jailbreakConversationId;
responseMessage.messageId = response.messageId || response.details.messageId;
responseMessage.parentMessageId = overrideParentMessageId || response.parentMessageId || userMessageId;
responseMessage.sender = 'Sydney';
} else {
responseMessage.conversationId = response.conversationId;
responseMessage.messageId = response.messageId || response.details.messageId;
responseMessage.parentMessageId =
overrideParentMessageId || response.parentMessageId || response.details.requestId || userMessageId;
responseMessage.sender = 'BingAI';
}
await saveMessage(responseMessage);
@ -159,14 +151,22 @@ const ask = async ({
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
// but in this situation, don't change the conversationId, but create new convo.
let conversationUpdate = { conversationId, endpoint: 'bingAI' };
if (conversationId != responseMessage.conversationId && isNewConversation)
let conversationUpdate = { conversationId: newConversationId, endpoint: 'bingAI' };
if (conversationId != newConversationId)
if (isNewConversation) {
// change the conversationId to new one
conversationUpdate = {
...conversationUpdate,
conversationId: conversationId,
newConversationId: responseMessage.conversationId || conversationId
newConversationId: newConversationId
};
conversationId = responseMessage.conversationId || conversationId;
} else {
// create new conversation
conversationUpdate = {
...conversationUpdate,
...endpointOption
};
}
if (endpointOption?.jailbreak) {
conversationUpdate.jailbreak = true;
@ -179,17 +179,16 @@ const ask = async ({
}
await saveConvo(req?.session?.user?.username, conversationUpdate);
conversationId = newConversationId;
// STEP3 update the user message
userMessage.conversationId = conversationId;
userMessage.messageId = responseMessage.parentMessageId;
userMessage.conversationId = newConversationId;
userMessage.messageId = newUserMassageId;
// If response has parentMessageId, the fake userMessage.messageId should be updated to the real one.
if (!overrideParentMessageId) {
const oldUserMessageId = userMessageId;
await saveMessage({ ...userMessage, messageId: oldUserMessageId, newMessageId: userMessage.messageId });
}
userMessageId = userMessage.messageId;
if (!overrideParentMessageId)
await saveMessage({ ...userMessage, messageId: userMessageId, newMessageId: newUserMassageId });
userMessageId = newUserMassageId;
sendMessage(res, {
title: await getConvoTitle(req?.session?.user?.username, conversationId),

View file

@ -1,5 +1,5 @@
import React from 'react';
import EndpointItem from './EndpointItem';
import EndpointItem from './EndpointItem.jsx';
export default function EndpointItems({ endpoints, onSelect }) {
return (

View file

@ -1,7 +1,7 @@
import React from 'react';
import { DropdownMenuRadioItem } from '../../ui/DropdownMenu.tsx';
import EditIcon from '../../svg/EditIcon';
import TrashIcon from '../../svg/TrashIcon';
import EditIcon from '../../svg/EditIcon.jsx';
import TrashIcon from '../../svg/TrashIcon.jsx';
import getIcon from '~/utils/getIcon';
export default function PresetItem({ preset = {}, value, onSelect, onChangePreset, onDeletePreset }) {
@ -47,7 +47,7 @@ export default function PresetItem({ preset = {}, value, onSelect, onChangePrese
<small className="ml-2">({getPresetTitle()})</small>
<div className="flex w-4 flex-1" />
<button
className="invisible m-0 p-2 mr-1 rounded-md text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 "
className="invisible m-0 mr-1 rounded-md p-2 text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 "
onClick={e => {
e.preventDefault();
onChangePreset(preset);

View file

@ -1,5 +1,5 @@
import React from 'react';
import PresetItem from './PresetItem';
import PresetItem from './PresetItem.jsx';
export default function PresetItems({ presets, onSelect, onChangePreset, onDeletePreset }) {
return (

View file

@ -1,11 +1,12 @@
import { useState, useEffect } from 'react';
import React, { useState, useEffect } from 'react';
import { useRecoilValue, useRecoilState } from 'recoil';
import EditPresetDialog from '../../Endpoints/EditPresetDialog';
import EndpointItems from './EndpointItems';
import PresetItems from './PresetItems';
import FileUpload from './FileUpload';
import EditPresetDialog from '../../Endpoints/EditPresetDialog.jsx';
import EndpointItems from './EndpointItems.jsx';
import PresetItems from './PresetItems.jsx';
import FileUpload from './FileUpload.jsx';
import getIcon from '~/utils/getIcon';
import { useDeletePresetMutation, useCreatePresetMutation } from '~/data-provider';
import manualSWR, { handleFileSelected } from '~/utils/fetchers';
import { Button } from '../../ui/Button.tsx';
import {
DropdownMenu,
@ -16,34 +17,33 @@ import {
DropdownMenuTrigger
} from '../../ui/DropdownMenu.tsx';
import { Dialog, DialogTrigger } from '../../ui/Dialog.tsx';
import DialogTemplate from '../../ui/DialogTemplate';
import DialogTemplate from '../../ui/DialogTemplate.jsx';
import store from '~/store';
export default function NewConversationMenu() {
// const [modelSave, setModelSave] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const [presetModelVisible, setPresetModelVisible] = useState(false);
const [preset, setPreset] = useState(false);
// const models = useRecoilValue(store.models);
const availableEndpoints = useRecoilValue(store.availableEndpoints);
// const setCustomGPTModels = useSetRecoilState(store.customGPTModels);
const [presets, setPresets] = useRecoilState(store.presets);
const conversation = useRecoilValue(store.conversation) || {};
const { endpoint, conversationId } = conversation;
// const { model, promptPrefix, chatGptLabel, conversationId } = conversation;
const { newConversation } = store.useConversation();
const deletePresetsMutation = useDeletePresetMutation();
const createPresetMutation = useCreatePresetMutation();
const { trigger: clearPresetsTrigger } = manualSWR(`/api/presets/delete`, 'post', res => {
console.log(res);
setPresets(res.data);
});
const importPreset = jsonData => {
createPresetMutation.mutate({...jsonData}, {
onSuccess: (data) => {
setPresets(data);
},
onError: (error) => {
console.error('Error uploading the preset:', error);
}
})
handleFileSelected(jsonData).then(setPresets);
};
// update the default model when availableModels changes
@ -65,6 +65,7 @@ export default function NewConversationMenu() {
setMenuOpen(false);
if (!newEndpoint) return;
// else if (newEndpoint === endpoint) return;
else {
newConversation({}, { endpoint: newEndpoint });
}
@ -74,6 +75,7 @@ export default function NewConversationMenu() {
const onSelectPreset = newPreset => {
setMenuOpen(false);
if (!newPreset) return;
// else if (newEndpoint === endpoint) return;
else {
newConversation({}, newPreset);
}
@ -84,12 +86,8 @@ export default function NewConversationMenu() {
setPreset(preset);
};
const clearAllPresets = () => {
deletePresetsMutation.mutate({arg: {}});
};
const onDeletePreset = preset => {
deletePresetsMutation.mutate({arg: preset});
const clearPreset = () => {
clearPresetsTrigger({});
};
const icon = getIcon({
@ -101,7 +99,9 @@ export default function NewConversationMenu() {
});
return (
<Dialog>
<Dialog
// onOpenChange={onOpenChange}
>
<DropdownMenu
open={menuOpen}
onOpenChange={setMenuOpen}
@ -109,9 +109,13 @@ export default function NewConversationMenu() {
<DropdownMenuTrigger asChild>
<Button
variant="outline"
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`}
// style={{backgroundColor: 'rgb(16, 163, 127)'}}
className={`group relative mt-[-8px] mb-[-12px] ml-0 items-center rounded-md border-0 p-1 outline-none focus:ring-0 focus:ring-offset-0 dark:data-[state=open]:bg-opacity-50 md:left-1 md:ml-[-12px] md:pl-1`}
>
{icon}
<span className="max-w-0 overflow-hidden whitespace-nowrap px-0 text-slate-600 transition-all group-hover:max-w-[80px] group-hover:px-2 group-data-[state=open]:max-w-[80px] group-data-[state=open]:px-2 dark:text-slate-300">
New Topic
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
@ -146,6 +150,7 @@ export default function NewConversationMenu() {
<Button
type="button"
className="h-auto bg-transparent px-2 py-1 text-xs font-medium font-normal text-red-700 hover:bg-slate-200 hover:text-red-700 dark:bg-transparent dark:text-red-400 dark:hover:bg-gray-800 dark:hover:text-red-400"
// onClick={clearPreset}
>
Clear All
</Button>
@ -154,7 +159,7 @@ export default function NewConversationMenu() {
title="Clear presets"
description="Are you sure you want to clear all presets? This is irreversible."
selection={{
selectHandler: clearAllPresets,
selectHandler: clearPreset,
selectClasses: 'bg-red-600 hover:bg-red-700 dark:hover:bg-red-800 text-white',
selectText: 'Clear'
}}
@ -171,7 +176,7 @@ export default function NewConversationMenu() {
presets={presets}
onSelect={onSelectPreset}
onChangePreset={onChangePreset}
onDeletePreset={onDeletePreset}
onDeletePreset={clearPresetsTrigger}
/>
) : (
<DropdownMenuLabel className="dark:text-gray-300">No preset yet.</DropdownMenuLabel>

View file

@ -5,7 +5,7 @@ import OpenAIOptions from './OpenAIOptions';
import ChatGPTOptions from './ChatGPTOptions';
import BingAIOptions from './BingAIOptions';
// import BingStyles from './BingStyles';
import NewConversationMenu from './Endpoints/NewConversationMenu';
import NewConversationMenu from './NewConversationMenu';
import Footer from './Footer';
import TextareaAutosize from 'react-textarea-autosize';
import { useMessageHandler } from '../../utils/handleSubmit';
@ -139,7 +139,7 @@ export default function TextChat({ isSearchView = false }) {
<form className="stretch mx-2 flex flex-row gap-3 last:mb-2 md:pt-2 md:last:mb-6 lg:mx-auto lg:max-w-3xl lg:pt-6">
<div className="relative flex h-full flex-1 md:flex-col">
<div
className={`relative flex flex-grow flex-col rounded-md border border-black/10 ${
className={`relative flex flex-grow flex-row rounded-md border border-black/10 ${
disabled ? 'bg-gray-100' : 'bg-white'
} py-2 shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 ${
disabled ? 'dark:bg-gray-900' : 'dark:bg-gray-700'
@ -160,7 +160,7 @@ export default function TextChat({ isSearchView = false }) {
onCompositionEnd={handleCompositionEnd}
placeholder={getPlaceholderText()}
disabled={disabled || isNotAppendable}
className="m-0 h-auto max-h-52 resize-none overflow-auto border-0 bg-transparent p-0 pl-12 pr-8 leading-6 placeholder:text-sm placeholder:text-gray-600 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder:text-gray-500 md:pl-8"
className="m-0 flex h-auto max-h-52 flex-1 resize-none overflow-auto border-0 bg-transparent p-0 pl-2 pr-12 leading-6 placeholder:text-sm placeholder:text-gray-600 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder:text-gray-500 md:pl-2"
/>
<SubmitButton
submitMessage={submitMessage}

View file

@ -8,7 +8,7 @@
"ESNext"
],
"allowJs": true,
"skipLibCheck": true,
"skipLibCheck": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,

View file

@ -1,6 +1,7 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import type { Plugin } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
@ -19,11 +20,20 @@ export default defineConfig({
}
}
},
plugins: [react()],
plugins: [react(), sourcemapExclude({ excludeNodeModules: true }),],
publicDir: './public',
build: {
sourcemap: true,
outDir: './dist'
outDir: './dist',
rollupOptions: {
output: {
manualChunks: (id) => {
if (id.includes("node_modules")) {
return "vendor";
}
},
},
},
},
resolve: {
alias: {
@ -31,3 +41,22 @@ export default defineConfig({
}
}
});
interface SourcemapExclude {
excludeNodeModules?: boolean;
}
export function sourcemapExclude(opts?: SourcemapExclude): Plugin {
return {
name: "sourcemap-exclude",
transform(code: string, id: string) {
if (opts?.excludeNodeModules && id.includes("node_modules")) {
return {
code,
// https://github.com/rollup/rollup/blob/master/docs/plugin-development/index.md#source-code-transformations
map: { mappings: "" },
};
}
},
};
}