mirror of
https://github.com/zhayujie/bot-on-anything.git
synced 2026-01-19 09:41:07 +08:00
初步增加web-http聊天功能
This commit is contained in:
34
README.md
34
README.md
@@ -11,7 +11,7 @@
|
|||||||
**应用:**
|
**应用:**
|
||||||
|
|
||||||
- [x] [终端](https://github.com/zhayujie/bot-on-anything#1%E5%91%BD%E4%BB%A4%E8%A1%8C%E7%BB%88%E7%AB%AF)
|
- [x] [终端](https://github.com/zhayujie/bot-on-anything#1%E5%91%BD%E4%BB%A4%E8%A1%8C%E7%BB%88%E7%AB%AF)
|
||||||
- [ ] Web
|
- [x] [Web](https://github.com/zhayujie/bot-on-anything#9web)
|
||||||
- [x] [个人微信](https://github.com/zhayujie/bot-on-anything#2%E4%B8%AA%E4%BA%BA%E5%BE%AE%E4%BF%A1)
|
- [x] [个人微信](https://github.com/zhayujie/bot-on-anything#2%E4%B8%AA%E4%BA%BA%E5%BE%AE%E4%BF%A1)
|
||||||
- [x] [订阅号](https://github.com/zhayujie/bot-on-anything#3%E4%B8%AA%E4%BA%BA%E8%AE%A2%E9%98%85%E5%8F%B7)
|
- [x] [订阅号](https://github.com/zhayujie/bot-on-anything#3%E4%B8%AA%E4%BA%BA%E8%AE%A2%E9%98%85%E5%8F%B7)
|
||||||
- [x] [服务号](https://github.com/zhayujie/bot-on-anything#4%E4%BC%81%E4%B8%9A%E6%9C%8D%E5%8A%A1%E5%8F%B7)
|
- [x] [服务号](https://github.com/zhayujie/bot-on-anything#4%E4%BC%81%E4%B8%9A%E6%9C%8D%E5%8A%A1%E5%8F%B7)
|
||||||
@@ -393,3 +393,35 @@ http:/你的固定公网ip或者域名:端口/slack/events
|
|||||||
```
|
```
|
||||||
https://slack.dev/bolt-python/tutorial/getting-started
|
https://slack.dev/bolt-python/tutorial/getting-started
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 9.Web
|
||||||
|
#### http
|
||||||
|
**需要:** 服务器
|
||||||
|
|
||||||
|
|
||||||
|
**依赖**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip3 install PyJWT flask
|
||||||
|
```
|
||||||
|
|
||||||
|
**配置**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"channel": {
|
||||||
|
"type": "http",
|
||||||
|
"http": {
|
||||||
|
"http_auth_secret_key": "6d25a684-9558-11e9-aa94-efccd7a0659b",//jwt认证秘钥
|
||||||
|
"http_auth_password": "6.67428e-11",//认证密码,仅仅只是自用,最初步的防御别人扫描端口后DDOS浪费tokens
|
||||||
|
"port": "80"//端口
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
URL,如果端口是 80 ,可不填
|
||||||
|
|
||||||
|
```
|
||||||
|
http:/你的固定公网ip或者域名:端口/
|
||||||
|
```
|
||||||
|
|||||||
@@ -41,5 +41,9 @@ def create_channel(channel_type):
|
|||||||
from channel.slack.slack_channel import SlackChannel
|
from channel.slack.slack_channel import SlackChannel
|
||||||
return SlackChannel()
|
return SlackChannel()
|
||||||
|
|
||||||
|
elif channel_type == const.HTTP:
|
||||||
|
from channel.http.http_channel import HttpChannel
|
||||||
|
return HttpChannel()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError
|
raise RuntimeError
|
||||||
|
|||||||
107
channel/http/auth.py
Normal file
107
channel/http/auth.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# encoding:utf-8
|
||||||
|
|
||||||
|
import jwt
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
|
from flask import jsonify, request
|
||||||
|
from common import const
|
||||||
|
from config import channel_conf
|
||||||
|
|
||||||
|
|
||||||
|
class Auth():
|
||||||
|
def __init__(self, login):
|
||||||
|
# argument 'privilegeRequired' is to set up your method's privilege
|
||||||
|
# name
|
||||||
|
self.login = login
|
||||||
|
super(Auth, self).__init__()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode_auth_token(user_id, login_time):
|
||||||
|
"""
|
||||||
|
生成认证Token
|
||||||
|
:param user_id: int
|
||||||
|
:param login_time: datetime
|
||||||
|
:return: string
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
payload = {
|
||||||
|
'iss': 'ken', # 签名
|
||||||
|
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=0, hours=10), # 过期时间
|
||||||
|
'iat': datetime.datetime.utcnow(), # 开始时间
|
||||||
|
'data': {
|
||||||
|
'id': user_id,
|
||||||
|
'login_time': login_time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jwt.encode(
|
||||||
|
payload,
|
||||||
|
channel_conf(const.HTTP).get('http_auth_secret_key'),
|
||||||
|
algorithm='HS256'
|
||||||
|
) # 加密生成字符串
|
||||||
|
except Exception as e:
|
||||||
|
return e
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decode_auth_token(auth_token):
|
||||||
|
"""
|
||||||
|
验证Token
|
||||||
|
:param auth_token:
|
||||||
|
:return: integer|string
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 取消过期时间验证
|
||||||
|
payload = jwt.decode(auth_token, channel_conf(const.HTTP).get(
|
||||||
|
'http_auth_secret_key'), algorithms='HS256') # options={'verify_exp': False} 加上后不验证token过期时间
|
||||||
|
if ('data' in payload and 'id' in payload['data']):
|
||||||
|
return payload
|
||||||
|
else:
|
||||||
|
raise jwt.InvalidTokenError
|
||||||
|
except jwt.ExpiredSignatureError:
|
||||||
|
return 'Token过期'
|
||||||
|
except jwt.InvalidTokenError:
|
||||||
|
return '无效Token'
|
||||||
|
|
||||||
|
|
||||||
|
def authenticate(password):
|
||||||
|
"""
|
||||||
|
用户登录,登录成功返回token
|
||||||
|
:param password:
|
||||||
|
:return: json
|
||||||
|
"""
|
||||||
|
authPassword = channel_conf(const.HTTP).get('http_auth_password')
|
||||||
|
if (authPassword != password):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
login_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||||
|
token = Auth.encode_auth_token(password, login_time)
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def identify(request):
|
||||||
|
"""
|
||||||
|
用户鉴权
|
||||||
|
:return: list
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if (request is None):
|
||||||
|
return False
|
||||||
|
authorization = request.cookies.get('Authorization')
|
||||||
|
if (authorization):
|
||||||
|
payload = Auth.decode_auth_token(authorization)
|
||||||
|
if not isinstance(payload, str):
|
||||||
|
authPassword = channel_conf(
|
||||||
|
const.HTTP).get('http_auth_password')
|
||||||
|
password = payload['data']['id']
|
||||||
|
if (password != authPassword):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
except jwt.ExpiredSignatureError:
|
||||||
|
#result = 'Token已更改,请重新登录获取'
|
||||||
|
return False
|
||||||
|
|
||||||
|
except jwt.InvalidTokenError:
|
||||||
|
#result = '没有提供认证token'
|
||||||
|
return False
|
||||||
66
channel/http/http_channel.py
Normal file
66
channel/http/http_channel.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# encoding:utf-8
|
||||||
|
|
||||||
|
import json
|
||||||
|
from channel.http import auth
|
||||||
|
from flask import Flask, request, render_template, make_response
|
||||||
|
from datetime import timedelta
|
||||||
|
from common import const
|
||||||
|
from config import channel_conf
|
||||||
|
from channel.channel import Channel
|
||||||
|
http_app = Flask(__name__,)
|
||||||
|
# 自动重载模板文件
|
||||||
|
http_app.jinja_env.auto_reload = True
|
||||||
|
http_app.config['TEMPLATES_AUTO_RELOAD'] = True
|
||||||
|
|
||||||
|
# 设置静态文件缓存过期时间
|
||||||
|
http_app.config['SEND_FILE_MAX_AGE_DEFAULT'] = timedelta(seconds=1)
|
||||||
|
|
||||||
|
|
||||||
|
@http_app.route("/chat", methods=['POST'])
|
||||||
|
def chat():
|
||||||
|
if (auth.identify(request) == False):
|
||||||
|
return
|
||||||
|
data = json.loads(request.data)
|
||||||
|
if data:
|
||||||
|
msg = data['msg']
|
||||||
|
if not msg:
|
||||||
|
return
|
||||||
|
reply_text = HttpChannel().handle(data=data)
|
||||||
|
return {'result': reply_text}
|
||||||
|
|
||||||
|
|
||||||
|
@http_app.route("/", methods=['GET'])
|
||||||
|
def index():
|
||||||
|
if (auth.identify(request) == False):
|
||||||
|
return login()
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
|
||||||
|
@http_app.route("/login", methods=['POST', 'GET'])
|
||||||
|
def login():
|
||||||
|
response = make_response("<html></html>",301)
|
||||||
|
response.headers.add_header('content-type','text/plain')
|
||||||
|
response.headers.add_header('location','./')
|
||||||
|
if (auth.identify(request) == True):
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
if request.method == "POST":
|
||||||
|
token = auth.authenticate(request.form['password'])
|
||||||
|
if (token != False):
|
||||||
|
response.set_cookie(key='Authorization', value=token)
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
return render_template('login.html')
|
||||||
|
response.headers.set('location','./login?err=登录失败')
|
||||||
|
return response
|
||||||
|
|
||||||
|
class HttpChannel(Channel):
|
||||||
|
def startup(self):
|
||||||
|
http_app.run(host='0.0.0.0', port=channel_conf(const.HTTP).get('port'))
|
||||||
|
|
||||||
|
def handle(self, data):
|
||||||
|
context = dict()
|
||||||
|
id = data["id"]
|
||||||
|
context['from_user_id'] = str(id)
|
||||||
|
return super().build_reply_content(data["msg"], context)
|
||||||
|
|
||||||
329
channel/http/static/1.css
Normal file
329
channel/http/static/1.css
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
|
||||||
|
.typing_loader {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
-webkit-animation: typing 1s linear infinite alternate;
|
||||||
|
-moz-animation: typing 1s linear infinite alternate;
|
||||||
|
-ms-animation: typing 1s linear infinite alternate;
|
||||||
|
animation: typing 1s linear infinite alternate;
|
||||||
|
position: relative;
|
||||||
|
left: -12px;
|
||||||
|
margin: 7px 15px 6px;
|
||||||
|
}
|
||||||
|
ol,pre {
|
||||||
|
background-color: #b1e3b1c4;
|
||||||
|
border: 1px solid #c285e3ab;
|
||||||
|
padding: 0.5rem 1.5rem 0.5rem;
|
||||||
|
color: black;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.to .typing_loader {
|
||||||
|
animation: typing-black 1s linear infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes typing {
|
||||||
|
0% {
|
||||||
|
background-color: rgba(255,255,255, 1);
|
||||||
|
box-shadow: 12px 0px 0px 0px rgba(255,255,255,0.4), 24px 0px 0px 0px rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background-color: rgba(255,255,255, 0.4);
|
||||||
|
box-shadow: 12px 0px 0px 0px rgba(255,255,255,1), 24px 0px 0px 0px rgba(255,255,255,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-color: rgba(255,255,255, 0.2);
|
||||||
|
box-shadow: 12px 0px 0px 0px rgba(255,255,255,0.4), 24px 0px 0px 0px rgba(255,255,255,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-moz-keyframes typing {
|
||||||
|
0% {
|
||||||
|
background-color: rgba(255,255,255, 1);
|
||||||
|
box-shadow: 12px 0px 0px 0px rgba(255,255,255,0.4), 24px 0px 0px 0px rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background-color: rgba(255,255,255, 0.4);
|
||||||
|
box-shadow: 12px 0px 0px 0px rgba(255,255,255,1), 24px 0px 0px 0px rgba(255,255,255,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-color: rgba(255,255,255, 0.2);
|
||||||
|
box-shadow: 12px 0px 0px 0px rgba(255,255,255,0.4), 24px 0px 0px 0px rgba(255,255,255,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes typing-black {
|
||||||
|
0% {
|
||||||
|
background-color: rgba(74, 74, 74, 1);
|
||||||
|
box-shadow: 12px 0px 0px 0px rgba(74, 74, 74, 0.4), 24px 0px 0px 0px rgba(74, 74, 74, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background-color: rgba(74, 74, 74, 0.4);
|
||||||
|
box-shadow: 12px 0px 0px 0px rgba(74, 74, 74, 1), 24px 0px 0px 0px rgba(74, 74, 74,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-color: rgba(74, 74, 74, 0.2);
|
||||||
|
box-shadow: 12px 0px 0px 0px rgba(74, 74, 74,0.4), 24px 0px 0px 0px rgba(74, 74, 74,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes typing {
|
||||||
|
0% {
|
||||||
|
background-color: rgba(255,255,255, 1);
|
||||||
|
box-shadow: 12px 0px 0px 0px rgba(255,255,255,0.4), 24px 0px 0px 0px rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background-color: rgba(255,255,255, 0.4);
|
||||||
|
box-shadow: 12px 0px 0px 0px rgba(255,255,255,1), 24px 0px 0px 0px rgba(255,255,255,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-color: rgba(255,255,255, 0.2);
|
||||||
|
box-shadow: 12px 0px 0px 0px rgba(255,255,255,0.4), 24px 0px 0px 0px rgba(255,255,255,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.convFormDynamic {
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px 10px;
|
||||||
|
padding: 0 !important;
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid rgba(0, 40, 100, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.convFormDynamic textarea.userInputDynamic {
|
||||||
|
border: none;
|
||||||
|
padding: 7px 10px;
|
||||||
|
overflow-x: hidden!important;
|
||||||
|
outline: none;
|
||||||
|
font-size: 0.905rem;
|
||||||
|
float: left;
|
||||||
|
width: calc(100% - 70px);
|
||||||
|
line-height: 1.3em;
|
||||||
|
min-height: 1.7em;
|
||||||
|
max-height: 10rem;
|
||||||
|
display: block;
|
||||||
|
max-width: 89vw;
|
||||||
|
margin-right: -1vw;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
.convFormDynamic textarea::-webkit-scrollbar{
|
||||||
|
width: 2px;
|
||||||
|
background-color: lawngreen;
|
||||||
|
}
|
||||||
|
.convFormDynamic textarea::-webkit-scrollbar-thumb{
|
||||||
|
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
|
||||||
|
background-color: dodgerblue;
|
||||||
|
}
|
||||||
|
.convFormDynamic input.userInputDynamic {
|
||||||
|
border: none;
|
||||||
|
padding: 7px 10px;
|
||||||
|
outline: none;
|
||||||
|
font-size: 0.905rem;
|
||||||
|
float: left;
|
||||||
|
width: calc(100% - 70px);
|
||||||
|
line-height: 1.3em;
|
||||||
|
min-height: 1.7em;
|
||||||
|
max-height: 10rem;
|
||||||
|
display: block;
|
||||||
|
max-width: 89vw;
|
||||||
|
margin-right: -1vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper div#messages {
|
||||||
|
max-height: 71vh;
|
||||||
|
height: auto !important;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper div#messages:after {
|
||||||
|
content: '';
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper div.wrapper-messages {
|
||||||
|
position: relative;
|
||||||
|
height: 76vh;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
height: 30px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
|
background: linear-gradient(#fff, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
div.conv-form-wrapper div.wrapper-messages, div.conv-form-wrapper div#messages {
|
||||||
|
max-height: 71vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper div.wrapper-messages::-webkit-scrollbar, div#feed ul::-webkit-scrollbar, div.conv-form-wrapper div.options::-webkit-scrollbar {
|
||||||
|
width: 0px;
|
||||||
|
height: 0px;
|
||||||
|
/* remove scrollbar space */
|
||||||
|
background: transparent;
|
||||||
|
/* optional: just make scrollbar invisible */
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"].userInputDynamic.error {
|
||||||
|
color: #ac0000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"].userInputDynamic {
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 7px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.userInputDynamic.error {
|
||||||
|
color: #ac0000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.userInputDynamic {
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 7px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper div#messages {
|
||||||
|
transition: bottom 0.15s, padding-bottom 0.15s;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
height: auto !important;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
/*max-height: 71vh;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper div.message {
|
||||||
|
animation: slideTop 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper div.message:after {
|
||||||
|
content: '';
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper div.message.ready {
|
||||||
|
animation: bounceIn 0.2s ease;
|
||||||
|
transform-origin: 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper div#messages div.message {
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 12px 22px;
|
||||||
|
font-size: 0.905rem;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 15px 8px;
|
||||||
|
border-radius: 20px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
float: right;
|
||||||
|
clear: both;
|
||||||
|
max-width: 65%;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper div#messages div.message.to {
|
||||||
|
float: left;
|
||||||
|
background: lawngreen;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper div#messages div.message.from {
|
||||||
|
background: dodgerblue;
|
||||||
|
color: #fff;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.to+.message.from, .message.from+.message.to {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideTop {
|
||||||
|
0% {
|
||||||
|
margin-bottom: -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounceIn {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.75, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1.0, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.convFormDynamic button.submit {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
border: none;
|
||||||
|
left:95%;
|
||||||
|
margin: 5px;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
width: 50px;
|
||||||
|
height: 42px;
|
||||||
|
border: 1px solid #b7b7b7;
|
||||||
|
background: #c3c3c3;
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-block {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
float: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.submit.glow {
|
||||||
|
border: 1px solid dodgerblue !important;
|
||||||
|
background: dodgerblue !important;
|
||||||
|
box-shadow: 0 0 5px 2px rgba(14, 144, 255,0.4);
|
||||||
|
}
|
||||||
|
.no-border {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragscroll {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conv-form-wrapper div#messages::-webkit-scrollbar, div#feed ul::-webkit-scrollbar {
|
||||||
|
width: 0px;
|
||||||
|
/* remove scrollbar space */
|
||||||
|
background: transparent;
|
||||||
|
/* optional: just make scrollbar invisible */
|
||||||
|
}
|
||||||
|
|
||||||
|
span.clear {
|
||||||
|
display: block;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
134
channel/http/static/1.js
Normal file
134
channel/http/static/1.js
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
|
||||||
|
function ConvState(wrapper, form, params) {
|
||||||
|
this.id='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
var r = Math.random() * 16 | 0,
|
||||||
|
v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
this.form = form;
|
||||||
|
this.wrapper = wrapper;
|
||||||
|
this.parameters = params;
|
||||||
|
this.scrollDown = function () {
|
||||||
|
$(this.wrapper).find('#messages').stop().animate({ scrollTop: $(this.wrapper).find('#messages')[0].scrollHeight }, 600);
|
||||||
|
}.bind(this);
|
||||||
|
};
|
||||||
|
ConvState.prototype.printAnswer = function (answer = '我是ChatGPT, 一个由OpenAI训练的大型语言模型, 我旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。') {
|
||||||
|
setTimeout(function () {
|
||||||
|
var messageObj = $(this.wrapper).find('.message.typing');
|
||||||
|
answer = marked.parse(answer);
|
||||||
|
messageObj.html(answer);
|
||||||
|
messageObj.removeClass('typing').addClass('ready');
|
||||||
|
this.scrollDown();
|
||||||
|
$(this.wrapper).find(this.parameters.inputIdHashTagName).focus();
|
||||||
|
}.bind(this), 500);
|
||||||
|
};
|
||||||
|
ConvState.prototype.sendMessage = function (msg) {
|
||||||
|
var message = $('<div class="message from">' + msg + '</div>');
|
||||||
|
|
||||||
|
$('button.submit').removeClass('glow');
|
||||||
|
$(this.wrapper).find(this.parameters.inputIdHashTagName).focus();
|
||||||
|
setTimeout(function () {
|
||||||
|
$(this.wrapper).find("#messages").append(message);
|
||||||
|
this.scrollDown();
|
||||||
|
}.bind(this), 100);
|
||||||
|
|
||||||
|
var messageObj = $('<div class="message to typing"><div class="typing_loader"></div></div>');
|
||||||
|
setTimeout(function () {
|
||||||
|
$(this.wrapper).find('#messages').append(messageObj);
|
||||||
|
this.scrollDown();
|
||||||
|
}.bind(this), 150);
|
||||||
|
var _this = this
|
||||||
|
$.ajax({
|
||||||
|
url: "./chat",
|
||||||
|
type: "POST",
|
||||||
|
timeout:60000,
|
||||||
|
data: JSON.stringify({
|
||||||
|
"id": _this.id,
|
||||||
|
"msg": msg
|
||||||
|
}),
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
dataType: "json",
|
||||||
|
success: function (data) {
|
||||||
|
_this.printAnswer(data.result)
|
||||||
|
},
|
||||||
|
error:function () {
|
||||||
|
_this.printAnswer("网络故障,对话未送达")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
};
|
||||||
|
(function ($) {
|
||||||
|
$.fn.convform = function () {
|
||||||
|
var wrapper = this;
|
||||||
|
$(this).addClass('conv-form-wrapper');
|
||||||
|
|
||||||
|
var parameters = $.extend(true, {}, {
|
||||||
|
placeHolder: 'Type Here',
|
||||||
|
typeInputUi: 'textarea',
|
||||||
|
formIdName: 'convForm',
|
||||||
|
inputIdName: 'userInput',
|
||||||
|
buttonText: '▶'
|
||||||
|
});
|
||||||
|
|
||||||
|
//hides original form so users cant interact with it
|
||||||
|
var form = $(wrapper).find('form').hide();
|
||||||
|
|
||||||
|
var inputForm;
|
||||||
|
parameters.inputIdHashTagName = '#' + parameters.inputIdName;
|
||||||
|
inputForm = $('<div id="' + parameters.formIdName + '" class="convFormDynamic"><div class="options dragscroll"></div><textarea id="' + parameters.inputIdName + '" rows="1" placeholder="' + parameters.placeHolder + '" class="userInputDynamic"></textarea><button type="submit" class="submit">' + parameters.buttonText + '</button><span class="clear"></span></form>');
|
||||||
|
|
||||||
|
//appends messages wrapper and newly created form with the spinner load
|
||||||
|
$(wrapper).append('<div class="wrapper-messages"><div class="spinLoader"></div><div id="messages"></div></div>');
|
||||||
|
$(wrapper).append(inputForm);
|
||||||
|
|
||||||
|
var state = new ConvState(wrapper, form, parameters);
|
||||||
|
|
||||||
|
//prints first contact
|
||||||
|
$.when($('div.spinLoader').addClass('hidden')).done(function () {
|
||||||
|
var messageObj = $('<div class="message to typing"><div class="typing_loader"></div></div>');
|
||||||
|
$(state.wrapper).find('#messages').append(messageObj);
|
||||||
|
state.scrollDown();
|
||||||
|
state.printAnswer();
|
||||||
|
});
|
||||||
|
|
||||||
|
//binds enter to send message
|
||||||
|
$(inputForm).find(parameters.inputIdHashTagName).keypress(function (e) {
|
||||||
|
if (e.which == 13) {
|
||||||
|
var input = $(this).val();
|
||||||
|
e.preventDefault();
|
||||||
|
if (input.trim() != '' && !state.wrapper.find(parameters.inputIdHashTagName).hasClass("error")) {
|
||||||
|
$(parameters.inputIdHashTagName).val("");
|
||||||
|
state.sendMessage(input);
|
||||||
|
} else {
|
||||||
|
$(state.wrapper).find(parameters.inputIdHashTagName).focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
autosize.update($(state.wrapper).find(parameters.inputIdHashTagName));
|
||||||
|
})
|
||||||
|
$(inputForm).find(parameters.inputIdHashTagName).on('input', function (e) {
|
||||||
|
if ($(this).val().length > 0) {
|
||||||
|
$('button.submit').addClass('glow');
|
||||||
|
} else {
|
||||||
|
$('button.submit').removeClass('glow');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(inputForm).find('button.submit').click(function (e) {
|
||||||
|
var input = $(state.wrapper).find(parameters.inputIdHashTagName).val();
|
||||||
|
e.preventDefault();
|
||||||
|
if (input.trim() != '' && !state.wrapper.find(parameters.inputIdHashTagName).hasClass("error")) {
|
||||||
|
$(parameters.inputIdHashTagName).val("");
|
||||||
|
state.sendMessage(input);
|
||||||
|
} else {
|
||||||
|
$(state.wrapper).find(parameters.inputIdHashTagName).focus();
|
||||||
|
}
|
||||||
|
autosize.update($(state.wrapper).find(parameters.inputIdHashTagName));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof autosize == 'function') {
|
||||||
|
$textarea = $(state.wrapper).find(parameters.inputIdHashTagName);
|
||||||
|
autosize($textarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
})(jQuery);
|
||||||
51
channel/http/templates/index.html
Normal file
51
channel/http/templates/index.html
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"><!-- Title -->
|
||||||
|
<title>ChatGPT</title><!-- Bootstrap Css -->
|
||||||
|
<link href="./static/1.css" rel="stylesheet" />
|
||||||
|
<style>
|
||||||
|
button {
|
||||||
|
font-family: 'Microsoft YaHei';
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="">
|
||||||
|
<div class="no-border">
|
||||||
|
<div id="chat" class="conv-form-wrapper">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
|
||||||
|
<script src="https://cdn.bootcdn.net/ajax/libs/marked/4.2.12/marked.min.js"></script>
|
||||||
|
<script src="https://cdn.bootcdn.net/ajax/libs/autosize.js/6.0.1/autosize.min.js"></script>
|
||||||
|
<script src="./static/1.js"></script>
|
||||||
|
<script>
|
||||||
|
var rollbackTo = false;
|
||||||
|
var originalState = false;
|
||||||
|
function storeState(a) {
|
||||||
|
rollbackTo = a.current
|
||||||
|
}
|
||||||
|
function rollback(a) {
|
||||||
|
if (rollbackTo != false) {
|
||||||
|
if (originalState == false) {
|
||||||
|
originalState = a.current.next
|
||||||
|
}
|
||||||
|
a.current.next = rollbackTo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function restore(a) {
|
||||||
|
if (originalState != false) {
|
||||||
|
a.current.next = originalState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jQuery(function (a) {
|
||||||
|
var b = a("#chat").convform()
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
63
channel/http/templates/login.html
Normal file
63
channel/http/templates/login.html
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"><!-- Title -->
|
||||||
|
<title>登录</title><!-- Bootstrap Css -->
|
||||||
|
<style>
|
||||||
|
.login-form {
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 350px;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 15px 35px 15px;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin: -160px 0 0 -200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button {
|
||||||
|
width: 80px;
|
||||||
|
margin: 3px 1px 0 5px;
|
||||||
|
padding: 0 10px;
|
||||||
|
background-color: #16a0d3;
|
||||||
|
border: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-family: "Microsoft Yahei";
|
||||||
|
font-size: 12px;
|
||||||
|
height: 27px;
|
||||||
|
color: #FFF;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="">
|
||||||
|
<form name="login" class="login-form" action="./login" method="post" autocomplete="off">
|
||||||
|
<input type="password" name="password" placeholder="Password" style="border: none; height: 25px;width: 250px;"
|
||||||
|
required>
|
||||||
|
</input>
|
||||||
|
<input type="submit" class="Button" value="登录" />
|
||||||
|
<span style="color:red">
|
||||||
|
<p id="err"></p>
|
||||||
|
</span>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
var err=getUrlParam('err')
|
||||||
|
$('#err')[0].innerHTML=err
|
||||||
|
});
|
||||||
|
function getUrlParam(name) {
|
||||||
|
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
|
||||||
|
var r = window.location.search.substr(1).match(reg);
|
||||||
|
if (r != null) return decodeURI(r[2]); return null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -7,6 +7,7 @@ QQ = "qq"
|
|||||||
GMAIL = "gmail"
|
GMAIL = "gmail"
|
||||||
TELEGRAM = "telegram"
|
TELEGRAM = "telegram"
|
||||||
SLACK = "slack"
|
SLACK = "slack"
|
||||||
|
HTTP = "http"
|
||||||
|
|
||||||
# model
|
# model
|
||||||
OPEN_AI = "openai"
|
OPEN_AI = "openai"
|
||||||
|
|||||||
@@ -41,6 +41,12 @@
|
|||||||
"slack": {
|
"slack": {
|
||||||
"slack_bot_token": "xoxb-xxxx",
|
"slack_bot_token": "xoxb-xxxx",
|
||||||
"slack_signing_secret": "xxxx"
|
"slack_signing_secret": "xxxx"
|
||||||
|
},
|
||||||
|
|
||||||
|
"http": {
|
||||||
|
"http_auth_secret_key": "6d25a684-9558-11e9-aa94-efccd7a0659b",
|
||||||
|
"http_auth_password": "6.67428e-11",
|
||||||
|
"port": "80"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user