from fastapi import APIRouter, HTTPException, Depends, Query from app.database import get_supabase from app.dependencies import get_current_user from app.config import PLAN_LIMITS from app.models import InboxConversation, InboxMessage from typing import List, Optional import logging logger = logging.getLogger(__name__) router = APIRouter(prefix="/inbox", tags=["Inbox"]) def _check_inbox_access(user_id: str, supabase): sub = supabase.table("subscriptions").select("plan").eq("user_id", user_id).eq("status", "active").execute() plan = sub.data[0]["plan"] if sub.data else "free" if plan not in ("starter", "business", "agency", "enterprise"): raise HTTPException(status_code=402, detail="Conversation inbox requires Starter plan or higher") return plan def _get_user_company_id(user_id: str, supabase) -> str: result = supabase.table("companies").select("id").eq("owner_id", user_id).execute() if not result.data: raise HTTPException(status_code=404, detail="Company not found") return result.data[0]["id"] @router.get("/conversations", response_model=List[InboxConversation]) async def list_inbox_conversations( chatbot_id: Optional[str] = Query(None), page: int = Query(1, ge=1), limit: int = Query(20, ge=1, le=100), user=Depends(get_current_user), ): """List conversations for all (or one) of the user's chatbots.""" supabase = get_supabase() _check_inbox_access(user.id, supabase) company_id = _get_user_company_id(user.id, supabase) # Get user's chatbots chatbots_query = supabase.table("chatbots").select("id, name").eq("company_id", company_id) if chatbot_id: chatbots_query = chatbots_query.eq("id", chatbot_id) chatbots_result = chatbots_query.execute() chatbot_map = {c["id"]: c["name"] for c in (chatbots_result.data or [])} if not chatbot_map: return [] chatbot_ids = list(chatbot_map.keys()) offset = (page - 1) * limit # Query conversations convs = supabase.table("conversations").select("*") \ .in_("chatbot_id", chatbot_ids) \ .order("created_at", desc=True) \ .range(offset, offset + limit - 1) \ .execute() results = [] for conv in (convs.data or []): cid = conv["id"] # Get first user message for preview first_msg = supabase.table("messages").select("content") \ .eq("conversation_id", cid) \ .eq("role", "user") \ .order("created_at", desc=False) \ .limit(1) \ .execute() first_message_text = first_msg.data[0]["content"] if first_msg.data else None results.append(InboxConversation( id=cid, chatbot_id=conv["chatbot_id"], chatbot_name=chatbot_map.get(conv["chatbot_id"], "Unknown"), session_id=conv.get("session_id"), language=conv.get("language", "en"), message_count=conv.get("message_count", 0), first_message=first_message_text, created_at=conv.get("created_at"), )) return results @router.get("/conversations/{conversation_id}") async def get_inbox_conversation( conversation_id: str, user=Depends(get_current_user), ): """Get a full conversation thread with all messages.""" supabase = get_supabase() _check_inbox_access(user.id, supabase) company_id = _get_user_company_id(user.id, supabase) # Verify ownership conv = supabase.table("conversations").select("*, chatbots(company_id, name)") \ .eq("id", conversation_id) \ .execute() if not conv.data: raise HTTPException(status_code=404, detail="Conversation not found") c = conv.data[0] chatbot_data = c.get("chatbots") or {} if chatbot_data.get("company_id") != company_id: raise HTTPException(status_code=403, detail="Not authorized") # Get messages msgs = supabase.table("messages").select("*") \ .eq("conversation_id", conversation_id) \ .order("created_at", desc=False) \ .execute() messages = [ InboxMessage( id=m["id"], role=m["role"], content=m["content"], sources=m.get("sources"), confidence_score=m.get("confidence_score"), is_handoff=m.get("is_handoff", False), created_at=m.get("created_at"), ) for m in (msgs.data or []) ] return { "conversation_id": conversation_id, "chatbot_name": chatbot_data.get("name", "Unknown"), "language": c.get("language", "en"), "session_id": c.get("session_id"), "created_at": c.get("created_at"), "messages": [m.model_dump() for m in messages], } @router.delete("/conversations/{conversation_id}") async def delete_inbox_conversation( conversation_id: str, user=Depends(get_current_user), ): """Delete a conversation and all its messages.""" supabase = get_supabase() _check_inbox_access(user.id, supabase) company_id = _get_user_company_id(user.id, supabase) # Verify ownership conv = supabase.table("conversations").select("*, chatbots(company_id)") \ .eq("id", conversation_id).execute() if not conv.data: raise HTTPException(status_code=404, detail="Conversation not found") chatbot_data = conv.data[0].get("chatbots") or {} if chatbot_data.get("company_id") != company_id: raise HTTPException(status_code=403, detail="Not authorized") supabase.table("conversations").delete().eq("id", conversation_id).execute() return {"success": True}