├── .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 |
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 | 
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 |
--------------------------------------------------------------------------------