├── .dockerignore ├── requirements.txt ├── Dockerfile ├── .github └── workflows │ └── docker-image.yml ├── main.py ├── templates └── chat.html ├── README.md └── static ├── script.js └── style.css /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | Dockerfile 3 | README.md 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | revchatgpt 2 | python-dotenv 3 | flask 4 | gunicorn 5 | gevent 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:slim 2 | WORKDIR /chatgpt-web 3 | COPY . . 4 | RUN pip install --no-cache-dir -r requirements.txt 5 | 6 | CMD ["gunicorn", "-b", "0.0.0.0:8088", "main:server", "--timeout", "200", "--worker-class", "gevent"] 7 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | 8 | jobs: 9 | docker: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: Checkout 14 | uses: actions/checkout@v3 15 | - 16 | name: Set up QEMU 17 | uses: docker/setup-qemu-action@v2 18 | - 19 | name: Set up Docker Buildx 20 | uses: docker/setup-buildx-action@v2 21 | - 22 | name: Login to Docker Hub 23 | uses: docker/login-action@v2 24 | with: 25 | username: ${{ secrets.DOCKERHUB_USERNAME }} 26 | password: ${{ secrets.DOCKERHUB_TOKEN }} 27 | - 28 | name: Build and push 29 | uses: docker/build-push-action@v4 30 | with: 31 | context: . 32 | platforms: linux/amd64, linux/arm64 33 | push: true 34 | tags: sheepgreen/chatgpt-web:latest 35 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from dotenv import dotenv_values 3 | from revChatGPT.V3 import Chatbot 4 | from flask import Flask, request, render_template 5 | 6 | server = Flask(__name__) 7 | 8 | parent_dir = Path(__file__).resolve().parent 9 | config = dotenv_values(f"{parent_dir}/.env") 10 | 11 | chatbot = Chatbot(api_key=config["OPENAI_API_KEY"]) 12 | 13 | 14 | def generate_response(prompt): 15 | try: 16 | response = chatbot.ask(prompt) 17 | return response 18 | except BaseException as e: 19 | return str(e) 20 | 21 | 22 | @server.route("/chat") 23 | def home(): 24 | chatbot.reset() 25 | return render_template("chat.html") 26 | 27 | 28 | @server.route("/chat/get") 29 | def get_bot_response(): 30 | user_text = request.args.get('msg') 31 | return str(generate_response(user_text)) 32 | 33 | 34 | if __name__ == '__main__': 35 | server.run(debug=False, host='0.0.0.0', port=8088) 36 | -------------------------------------------------------------------------------- /templates/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | OpenAI 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |
ChatGPT API
20 |
21 | 26 |
27 | 28 | 29 |
30 |
31 |
32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chatgpt-web 2 | ### 使用官方ChatGPT API实现简单HTML网页版在线聊天(基于[此项目](https://github.com/AlliotTech/chatgpt-web)调整而来) 3 | > 该版本基于OPENAI ChatGPT API开发(付费),想使用`ChatGPT`(免费)的请访问[chatgpt-html](https://github.com/slippersheepig/chatgpt-html) 4 | ## 特性 5 | - 文件结构简单,主要面向小白用户 6 | - 功能不多,但核心的连续对话、多用户会话隔离、markdown格式输出都具备 7 | ## 部署 8 | ### 获取OpenAI API KEY 9 | - 建议官方渠道获取https://platform.openai.com/account/api-keys 10 | ### 配置 11 | #### 从源码配置 12 | - 请参考原作者[github](https://github.com/AlliotTech/chatgpt-web) 13 | #### 使用Docker Compose 14 | > 以下所有文件放同一目录 15 | - 新建`.env`配置文件,粘贴以下内容并保存 16 | ```bash 17 | OPENAI_API_KEY="前面你获取到的OpenAI API KEY" 18 | ``` 19 | - 新建`docker-compose.yml`配置文件,粘贴以下内容并保存 20 | ```bash 21 | services: 22 | chatgpt: 23 | image: sheepgreen/chatgpt-web 24 | container_name: webchat 25 | volumes: 26 | - ./.env:/chatgpt-web/.env 27 | # - ./chat.html:/chatgpt-web/templates/chat.html #默认内置我的UI,如需替换自用网页请取消注释,需与docker-compose.yml文件在同一目录 28 | ports: 29 | - "8888:8088" #8088为容器内部端口,不可更改;8888为外部映射端口,可自行更改 30 | restart: always 31 | ``` 32 | - 输入`docker-compose up -d`即启动成功 33 | ## 注意事项 34 | - 访问地址为http://ip:port/chat 35 | - 修改`chat.html`文件后,需要docker restart webchat才能生效 36 | ## 其他相关 37 | - [ChatGPT电报机器人](https://github.com/slippersheepig/chatgpt-telegram-bot),[ChatGPT企业微信应用机器人](https://github.com/slippersheepig/chatgpt-bizwechat-bot),[ChatGPT的QQ频道机器人DOCKER版](https://github.com/slippersheepig/QQChannelChatGPT),[微软BING电报机器人DOCKER版](https://github.com/slippersheepig/BingChatBot),[谷歌BARD网页版](https://github.com/slippersheepig/bard-web),[谷歌BARD电报机器人](https://github.com/slippersheepig/bard-telegram-bot) 38 | - 出于玩玩bing的chatgpt心态,按[danny-avila](https://github.com/danny-avila/chatgpt-clone)搞了一套[测试站](https://ms.sheepig.top)(需要先点击聊天框左边的图标切换模型,默认模型是API,我的KEY没额度了),`BingAI`就是GPT-4,`Sydney`是“破解”过的BingAI(没有每轮对话最多15次和每天对话最多150次的限制,但是智商差一点)。另外此项目代码也有bug需要完善(如果你去体验会发现的),不做详细介绍。 39 | ![image](https://user-images.githubusercontent.com/58287293/225885666-ff56fb90-13ac-46a4-b685-d4188c3fee36.png#pic_center) 40 | -------------------------------------------------------------------------------- /static/script.js: -------------------------------------------------------------------------------- 1 | // 加入回车提交支持,shift+回车换行 2 | var input = document.getElementById("chatinput"); 3 | input.addEventListener("keydown", function (event) { 4 | if (event.keyCode === 13 && !event.shiftKey) { 5 | event.preventDefault(); 6 | document.getElementById("sendbutton").click(); 7 | } 8 | }); 9 | 10 | // Add your JavaScript here 11 | document.getElementById("sendbutton").addEventListener("click", function () { 12 | // Get the user's message from the input field 13 | var message = document.getElementById("chatinput").value; 14 | var chatlog = document.getElementById("chatlog"); 15 | var response = document.createElement("div"); 16 | if (message.length < 1) { 17 | response.innerHTML = "🤔
🤖
Message cannot be null\n问题不能为空"; 18 | // 给response添加一个动画类 19 | response.classList.add("animate__animated", "animate__lightSpeedInLeft", "dark"); 20 | chatlog.appendChild(response); 21 | response.scrollIntoView({ behavior: 'smooth', block: 'end' }); 22 | } else { 23 | // Clear the input field 24 | document.getElementById("chatinput").value = ""; 25 | // Send the message to the chatbot 26 | var xhr = new XMLHttpRequest(); 27 | xhr.open("GET", "/chat/get?msg=" + message); 28 | xhr.send(); 29 | // Display "typing" message while the bot is thinking 30 | var typingMessage = document.createElement("div"); 31 | // 新增一个小圆点元素,添加typing类 32 | var dot = document.createElement("div"); 33 | dot.classList.add("typing"); 34 | typingMessage.appendChild(dot); 35 | chatlog.appendChild(typingMessage); 36 | typingMessage.scrollIntoView({ behavior: 'smooth', block: 'end' }); 37 | xhr.onload = function () { 38 | // Append the chatbot's response to the chatlog 39 | chatlog.removeChild(typingMessage); 40 | response.innerHTML = "🤔
" + message + "
🤖" + marked.parse(xhr.responseText); 41 | // 给response添加一个动画类 42 | response.classList.add("animate__animated", "animate__lightSpeedInLeft", "dark"); 43 | chatlog.appendChild(response); 44 | response.scrollIntoView({ behavior: 'smooth', block: 'end' }); 45 | } 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | body { 8 | background-color: #edeff2; 9 | font-family: "Calibri", "Roboto", sans-serif; 10 | } 11 | 12 | @media (prefers-color-scheme: dark) { 13 | body { 14 | background: black; 15 | color: white; 16 | } 17 | } 18 | 19 | .chat_window { 20 | position: absolute; 21 | width: calc(100% - 20px); 22 | max-width: 800px; 23 | border-radius: 10px; 24 | background-color: #f8f8f8; 25 | left: 50%; 26 | top: 50%; 27 | transform: translate(-50%, -50%); 28 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15); 29 | overflow: hidden; 30 | } 31 | 32 | .top_menu { 33 | background-color: #fff; 34 | width: 100%; 35 | padding: 20px 0 15px; 36 | box-shadow: 0 1px 30px rgba(0, 0, 0, 0.1); 37 | } 38 | 39 | .top_menu .title { 40 | text-align: center; 41 | color: #bcbdc0; 42 | font-size: 20px; 43 | } 44 | 45 | .messages { 46 | position: relative; 47 | list-style: none; 48 | padding: 20px 10px 0; 49 | overflow-x: hidden; 50 | overflow-y: auto; 51 | } 52 | 53 | .messages div { 54 | text-align: left; 55 | color: green; 56 | } 57 | 58 | .messages pre { 59 | white-space: pre-wrap; 60 | word-wrap: break-word; 61 | } 62 | 63 | .bottom_wrapper { 64 | clear: both; 65 | position: relative; 66 | width: 100%; 67 | background-color: #f6f6f6; 68 | padding: 20px; 69 | border-radius: 0 0 10px 10px; 70 | } 71 | 72 | .bottom_wrapper textarea { 73 | width: calc(100% - 80px); 74 | border: none; 75 | padding: 10px; 76 | border-radius: 3px; 77 | resize: none; 78 | font-size: 16px; 79 | height: 40px; /* 设置输入框的高度 */ 80 | vertical-align: middle; /* 垂直居中 */ 81 | } 82 | 83 | .bottom_wrapper button { 84 | width: 60px; 85 | height: 40px; 86 | margin-left: 10px; /* 增加左边距 */ 87 | font-size: 16px; 88 | border: none; 89 | border-radius: 3px; 90 | background-color: #4CAF50; 91 | color: white; 92 | cursor: pointer; 93 | vertical-align: middle; /* 垂直居中 */ 94 | } 95 | 96 | .bottom_wrapper button:hover { 97 | background-color: #3e8e41; 98 | } 99 | 100 | .bottom_wrapper button:focus { 101 | outline: none; 102 | } 103 | 104 | /* 新增一个typing类,用于显示动画效果 */ 105 | .typing { 106 | width: 20px; 107 | height: 20px; 108 | border-radius: 50%; 109 | background-color: green; 110 | position: relative; 111 | animation: typing 1.5s infinite; 112 | } 113 | 114 | .dark { 115 | color: #fff; 116 | } 117 | 118 | @media (prefers-color-scheme: dark) { 119 | .chat_window { 120 | background-color: #333; 121 | box-shadow: 0 10px 20px rgba(255, 255, 255, 0.1); 122 | } 123 | 124 | .top_menu { 125 | background-color: #222; 126 | box-shadow: 0 1px 30px rgba(255, 255, 255, 0.1); 127 | } 128 | 129 | .top_menu .title { 130 | color: #ccc; 131 | } 132 | 133 | .bottom_wrapper { 134 | background-color: #222; 135 | box-shadow: 0 1px 30px; 136 | rgba(255, 255, 255, 0.1); 137 | } 138 | 139 | .bottom_wrapper textarea { 140 | background-color: #333; 141 | color: #fff; 142 | } 143 | 144 | .bottom_wrapper button { 145 | background-color: #4CAF50; 146 | } 147 | 148 | .bottom_wrapper button:hover { 149 | background-color: #3e8e41; 150 | } 151 | } 152 | 153 | /* 定义typing动画,让小圆点在水平方向上移动 */ 154 | @keyframes typing { 155 | 0% { 156 | left: 0; 157 | } 158 | 50% { 159 | left: calc(100% - 20px); 160 | } 161 | 100% { 162 | left: 0; 163 | } 164 | } 165 | --------------------------------------------------------------------------------