Arag / app /api /conversations.py
AuthorBot
Add enterprise growth APIs, billing, admin UI, and test suite.
12930b5
Raw
History Blame Contribute Delete
4.92 kB
"""Author RAG — Conversation Viewer API.
Provides read-only access to conversation history for:
- Admin panel: author sees their chatbot's real conversation transcripts
- SuperAdmin: cross-author audit trail
- Analytics: session-level review
Routes:
GET /api/admin/{slug}/conversations → paginated list of sessions
GET /api/admin/{slug}/conversations/{id} → full message thread for a session
"""
from datetime import datetime, timezone
from typing import Optional
from fastapi import APIRouter, Depends, Query, HTTPException
from sqlalchemy import select, func, desc
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_current_author, get_db
from app.models.chat_session import ChatSession, ChatMessage
router = APIRouter()
@router.get("/{slug}/conversations")
async def list_conversations(
slug: str,
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
search: Optional[str] = Query(None, max_length=200),
db: AsyncSession = Depends(get_db),
author=Depends(get_current_author),
):
"""List recent chat sessions for this author's chatbot.
Paginated. Each item includes session metadata (start time, message count,
last user message preview) for the conversation list UI.
"""
if author.id != slug:
raise HTTPException(403, "Forbidden")
offset = (page - 1) * page_size
# Base query: sessions for this author
query = select(ChatSession).where(ChatSession.author_id == slug)
# Optional: filter sessions that contain a specific search term
# (searches first user message as a proxy — full search in Phase 3)
if search:
query = query.where(ChatSession.session_id.ilike(f"%{search}%"))
# Count total
count_result = await db.execute(
select(func.count()).select_from(query.subquery())
)
total = count_result.scalar() or 0
# Fetch page
result = await db.execute(
query.order_by(desc(ChatSession.created_at))
.offset(offset)
.limit(page_size)
)
sessions = result.scalars().all()
# For each session, get the message count and last user message
items = []
for s in sessions:
msg_result = await db.execute(
select(ChatMessage)
.where(ChatMessage.session_id == s.session_id)
.order_by(ChatMessage.created_at)
)
msgs = msg_result.scalars().all()
user_msgs = [m for m in msgs if m.role == "user"]
items.append({
"session_id": s.session_id,
"visitor_id": s.visitor_id if hasattr(s, "visitor_id") else None,
"book_id": s.book_id if hasattr(s, "book_id") else None,
"started_at": s.created_at.isoformat() if s.created_at else None,
"message_count": len(msgs),
"first_message": user_msgs[0].content[:120] if user_msgs else None,
"last_message": user_msgs[-1].content[:120] if user_msgs else None,
})
return {
"sessions": items,
"total": total,
"page": page,
"page_size": page_size,
"total_pages": max(1, (total + page_size - 1) // page_size),
}
@router.get("/{slug}/conversations/{session_id}")
async def get_conversation(
slug: str,
session_id: str,
db: AsyncSession = Depends(get_db),
author=Depends(get_current_author),
):
"""Get full message thread for a specific chat session.
Returns the complete conversation as an ordered list of messages
for the conversation viewer UI in the admin panel.
"""
if author.id != slug:
raise HTTPException(403, "Forbidden")
# Verify session belongs to this author
session_result = await db.execute(
select(ChatSession).where(
ChatSession.session_id == session_id,
ChatSession.author_id == slug,
)
)
session = session_result.scalar_one_or_none()
if not session:
raise HTTPException(404, "Session not found")
# Get messages
msg_result = await db.execute(
select(ChatMessage)
.where(ChatMessage.session_id == session_id)
.order_by(ChatMessage.created_at)
)
messages = msg_result.scalars().all()
return {
"session_id": session_id,
"author_id": slug,
"book_id": session.book_id if hasattr(session, "book_id") else None,
"started_at": session.created_at.isoformat() if session.created_at else None,
"messages": [
{
"id": m.id,
"role": m.role,
"content": m.content,
"tokens_used": m.tokens_used if hasattr(m, "tokens_used") else None,
"intent": m.intent if hasattr(m, "intent") else None,
"created_at": m.created_at.isoformat() if m.created_at else None,
}
for m in messages
],
}