This commit is contained in:
Iliyan Angelov
2025-11-30 22:43:09 +02:00
parent 24b40450dd
commit 39fcfff811
1610 changed files with 5442 additions and 1383 deletions

View File

@@ -0,0 +1,295 @@
"""
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))