Files
contexta_be/app/routers/chat.py
belviskhoremk 2ed998058e added analytics
2026-02-23 17:24:41 +00:00

199 lines
6.7 KiB
Python

from fastapi import APIRouter, HTTPException, Depends
from app.models import ChatMessage, ChatResponse, ConversationResponse, MessageResponse
from app.database import get_supabase
from app.dependencies import get_current_user, get_optional_user
from app.services.rag import rag_engine
from typing import List, Optional
import uuid
import logging
logger = logging.getLogger(__name__)
router = APIRouter(tags=["Chat"])
def _get_public_chatbot(chatbot_id: str, supabase) -> dict:
"""Get a published chatbot (or any chatbot for preview)"""
result = supabase.table("chatbots").select("*, companies(name, logo_url)").eq("id", chatbot_id).execute()
if not result.data:
raise HTTPException(status_code=404, detail="Chatbot not found")
return result.data[0]
@router.post("/chat/{chatbot_id}", response_model=ChatResponse)
async def chat(
chatbot_id: str,
message: ChatMessage,
user=Depends(get_optional_user),
):
supabase = get_supabase()
chatbot = _get_public_chatbot(chatbot_id, supabase)
# Allow preview access for owner, require published for public
if not chatbot.get("is_published"):
if not user:
raise HTTPException(status_code=403, detail="This chatbot is in preview mode")
# Check ownership
company = supabase.table("companies").select("id").eq("owner_id", user.id).execute()
if not company.data or company.data[0]["id"] != chatbot.get("company_id"):
raise HTTPException(status_code=403, detail="This chatbot is in preview mode")
collection_name = chatbot.get("qdrant_collection_name")
if not collection_name:
raise HTTPException(status_code=400, detail="Chatbot has no knowledge base configured")
# Get or create conversation
session_id = message.session_id or str(uuid.uuid4())
conversation = _get_or_create_conversation(
chatbot_id=chatbot_id,
session_id=session_id,
user_id=user.id if user else None,
language=message.language,
supabase=supabase,
)
# Get conversation history
history = _get_conversation_history(conversation["id"], supabase)
# Get company info for context
company_data = chatbot.get("companies", {}) or {}
chatbot_config = {
**chatbot,
"company_name": company_data.get("name", ""),
}
# Run RAG
result = await rag_engine.process_query(
query=message.message,
collection_name=collection_name,
chatbot_config=chatbot_config,
conversation_history=history,
language=message.language,
)
# Save messages
_save_message(conversation["id"], "user", message.message, supabase)
_save_message(
conversation["id"],
"assistant",
result["response"],
supabase,
sources=[s.model_dump() for s in result.get("sources", [])],
model=result.get("model", ""),
)
# Update conversation message count
supabase.table("conversations").update({
"message_count": len(history) + 2
}).eq("id", conversation["id"]).execute()
return ChatResponse(
response=result["response"],
session_id=session_id,
sources=result.get("sources", []),
model_used=result.get("model", ""),
tokens_used=result.get("tokens_used", 0),
)
@router.get("/chat/{chatbot_id}/history/{session_id}", response_model=List[MessageResponse])
async def get_chat_history(
chatbot_id: str,
session_id: str,
user=Depends(get_optional_user),
):
supabase = get_supabase()
conversation = supabase.table("conversations").select("*") \
.eq("chatbot_id", chatbot_id) \
.eq("session_id", session_id) \
.execute()
if not conversation.data:
return []
conv_id = conversation.data[0]["id"]
messages = supabase.table("messages").select("*") \
.eq("conversation_id", conv_id) \
.order("created_at", desc=False) \
.execute()
return [
MessageResponse(
id=m["id"],
role=m["role"],
content=m["content"],
sources=m.get("sources"),
created_at=m.get("created_at"),
)
for m in (messages.data or [])
]
# ── OLD analytics endpoint REMOVED ───────────────────────────────────────────
# The /analytics/{chatbot_id} endpoint that was here has been replaced by
# the dedicated analytics router (app/routers/analytics.py) which provides:
# GET /api/v1/analytics/overview — All chatbots overview
# GET /api/v1/analytics/chatbot/{id} — Single chatbot detail
# The old endpoint conflicted with the new router because FastAPI matched
# "overview" as a chatbot_id UUID, causing a 500 error.
# ─────────────────────────────────────────────────────────────────────────────
# ── Helpers ───────────────────────────────────────────────────────────────────
def _get_or_create_conversation(
chatbot_id: str,
session_id: str,
user_id: Optional[str],
language: str,
supabase,
) -> dict:
existing = supabase.table("conversations").select("*") \
.eq("chatbot_id", chatbot_id) \
.eq("session_id", session_id) \
.execute()
if existing.data:
return existing.data[0]
new_conv = {
"id": str(uuid.uuid4()),
"chatbot_id": chatbot_id,
"user_id": user_id,
"session_id": session_id,
"language": language,
"message_count": 0,
}
result = supabase.table("conversations").insert(new_conv).execute()
return result.data[0]
def _get_conversation_history(conversation_id: str, supabase) -> List[dict]:
"""
Returns conversation history in chronological order (oldest first)
for the LLM to correctly understand the conversation flow.
"""
messages = supabase.table("messages").select("role, content") \
.eq("conversation_id", conversation_id) \
.order("created_at", desc=False) \
.limit(20) \
.execute()
return messages.data or []
def _save_message(
conversation_id: str,
role: str,
content: str,
supabase,
sources: Optional[list] = None,
model: str = "",
):
supabase.table("messages").insert({
"id": str(uuid.uuid4()),
"conversation_id": conversation_id,
"role": role,
"content": content,
"sources": sources,
"model": model,
}).execute()