diff --git a/common/templates/error/401.html b/common/templates/error/401.html
new file mode 100644
index 0000000..f9b8f21
--- /dev/null
+++ b/common/templates/error/401.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+ Unauthorized
+
+
+
+
+ Not authorized
+ Your session may have expired or this action is not permitted.
+ Go to home
+
+
+
diff --git a/common/templates/error/403.html b/common/templates/error/403.html
new file mode 100644
index 0000000..3e40bbb
--- /dev/null
+++ b/common/templates/error/403.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+ Forbidden
+
+
+
+
+ Access forbidden
+ You don't have permission to access this resource.
+ Go to home
+
+
+
diff --git a/common/templates/error/404.html b/common/templates/error/404.html
new file mode 100644
index 0000000..def72a8
--- /dev/null
+++ b/common/templates/error/404.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+ Page not found
+
+
+
+
+ Page not found
+ The page you are looking for doesn’t exist or has been moved.
+ Go to home
+
+
+
diff --git a/common/templates/error/500.html b/common/templates/error/500.html
new file mode 100644
index 0000000..0e58f10
--- /dev/null
+++ b/common/templates/error/500.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+ Something went wrong
+
+
+
+
+ We’re sorry — something went wrong
+ Please try again later. If the issue persists, contact support.
+ Go to home
+
+
+
diff --git a/common/templates/error/generic.html b/common/templates/error/generic.html
new file mode 100644
index 0000000..a9c2f32
--- /dev/null
+++ b/common/templates/error/generic.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+ Error
+
+
+
+
+ Oops! Something went wrong
+ Please try again. If the issue persists, contact support.
+ Go to home
+
+
+
diff --git a/common/utils/errors.py b/common/utils/errors.py
index 8578236..7f6d76c 100644
--- a/common/utils/errors.py
+++ b/common/utils/errors.py
@@ -10,41 +10,54 @@ from common.utils.nginx_utils import prefixed_url_for
def not_found_error(error):
- if not current_user.is_authenticated:
- return redirect(prefixed_url_for('security.login'))
+ profile = current_app.config.get('ERRORS_PROFILE', 'web_app')
+ if profile == 'web_app':
+ if not current_user.is_authenticated:
+ return redirect(prefixed_url_for('security.login', for_redirect=True))
current_app.logger.error(f"Not Found Error: {error}")
current_app.logger.error(traceback.format_exc())
return render_template('error/404.html'), 404
def internal_server_error(error):
- if not current_user.is_authenticated:
- return redirect(prefixed_url_for('security.login'))
+ profile = current_app.config.get('ERRORS_PROFILE', 'web_app')
+ if profile == 'web_app':
+ if not current_user.is_authenticated:
+ return redirect(prefixed_url_for('security.login', for_redirect=True))
current_app.logger.error(f"Internal Server Error: {error}")
current_app.logger.error(traceback.format_exc())
return render_template('error/500.html'), 500
def not_authorised_error(error):
- if not current_user.is_authenticated:
- return redirect(prefixed_url_for('security.login'))
+ profile = current_app.config.get('ERRORS_PROFILE', 'web_app')
+ if profile == 'web_app':
+ if not current_user.is_authenticated:
+ return redirect(prefixed_url_for('security.login', for_redirect=True))
current_app.logger.error(f"Not Authorised Error: {error}")
current_app.logger.error(traceback.format_exc())
- return render_template('error/401.html')
+ return render_template('error/401.html'), 401
def access_forbidden(error):
- if not current_user.is_authenticated:
- return redirect(prefixed_url_for('security.login'))
+ profile = current_app.config.get('ERRORS_PROFILE', 'web_app')
+ if profile == 'web_app':
+ if not current_user.is_authenticated:
+ return redirect(prefixed_url_for('security.login', for_redirect=True))
current_app.logger.error(f"Access Forbidden: {error}")
current_app.logger.error(traceback.format_exc())
- return render_template('error/403.html')
+ return render_template('error/403.html'), 403
def key_error_handler(error):
+ profile = current_app.config.get('ERRORS_PROFILE', 'web_app')
# Check if the KeyError is specifically for 'tenant'
if str(error) == "'tenant'":
- return redirect(prefixed_url_for('security.login'))
+ if profile == 'web_app':
+ return redirect(prefixed_url_for('security.login', for_redirect=True))
+ else:
+ current_app.logger.warning("Session tenant missing in chat_client context")
+ return render_template('error/401.html'), 401
# For other KeyErrors, you might want to log the error and return a generic error page
current_app.logger.error(f"Key Error: {error}")
current_app.logger.error(traceback.format_exc())
@@ -79,19 +92,24 @@ def no_tenant_selected_error(error):
"""Handle errors when no tenant is selected in the current session.
This typically happens when a session expires or becomes invalid after
- a long period of inactivity. The user will be redirected to the login page.
+ a long period of inactivity. The user will be redirected to the login page (web_app)
+ or shown an error page (chat_client).
"""
+ profile = current_app.config.get('ERRORS_PROFILE', 'web_app')
current_app.logger.error(f"No Session Tenant Error: {error}")
current_app.logger.error(traceback.format_exc())
flash('Your session expired. You will have to re-enter your credentials', 'warning')
- # Perform logout if user is authenticated
- if current_user.is_authenticated:
- from flask_security.utils import logout_user
- logout_user()
-
- # Redirect to login page
- return redirect(prefixed_url_for('security.login'))
+ if profile == 'web_app':
+ # Perform logout if user is authenticated
+ if current_user.is_authenticated:
+ from flask_security.utils import logout_user
+ logout_user()
+ # Redirect to login page
+ return redirect(prefixed_url_for('security.login', for_redirect=True))
+ else:
+ # chat_client: render 401 page
+ return render_template('error/401.html'), 401
def general_exception(e):
@@ -122,7 +140,10 @@ def template_syntax_error(error):
error_details=f"Error in template '{error.filename}' at line {error.lineno}: {error.message}"), 500
-def register_error_handlers(app):
+def register_error_handlers(app, profile: str = 'web_app'):
+ # Store profile in app config to drive handler behavior
+ app.config['ERRORS_PROFILE'] = profile
+
app.register_error_handler(404, not_found_error)
app.register_error_handler(500, internal_server_error)
app.register_error_handler(401, not_authorised_error)
diff --git a/config/config.py b/config/config.py
index 3c786af..ea646c4 100644
--- a/config/config.py
+++ b/config/config.py
@@ -363,8 +363,6 @@ class DevConfig(Config):
EXPLAIN_TEMPLATE_LOADING = False
# Define the nginx prefix used for the specific apps
- EVEAI_APP_LOCATION_PREFIX = ''
- EVEAI_CHAT_LOCATION_PREFIX = '/chat'
CHAT_CLIENT_PREFIX = 'chat-client/chat/'
# Define the static path
@@ -391,8 +389,6 @@ class TestConfig(Config):
EXPLAIN_TEMPLATE_LOADING = False
# Define the nginx prefix used for the specific apps
- EVEAI_APP_LOCATION_PREFIX = ''
- EVEAI_CHAT_LOCATION_PREFIX = '/chat'
CHAT_CLIENT_PREFIX = 'chat-client/chat/'
# Define the static path
@@ -419,9 +415,7 @@ class StagingConfig(Config):
EXPLAIN_TEMPLATE_LOADING = False
# Define the nginx prefix used for the specific apps
- EVEAI_APP_LOCATION_PREFIX = ''
- EVEAI_CHAT_LOCATION_PREFIX = ''
- CHAT_CLIENT_PREFIX = ''
+ CHAT_CLIENT_PREFIX = 'chat-client/chat/'
# Define the static path
STATIC_URL = 'https://evie-staging-static.askeveai.com'
@@ -452,11 +446,10 @@ class ProdConfig(Config):
WTF_CSRF_SSL_STRICT = True # Set to True if using HTTPS
# Define the nginx prefix used for the specific apps
- EVEAI_APP_LOCATION_PREFIX = ''
- EVEAI_CHAT_LOCATION_PREFIX = ''
+ EVEAI_CHAT_LOCATION_PREFIX = 'EVEAI_APP_LOCATION_PREFIX'
# Define the static path
- STATIC_URL = 'https://evie-staging-static.askeveai.com'
+ STATIC_URL = 'https://evie-prod-static.askeveai.com'
# PATH settings
ffmpeg_path = '/usr/bin/ffmpeg'
diff --git a/eveai_chat_client/__init__.py b/eveai_chat_client/__init__.py
index db933e9..5968ded 100644
--- a/eveai_chat_client/__init__.py
+++ b/eveai_chat_client/__init__.py
@@ -1,18 +1,20 @@
import logging
import os
-from flask import Flask, jsonify, request, url_for
+from flask import Flask, jsonify, request, url_for, session as flask_session
from werkzeug.middleware.proxy_fix import ProxyFix
import logging.config
+from jinja2 import ChoiceLoader, FileSystemLoader
from common.extensions import (db, bootstrap, cors, csrf, session,
minio_client, simple_encryption, metrics, cache_manager, content_manager)
from common.models.user import Tenant, SpecialistMagicLinkTenant
from config.logging_config import configure_logging
-from eveai_chat_client.utils.errors import register_error_handlers
+from common.utils.errors import register_error_handlers
from common.utils.celery_utils import make_celery, init_celery
from common.utils.template_filters import register_filters
from config.config import get_config
+from common.utils.chat_utils import get_default_chat_customisation
def create_app(config_file=None):
@@ -57,11 +59,23 @@ def create_app(config_file=None):
app.celery = make_celery(app.name, app.config)
init_celery(app.celery, app)
+ # Configure template loader with fallback to common/templates
+ try:
+ import os as _os
+ common_templates_path = _os.path.normpath(_os.path.join(app.root_path, '..', 'common', 'templates'))
+ app.jinja_loader = ChoiceLoader([
+ app.jinja_loader,
+ FileSystemLoader(common_templates_path),
+ ])
+ app.logger.debug(f"Added common templates path: {common_templates_path}")
+ except Exception as e:
+ app.logger.error(f"Failed to configure ChoiceLoader for common templates: {e}")
+
# Register Blueprints
register_blueprints(app)
- # Register Error Handlers
- register_error_handlers(app)
+ # Register Error Handlers (shared, profile-aware)
+ register_error_handlers(app, profile='chat_client')
# Register Cache Handlers
register_cache_handlers(app)
@@ -85,6 +99,19 @@ def create_app(config_file=None):
# Register template filters
register_filters(app)
+ # Always inject chat customisation for templates (safe defaults)
+ @app.context_processor
+ def inject_customisation():
+ try:
+ tm = flask_session.get('tenant_make')
+ options = None
+ if tm and isinstance(tm, dict):
+ options = tm.get('chat_customisation_options')
+ customisation = get_default_chat_customisation(options)
+ except Exception:
+ customisation = get_default_chat_customisation(None)
+ return {'customisation': customisation}
+
app.logger.info(f"EveAI Chat Client Started Successfully (PID: {os.getpid()})")
app.logger.info("-------------------------------------------------------------------------------------------------")
@@ -115,8 +142,7 @@ def register_extensions(app):
def register_blueprints(app):
from .views.chat_views import chat_bp
app.register_blueprint(chat_bp)
- from .views.error_views import error_bp
- app.register_blueprint(error_bp)
+ # Do not register local error blueprint; use shared error handlers from common
from .views.healthz_views import healthz_bp
app.register_blueprint(healthz_bp)
diff --git a/eveai_chat_client/utils/errors.py b/eveai_chat_client/utils/errors.py
index 73035dc..8c2915e 100644
--- a/eveai_chat_client/utils/errors.py
+++ b/eveai_chat_client/utils/errors.py
@@ -9,31 +9,31 @@ from common.utils.eveai_exceptions import EveAINoSessionTenant
def not_found_error(error):
current_app.logger.error(f"Not Found Error: {error}")
current_app.logger.error(traceback.format_exc())
- return render_template('error.html', message="Page not found."), 404
+ return render_template('error/404.html'), 404
def internal_server_error(error):
current_app.logger.error(f"Internal Server Error: {error}")
current_app.logger.error(traceback.format_exc())
- return render_template('error.html', message="Internal server error."), 500
+ return render_template('error/500.html'), 500
def not_authorised_error(error):
current_app.logger.error(f"Not Authorised Error: {error}")
current_app.logger.error(traceback.format_exc())
- return render_template('error.html', message="Not authorized."), 401
+ return render_template('error/401.html'), 401
def access_forbidden(error):
current_app.logger.error(f"Access Forbidden: {error}")
current_app.logger.error(traceback.format_exc())
- return render_template('error.html', message="Access forbidden."), 403
+ return render_template('error/403.html'), 403
def key_error_handler(error):
current_app.logger.error(f"Key Error: {error}")
current_app.logger.error(traceback.format_exc())
- return render_template('error.html', message="An unexpected error occurred."), 500
+ return render_template('error/500.html'), 500
def attribute_error_handler(error):
@@ -41,7 +41,7 @@ def attribute_error_handler(error):
error_msg = str(error)
current_app.logger.error(f"AttributeError: {error_msg}")
current_app.logger.error(traceback.format_exc())
- return render_template('error.html', message="An application error occurred."), 500
+ return render_template('error/500.html'), 500
def no_tenant_selected_error(error):
diff --git a/eveai_chat_client/views/chat_views.py b/eveai_chat_client/views/chat_views.py
index e14013e..39abd02 100644
--- a/eveai_chat_client/views/chat_views.py
+++ b/eveai_chat_client/views/chat_views.py
@@ -37,9 +37,7 @@ def log_after_request(response):
@chat_bp.route('/')
def index():
- customisation = get_default_chat_customisation()
- return render_template('error.html', message="Please use a valid magic link to access the chat.",
- customisation=customisation)
+ return render_template('error/404.html'), 404
@chat_bp.route('/')
@@ -53,14 +51,14 @@ def chat(magic_link_code):
if not magic_link_tenant:
current_app.logger.error(f"Invalid magic link code: {magic_link_code}")
- return render_template('error.html', message="Invalid magic link code.")
+ return render_template('error/404.html'), 404
# Get tenant information
tenant_id = magic_link_tenant.tenant_id
tenant = Tenant.query.get(tenant_id)
if not tenant:
current_app.logger.error(f"Tenant not found for ID: {tenant_id}")
- return render_template('error.html', message="Tenant not found.")
+ return render_template('error/404.html'), 404
# Switch to tenant schema
Database(tenant_id).switch_schema()
@@ -68,19 +66,19 @@ def chat(magic_link_code):
specialist_ml = SpecialistMagicLink.query.filter_by(magic_link_code=magic_link_code).first()
if not specialist_ml:
current_app.logger.error(f"Specialist magic link not found in tenant schema: {tenant_id}")
- return render_template('error.html', message="Specialist configuration not found.")
+ return render_template('error/404.html'), 404
# Get relevant TenantMake
tenant_make = TenantMake.query.get(specialist_ml.tenant_make_id)
if not tenant_make:
current_app.logger.error(f"Tenant make not found: {specialist_ml.tenant_make_id}")
- return render_template('error.html', message="Tenant make not found.")
+ return render_template('error/500.html'), 500
# Get specialist details
specialist = Specialist.query.get(specialist_ml.specialist_id)
if not specialist:
current_app.logger.error(f"Specialist not found: {specialist_ml.specialist_id}")
- return render_template('error.html', message="Specialist not found.")
+ return render_template('error/404.html'), 404
# Store necessary information in session
session['tenant'] = tenant.to_dict()
@@ -124,7 +122,7 @@ def chat(magic_link_code):
except Exception as e:
current_app.logger.error(f"Error in chat view: {str(e)}", exc_info=True)
- return render_template('error.html', message="An error occurred while setting up the chat.")
+ return render_template('error/500.html'), 500
@chat_bp.route('/api/send_message', methods=['POST'])
diff --git a/eveai_chat_workers/__init__.py b/eveai_chat_workers/__init__.py
index e2dd4fc..154db5c 100644
--- a/eveai_chat_workers/__init__.py
+++ b/eveai_chat_workers/__init__.py
@@ -28,18 +28,19 @@ def create_app(config_file=None):
configure_logging()
- app.logger.info('Starting up eveai_chat_workers...')
register_extensions(app)
+ register_cache_handlers(app)
+
from . import specialists, retrievers
celery = make_celery(app.name, app.config)
init_celery(celery, app)
- register_cache_handlers(app)
+ from . import tasks
- from eveai_chat_workers import tasks
- print(tasks.tasks_ping())
+ app.logger.info("EveAI Worker Server Started Successfully")
+ app.logger.info("-------------------------------------------------------------------------------------------------")
return app, celery
diff --git a/eveai_chat_workers/tasks.py b/eveai_chat_workers/tasks.py
index 3140101..b3411ea 100644
--- a/eveai_chat_workers/tasks.py
+++ b/eveai_chat_workers/tasks.py
@@ -5,6 +5,7 @@ import traceback
from flask import current_app
from celery import states
from sqlalchemy.exc import SQLAlchemyError, InterfaceError, OperationalError
+from redis.exceptions import ConnectionError as RedisConnectionError, TimeoutError as RedisTimeoutError
from common.utils.config_field_types import TaggingFields
from common.utils.database import Database
@@ -21,7 +22,9 @@ from common.utils.execution_progress import ExecutionProgressTracker
# Healthcheck task
-@current_celery.task(name='ping', queue='llm_interactions')
+@current_celery.task(bind=True, name='ping', queue='llm_interactions',
+ autoretry_for=(InterfaceError, OperationalError, RedisConnectionError, RedisTimeoutError, OSError),
+ retry_backoff=True, retry_jitter=True, max_retries=5)
def ping():
return 'pong'
@@ -215,7 +218,9 @@ def prepare_arguments(specialist: Any, arguments: Dict[str, Any]) -> Dict[str, A
raise ArgumentPreparationError(str(e))
-@current_celery.task(bind=True, name='execute_specialist', queue='llm_interactions', autoretry_for=(InterfaceError, OperationalError), retry_backoff=True, retry_jitter=True, max_retries=5)
+@current_celery.task(bind=True, name='execute_specialist', queue='llm_interactions',
+ autoretry_for=(InterfaceError, OperationalError),
+ retry_backoff=True, retry_jitter=True, max_retries=5)
def execute_specialist(self, tenant_id: int, specialist_id: int, arguments: Dict[str, Any],
session_id: str, user_timezone: str) -> dict:
"""
diff --git a/eveai_workers/tasks.py b/eveai_workers/tasks.py
index 3ec8bd1..bddb477 100644
--- a/eveai_workers/tasks.py
+++ b/eveai_workers/tasks.py
@@ -11,6 +11,7 @@ from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from sqlalchemy import or_
from sqlalchemy.exc import SQLAlchemyError, InterfaceError, OperationalError
+from redis.exceptions import ConnectionError as RedisConnectionError, TimeoutError as RedisTimeoutError
import traceback
from common.extensions import db, cache_manager
@@ -32,13 +33,15 @@ from common.utils.config_field_types import json_to_pattern_list
# Healthcheck task
-@current_celery.task(name='ping', queue='embeddings')
+@current_celery.task(bind=True, name='ping', queue='embeddings',
+ autoretry_for=(InterfaceError, OperationalError, RedisConnectionError, RedisTimeoutError, OSError),
+ retry_backoff=True, retry_jitter=True, max_retries=5)
def ping():
return 'pong'
@current_celery.task(bind=True, name='create_embeddings', queue='embeddings',
- autoretry_for=(InterfaceError, OperationalError),
+ autoretry_for=(InterfaceError, OperationalError, RedisConnectionError, RedisTimeoutError, OSError),
retry_backoff=True, retry_jitter=True, max_retries=5)
def create_embeddings(self, tenant_id, document_version_id):
document_version = None