diff --git a/README.md b/README.md index d0ca06a9ca..754dc0b844 100644 --- a/README.md +++ b/README.md @@ -55,21 +55,27 @@ Currently, this project is only functional with the `text-davinci-003` model. # Table of Contents - * [Roadmap](#roadmap) - * [Features](#features) - * [Tech Stack](#tech-stack) - * [Getting Started](#getting-started) - * [Prerequisites](#prerequisites) - * [Usage](#usage) - * [Local (npm)](#npm) - * [Docker](#docker) - * [Access Tokens](#access-tokens) - * [Updating](#updating) - * [Use Cases](#use-cases) - * [Origin](#origin) - * [Caveats](#caveats) - * [Contributing](#contributing) - * [License](#license) +- [ChatGPT Clone](#chatgpt-clone) + - [All AI Conversations under One Roof.](#all-ai-conversations-under-one-roof) + - [Updates](#updates) +- [Table of Contents](#table-of-contents) + - [Roadmap](#roadmap) + - [Features](#features) + - [Tech Stack](#tech-stack) + - [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Usage](#usage) + - [Local](#local) + - [Docker](#docker) + - [Access Tokens](#access-tokens) + - [Proxy](#proxy) + - [Updating](#updating) + - [Use Cases](#use-cases) + - [Origin](#origin) + - [Caveats](#caveats) + - [Regarding use of Official ChatGPT API](#regarding-use-of-official-chatgpt-api) + - [Contributing](#contributing) + - [License](#license) ## Roadmap @@ -143,6 +149,8 @@ Here are my recently completed and planned features: - **Run** `npm run build` in /client/ dir, `npm start` in /api/ dir - **Visit** http://localhost:3080 (default port) & enjoy +By default, only local machine can access this server. To share within network or serve as a public server, set `HOST` to `0.0.0.0` in `.env` file + ### Docker - **Provide** all credentials, (API keys, access tokens, and Mongo Connection String) in [docker-compose.yml](docker-compose.yml) under api service @@ -180,6 +188,42 @@ The Bing Access Token is the "_U" cookie from bing.com. Use dev tools or an exte **Note:** Specific error handling and styling for this model is still in progress. +### Proxy + +If your server cannot connect to the chatGPT API server by some reason, (eg in China). You can set a environment variable `PROXY`. This will be transmitted to `node-chatgpt-api` interface. + +**Warning:** `PROXY` is not `reverseProxyUrl` in `node-chatgpt-api` + +
+Set up proxy in local environment + +Here is two ways to set proxy. +- Option 1: system level environment +`export PROXY="http://127.0.0.1:7890"` +- Option 2: set in .env file +`PROXY="http://127.0.0.1:7890"` + +**Change `http://127.0.0.1:7890` to your proxy server** +
+ +
+Set up proxy in docker environment + +set in docker-compose.yml file, under services - api - environment + +``` + api: + ... + environment: + ... + - "PROXY=http://127.0.0.1:7890" + # add this line ↑ +``` + +**Change `http://127.0.0.1:7890` to your proxy server** + +
+ ### Updating - As the project is still a work-in-progress, you should pull the latest and run the steps over. Reset your browser cache/clear site data. diff --git a/api/.env.example b/api/.env.example index 14209cff03..e679f6a0e0 100644 --- a/api/.env.example +++ b/api/.env.example @@ -1,13 +1,22 @@ -OPENAI_KEY= -HOST= +# Server configuration. +# The server will listen to localhost:3080 request by default. You can set the target ip as you want. +# If you want this server can be used outside your local machine, for example to share with other +# machine or expose this from a docker container, set HOST=0.0.0.0 or your external ip interface. +# +# Tips: HOST=0.0.0.0 means listening on all interface. It's not a real ip. Use localhost:port rather +# than 0.0.0.0:port to open it. +HOST=localhost PORT=3080 NODE_ENV=development +# Change this to proxy any API request. It's useful if your machine have difficulty calling the original API server. +# PROXY="http://YOUR_PROXY_SERVER" + # Change this to your MongoDB URI if different and I recommend appending chatgpt-clone MONGO_URI="mongodb://127.0.0.1:27017/chatgpt-clone" -# Change this to proxy any request. -PROXY= - -CHATGPT_TOKEN="" -BING_TOKEN="" \ No newline at end of file +# API key configuration. +# Leave blank if you don't want them. +OPENAI_KEY= +CHATGPT_TOKEN= +BING_TOKEN= \ No newline at end of file diff --git a/api/Dockerfile b/api/Dockerfile index fd21cf4d1a..b861217beb 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -8,6 +8,8 @@ RUN npm install COPY . /api/ # Make port 3080 available to the world outside this container EXPOSE 3080 +# Expose the server to 0.0.0.0 +ENV HOST=0.0.0.0 # Run the app when the container launches CMD ["npm", "start"] diff --git a/api/app/bingai.js b/api/app/bingai.js index 4dfa8a71d5..d1175c0bfc 100644 --- a/api/app/bingai.js +++ b/api/app/bingai.js @@ -10,7 +10,8 @@ const askBing = async ({ text, progressCallback, convo }) => { // 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' }) } + cache: { store: new KeyvFile({ filename: './data/cache.json' }) }, + proxy: process.env.PROXY || null, }); let options = { diff --git a/api/app/chatgpt-browser.js b/api/app/chatgpt-browser.js index 442d2a731d..a17f277822 100644 --- a/api/app/chatgpt-browser.js +++ b/api/app/chatgpt-browser.js @@ -7,6 +7,7 @@ const clientOptions = { // Access token from https://chat.openai.com/api/auth/session accessToken: process.env.CHATGPT_TOKEN, // debug: true + proxy: process.env.PROXY || null, }; const browserClient = async ({ text, progressCallback, convo }) => { diff --git a/api/app/chatgpt-client.js b/api/app/chatgpt-client.js index ce3c0e2271..afd31e0a81 100644 --- a/api/app/chatgpt-client.js +++ b/api/app/chatgpt-client.js @@ -5,6 +5,7 @@ const clientOptions = { modelOptions: { model: 'gpt-3.5-turbo' }, + proxy: process.env.PROXY || null, debug: false }; diff --git a/api/app/chatgpt-custom.js b/api/app/chatgpt-custom.js index d31901c75f..a356ba4b1a 100644 --- a/api/app/chatgpt-custom.js +++ b/api/app/chatgpt-custom.js @@ -5,6 +5,7 @@ const clientOptions = { modelOptions: { model: 'gpt-3.5-turbo' }, + proxy: process.env.PROXY || null, debug: false }; diff --git a/api/server/index.js b/api/server/index.js index 347491f0af..b380932531 100644 --- a/api/server/index.js +++ b/api/server/index.js @@ -1,28 +1,32 @@ -const express = require('express'); -const dbConnect = require('../models/dbConnect'); -const path = require('path'); -const cors = require('cors'); -const routes = require('./routes'); -const app = express(); -const port = process.env.PORT || 3080; -const projectPath = path.join(__dirname, '..', '..', 'client'); -dbConnect().then(() => console.log('Connected to MongoDB')); - -app.use(cors()); -app.use(express.json()); -app.use(express.static(path.join(projectPath, 'public'))); - -app.get('/', function (req, res) { - console.log(path.join(projectPath, 'public', 'index.html')); - res.sendFile(path.join(projectPath, 'public', 'index.html')); -}); - -app.use('/api/ask', routes.ask); -app.use('/api/messages', routes.messages); -app.use('/api/convos', routes.convos); -app.use('/api/customGpts', routes.customGpts); -app.use('/api/prompts', routes.prompts); - -app.listen(port, () => { - console.log(`Server listening at http://localhost:${port}`); -}); \ No newline at end of file +const express = require('express'); +const dbConnect = require('../models/dbConnect'); +const path = require('path'); +const cors = require('cors'); +const routes = require('./routes'); +const app = express(); +const port = process.env.PORT || 3080; +const host = process.env.HOST || 'localhost' +const projectPath = path.join(__dirname, '..', '..', 'client'); +dbConnect().then(() => console.log('Connected to MongoDB')); + +app.use(cors()); +app.use(express.json()); +app.use(express.static(path.join(projectPath, 'public'))); + +app.get('/', function (req, res) { + console.log(path.join(projectPath, 'public', 'index.html')); + res.sendFile(path.join(projectPath, 'public', 'index.html')); +}); + +app.use('/api/ask', routes.ask); +app.use('/api/messages', routes.messages); +app.use('/api/convos', routes.convos); +app.use('/api/customGpts', routes.customGpts); +app.use('/api/prompts', routes.prompts); + +app.listen(port, host, () => { + if (host=='0.0.0.0') + console.log(`Server listening on all interface at port ${port}. Use http://localhost:${port} to access it`); + else + console.log(`Server listening at http://${host=='0.0.0.0'?'localhost':host}:${port}`); +}); \ No newline at end of file diff --git a/client/src/components/Conversations/Conversation.jsx b/client/src/components/Conversations/Conversation.jsx index 745671e1cc..ce4cf3c6be 100644 --- a/client/src/components/Conversations/Conversation.jsx +++ b/client/src/components/Conversations/Conversation.jsx @@ -23,8 +23,8 @@ export default function Conversation({ const { modelMap } = useSelector((state) => state.models); const inputRef = useRef(null); const dispatch = useDispatch(); - const { trigger } = manualSWR(`http://localhost:3080/api/messages/${id}`, 'get'); - const rename = manualSWR(`http://localhost:3080/api/convos/update`, 'post'); + const { trigger } = manualSWR(`/api/messages/${id}`, 'get'); + const rename = manualSWR(`/api/convos/update`, 'post'); const clickHandler = async () => { if (conversationId === id) { diff --git a/client/src/components/Conversations/DeleteButton.jsx b/client/src/components/Conversations/DeleteButton.jsx index 03c9f47f81..9b8e688cd0 100644 --- a/client/src/components/Conversations/DeleteButton.jsx +++ b/client/src/components/Conversations/DeleteButton.jsx @@ -9,7 +9,7 @@ import { setMessages } from '~/store/messageSlice'; export default function DeleteButton({ conversationId, renaming, cancelHandler }) { const dispatch = useDispatch(); const { trigger } = manualSWR( - `http://localhost:3080/api/convos/clear`, + `/api/convos/clear`, 'post', () => { dispatch(setMessages([])); diff --git a/client/src/components/Models/ModelDialog.jsx b/client/src/components/Models/ModelDialog.jsx index 47592d8a93..a1b05dfcdd 100644 --- a/client/src/components/Models/ModelDialog.jsx +++ b/client/src/components/Models/ModelDialog.jsx @@ -25,7 +25,7 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { const [saveText, setSaveText] = useState('Save'); const [required, setRequired] = useState(false); const inputRef = useRef(null); - const updateCustomGpt = manualSWR(`http://localhost:3080/api/customGpts/`, 'post'); + const updateCustomGpt = manualSWR(`/api/customGpts/`, 'post'); const submitHandler = (e) => { if (chatGptLabel.length === 0) { diff --git a/client/src/components/Models/ModelItem.jsx b/client/src/components/Models/ModelItem.jsx index 8b7d8d082d..494b20a2ac 100644 --- a/client/src/components/Models/ModelItem.jsx +++ b/client/src/components/Models/ModelItem.jsx @@ -15,8 +15,8 @@ export default function ModelItem({ modelName, value, onSelect }) { const [currentName, setCurrentName] = useState(modelName); const [modelInput, setModelInput] = useState(modelName); const inputRef = useRef(null); - const rename = manualSWR(`http://localhost:3080/api/customGpts`, 'post'); - const deleteCustom = manualSWR(`http://localhost:3080/api/customGpts/delete`, 'post'); + const rename = manualSWR(`/api/customGpts`, 'post'); + const deleteCustom = manualSWR(`/api/customGpts/delete`, 'post'); if (value === 'chatgptCustom') { return ( diff --git a/client/src/components/Models/ModelMenu.jsx b/client/src/components/Models/ModelMenu.jsx index 607b48921f..94e9dd5c86 100644 --- a/client/src/components/Models/ModelMenu.jsx +++ b/client/src/components/Models/ModelMenu.jsx @@ -27,7 +27,7 @@ export default function ModelMenu() { const [menuOpen, setMenuOpen] = useState(false); const { model, customModel } = useSelector((state) => state.submit); const { models, modelMap, initial } = useSelector((state) => state.models); - const { trigger } = manualSWR(`http://localhost:3080/api/customGpts`, 'get', (res) => { + const { trigger } = manualSWR(`/api/customGpts`, 'get', (res) => { const fetchedModels = res.map((modelItem) => ({ ...modelItem, name: modelItem.chatGptLabel diff --git a/client/src/components/Nav/ClearConvos.jsx b/client/src/components/Nav/ClearConvos.jsx index d262226bb9..e6402b0c40 100644 --- a/client/src/components/Nav/ClearConvos.jsx +++ b/client/src/components/Nav/ClearConvos.jsx @@ -10,10 +10,10 @@ export default function ClearConvos() { const dispatch = useDispatch(); const { mutate } = useSWRConfig(); - const { trigger } = manualSWR(`http://localhost:3080/api/convos/clear`, 'post', () => { + const { trigger } = manualSWR(`/api/convos/clear`, 'post', () => { dispatch(setMessages([])); dispatch(setNewConvo()); - mutate(`http://localhost:3080/api/convos`); + mutate(`/api/convos`); }); const clickHandler = () => { diff --git a/client/src/components/Nav/index.jsx b/client/src/components/Nav/index.jsx index 2ae96d22cc..fa74e069e9 100644 --- a/client/src/components/Nav/index.jsx +++ b/client/src/components/Nav/index.jsx @@ -17,7 +17,7 @@ export default function Nav() { }; const { data, isLoading, mutate } = swr( - `http://localhost:3080/api/convos?pageNumber=${pageNumber}`, + `/api/convos?pageNumber=${pageNumber}`, onSuccess ); const containerRef = useRef(null); diff --git a/client/src/utils/handleSubmit.js b/client/src/utils/handleSubmit.js index 09e1aa52b6..4ba262ac08 100644 --- a/client/src/utils/handleSubmit.js +++ b/client/src/utils/handleSubmit.js @@ -11,7 +11,7 @@ export default function handleSubmit({ chatGptLabel, promptPrefix }) { - const endpoint = `http://localhost:3080/api/ask`; + const endpoint = `/api/ask`; let payload = { model, text, chatGptLabel, promptPrefix }; if (convo.conversationId && convo.parentMessageId) { payload = { diff --git a/docker-compose.yml b/docker-compose.yml index 83fa98fd21..f20d03c1e4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,11 +17,11 @@ services: image: node-api restart: always environment: - - PORT=3080 - - MONGO_URI=mongodb://mongodb:27017/chatgpt-clone - - OPENAI_KEY="" - - CHATGPT_TOKEN="" - - BING_TOKEN="" + - "PORT=3080" + - "MONGO_URI=mongodb://mongodb:27017/chatgpt-clone" + - "OPENAI_KEY=" + - "CHATGPT_TOKEN=" + - "BING_TOKEN=" ports: - "9000:3080" volumes: