rag-api-node-1 / src /api /routes /interactions.py
Peterase's picture
feat(rag): implement hybrid search with live sources and production-grade intent classification
a63c61f
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from src.infrastructure.database import get_db
from sqlalchemy import func
from src.core.domain.schemas import FeedbackRequest, ChatSession
from src.core.domain.db_models import ChatHistory, Feedback
from src.core.security import get_current_user
from src.core.domain.db_models import User
from typing import Optional
router = APIRouter()
@router.get("/history/{session_id}")
def get_chat_history(session_id: str, db: Session = Depends(get_db)):
history = db.query(ChatHistory).filter(
ChatHistory.session_id == session_id
).order_by(ChatHistory.timestamp.asc()).all()
formatted_history = []
for h in history:
formatted_history.append({
"id": h.id,
"role": h.role,
"content": h.content,
"timestamp": h.timestamp,
"pinned": getattr(h, "pinned", False),
})
return {"session_id": session_id, "history": formatted_history}
@router.post("/feedback")
def submit_feedback(req: FeedbackRequest, db: Session = Depends(get_db)):
msg = db.query(ChatHistory).filter(
ChatHistory.id == req.message_id,
ChatHistory.session_id == req.session_id
).first()
if not msg:
raise HTTPException(status_code=404, detail="Message not found in session")
# Upsert: update existing feedback or create new
existing = db.query(Feedback).filter(
Feedback.message_id == req.message_id,
Feedback.session_id == req.session_id
).first()
if existing:
existing.rating = req.rating
existing.comment = req.comment
else:
feedback = Feedback(
session_id=req.session_id,
message_id=req.message_id,
rating=req.rating,
comment=req.comment
)
db.add(feedback)
db.commit()
return {"status": "success", "message": "Feedback recorded."}
@router.get("/feedback/{session_id}")
def get_session_feedback(session_id: str, db: Session = Depends(get_db)):
"""Get all feedback ratings for a session (so UI can restore like/dislike state)."""
feedbacks = db.query(Feedback).filter(Feedback.session_id == session_id).all()
return {str(f.message_id): f.rating for f in feedbacks}
@router.post("/pin/{message_id}")
def pin_message(message_id: int, db: Session = Depends(get_db)):
"""Toggle pin on a message."""
msg = db.query(ChatHistory).filter(ChatHistory.id == message_id).first()
if not msg:
raise HTTPException(status_code=404, detail="Message not found")
# Toggle pinned — add column if missing via getattr
current = getattr(msg, "pinned", False) or False
msg.pinned = not current
db.commit()
return {"pinned": msg.pinned}
@router.get("/sessions")
def get_chat_sessions(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Retrieve sessions for the authenticated user only."""
sessions = db.query(
ChatHistory.session_id,
func.count(ChatHistory.id).label("message_count"),
func.max(ChatHistory.timestamp).label("last_active")
).filter(
ChatHistory.user_id == current_user.id
).group_by(ChatHistory.session_id).order_by(
func.max(ChatHistory.timestamp).desc()
).all()
return [
ChatSession(
session_id=s.session_id,
message_count=s.message_count,
last_active=s.last_active
)
for s in sessions
]
@router.delete("/sessions/{session_id}")
def delete_chat_session(
session_id: str,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Delete session — only owner can delete."""
# Verify ownership
owned = db.query(ChatHistory).filter(
ChatHistory.session_id == session_id,
ChatHistory.user_id == current_user.id
).first()
if not owned:
raise HTTPException(status_code=404, detail="Session not found")
db.query(Feedback).filter(Feedback.session_id == session_id).delete()
deleted_msgs = db.query(ChatHistory).filter(ChatHistory.session_id == session_id).delete()
db.commit()
return {"status": "success", "message": f"Deleted session {session_id} with {deleted_msgs} messages."}