mirror of
http://88.130.71.182:3000/BlitTech/contexta_be.git
synced 2026-06-12 23:23:21 +00:00
199 lines
6.7 KiB
Python
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() |