"""Main application entry point for the multi-language chat agent.""" import os from flask import Flask from flask_socketio import SocketIO from flask_session import Session import redis from config import config from chat_agent.models.base import db from chat_agent.utils.logging_config import setup_logging from chat_agent.utils.error_handler import set_error_handler, ErrorHandler from chat_agent.utils.connection_pool import initialize_connection_pools, get_connection_pool_manager from chat_agent.services.cache_service import initialize_cache_service, get_cache_service from chat_agent.utils.response_optimization import ResponseMiddleware # Initialize extensions socketio = SocketIO() session = Session() def create_app(config_name=None): """Application factory pattern.""" if config_name is None: config_name = os.getenv('FLASK_ENV', 'development') app = Flask(__name__) app.config.from_object(config[config_name]) # Setup comprehensive logging loggers = setup_logging("chat_agent", app.config.get('LOG_LEVEL', 'INFO')) app.logger = loggers['main'] # Setup global error handler error_handler = ErrorHandler(loggers['error']) set_error_handler(error_handler) app.logger.info("Chat agent application starting", extra={ 'config': config_name, 'debug': app.config.get('DEBUG', False), 'logging_level': app.config.get('LOG_LEVEL', 'INFO') }) # Initialize connection pools for performance optimization database_url = app.config.get('SQLALCHEMY_DATABASE_URI') redis_url = app.config.get('REDIS_URL') connection_pool_manager = initialize_connection_pools(database_url, redis_url) # Configure SQLAlchemy to use connection pool if connection_pool_manager: app.config['SQLALCHEMY_ENGINE_OPTIONS'] = { 'pool_size': int(os.getenv('DB_POOL_SIZE', '10')), 'max_overflow': int(os.getenv('DB_MAX_OVERFLOW', '20')), 'pool_recycle': int(os.getenv('DB_POOL_RECYCLE', '3600')), 'pool_pre_ping': True, 'pool_timeout': int(os.getenv('DB_POOL_TIMEOUT', '30')) } # Initialize extensions with app db.init_app(app) socketio.init_app(app, cors_allowed_origins="*") # Initialize response optimization middleware ResponseMiddleware(app) # Configure Redis for sessions and caching (if available) redis_client = None if redis_url and redis_url != 'None': try: # Use connection pool manager's Redis client if available if connection_pool_manager: redis_client = connection_pool_manager.get_redis_client() else: redis_client = redis.from_url(redis_url) if redis_client: redis_client.ping() # Test connection app.config['SESSION_REDIS'] = redis_client session.init_app(app) app.logger.info("Redis connection established for sessions and caching") else: raise Exception("Redis client not available") except Exception as e: app.logger.warning(f"Redis connection failed: {e}. Sessions will use filesystem.") app.config['SESSION_TYPE'] = 'filesystem' session.init_app(app) redis_client = None else: app.logger.info("Redis disabled. Using filesystem sessions.") app.config['SESSION_TYPE'] = 'filesystem' session.init_app(app) # Initialize cache service with Redis client cache_service = initialize_cache_service(redis_client) app.logger.info(f"Cache service initialized", extra={ 'redis_enabled': bool(redis_client) }) # Register API blueprints from chat_agent.api import chat_bp, create_limiter, setup_error_handlers, RequestLoggingMiddleware from chat_agent.api.health import health_bp from chat_agent.api.performance_routes import performance_bp app.register_blueprint(chat_bp) app.register_blueprint(health_bp) app.register_blueprint(performance_bp) # Configure rate limiting limiter = create_limiter(app) if redis_url and redis_url != 'None': limiter.storage_uri = redis_url # If no Redis, limiter will use in-memory storage (with warning) # Setup error handlers setup_error_handlers(app) # Setup request logging middleware RequestLoggingMiddleware(app) # Add chat interface route @app.route('/') @app.route('/chat') def chat_interface(): """Serve the chat interface.""" from flask import render_template return render_template('chat.html') # Initialize real chat agent services from chat_agent.services.groq_client import GroqClient from chat_agent.services.language_context import LanguageContextManager from chat_agent.services.session_manager import SessionManager from chat_agent.services.chat_history import ChatHistoryManager from chat_agent.services.chat_agent import ChatAgent from chat_agent.services.programming_assistance import ProgrammingAssistanceService # Initialize services try: # Initialize Redis client redis_url = app.config.get('REDIS_URL', 'redis://localhost:6379/0') redis_client = redis.from_url(redis_url) # Test Redis connection redis_client.ping() print("✅ Redis connection successful") groq_client = GroqClient() language_context_manager = LanguageContextManager() session_manager = SessionManager(redis_client) chat_history_manager = ChatHistoryManager(redis_client) programming_assistance_service = ProgrammingAssistanceService() # Initialize main chat agent chat_agent = ChatAgent( groq_client=groq_client, language_context_manager=language_context_manager, session_manager=session_manager, chat_history_manager=chat_history_manager, programming_assistance_service=programming_assistance_service ) print("✅ Chat agent services initialized successfully") except Exception as e: print(f"⚠️ Error initializing chat agent services: {e}") print("🔄 Falling back to demo mode") chat_agent = None # Store session mapping for WebSocket connections websocket_sessions = {} # Initialize WebSocket handlers for chat interface @socketio.on('connect') def handle_connect(auth=None): """Handle WebSocket connection for chat interface.""" from flask_socketio import emit from flask import request import uuid try: # Create a new session for this connection user_id = f"user_{request.sid}" # Use socket ID as user ID for demo if chat_agent and session_manager: session = session_manager.create_session(user_id, language='python') websocket_sessions[request.sid] = session.id emit('connection_status', { 'status': 'connected', 'session_id': session.id, 'language': session.language, 'message_count': session.message_count, 'timestamp': datetime.now().isoformat() }) print(f"WebSocket connected: session={session.id}, user={user_id}") else: # Fallback to demo mode session_id = str(uuid.uuid4()) websocket_sessions[request.sid] = session_id emit('connection_status', { 'status': 'connected', 'session_id': session_id, 'language': 'python', 'message_count': 0, 'timestamp': datetime.now().isoformat() }) print(f"WebSocket connected (demo mode): session={session_id}, user={user_id}") except Exception as e: print(f"Error connecting WebSocket: {e}") emit('error', {'message': 'Connection failed', 'code': 'CONNECTION_ERROR'}) @socketio.on('disconnect') def handle_disconnect(reason=None): """Handle WebSocket disconnection.""" from flask import request # Clean up session mapping if request.sid in websocket_sessions: session_id = websocket_sessions[request.sid] del websocket_sessions[request.sid] print(f"WebSocket disconnected: session={session_id}") else: print("WebSocket disconnected") @socketio.on('message') def handle_message(data): """Handle chat messages using real chat agent.""" from flask_socketio import emit from flask import request try: print(f"Received message: {data}") # Get session ID for this connection if request.sid not in websocket_sessions: emit('error', {'message': 'No active session', 'code': 'NO_SESSION'}) return session_id = websocket_sessions[request.sid] content = data.get('content', '').strip() language = data.get('language', 'python') if not content: emit('error', {'message': 'Empty message received', 'code': 'EMPTY_MESSAGE'}) return # Process message with real chat agent emit('response_start', { 'session_id': session_id, 'language': language, 'timestamp': datetime.now().isoformat() }) try: if chat_agent: # Use the real chat agent to process the message print(f"🤖 Processing message with chat agent: '{content}' (language: {language})") result = chat_agent.process_message(session_id, content, language) # Extract response content from the result dictionary if isinstance(result, dict) and 'response' in result: response = result['response'] print(f"✅ Chat agent response: {response[:100]}..." if len(response) > 100 else f"✅ Chat agent response: {response}") else: print(f"✅ Unexpected response format: {type(result)}, value: {result}") response = str(result) else: # Fallback response if chat agent is not available response = f"I understand you're asking about: '{content}'. I'm currently in demo mode, but I can help you with {language} programming concepts, debugging, and best practices. The full AI-powered assistant will provide more detailed responses." # Ensure response is a string before processing if not isinstance(response, str): response = str(response) # Send response in chunks to simulate streaming words = response.split() chunk_size = 5 total_chunks = (len(words) + chunk_size - 1) // chunk_size for i in range(0, len(words), chunk_size): chunk = ' '.join(words[i:i+chunk_size]) + ' ' emit('response_chunk', { 'content': chunk, 'timestamp': datetime.now().isoformat() }) socketio.sleep(0.02) # Small delay for streaming effect emit('response_complete', { 'message_id': str(uuid.uuid4()), 'total_chunks': total_chunks, 'processing_time': 1.0, 'timestamp': datetime.now().isoformat() }) except Exception as e: print(f"❌ Error processing message with chat agent: {e}") # Fallback to demo response if chat agent fails demo_response = f"I apologize, but I'm having trouble processing your request right now. You asked about: '{content}'. Please try again in a moment, or check that the Groq API key is properly configured." emit('response_chunk', { 'content': demo_response, 'timestamp': datetime.now().isoformat() }) emit('response_complete', { 'message_id': str(uuid.uuid4()), 'total_chunks': 1, 'processing_time': 0.1, 'timestamp': datetime.now().isoformat() }) except Exception as e: print(f"Error handling message: {e}") emit('error', {'message': 'Failed to process message', 'code': 'PROCESSING_ERROR'}) @socketio.on('language_switch') def handle_language_switch(data): """Handle language switching using real chat agent.""" from flask_socketio import emit from flask import request try: # Get session ID for this connection if request.sid not in websocket_sessions: emit('error', {'message': 'No active session', 'code': 'NO_SESSION'}) return session_id = websocket_sessions[request.sid] new_language = data.get('language', 'python') language_names = { 'python': 'Python', 'javascript': 'JavaScript', 'java': 'Java', 'cpp': 'C++', 'csharp': 'C#', 'go': 'Go', 'rust': 'Rust', 'typescript': 'TypeScript' } try: if chat_agent: # Use real chat agent to switch language result = chat_agent.switch_language(session_id, new_language) emit('language_switched', { 'previous_language': result.get('previous_language', 'python'), 'new_language': result.get('new_language', new_language), 'message': result.get('message', f'Language switched to {language_names.get(new_language, new_language)}'), 'timestamp': datetime.now().isoformat() }) print(f"🔄 Language switched to: {new_language} for session {session_id}") else: # Fallback for demo mode emit('language_switched', { 'previous_language': 'python', 'new_language': new_language, 'message': f"Switched to {language_names.get(new_language, new_language)}. I'm now ready to help you with {language_names.get(new_language, new_language)} programming!", 'timestamp': datetime.now().isoformat() }) print(f"🔄 Language switched to: {new_language} (demo mode)") except Exception as e: print(f"❌ Error switching language: {e}") emit('error', {'message': 'Failed to switch language', 'code': 'LANGUAGE_SWITCH_ERROR'}) except Exception as e: print(f"Error handling language switch: {e}") emit('error', {'message': 'Failed to switch language', 'code': 'LANGUAGE_SWITCH_ERROR'}) # Add error handlers for WebSocket @socketio.on_error_default def default_error_handler(e): """Handle WebSocket errors.""" print(f"WebSocket error: {e}") from flask_socketio import emit emit('error', {'message': 'Connection error occurred', 'code': 'WEBSOCKET_ERROR'}) # Import datetime for timestamps from datetime import datetime import uuid return app if __name__ == '__main__': app = create_app() socketio.run(app, debug=True, host='0.0.0.0', port=7860)