feat: update env example.

feat: support OPENAI_REVERSE_PROXY
feat: support set availModels in env file

fix: chatgpt Browser send logic refactor.

fix: title wrong usage of responseMessage
BREAKING: some env paramaters has been changed!
This commit is contained in:
Wentao Lyu 2023-04-05 21:21:02 +08:00
parent a5202f84cc
commit 22b9524ad3
19 changed files with 259 additions and 197 deletions

View file

@ -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=<YOUR 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=<YOUR 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

View file

@ -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,

View file

@ -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
};

View file

@ -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,

View file

@ -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 };

View file

@ -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

View file

@ -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
});
}

View file

@ -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

View file

@ -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 };

View file

@ -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 = {