296 lines
10 KiB
Python
296 lines
10 KiB
Python
"""
|
|
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))
|
|
|