diff --git a/LOCAL_INSTALL.md b/LOCAL_INSTALL.md index 7f22283a8b..988fbd46d7 100644 --- a/LOCAL_INSTALL.md +++ b/LOCAL_INSTALL.md @@ -1,81 +1,82 @@ ### Local -- **Install the prerequisites** - - **Download chatgpt-clone** - - Download the latest release here: https://github.com/danny-avila/chatgpt-clone/releases/ - - Or by clicking on the green code button in the top of the page and selecting "Download ZIP" - - Or (Recommended if you have Git installed) pull the latest release from the main branch - - If you downloaded a zip file, extract the content in "C:/chatgpt-clone/" - -**IMPORTANT : If you install the files somewhere else modify the instructions accordingly** - - - **To enable the Conversation search feature:** - -IF YOU DON'T WANT THIS FEATURE YOU CAN SKIP THIS STEP - - Download MeileSearch latest release from : https://github.com/meilisearch/meilisearch/releases - - Copy it to "C:/chatgpt-clone/" - - Rename the file to "meilisearch.exe" - - Open it by double clicking on it - - Copy the generated Master Key and save it somewhere (You will need it later) - - **Download and Install Node.js** - - Navigate to https://nodejs.org/en/download and to download the latest Node.js version for your OS (The Node.js installer includes the NPM package manager.) - - - **Create a MongoDB database** - - Navigate to https://www.mongodb.com/ and Sign In or Create an account - - Create a new project - - Build a Database using the free plan and name the cluster (example: chatgpt-clone) - - Use the "Username and Password" method for authentication - - Add your current IP to the access list - - Then in the Database Deployment tab click on Connect - - In "Choose a connection method" select "Connect your application" - - Driver = Node.js / Version = 4.1 or later - - Copy the connection string and save it somewhere(you will need it later) - - - **Get your OpenAI API key** here: https://platform.openai.com/account/api-keys and save it somewhere safe (you will need it later) - - - **Get your Bing Access Token** - - Using MS Edge, navigate to bing.com - - Make sure you are logged in - - Open the DevTools by pressing F12 on your keyboard - - Click on the tab "Application" (On the left of the DevTools) - - Expand the "Cookies" (Under "Storage") - - You need to copy the value of the "_U" cookie, save it somewhere, you will need it later - +- **Install the prerequisites** + + - **Download chatgpt-clone** + - Download the latest release here: https://github.com/danny-avila/chatgpt-clone/releases/ + - Or by clicking on the green code button in the top of the page and selecting "Download ZIP" + - Or (Recommended if you have Git installed) pull the latest release from the main branch + - If you downloaded a zip file, extract the content in "C:/chatgpt-clone/" -**IMPORTANT : If you install the files somewhere else modify the instructions accordingly** + - **To enable the Conversation search feature:** + -IF YOU DON'T WANT THIS FEATURE YOU CAN SKIP THIS STEP + + - Download MeileSearch latest release from : https://github.com/meilisearch/meilisearch/releases + - Copy it to "C:/chatgpt-clone/" + - Rename the file to "meilisearch.exe" + - Open it by double clicking on it + - Copy the generated Master Key and save it somewhere (You will need it later) + + - **Download and Install Node.js** + - Navigate to https://nodejs.org/en/download and to download the latest Node.js version for your OS (The Node.js installer includes the NPM package manager.) + - **Create a MongoDB database** + - Navigate to https://www.mongodb.com/ and Sign In or Create an account + - Create a new project + - Build a Database using the free plan and name the cluster (example: chatgpt-clone) + - Use the "Username and Password" method for authentication + - Add your current IP to the access list + - Then in the Database Deployment tab click on Connect + - In "Choose a connection method" select "Connect your application" + - Driver = Node.js / Version = 4.1 or later + - Copy the connection string and save it somewhere(you will need it later) + - **Get your OpenAI API key** here: https://platform.openai.com/account/api-keys and save it somewhere safe (you will need it later) + + - **Get your Bing Access Token** + - Using MS Edge, navigate to bing.com + - Make sure you are logged in + - Open the DevTools by pressing F12 on your keyboard + - Click on the tab "Application" (On the left of the DevTools) + - Expand the "Cookies" (Under "Storage") + - You need to copy the value of the "\_U" cookie, save it somewhere, you will need it later + - **Create the ".env" File** You will need all your credentials, (API keys, access tokens, and Mongo Connection String, MeileSearch Master Key) - - Open "C:/chatgpt-clone/api/.env.example" in a text editor - - At this line **MONGO_URI="mongodb://127.0.0.1:27017/chatgpt-clone"** - Replace mongodb://127.0.0.1:27017/chatgpt-clone with the MondoDB connection string you saved earlier, **remove "&w=majority" at the end** - - It should look something like this: "MONGO_URI="mongodb+srv://username:password@chatgpt-clone.lfbcwz3.mongodb.net/?retryWrites=true" - - At this line **OPENAI_KEY=** you need to add your openai API key - - Add your Bing token to this line **BING_TOKEN=** (needed for BingChat & Sydney) - - If you want to enable Search, **SEARCH=TRUE** if you do not want to enable search **SEARCH=FALSE** - - Add your previously saved MeiliSearch Master key to this line **MEILI_MASTER_KEY=** (the key is needed if search is enabled even on local install or you may encounter errors) - - Save the file as **"C:/chatgpt-clone/api/.env"** + - Open "C:/chatgpt-clone/api/.env.example" in a text editor + - At this line **MONGO_URI="mongodb://127.0.0.1:27017/chatgpt-clone"** + Replace mongodb://127.0.0.1:27017/chatgpt-clone with the MondoDB connection string you saved earlier, **remove "&w=majority" at the end** + - It should look something like this: "MONGO_URI="mongodb+srv://username:password@chatgpt-clone.lfbcwz3.mongodb.net/?retryWrites=true" + - At this line **OPENAI_KEY=** you need to add your openai API key + - Add your Bing token to this line **BINGAI_TOKEN=** (needed for BingChat & Sydney) + - If you want to enable Search, **SEARCH=TRUE** if you do not want to enable search **SEARCH=FALSE** + - Add your previously saved MeiliSearch Master key to this line **MEILI_MASTER_KEY=** (the key is needed if search is enabled even on local install or you may encounter errors) + - Save the file as **"C:/chatgpt-clone/api/.env"** **DO THIS ONCE AFTER EVERY UPDATE** + - **Run** `npm ci` in the "C:/chatgpt-clone/api" directory - **Run** `npm ci` in the "C:/chatgpt-clone/client" directory - **Run** `npm run build` in the "C:/chatgpt-clone/client" **DO THIS EVERY TIME YOU WANT TO START CHATGPT-CLONE** + - **Run** `"meilisearch --master-key put_your_meilesearch_Master_Key_here"` in the "C:/chatgpt-clone" directory (Only if SEARCH=TRUE) -- **Run** `npm start` in the "C:/chatgpt-clone/api" directory +- **Run** `npm start` in the "C:/chatgpt-clone/api" directory - **Visit** http://localhost:3080 (default port) & enjoy - OPTIONAL BUT RECOMMENDED + - **Make a batch file to automate the starting process** - - Open a text editor - - Paste the following code in a new document - - Put your MeiliSearch master key instead of "your_master_key_goes_here" - - Save the file as "C:/chatgpt-clone/chatgpt-clone.bat" - - you can make a shortcut of this batch file and put it anywhere + - Open a text editor + - Paste the following code in a new document + - Put your MeiliSearch master key instead of "your_master_key_goes_here" + - Save the file as "C:/chatgpt-clone/chatgpt-clone.bat" + - you can make a shortcut of this batch file and put it anywhere ``` REM the meilisearch executable needs to be at the root of the chatgpt-clone directory start "MeiliSearch" cmd /k "meilisearch --master-key your_master_key_goes_here -REM ↑↑↑ meilisearch is the name of the meilisearch executable, put your own master key there +REM ↑↑↑ meilisearch is the name of the meilisearch executable, put your own master key there start "ChatGPT-Clone" cmd /k "cd api && npm start" diff --git a/api/.env.example b/api/.env.example index 30f097f759..c952ebe491 100644 --- a/api/.env.example +++ b/api/.env.example @@ -15,34 +15,68 @@ NODE_ENV=development # Change this to your MongoDB URI if different and I recommend appending chatgpt-clone MONGO_URI="mongodb://127.0.0.1:27017/chatgpt-clone" -# API key configuration. -# Leave blank if you don't want them. + +############################# +# Endpoint OpenAI: +############################# + +# Access key from OpenAI platform +# Leave it blank to disable this endpoint OPENAI_KEY= -# Default ChatGPT API Model, options: 'gpt-4', 'text-davinci-003', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0301' -# you will have errors if you don't have access to a model like 'gpt-4', defaults to turbo if left empty/excluded. -DEFAULT_API_GPT=gpt-3.5-turbo +# Identify the available models, sperate by comma, and not space in it +# Leave it blank to use internal settings. +# OPENAI_MODELS=gpt-4,text-davinci-003,gpt-3.5-turbo,gpt-3.5-turbo-0301 -# _U Cookies Value from bing.com -BING_TOKEN= +# Reverse proxy setting for OpenAI +# https://github.com/waylaidwanderer/node-chatgpt-api#using-a-reverse-proxy +# OPENAI_REVERSE_PROXY= + + +############################# +# Endpoint BingAI (Also jailbreak Sydney): +############################# + +# BingAI Tokens: the "_U" cookies value from bing.com +# Leave it and BINGAI_USER_TOKEN blank to disable this endpoint. +BINGAI_TOKEN= + +# BingAI User defined Token +# Allow user to set their own token by client +# Uncomment this to enable this feature. +# (Not implemented yet.) +# BINGAI_USER_TOKEN=1 + + +############################# +# Endpoint chatGPT: +############################# # ChatGPT Browser Client (free but use at your own risk) # Access token from https://chat.openai.com/api/auth/session -# Exposes your access token to a 3rd party +# Exposes your access token to CHATGPT_REVERSE_PROXY +# Leave it blank to disable this endpoint CHATGPT_TOKEN= -# If you have access to other models on the official site, you can use them here. -# Defaults to 'text-davinci-002-render-sha' if left empty. -# options: gpt-4, text-davinci-002-render, text-davinci-002-render-paid, or text-davinci-002-render-sha -# You cannot use a model that your account does not have access to. You can check -# which ones you have access to by opening DevTools and going to the Network tab. -# Refresh the page and look at the response body for https://chat.openai.com/backend-api/models. -BROWSER_MODEL= + +# Identify the available models, sperate by comma, and not space in it +# Leave it blank to use internal settings. +# CHATGPT_MODELS=text-davinci-002-render-sha,text-davinci-002-render-paid,gpt-4 + +# Reverse proxy setting for OpenAI +# https://github.com/waylaidwanderer/node-chatgpt-api#using-a-reverse-proxy +# By default it will use the node-chatgpt-api recommended proxy, (it's a third party server) +# CHATGPT_REVERSE_PROXY= + + +############################# +# Search: +############################# # ENABLING SEARCH MESSAGES/CONVOS # Requires installation of free self-hosted Meilisearch or Paid Remote Plan (Remote not tested) # The easiest setup for this is through docker-compose, which takes care of it for you. -# SEARCH=TRUE -SEARCH=TRUE +# SEARCH=1 +SEARCH=1 # REQUIRED FOR SEARCH: MeiliSearch Host, mainly for api server to connect to the search server. # must replace '0.0.0.0' with 'meilisearch' if serving meilisearch with docker-compose @@ -63,8 +97,11 @@ MEILI_HTTP_ADDR='0.0.0.0:7700' # <-- local/remote MEILI_MASTER_KEY=JKMW-hGc7v_D1FkJVdbRSDNFLZcUv3S75yrxXP0SmcU # <-- ready made secure key for docker-compose +############################# # User System -# global enable/disable the sample user system. +############################# + +# Enable the user system. # this is not a ready to use user system. # dont't use it, unless you can write your own code. # ENABLE_USER_SYSTEM= # <-- make sure you don't comment this back in if you're not using your own user system diff --git a/api/app/clients/bingai.js b/api/app/clients/bingai.js index 916485e435..015da79069 100644 --- a/api/app/clients/bingai.js +++ b/api/app/clients/bingai.js @@ -22,7 +22,7 @@ const askBing = async ({ const bingAIClient = new BingAIClient({ // "_U" cookie from bing.com - userToken: process.env.BING_TOKEN, + userToken: process.env.BINGAI_TOKEN, // If the above doesn't work, provide all your cookies as a string instead // cookies: '', debug: false, diff --git a/api/app/clients/chatgpt-browser.js b/api/app/clients/chatgpt-browser.js index f58f4f7227..4eb50a1476 100644 --- a/api/app/clients/chatgpt-browser.js +++ b/api/app/clients/chatgpt-browser.js @@ -1,12 +1,6 @@ require('dotenv').config(); const { KeyvFile } = require('keyv-file'); -const modelMap = new Map([ - ['Default (GPT-3.5)', 'text-davinci-002-render-sha'], - ['Legacy (GPT-3.5)', 'text-davinci-002-render-paid'], - ['GPT-4', 'gpt-4'] -]); - const browserClient = async ({ text, parentMessageId, @@ -21,11 +15,11 @@ const browserClient = async ({ }; const clientOptions = { - // Warning: This will expose your access token to a third party. Consider the risks before using this. + // Warning: This will expose your access token to a third party. Consider the risks before using this. reverseProxyUrl: process.env.CHATGPT_REVERSE_PROXY || 'https://bypass.churchless.tech/api/conversation', // Access token from https://chat.openai.com/api/auth/session accessToken: process.env.CHATGPT_TOKEN, - model: modelMap.get(model), + model: model, // debug: true proxy: process.env.PROXY || null }; diff --git a/api/app/clients/chatgpt-client.js b/api/app/clients/chatgpt-client.js index 1cf64656b0..5a591c62ae 100644 --- a/api/app/clients/chatgpt-client.js +++ b/api/app/clients/chatgpt-client.js @@ -22,6 +22,9 @@ const askClient = async ({ }; const clientOptions = { + // Warning: This will expose your access token to a third party. Consider the risks before using this. + reverseProxyUrl: process.env.OPENAI_REVERSE_PROXY || null, + modelOptions: { model: model, temperature, @@ -29,6 +32,7 @@ const askClient = async ({ presence_penalty, frequency_penalty }, + chatGptLabel, promptPrefix, proxy: process.env.PROXY || null, diff --git a/api/app/clients/sydney.js b/api/app/clients/sydney.js deleted file mode 100644 index beaa6f4029..0000000000 --- a/api/app/clients/sydney.js +++ /dev/null @@ -1,40 +0,0 @@ -require('dotenv').config(); -const { KeyvFile } = require('keyv-file'); - -const askSydney = async ({ text, onProgress, convo }) => { - const { BingAIClient } = (await import('@waylaidwanderer/chatgpt-api')); - - const sydneyClient = new BingAIClient({ - // "_U" cookie from bing.com - userToken: process.env.BING_TOKEN, - // If the above doesn't work, provide all your cookies as a string instead - // cookies: '', - debug: false, - cache: { store: new KeyvFile({ filename: './data/cache.json' }) } - }); - - let options = { - jailbreakConversationId: true, - onProgress, - }; - - if (convo.jailbreakConversationId) { - options = { ...options, jailbreakConversationId: convo.jailbreakConversationId, parentMessageId: convo.parentMessageId }; - } - - if (convo.toneStyle) { - options.toneStyle = convo.toneStyle; - } - - console.log('sydney options', options); - - const res = await sydneyClient.sendMessage(text, options - ); - - return res; - - // for reference: - // https://github.com/waylaidwanderer/node-chatgpt-api/blob/main/demos/use-bing-client.js -}; - -module.exports = { askSydney }; diff --git a/api/app/index.js b/api/app/index.js index c875a0d2e8..c03b854e6d 100644 --- a/api/app/index.js +++ b/api/app/index.js @@ -1,7 +1,6 @@ const { askClient } = require('./clients/chatgpt-client'); const { browserClient } = require('./clients/chatgpt-browser'); const { askBing } = require('./clients/bingai'); -const { askSydney } = require('./clients/sydney'); const titleConvo = require('./titleConvo'); const getCitations = require('../lib/parse/getCitations'); const citeText = require('../lib/parse/citeText'); @@ -10,7 +9,6 @@ module.exports = { askClient, browserClient, askBing, - askSydney, titleConvo, getCitations, citeText diff --git a/api/server/routes/ask/askChatGPTBrowser.js b/api/server/routes/ask/askChatGPTBrowser.js index 621f5c67bd..c3f3a0d735 100644 --- a/api/server/routes/ask/askChatGPTBrowser.js +++ b/api/server/routes/ask/askChatGPTBrowser.js @@ -1,6 +1,7 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); +const { getChatGPTBrowserModels } = require('../endpoints'); const { titleConvo, browserClient } = require('../../../app/'); const { saveMessage, getConvoTitle, saveConvo, updateConvo, getConvo } = require('../../../models'); const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); @@ -18,6 +19,7 @@ router.post('/', async (req, res) => { // build user message const conversationId = oldConversationId || crypto.randomUUID(); + const isNewConversation = !oldConversationId; const userMessageId = crypto.randomUUID(); const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000'; const userMessage = { @@ -34,6 +36,10 @@ router.post('/', async (req, res) => { model: req.body?.model || 'text-davinci-002-render-sha' }; + const availableModels = getChatGPTBrowserModels(); + if (availableModels.find(model => model === endpointOption.model) === undefined) + return handleError(res, { text: 'Illegal request: model' }); + console.log('ask log', { userMessage, endpointOption, @@ -52,6 +58,7 @@ router.post('/', async (req, res) => { // eslint-disable-next-line no-use-before-define return await ask({ + isNewConversation, userMessage, endpointOption, conversationId, @@ -63,6 +70,7 @@ router.post('/', async (req, res) => { }); const ask = async ({ + isNewConversation, userMessage, endpointOption, conversationId, @@ -71,9 +79,7 @@ const ask = async ({ req, res }) => { - const { text, parentMessageId: userParentMessageId, messageId: userMessageId } = userMessage; - - const client = browserClient; + let { text, parentMessageId: userParentMessageId, messageId: userMessageId } = userMessage; res.writeHead(200, { Connection: 'keep-alive', @@ -89,7 +95,7 @@ const ask = async ({ const progressCallback = createOnProgress(); const abortController = new AbortController(); res.on('close', () => abortController.abort()); - let gptResponse = await client({ + let response = await browserClient({ text, parentMessageId: userParentMessageId, conversationId, @@ -98,50 +104,60 @@ const ask = async ({ abortController }); - gptResponse.text = gptResponse.response; - console.log('CLIENT RESPONSE', gptResponse); + console.log('CLIENT RESPONSE', response); - if (!gptResponse.parentMessageId) { - gptResponse.parentMessageId = overrideParentMessageId || userMessageId; - delete gptResponse.response; + // STEP1 generate response message + response.text = response.response || '**ChatGPT refused to answer.**'; + + let responseMessage = { + conversationId: response.conversationId, + messageId: response.messageId, + parentMessageId: overrideParentMessageId || response.parentMessageId || userMessageId, + text: await handleText(response), + sender: endpointOption?.chatGptLabel || 'ChatGPT' + }; + + await saveMessage(responseMessage); + + // STEP2 update the conversation + conversationId = responseMessage.conversationId || conversationId; + + // First update conversationId if needed + let conversationUpdate = { conversationId, endpoint: 'chatGPTBrowser' }; + if (conversationId != responseMessage.conversationId && isNewConversation) + conversationUpdate = { + ...conversationUpdate, + conversationId: conversationId, + newConversationId: responseMessage.conversationId || conversationId + }; + conversationId = responseMessage.conversationId || conversationId; + + await saveConvo(req?.session?.user?.username, conversationUpdate); + + // STEP3 update the user message + userMessage.conversationId = conversationId; + userMessage.messageId = responseMessage.parentMessageId; + + // 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 }); } - - gptResponse.sender = 'ChatGPT'; - // gptResponse.model = model; - gptResponse.text = await handleText(gptResponse); - // if (convo.chatGptLabel?.length > 0 && model === 'chatgptCustom') { - // gptResponse.chatGptLabel = convo.chatGptLabel; - // } - - // if (convo.promptPrefix?.length > 0 && model === 'chatgptCustom') { - // gptResponse.promptPrefix = convo.promptPrefix; - // } - - gptResponse.parentMessageId = overrideParentMessageId || userMessageId; - - if (userParentMessageId.startsWith('000')) { - await saveMessage({ ...userMessage, conversationId: gptResponse.conversationId }); - } - - await saveMessage(gptResponse); - await updateConvo(req?.session?.user?.username, { - ...gptResponse, - oldConvoId: conversationId - }); + userMessageId = userMessage.messageId; sendMessage(res, { title: await getConvoTitle(req?.session?.user?.username, conversationId), final: true, conversation: await getConvo(req?.session?.user?.username, conversationId), requestMessage: userMessage, - responseMessage: gptResponse + responseMessage: responseMessage }); res.end(); if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { - const title = await titleConvo({ endpoint: endpointOption?.endpoint, text, response: gptResponse }); + const title = await titleConvo({ endpoint: endpointOption?.endpoint, text, response: responseMessage }); await updateConvo(req?.session?.user?.username, { - conversationId: gptResponse.conversationId, + conversationId: conversationId, title }); } diff --git a/api/server/routes/ask/askOpenAI.js b/api/server/routes/ask/askOpenAI.js index f58d529bdd..e44a059090 100644 --- a/api/server/routes/ask/askOpenAI.js +++ b/api/server/routes/ask/askOpenAI.js @@ -1,6 +1,7 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); +const { getOpenAIModels } = require('../endpoints'); const { titleConvo, askClient } = require('../../../app/'); const { saveMessage, getConvoTitle, saveConvo, updateConvo, getConvo } = require('../../../models'); const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); @@ -40,6 +41,10 @@ router.post('/', async (req, res) => { frequency_penalty: req.body?.frequency_penalty || 0 }; + const availableModels = getOpenAIModels(); + if (availableModels.find(model => model === endpointOption.model) === undefined) + return handleError(res, { text: 'Illegal request: model' }); + console.log('ask log', { userMessage, endpointOption, @@ -150,7 +155,7 @@ const ask = async ({ res.end(); if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { - const title = await titleConvo({ endpoint: endpointOption?.endpoint, text, response }); + const title = await titleConvo({ endpoint: endpointOption?.endpoint, text, response: responseMessage }); await updateConvo(req?.session?.user?.username, { conversationId: conversationId, title diff --git a/api/server/routes/endpoints.js b/api/server/routes/endpoints.js index b034d2def5..bcdd051e2f 100644 --- a/api/server/routes/endpoints.js +++ b/api/server/routes/endpoints.js @@ -1,17 +1,27 @@ const express = require('express'); const router = express.Router(); +const getOpenAIModels = () => { + let models = ['gpt-4', 'text-davinci-003', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0301']; + if (process.env.OPENAI_MODELS) models = String(process.env.OPENAI_MODELS).split(','); + + return models; +}; + +const getChatGPTBrowserModels = () => { + let models = ['text-davinci-002-render-sha', 'text-davinci-002-render-paid', 'gpt-4']; + if (process.env.CHATGPT_MODELS) models = String(process.env.CHATGPT_MODELS).split(','); + + return models; +}; + router.get('/', function (req, res) { const azureOpenAI = !!process.env.AZURE_OPENAI_KEY; - const openAI = process.env.OPENAI_KEY - ? { availableModels: ['gpt-4', 'text-davinci-003', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0301'] } - : false; - const bingAI = !!process.env.BING_TOKEN; - const chatGPTBrowser = process.env.OPENAI_KEY - ? { availableModels: ['Default (GPT-3.5)', 'Legacy (GPT-3.5)', 'GPT-4'] } - : false; + const openAI = process.env.OPENAI_KEY ? { availableModels: getOpenAIModels() } : false; + const bingAI = !!process.env.BINGAI_TOKEN; + const chatGPTBrowser = process.env.CHATGPT_TOKEN ? { availableModels: getChatGPTBrowserModels() } : false; res.send(JSON.stringify({ azureOpenAI, openAI, bingAI, chatGPTBrowser })); }); -module.exports = router; +module.exports = { router, getOpenAIModels, getChatGPTBrowserModels }; diff --git a/api/server/routes/index.js b/api/server/routes/index.js index 3be55cd4ad..5399cfdaa2 100644 --- a/api/server/routes/index.js +++ b/api/server/routes/index.js @@ -6,7 +6,7 @@ const prompts = require('./prompts'); const search = require('./search'); const tokenizer = require('./tokenizer'); const me = require('./me'); -const endpoints = require('./endpoints'); +const { router: endpoints } = require('./endpoints'); const { router: auth, authenticatedOr401, authenticatedOrRedirect } = require('./auth'); module.exports = { diff --git a/client/src/components/Endpoints/EditPresetDialog.jsx b/client/src/components/Endpoints/EditPresetDialog.jsx index 698da84b9d..8457f27f57 100644 --- a/client/src/components/Endpoints/EditPresetDialog.jsx +++ b/client/src/components/Endpoints/EditPresetDialog.jsx @@ -20,13 +20,18 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => { const setPresets = useSetRecoilState(store.presets); const availableEndpoints = useRecoilValue(store.availableEndpoints); + const endpointsFilter = useRecoilValue(store.endpointsFilter); + const setOption = param => newValue => { let update = {}; update[param] = newValue; setPreset(prevState => cleanupPreset({ - ...prevState, - ...update + preset: { + ...prevState, + ...update + }, + endpointsFilter }) ); }; @@ -38,7 +43,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => { axios({ method: 'post', url: '/api/presets', - data: cleanupPreset(preset), + data: cleanupPreset({ preset, endpointsFilter }), withCredentials: true }).then(res => { setPresets(res?.data); @@ -47,7 +52,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => { const exportPreset = () => { exportFromJSON({ - data: cleanupPreset(preset), + data: cleanupPreset({ preset, endpointsFilter }), fileName: `${preset?.title}.json`, exportType: exportFromJSON.types.json }); diff --git a/client/src/components/Endpoints/EndpointOptionsDialog.jsx b/client/src/components/Endpoints/EndpointOptionsDialog.jsx index be4deb80cd..dc62499584 100644 --- a/client/src/components/Endpoints/EndpointOptionsDialog.jsx +++ b/client/src/components/Endpoints/EndpointOptionsDialog.jsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { useRecoilValue } from 'recoil'; import exportFromJSON from 'export-from-json'; import DialogTemplate from '../ui/DialogTemplate.jsx'; import { Dialog, DialogButton } from '../ui/Dialog.tsx'; @@ -7,12 +8,15 @@ import cleanupPreset from '~/utils/cleanupPreset'; import Settings from './Settings'; +import store from '~/store'; + // A preset dialog to show readonly preset values. const EndpointOptionsDialog = ({ open, onOpenChange, preset: _preset, title }) => { // const [title, setTitle] = useState('My Preset'); const [preset, setPreset] = useState(_preset); const [saveAsDialogShow, setSaveAsDialogShow] = useState(false); + const endpointsFilter = useRecoilValue(store.endpointsFilter); const setOption = param => newValue => { let update = {}; @@ -29,7 +33,7 @@ const EndpointOptionsDialog = ({ open, onOpenChange, preset: _preset, title }) = const exportPreset = () => { exportFromJSON({ - data: cleanupPreset(preset), + data: cleanupPreset({ preset, endpointsFilter }), fileName: `${preset?.title}.json`, exportType: exportFromJSON.types.json }); diff --git a/client/src/components/Endpoints/SaveAsPresetDialog.jsx b/client/src/components/Endpoints/SaveAsPresetDialog.jsx index b2e8a193d7..e06e9ad807 100644 --- a/client/src/components/Endpoints/SaveAsPresetDialog.jsx +++ b/client/src/components/Endpoints/SaveAsPresetDialog.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { useSetRecoilState } from 'recoil'; +import { useSetRecoilState, useRecoilValue } from 'recoil'; import axios from 'axios'; import DialogTemplate from '../ui/DialogTemplate'; import { Dialog } from '../ui/Dialog.tsx'; @@ -13,14 +13,18 @@ import store from '~/store'; const SaveAsPresetDialog = ({ open, onOpenChange, preset }) => { const [title, setTitle] = useState(preset?.title || 'My Preset'); const setPresets = useSetRecoilState(store.presets); + const endpointsFilter = useRecoilValue(store.endpointsFilter); 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'; const submitPreset = () => { const _preset = cleanupPreset({ - ...preset, - title + preset: { + ...preset, + title + }, + endpointsFilter }); axios({ diff --git a/client/src/components/Input/ChatGPTOptions/index.jsx b/client/src/components/Input/ChatGPTOptions/index.jsx index fd5a3033ba..88e47250ac 100644 --- a/client/src/components/Input/ChatGPTOptions/index.jsx +++ b/client/src/components/Input/ChatGPTOptions/index.jsx @@ -21,6 +21,12 @@ function ChatGPTOptions() { const models = endpointsConfig?.['chatGPTBrowser']?.['availableModels'] || []; + // const modelMap = new Map([ + // ['Default (GPT-3.5)', 'text-davinci-002-render-sha'], + // ['Legacy (GPT-3.5)', 'text-davinci-002-render-paid'], + // ['GPT-4', 'gpt-4'] + // ]); + const setOption = param => newValue => { let update = {}; update[param] = newValue; @@ -43,7 +49,7 @@ function ChatGPTOptions() { showLabel={false} className={cn( cardStyle, - 'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600' + 'z-50 flex h-[40px] w-[260px] min-w-[260px] flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600' )} /> diff --git a/client/src/components/Input/Endpoints/FileUpload.jsx b/client/src/components/Input/Endpoints/FileUpload.jsx index cf99afed18..7210a2d3f5 100644 --- a/client/src/components/Input/Endpoints/FileUpload.jsx +++ b/client/src/components/Input/Endpoints/FileUpload.jsx @@ -1,9 +1,9 @@ import React from 'react'; -import { useSetRecoilState } from 'recoil'; import { FileUp } from 'lucide-react'; -import store from '~/store'; -import axios from 'axios'; import cleanupPreset from '~/utils/cleanupPreset.js'; +import { useRecoilValue } from 'recoil'; + +import store from '~/store'; // async function fetchPresets(callback) { // try { @@ -21,6 +21,7 @@ import cleanupPreset from '~/utils/cleanupPreset.js'; const FileUpload = ({ onFileSelected }) => { // const setPresets = useSetRecoilState(store.presets); + const endpointsFilter = useRecoilValue(store.endpointsFilter); const handleFileChange = event => { const file = event.target.files[0]; @@ -29,7 +30,7 @@ const FileUpload = ({ onFileSelected }) => { const reader = new FileReader(); reader.onload = e => { const jsonData = JSON.parse(e.target.result); - onFileSelected({ ...cleanupPreset(jsonData), presetId: null }); + onFileSelected({ ...cleanupPreset({ preset: jsonData, endpointsFilter }), presetId: null }); }; reader.readAsText(file); }; diff --git a/client/src/utils/cleanupPreset.js b/client/src/utils/cleanupPreset.js index 726068ea35..e80076ed16 100644 --- a/client/src/utils/cleanupPreset.js +++ b/client/src/utils/cleanupPreset.js @@ -1,4 +1,4 @@ -const cleanupPreset = _preset => { +const cleanupPreset = ({ preset: _preset, endpointsFilter = {} }) => { const { endpoint } = _preset; let preset = {}; @@ -6,7 +6,7 @@ const cleanupPreset = _preset => { preset = { endpoint, presetId: _preset?.presetId ?? null, - model: _preset?.model ?? 'gpt-3.5-turbo', + model: _preset?.model ?? endpointsFilter[endpoint]?.availableModels?.[0] ?? 'gpt-3.5-turbo', chatGptLabel: _preset?.chatGptLabel ?? null, promptPrefix: _preset?.promptPrefix ?? null, temperature: _preset?.temperature ?? 1, @@ -25,11 +25,12 @@ const cleanupPreset = _preset => { toneStyle: _preset?.toneStyle ?? 'fast', title: _preset?.title ?? 'New Preset' }; - } else if (endpoint === 'chatGPTBrowser') { + } else if (endpoint === 'chatGPT') { preset = { endpoint, presetId: _preset?.presetId ?? null, - model: _preset?.model ?? 'Default (GPT-3.5)', + model: + _preset?.model ?? endpointsFilter[endpoint]?.availableModels?.[0] ?? 'text-davinci-002-render-sha', title: _preset?.title ?? 'New Preset' }; } else if (endpoint === null) { diff --git a/client/src/utils/getDefaultConversation.js b/client/src/utils/getDefaultConversation.js index cb6f4a04ee..98a920f94b 100644 --- a/client/src/utils/getDefaultConversation.js +++ b/client/src/utils/getDefaultConversation.js @@ -1,9 +1,15 @@ -const buildDefaultConversation = ({ conversation, endpoint, lastConversationSetup = {} }) => { +const buildDefaultConversation = ({ + conversation, + endpoint, + endpointsFilter = {}, + lastConversationSetup = {} +}) => { if (endpoint === 'azureOpenAI' || endpoint === 'openAI') { conversation = { ...conversation, endpoint, - model: lastConversationSetup?.model ?? 'gpt-3.5-turbo', + model: + lastConversationSetup?.model ?? endpointsFilter[endpoint]?.availableModels?.[0] ?? 'gpt-3.5-turbo', chatGptLabel: lastConversationSetup?.chatGptLabel ?? null, promptPrefix: lastConversationSetup?.promptPrefix ?? null, temperature: lastConversationSetup?.temperature ?? 1, @@ -28,7 +34,10 @@ const buildDefaultConversation = ({ conversation, endpoint, lastConversationSetu conversation = { ...conversation, endpoint, - model: lastConversationSetup?.model ?? 'Default (GPT-3.5)' + model: + lastConversationSetup?.model ?? + endpointsFilter[endpoint]?.availableModels?.[0] ?? + 'text-davinci-002-render-sha' }; } else if (endpoint === null) { conversation = { @@ -56,7 +65,8 @@ const getDefaultConversation = ({ conversation, prevConversation, endpointsFilte conversation = buildDefaultConversation({ conversation, endpoint, - lastConversationSetup: preset + lastConversationSetup: preset, + endpointsFilter }); return conversation; } else { @@ -72,7 +82,8 @@ const getDefaultConversation = ({ conversation, prevConversation, endpointsFilte // conversation = buildDefaultConversation({ // conversation, // endpoint, - // lastConversationSetup: prevConversation + // lastConversationSetup: prevConversation, + // endpointsFilter // }); // return conversation; // } @@ -84,7 +95,7 @@ const getDefaultConversation = ({ conversation, prevConversation, endpointsFilte const { endpoint = null } = lastConversationSetup; if (endpointsFilter?.[endpoint]) { - conversation = buildDefaultConversation({ conversation, endpoint }); + conversation = buildDefaultConversation({ conversation, endpoint, endpointsFilter }); return conversation; } } catch (error) {} @@ -93,10 +104,10 @@ const getDefaultConversation = ({ conversation, prevConversation, endpointsFilte const endpoint = ['openAI', 'azureOpenAI', 'bingAI', 'chatGPTBrowser'].find(e => endpointsFilter?.[e]); if (endpoint) { - conversation = buildDefaultConversation({ conversation, endpoint }); + conversation = buildDefaultConversation({ conversation, endpoint, endpointsFilter }); return conversation; } else { - conversation = buildDefaultConversation({ conversation, endpoint: null }); + conversation = buildDefaultConversation({ conversation, endpoint: null, endpointsFilter }); return conversation; } }; diff --git a/client/src/utils/handleSubmit.js b/client/src/utils/handleSubmit.js index f7ac799a74..3c0db371ef 100644 --- a/client/src/utils/handleSubmit.js +++ b/client/src/utils/handleSubmit.js @@ -7,6 +7,7 @@ const useMessageHandler = () => { const currentConversation = useRecoilValue(store.conversation) || {}; const setSubmission = useSetRecoilState(store.submission); const isSubmitting = useRecoilValue(store.isSubmitting); + const endpointsFilter = useRecoilValue(store.endpointsFilter); const latestMessage = useRecoilValue(store.latestMessage); @@ -27,7 +28,8 @@ const useMessageHandler = () => { if (endpoint === 'azureOpenAI' || endpoint === 'openAI') { endpointOption = { endpoint, - model: currentConversation?.model ?? 'gpt-3.5-turbo', + model: + currentConversation?.model ?? endpointsFilter[endpoint]?.availableModels?.[0] ?? 'gpt-3.5-turbo', chatGptLabel: currentConversation?.chatGptLabel ?? null, promptPrefix: currentConversation?.promptPrefix ?? null, temperature: currentConversation?.temperature ?? 1, @@ -52,7 +54,10 @@ const useMessageHandler = () => { } else if (endpoint === 'chatGPTBrowser') { endpointOption = { endpoint, - model: currentConversation?.model ?? 'Default (GPT-3.5)' + model: + currentConversation?.model ?? + endpointsFilter[endpoint]?.availableModels?.[0] ?? + 'text-davinci-002-render-sha' }; responseSender = 'ChatGPT'; } else if (endpoint === null) {