diff --git a/common/extensions.py b/common/extensions.py index bf6bd1c..f706f24 100644 --- a/common/extensions.py +++ b/common/extensions.py @@ -5,6 +5,7 @@ from flask_security import Security from flask_mailman import Mail from flask_login import LoginManager from flask_cors import CORS +from flask_socketio import SocketIO # Create extensions @@ -15,3 +16,4 @@ security = Security() mail = Mail() login_manager = LoginManager() cors = CORS() +socketio = SocketIO() diff --git a/common/models/interaction.py b/common/models/interaction.py index e69de29..dd10559 100644 --- a/common/models/interaction.py +++ b/common/models/interaction.py @@ -0,0 +1,37 @@ +from ..extensions import db +from .user import User, Tenant + + +class ChatSession(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True) + session_start = db.Column(db.DateTime, nullable=False) + session_end = db.Column(db.DateTime, nullable=True) + + # Relations + chat_interactions = db.relationship('Interaction', backref='chat_session', lazy=True) + user = db.relationship('User', backref='chat_sessions', lazy=True) + + def __repr__(self): + return f"" + + +class Interaction(db.Model): + id = db.Column(db.Integer, primary_key=True) + chat_session_id = db.Column(db.Integer, db.ForeignKey('public.chat_session.id'), nullable=False) + question = db.Column(db.Text, nullable=False) + answer = db.Column(db.Text, nullable=True) + language = db.Column(db.String(2), nullable=False) + appreciation = db.Column(db.Integer, nullable=True, default=100) + + # Timing information + question_at = db.Column(db.DateTime, nullable=False) + answer_at = db.Column(db.DateTime, nullable=True) + + def __repr__(self): + return f"" + + +class InteractionEmbedding(db.Model): + interaction_id = db.Column(db.Integer, db.ForeignKey('interaction.id', ondelete='CASCADE'), primary_key=True) + embedding_id = db.Column(db.Integer, db.ForeignKey('embedding.id', ondelete='CASCADE'), primary_key=True) diff --git a/config/config.py b/config/config.py index 3bd71a8..a627edc 100644 --- a/config/config.py +++ b/config/config.py @@ -63,6 +63,9 @@ class Config(object): Text is delimited between triple backquotes. ```{text}```""" + # SocketIO settings + SOCKETIO_ASYNC_MODE = 'gevent' + class DevConfig(Config): DEVELOPMENT = True @@ -92,6 +95,17 @@ class DevConfig(Config): UNSTRUCTURED_BASE_URL = 'https://flowitbv-16c4us0m.api.unstructuredapp.io' UNSTRUCTURED_FULL_URL = 'https://flowitbv-16c4us0m.api.unstructuredapp.io/general/v0/general' + # SocketIO settings + SOCKETIO_MESSAGE_QUEUE = 'redis://localhost:6379/1' + SOCKETIO_CORS_ALLOWED_ORIGINS = '*' + SOCKETIO_LOGGER = True + SOCKETIO_ENGINEIO_LOGGER = True + + # Google Cloud settings + GC_PROJECT_NAME = 'EveAI' + GC_KEY_RING = 'eveai-chat' + GC_CRYPTO_KEY = 'envelope-encryption-key' + class ProdConfig(Config): DEVELOPMENT = False diff --git a/config/logging_config.py b/config/logging_config.py index 5cf2fb5..9156dfa 100644 --- a/config/logging_config.py +++ b/config/logging_config.py @@ -18,6 +18,14 @@ LOGGING = { 'backupCount': 10, 'formatter': 'standard', }, + 'file_chat': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': 'logs/eveai_chat.log', + 'maxBytes': 1024*1024*5, # 5MB + 'backupCount': 10, + 'formatter': 'standard', + }, 'console': { 'class': 'logging.StreamHandler', 'level': 'DEBUG', @@ -40,6 +48,11 @@ LOGGING = { 'level': 'DEBUG', 'propagate': False }, + 'eveai_chat': { # logger for the eveai_chat + 'handlers': ['file_chat', 'console'], + 'level': 'DEBUG', + 'propagate': False + }, '': { # root logger 'handlers': ['console'], 'level': 'WARNING', # Set higher level for root to minimize noise diff --git a/eveai_app/__init__.py b/eveai_app/__init__.py index 7b1520a..9ffdfa6 100644 --- a/eveai_app/__init__.py +++ b/eveai_app/__init__.py @@ -7,7 +7,7 @@ from werkzeug.middleware.proxy_fix import ProxyFix import logging.config from common.extensions import db, migrate, bootstrap, security, mail, login_manager, cors -from common.models.user import User, Role +from common.models.user import User, Role, Tenant, TenantDomain from config.logging_config import LOGGING from common.utils.security import set_tenant_session_data from .errors import register_error_handlers diff --git a/eveai_chat/__init__.py b/eveai_chat/__init__.py new file mode 100644 index 0000000..de147f4 --- /dev/null +++ b/eveai_chat/__init__.py @@ -0,0 +1,33 @@ +import logging +import logging.config +from flask import Flask +from flask_socketio import emit + +from common.extensions import db, socketio +from config.logging_config import LOGGING + + +def create_app(config_file=None): + app = Flask(__name__) + + if config_file is None: + app.config.from_object('config.config.DevConfig') + else: + app.config.from_object(config_file) + + logging.config.dictConfig(LOGGING) + register_extensions(app) + + return app + + +def register_extensions(app): + db.init_app(app) + socketio.init_app(app, + message_queue=app.config.get('SOCKETIO_MESSAGE_QUEUE'), + cors_allowed_origins=app.config.get('SOCKETIO_CORS_ALLOWED_ORIGINS'), + async_mode=app.config.get('SOCKETIO_ASYNC_MODE'), + logger=app.config.get('SOCKETIO_LOGGER'), + engineio_logger=app.config.get('SOCKETIO_ENGINEIO_LOGGER'), + ) + diff --git a/eveai_chat/views/chat_views.py b/eveai_chat/views/chat_views.py new file mode 100644 index 0000000..4e9d549 --- /dev/null +++ b/eveai_chat/views/chat_views.py @@ -0,0 +1,34 @@ +# from . import user_bp +import uuid +from datetime import datetime as dt, timezone as tz +from flask import request, redirect, url_for, flash, render_template, Blueprint, session, current_app +from flask_security import hash_password, roles_required, roles_accepted +from sqlalchemy.exc import SQLAlchemyError + +from common.models.user import User, Tenant +from common.models.interaction import ChatSession, Interaction, InteractionEmbedding +from common.models.document import Embedding +from common.extensions import db, socketio +from common.utils.database import Database + +chat_bp = Blueprint('chat_bp', __name__, url_prefix='/chat') + + +@chat_bp.route('/', methods=['GET', 'POST']) +def chat(): + return render_template('chat.html') + + +@chat.record_once +def on_register(state): + # TODO: write initialisation code when the blueprint is registered (only once) + # socketio.init_app(state.app) + pass + + +@socketio.on('message', namespace='/chat') +def handle_message(message): + # TODO: write message handling code to actually realise chat + # print('Received message:', message) + # socketio.emit('response', {'data': message}, namespace='/chat') + pass diff --git a/requirements.txt b/requirements.txt index 05feb11..6c24e9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ celery~=5.4.0 kombu~=5.3.7 langchain~=0.1.17 requests~=2.31.0 -beautifulsoup4~=4.12.3 \ No newline at end of file +beautifulsoup4~=4.12.3 +google~=3.0.0 \ No newline at end of file diff --git a/scripts/run_eveai_chat.py b/scripts/run_eveai_chat.py new file mode 100644 index 0000000..c7a0a71 --- /dev/null +++ b/scripts/run_eveai_chat.py @@ -0,0 +1,14 @@ +from eveai_chat import create_app +from gevent.pywsgi import WSGIServer +from geventwebsocket.handler import WebSocketHandler + + +app = create_app() + +if __name__ == '__main__': + print("Server starting on port 5001") + http_server = WSGIServer(('0.0.0.0', 5001), app, handler_class=WebSocketHandler) + http_server.serve_forever() # Continuously listens for incoming requests + + + diff --git a/scripts/start_eveai_chat.sh b/scripts/start_eveai_chat.sh new file mode 100755 index 0000000..ea2dc88 --- /dev/null +++ b/scripts/start_eveai_chat.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +cd "/Volumes/OWC4M2_1/Dropbox/Josako's Dev/Josako/EveAI/Development/eveAI/" || exit 1 +source "/Volumes/OWC4M2_1/Dropbox/Josako's Dev/Josako/EveAI/Development/eveAI/.venv/bin/activate" + +export PYTHONPATH="$PYTHONPATH:/Volumes/OWC4M2_1/Dropbox/Josako's Dev/Josako/EveAI/Development/eveAI/" + +# Set flask environment variables +export FLASK_ENV=development # Use 'production' as appropriate +export FLASK_DEBUG=1 # Use 0 for production + +# Start Flask app +python scripts/run_eveai_chat.py + +deactivate \ No newline at end of file