diff --git a/.DS_Store b/.DS_Store index 3671ca5..c678337 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..52d3159 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +docker/db/postgresql/ +docker/db/redis/ +docker/logs/ +docker/tenant_files/ diff --git a/common/utils/view_assistants.py b/common/utils/view_assistants.py index 6780fa2..3d94a8f 100644 --- a/common/utils/view_assistants.py +++ b/common/utils/view_assistants.py @@ -43,4 +43,8 @@ def form_validation_failed(request, form): if request.method == 'POST': for fieldName, errorMessages in form.errors.items(): for err in errorMessages: - flash(f"Error in {fieldName}: {err}", 'danger') \ No newline at end of file + flash(f"Error in {fieldName}: {err}", 'danger') + + +def form_to_dict(form): + return {field.name: field.data for field in form if field.name != 'csrf_token' and hasattr(field, 'data')} \ No newline at end of file diff --git a/config/config.py b/config/config.py index bdc07d0..b7462e3 100644 --- a/config/config.py +++ b/config/config.py @@ -169,7 +169,7 @@ class DevConfig(Config): MAIL_PASSWORD = '$6xsWGbNtx$CFMQZqc*' # file upload settings - UPLOAD_FOLDER = '/Volumes/OWC4M2_1/Development/eveAI/file_store' + UPLOAD_FOLDER = '/app/tenant_files' # Celery settings # eveai_app Redis Settings @@ -192,6 +192,10 @@ class DevConfig(Config): SOCKETIO_CORS_ALLOWED_ORIGINS = '*' SOCKETIO_LOGGER = True SOCKETIO_ENGINEIO_LOGGER = True + SOCKETIO_PING_TIMEOUT = 20000 + SOCKETIO_PING_INTERVAL = 25000 + SOCKETIO_MAX_IDLE_TIME = timedelta(minutes=60) # Changing this value ==> change maxConnectionDuration value in + # eveai-chat-widget.js # Google Cloud settings GC_PROJECT_NAME = 'eveai-420711' diff --git a/docker/.DS_Store b/docker/.DS_Store index b78cc15..b268a9d 100644 Binary files a/docker/.DS_Store and b/docker/.DS_Store differ diff --git a/docker/compose.yaml b/docker/compose.yaml index c8fb89f..30822df 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -39,6 +39,7 @@ services: - ../migrations:/app/migrations - ../scripts:/app/scripts - ./logs:/app/logs + - ./tenant_files:/app/tenant_files depends_on: db: condition: service_healthy @@ -66,6 +67,7 @@ services: - ../config:/app/config - ../scripts:/app/scripts - ./logs:/app/logs + - ./tenant_files:/app/tenant_files depends_on: db: condition: service_healthy @@ -103,6 +105,7 @@ services: interval: 10s timeout: 5s retries: 5 + command: ["sh", "-c", "scripts/start_eveai_chat.sh"] eveai_chat_workers: build: @@ -162,9 +165,10 @@ services: interval: 10s timeout: 5s retries: 5 -volumes: - db-data: - redis-data: +#volumes: +# db-data: +# redis-data: +# tenant-files: #secrets: # db-password: # file: ./db/password.txt diff --git a/docker/db/.DS_Store b/docker/db/.DS_Store index 0d7af7f..ed1ea85 100644 Binary files a/docker/db/.DS_Store and b/docker/db/.DS_Store differ diff --git a/docker/reload-nginx.sh b/docker/reload-nginx.sh new file mode 100755 index 0000000..6d922eb --- /dev/null +++ b/docker/reload-nginx.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Check if the service name is provided +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +SERVICE_NAME=$1 + +# Get the container ID of the service +CONTAINER_ID=$(docker-compose ps -q $SERVICE_NAME) + +# Check if the container ID is found +if [ -z "$CONTAINER_ID" ]; then + echo "Service $SERVICE_NAME not found or not running." + exit 1 +fi + +# Reload Nginx inside the container +docker exec $CONTAINER_ID nginx -s reload + +# Output the result +if [ $? -eq 0 ]; then + echo "Nginx reloaded successfully in $SERVICE_NAME." +else + echo "Failed to reload Nginx in $SERVICE_NAME." + exit 1 +fi \ No newline at end of file diff --git a/eveai_app/templates/document/edit_document_language.html b/eveai_app/templates/document/edit_document_version.html similarity index 66% rename from eveai_app/templates/document/edit_document_language.html rename to eveai_app/templates/document/edit_document_version.html index 73d68e6..cdc3888 100644 --- a/eveai_app/templates/document/edit_document_language.html +++ b/eveai_app/templates/document/edit_document_version.html @@ -1,9 +1,9 @@ {% extends "base.html" %} {% from "macros.html" import render_field %} -{% block title %}Update Document Language{% endblock %} +{% block title %}Update Document Version{% endblock %} -{% block content_title %}Update Document Language{% endblock %} -{% block content_description %}Update document language for {{ doc_details }}.{% endblock %} +{% block content_title %}Update Document Version{% endblock %} +{% block content_description %}Update document version for {{ doc_details }}.{% endblock %} {% block content %}
@@ -13,6 +13,6 @@ {% for field in form %} {{ render_field(field, disabled_fields, exclude_fields) }} {% endfor %} - +
{% endblock %} diff --git a/eveai_app/templates/user/tenant_overview.html b/eveai_app/templates/user/tenant_overview.html index e35a663..1145fb2 100644 --- a/eveai_app/templates/user/tenant_overview.html +++ b/eveai_app/templates/user/tenant_overview.html @@ -62,7 +62,14 @@ {{ render_included_field(field, disabled_fields=license_fields, include_fields=license_fields) }} {% endfor %} - + + +
@@ -89,26 +96,6 @@
- - {% endblock %} @@ -118,78 +105,88 @@ {% block scripts %} - + + }); + {% endblock %} \ No newline at end of file diff --git a/eveai_app/views/document_views.py b/eveai_app/views/document_views.py index 79f6935..bd42f00 100644 --- a/eveai_app/views/document_views.py +++ b/eveai_app/views/document_views.py @@ -21,7 +21,7 @@ from .document_forms import AddDocumentForm, AddURLForm, EditDocumentForm, EditD from common.utils.middleware import mw_before_request from common.utils.celery_utils import current_celery from common.utils.nginx_utils import prefixed_url_for -from common.utils.view_assistants import form_validation_failed, prepare_table_for_macro +from common.utils.view_assistants import form_validation_failed, prepare_table_for_macro, form_to_dict document_bp = Blueprint('document_bp', __name__, url_prefix='/document') @@ -60,8 +60,9 @@ def add_document(): file = form.file.data filename = secure_filename(file.filename) extension = filename.rsplit('.', 1)[1].lower() + form_dict = form_to_dict(form) - new_doc, new_doc_vers = create_document_stack(form, file, filename, extension) + new_doc, new_doc_vers = create_document_stack(form_dict, file, filename, extension) task = current_celery.send_task('create_embeddings', queue='embeddings', args=[ session['tenant']['id'], @@ -90,6 +91,12 @@ def add_url(): current_app.logger.info(f'Adding document for tenant {session["tenant"]["id"]}') url = form.url.data + doc_vers = DocumentVersion.query.filter_by(url=url).all() + if doc_vers: + current_app.logger.info(f'A document with url {url} already exists. No new document created.') + flash(f'A document with url {url} already exists. No new document created.', 'info') + return redirect(prefixed_url_for('document_bp.documents')) + # Only when no document with URL exists html = fetch_html(url) file = io.BytesIO(html) @@ -101,8 +108,9 @@ def add_url(): if not filename.endswith('.html'): filename += '.html' extension = 'html' + form_dict = form_to_dict(form) - new_doc, new_doc_vers = create_document_stack(form, file, filename, extension) + new_doc, new_doc_vers = create_document_stack(form_dict, file, filename, extension) task = current_celery.send_task('create_embeddings', queue='embeddings', args=[ session['tenant']['id'], @@ -373,7 +381,7 @@ def create_document_stack(form, file, filename, extension): new_doc = create_document(form, filename) # Create the DocumentVersion - new_doc_vers = create_version_for_document(new_doc, form.url.data, form.language.data, form.user_context.data) + new_doc_vers = create_version_for_document(new_doc, form.get('url', ''), form['language'], form['user_context']) try: db.session.add(new_doc) @@ -404,13 +412,13 @@ def log_session_state(session, msg=""): def create_document(form, filename): new_doc = Document() - if form.name.data == '': + if form['name'] == '': new_doc.name = filename.rsplit('.', 1)[0] else: - new_doc.name = form.name.data + new_doc.name = form['name'] - if form.valid_from.data or form.valid_from.data != '': - new_doc.valid_from = form.valid_from.data + if form['valid_from'] and form['valid_from'] != '': + new_doc.valid_from = form['valid_from'] else: new_doc.valid_from = dt.now(tz.utc) new_doc.tenant_id = session['tenant']['id'] diff --git a/eveai_app/views/user_views.py b/eveai_app/views/user_views.py index 9bf5f91..bf6e548 100644 --- a/eveai_app/views/user_views.py +++ b/eveai_app/views/user_views.py @@ -430,7 +430,7 @@ def generate_chat_api_key(): db.session.rollback() current_app.logger.error(f'Unable to store api key for tenant {tenant.id}. Error: {str(e)}') - return jsonify({'new_api_key': 'API key generated successfully.', 'api_key': new_api_key}), 200 + return jsonify({'api_key': new_api_key}), 200 @user_bp.route('/tenant_overview', methods=['GET']) diff --git a/eveai_chat/__init__.py b/eveai_chat/__init__.py index d332baf..e8471c2 100644 --- a/eveai_chat/__init__.py +++ b/eveai_chat/__init__.py @@ -50,7 +50,9 @@ def register_extensions(app): async_mode=app.config.get('SOCKETIO_ASYNC_MODE'), logger=app.config.get('SOCKETIO_LOGGER'), engineio_logger=app.config.get('SOCKETIO_ENGINEIO_LOGGER'), - path='/socket.io' + path='/socket.io', + ping_timeout=app.config.get('SOCKETIO_PING_TIMEOUT'), + ping_interval=app.config.get('SOCKETIO_PING_INTERVAL'), ) jwt.init_app(app) kms_client.init_app(app) diff --git a/eveai_chat/socket_handlers/chat_handler.py b/eveai_chat/socket_handlers/chat_handler.py index 27f065b..812209e 100644 --- a/eveai_chat/socket_handlers/chat_handler.py +++ b/eveai_chat/socket_handlers/chat_handler.py @@ -4,6 +4,7 @@ from flask_jwt_extended import create_access_token, get_jwt_identity, verify_jwt from flask_socketio import emit, disconnect from flask import current_app, request, session from sqlalchemy.exc import SQLAlchemyError +from datetime import datetime, timedelta from common.extensions import socketio, kms_client, db from common.models.user import Tenant @@ -35,6 +36,8 @@ def handle_connect(): if 'session_id' not in session: session['session_id'] = str(uuid.uuid4()) + session['last_activity'] = datetime.now() + # Communicate connection to client current_app.logger.debug(f'SocketIO: Connection handling sending status to client for tenant {tenant_id}') emit('connect', {'status': 'Connected', 'tenant_id': tenant_id}) @@ -53,11 +56,21 @@ def handle_disconnect(): current_app.logger.debug('SocketIO: Client disconnected') +@socketio.on('heartbeat') +def handle_heartbeat(): + current_app.logger.debug('SocketIO: Heartbeat received') + last_activity = session.get('last_activity') + if datetime.now() - last_activity > current_app.config.get('SOCKETIO_MAX_IDLE_TIME'): + current_app.logger.debug('SocketIO: Heartbeat timed out, connection closed') + disconnect() + + @socketio.on('user_message') def handle_message(data): try: current_app.logger.debug(f"SocketIO: Message handling received message from tenant {data['tenantId']}: " f"{data['message']} with token {data['token']}") + session['last_activity'] = datetime.now() current_tenant_id = validate_incoming_data(data) diff --git a/nginx/.DS_Store b/nginx/.DS_Store index a549e0b..96e4f84 100644 Binary files a/nginx/.DS_Store and b/nginx/.DS_Store differ diff --git a/nginx/public/chat_ae.html b/nginx/public/chat_ae.html index 6199763..1edae3e 100644 --- a/nginx/public/chat_ae.html +++ b/nginx/public/chat_ae.html @@ -7,19 +7,19 @@ - - - + + +