ct / src /pandora /bots /server.py
BG5's picture
Upload 105 files
8201de9
# -*- coding: utf-8 -*-
import logging
from datetime import timedelta
from os.path import join, abspath, dirname
from flask import Flask, jsonify, make_response, request, Response, render_template
from flask_cors import CORS
from waitress import serve
from werkzeug.exceptions import default_exceptions
from werkzeug.middleware.proxy_fix import ProxyFix
from werkzeug.serving import WSGIRequestHandler
from .. import __version__
from ..exts.hooks import hook_logging
from ..openai.api import API
class ChatBot:
__default_ip = '127.0.0.1'
__default_port = 8008
def __init__(self, chatgpt, debug=False, sentry=False):
self.chatgpt = chatgpt
self.debug = debug
self.sentry = sentry
self.log_level = logging.DEBUG if debug else logging.WARN
hook_logging(level=self.log_level, format='[%(asctime)s] %(levelname)s in %(module)s: %(message)s')
self.logger = logging.getLogger('waitress')
def run(self, bind_str, threads=8):
host, port = self.__parse_bind(bind_str)
resource_path = abspath(join(dirname(__file__), '..', 'flask'))
app = Flask(__name__, static_url_path='',
static_folder=join(resource_path, 'static'),
template_folder=join(resource_path, 'templates'))
app.wsgi_app = ProxyFix(app.wsgi_app, x_port=1)
app.after_request(self.__after_request)
CORS(app, resources={r'/api/*': {'supports_credentials': True, 'expose_headers': [
'Content-Type',
'Authorization',
'X-Requested-With',
'Accept',
'Origin',
'Access-Control-Request-Method',
'Access-Control-Request-Headers',
'Content-Disposition',
], 'max_age': 600}})
for ex in default_exceptions:
app.register_error_handler(ex, self.__handle_error)
app.route('/api/models')(self.list_models)
app.route('/api/conversations')(self.list_conversations)
app.route('/api/conversations', methods=['DELETE'])(self.clear_conversations)
app.route('/api/conversation/<conversation_id>')(self.get_conversation)
app.route('/api/conversation/<conversation_id>', methods=['DELETE'])(self.del_conversation)
app.route('/api/conversation/<conversation_id>', methods=['PATCH'])(self.set_conversation_title)
app.route('/api/conversation/gen_title/<conversation_id>', methods=['POST'])(self.gen_conversation_title)
app.route('/api/conversation/talk', methods=['POST'])(self.talk)
app.route('/api/conversation/regenerate', methods=['POST'])(self.regenerate)
app.route('/api/conversation/goon', methods=['POST'])(self.goon)
app.route('/api/auth/session')(self.session)
app.route('/api/accounts/check')(self.check)
app.route('/_next/data/olf4sv64FWIcQ_zCGl90t/chat.json')(self.chat_info)
app.route('/')(self.chat)
app.route('/chat')(self.chat)
app.route('/chat/<conversation_id>')(self.chat)
if not self.debug:
self.logger.warning('Serving on http://{}:{}'.format(host, port))
WSGIRequestHandler.protocol_version = 'HTTP/1.1'
serve(app, host=host, port=port, ident=None, threads=threads)
@staticmethod
def __after_request(resp):
resp.headers['X-Server'] = 'pandora/{}'.format(__version__)
return resp
def __parse_bind(self, bind_str):
sections = bind_str.split(':', 2)
if len(sections) < 2:
try:
port = int(sections[0])
return self.__default_ip, port
except ValueError:
return sections[0], self.__default_port
return sections[0], int(sections[1])
def __handle_error(self, e):
self.logger.error(e)
return make_response(jsonify({
'code': e.code,
'message': str(e.original_exception if self.debug and hasattr(e, 'original_exception') else e.name)
}), 500)
@staticmethod
def __set_cookie(resp, token_key, max_age):
resp.set_cookie('token-key', token_key, max_age=max_age, path='/', domain=None, httponly=True, samesite='Lax')
@staticmethod
def __get_token_key():
return request.headers.get('X-Use-Token', request.cookies.get('token-key'))
def chat(self, conversation_id=None):
query = {'chatId': [conversation_id]} if conversation_id else {}
token_key = request.args.get('token')
rendered = render_template('chat.html', pandora_base=request.url_root.strip('/'), query=query)
resp = make_response(rendered)
if token_key:
self.__set_cookie(resp, token_key, timedelta(days=30))
return resp
@staticmethod
def session():
ret = {
'user': {
'id': 'user-000000000000000000000000',
'name': 'admin@openai.com',
'email': 'admin@openai.com',
'image': None,
'picture': None,
'groups': []
},
'expires': '2089-08-08T23:59:59.999Z',
'accessToken': 'secret',
}
return jsonify(ret)
@staticmethod
def chat_info():
ret = {
'pageProps': {
'user': {
'id': 'user-000000000000000000000000',
'name': 'admin@openai.com',
'email': 'admin@openai.com',
'image': None,
'picture': None,
'groups': []
},
'serviceStatus': {},
'userCountry': 'US',
'geoOk': True,
'serviceAnnouncement': {
'paid': {},
'public': {}
},
'isUserInCanPayGroup': True
},
'__N_SSP': True
}
return jsonify(ret)
@staticmethod
def check():
ret = {
'account_plan': {
'is_paid_subscription_active': True,
'subscription_plan': 'chatgptplusplan',
'account_user_role': 'account-owner',
'was_paid_customer': True,
'has_customer_object': True,
'subscription_expires_at_timestamp': 3774355199
},
'user_country': 'US',
'features': [
'model_switcher',
'dfw_message_feedback',
'dfw_inline_message_regen_comparison',
'model_preview',
'system_message',
'can_continue',
],
}
return jsonify(ret)
def list_models(self):
return self.__proxy_result(self.chatgpt.list_models(True, self.__get_token_key()))
def list_conversations(self):
offset = request.args.get('offset', '0')
limit = request.args.get('limit', '20')
return self.__proxy_result(self.chatgpt.list_conversations(offset, limit, True, self.__get_token_key()))
def get_conversation(self, conversation_id):
return self.__proxy_result(self.chatgpt.get_conversation(conversation_id, True, self.__get_token_key()))
def del_conversation(self, conversation_id):
return self.__proxy_result(self.chatgpt.del_conversation(conversation_id, True, self.__get_token_key()))
def clear_conversations(self):
return self.__proxy_result(self.chatgpt.clear_conversations(True, self.__get_token_key()))
def set_conversation_title(self, conversation_id):
title = request.json['title']
return self.__proxy_result(
self.chatgpt.set_conversation_title(conversation_id, title, True, self.__get_token_key()))
def gen_conversation_title(self, conversation_id):
payload = request.json
model = payload['model']
message_id = payload['message_id']
return self.__proxy_result(
self.chatgpt.gen_conversation_title(conversation_id, model, message_id, True, self.__get_token_key()))
def talk(self):
payload = request.json
prompt = payload['prompt']
model = payload['model']
message_id = payload['message_id']
parent_message_id = payload['parent_message_id']
conversation_id = payload.get('conversation_id')
stream = payload.get('stream', True)
return self.__process_stream(
*self.chatgpt.talk(prompt, model, message_id, parent_message_id, conversation_id, stream,
self.__get_token_key()), stream)
def goon(self):
payload = request.json
model = payload['model']
parent_message_id = payload['parent_message_id']
conversation_id = payload.get('conversation_id')
stream = payload.get('stream', True)
return self.__process_stream(
*self.chatgpt.goon(model, parent_message_id, conversation_id, stream, self.__get_token_key()), stream)
def regenerate(self):
payload = request.json
conversation_id = payload.get('conversation_id')
if not conversation_id:
return self.talk()
prompt = payload['prompt']
model = payload['model']
message_id = payload['message_id']
parent_message_id = payload['parent_message_id']
stream = payload.get('stream', True)
return self.__process_stream(
*self.chatgpt.regenerate_reply(prompt, model, conversation_id, message_id, parent_message_id, stream,
self.__get_token_key()), stream)
@staticmethod
def __process_stream(status, headers, generator, stream):
if stream:
return Response(API.wrap_stream_out(generator, status), mimetype=headers['Content-Type'], status=status)
last_json = None
for json in generator:
last_json = json
return make_response(last_json, status)
@staticmethod
def __proxy_result(remote_resp):
resp = make_response(remote_resp.text)
resp.content_type = remote_resp.headers['Content-Type']
resp.status_code = remote_resp.status_code
return resp