""" AI Assistant API Routes """ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import Optional from pydantic import BaseModel from ...shared.config.database import get_db from ...security.middleware.auth import get_current_user from ...auth.models.user import User from ...auth.models.role import Role from ..services.ai_chat_service import AIChatService from ..services.ai_assistant_service import AIAssistantService from ..services.ai_learning_service import AILearningService from ..services.ai_training_scheduler import get_training_scheduler from ...shared.config.logging_config import get_logger logger = get_logger(__name__) router = APIRouter(prefix="/ai-assistant", tags=["AI Assistant"]) class ChatMessageRequest(BaseModel): message: str context: Optional[dict] = None class ChatMessageResponse(BaseModel): response: str intent: str data_used: dict timestamp: str @router.post("/chat") async def chat_with_ai( request: ChatMessageRequest, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """ Chat with AI assistant Accessible to all authenticated users (admin, staff, accountant, customer) Responses are filtered based on user role """ try: # Load user role user_role = db.query(Role).filter(Role.id == current_user.role_id).first() if not user_role: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User role not found" ) # Initialize AI chat service with current user for role-based responses ai_service = AIChatService(db, current_user) # Generate response with role awareness result = ai_service.generate_response( user_query=request.message, context=request.context ) return { "status": "success", "data": { "response": result["response"], "intent": result.get("intent", "unknown"), "data_used": result.get("data_used", {}), "timestamp": result.get("timestamp", ""), "user_role": user_role.name } } except HTTPException: raise except Exception as e: logger.error(f"Error in AI chat: {str(e)}", exc_info=True) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error processing AI request: {str(e)}" ) @router.get("/status") async def get_ai_status( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """ Get comprehensive system status for AI assistant Status is filtered based on user role """ try: # Load user role user_role = db.query(Role).filter(Role.id == current_user.role_id).first() if not user_role: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User role not found" ) # Initialize AI assistant with current user for role-based context ai_assistant = AIAssistantService(db, current_user) context = ai_assistant.generate_context_for_ai() return { "status": "success", "data": context } except HTTPException: raise except Exception as e: logger.error(f"Error getting AI status: {str(e)}", exc_info=True) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e) ) @router.get("/rooms/occupied") async def get_occupied_rooms( room_number: Optional[str] = None, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Get list of occupied rooms - Admin and Staff only""" try: user_role = db.query(Role).filter(Role.id == current_user.role_id).first() if not user_role or user_role.name not in ['staff', 'admin']: raise HTTPException(status_code=403, detail="Only staff and admin can access this information") ai_assistant = AIAssistantService(db, current_user) rooms = ai_assistant.search_occupied_rooms(room_number) return {"status": "success", "data": {"rooms": rooms}} except HTTPException: raise except Exception as e: logger.error(f"Error getting occupied rooms: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) @router.get("/rooms/problems") async def get_room_problems( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Get rooms with problems - Admin and Staff only""" try: user_role = db.query(Role).filter(Role.id == current_user.role_id).first() if not user_role or user_role.name not in ['staff', 'admin']: raise HTTPException(status_code=403, detail="Only staff and admin can access this information") ai_assistant = AIAssistantService(db, current_user) problems = ai_assistant.get_room_problems() return {"status": "success", "data": {"problems": problems}} except HTTPException: raise except Exception as e: logger.error(f"Error getting room problems: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) @router.get("/chats/unanswered") async def get_unanswered_chats( hours: int = 24, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Get unanswered chat messages - Admin and Staff only""" try: user_role = db.query(Role).filter(Role.id == current_user.role_id).first() if not user_role or user_role.name not in ['staff', 'admin']: raise HTTPException(status_code=403, detail="Only staff and admin can access this information") ai_assistant = AIAssistantService(db, current_user) chats = ai_assistant.get_unanswered_chats(hours) return {"status": "success", "data": {"chats": chats}} except HTTPException: raise except Exception as e: logger.error(f"Error getting unanswered chats: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) @router.post("/training/trigger") async def trigger_manual_training( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Manually trigger AI training - Admin only""" try: user_role = db.query(Role).filter(Role.id == current_user.role_id).first() if not user_role or user_role.name != 'admin': raise HTTPException(status_code=403, detail="Only admin can trigger manual training") learning_service = AILearningService(db) result = learning_service.auto_train_from_all_conversations() return { "status": "success", "message": "Training completed", "data": result } except HTTPException: raise except Exception as e: logger.error(f"Error triggering training: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) @router.post("/training/analyze") async def trigger_manual_analysis( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Manually trigger AI analysis and improvement - Admin only""" try: user_role = db.query(Role).filter(Role.id == current_user.role_id).first() if not user_role or user_role.name != 'admin': raise HTTPException(status_code=403, detail="Only admin can trigger manual analysis") learning_service = AILearningService(db) result = learning_service.auto_analyze_and_improve() return { "status": "success", "message": "Analysis completed", "data": result } except HTTPException: raise except Exception as e: logger.error(f"Error triggering analysis: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) @router.get("/training/status") async def get_training_status( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Get AI training status and metrics - Admin only""" try: user_role = db.query(Role).filter(Role.id == current_user.role_id).first() if not user_role or user_role.name != 'admin': raise HTTPException(status_code=403, detail="Only admin can view training status") from ..models.ai_conversation import AIConversation, AILearnedPattern, AIKnowledgeEntry, AITrainingMetrics from sqlalchemy import func from datetime import datetime, timedelta # Get statistics total_conversations = db.query(AIConversation).count() total_patterns = db.query(AILearnedPattern).filter(AILearnedPattern.is_active == True).count() total_knowledge = db.query(AIKnowledgeEntry).count() # Get recent metrics recent_metrics = db.query(AITrainingMetrics).order_by( AITrainingMetrics.metric_date.desc() ).limit(1).first() # Get scheduler status scheduler = get_training_scheduler() return { "status": "success", "data": { "scheduler_running": scheduler.running, "last_training": scheduler.last_training_time.isoformat() if scheduler.last_training_time else None, "last_analysis": scheduler.last_analysis_time.isoformat() if scheduler.last_analysis_time else None, "statistics": { "total_conversations": total_conversations, "total_patterns": total_patterns, "total_knowledge_entries": total_knowledge, }, "recent_metrics": { "average_rating": float(recent_metrics.average_rating) if recent_metrics and recent_metrics.average_rating else None, "helpful_rate": float(recent_metrics.helpful_rate) if recent_metrics and recent_metrics.helpful_rate else None, "average_response_time_ms": recent_metrics.average_response_time_ms if recent_metrics else None, } if recent_metrics else None } } except HTTPException: raise except Exception as e: logger.error(f"Error getting training status: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=str(e))