import uuid from flask import Blueprint, render_template, request, session, current_app, jsonify, abort from sqlalchemy.exc import SQLAlchemyError from common.extensions import db from common.models.user import Tenant, SpecialistMagicLinkTenant, TenantMake from common.models.interaction import SpecialistMagicLink, Specialist, ChatSession, Interaction from common.services.interaction.specialist_services import SpecialistServices from common.utils.database import Database from common.utils.chat_utils import get_default_chat_customisation chat_bp = Blueprint('chat_bp', __name__, url_prefix='/chat') @chat_bp.before_request def log_before_request(): current_app.logger.debug(f'Before request: {request.path} =====================================') @chat_bp.after_request def log_after_request(response): return response # @chat_bp.before_request # def before_request(): # try: # mw_before_request() # except Exception as e: # current_app.logger.error(f'Error switching schema in Document Blueprint: {e}') # raise @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) @chat_bp.route('/') def chat(magic_link_code): """ Main chat interface accessed via magic link """ try: # Find the tenant using the magic link code magic_link_tenant = SpecialistMagicLinkTenant.query.filter_by(magic_link_code=magic_link_code).first() 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.") # 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.") # Switch to tenant schema Database(tenant_id).switch_schema() # Get specialist magic link details from tenant schema 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.") # 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.") # 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.") # Store necessary information in session session['tenant'] = tenant.to_dict() session['specialist'] = specialist.to_dict() session['magic_link'] = specialist_ml.to_dict() session['tenant_make'] = tenant_make.to_dict() # Get customisation options with defaults customisation = get_default_chat_customisation(tenant_make.chat_customisation_options) # Start a new chat session session['chat_session_id'] = SpecialistServices.start_session() return render_template('chat.html', tenant=tenant, tenant_make=tenant_make, specialist=specialist, customisation=customisation) 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.") @chat_bp.route('/api/send_message', methods=['POST']) def send_message(): """ API endpoint to send a message to the specialist """ try: data = request.json message = data.get('message') if not message: return jsonify({'error': 'No message provided'}), 400 tenant_id = session.get('tenant_id') specialist_id = session.get('specialist_id') chat_session_id = session.get('chat_session_id') specialist_args = session.get('specialist_args', {}) if not all([tenant_id, specialist_id, chat_session_id]): return jsonify({'error': 'Session expired or invalid'}), 400 # Switch to tenant schema Database(tenant_id).switch_schema() # Add user message to specialist arguments specialist_args['user_message'] = message # Execute specialist result = SpecialistServices.execute_specialist( tenant_id=tenant_id, specialist_id=specialist_id, specialist_arguments=specialist_args, session_id=chat_session_id, user_timezone=data.get('timezone', 'UTC') ) # Store the task ID for polling session['current_task_id'] = result['task_id'] return jsonify({ 'status': 'processing', 'task_id': result['task_id'] }) except Exception as e: current_app.logger.error(f"Error sending message: {str(e)}", exc_info=True) return jsonify({'error': str(e)}), 500 @chat_bp.route('/api/check_status', methods=['GET']) def check_status(): """ API endpoint to check the status of a task """ try: task_id = request.args.get('task_id') or session.get('current_task_id') if not task_id: return jsonify({'error': 'No task ID provided'}), 400 tenant_id = session.get('tenant_id') if not tenant_id: return jsonify({'error': 'Session expired or invalid'}), 400 # Switch to tenant schema Database(tenant_id).switch_schema() # Check task status using Celery task_result = current_app.celery.AsyncResult(task_id) if task_result.state == 'PENDING': return jsonify({'status': 'pending'}) elif task_result.state == 'SUCCESS': result = task_result.result # Format the response specialist_result = result.get('result', {}) response = { 'status': 'success', 'answer': specialist_result.get('answer', ''), 'citations': specialist_result.get('citations', []), 'insufficient_info': specialist_result.get('insufficient_info', False), 'interaction_id': result.get('interaction_id') } return jsonify(response) else: return jsonify({ 'status': 'error', 'message': str(task_result.info) }) except Exception as e: current_app.logger.error(f"Error checking status: {str(e)}", exc_info=True) return jsonify({'error': str(e)}), 500