Spaces:
Running
Running
| """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() | |
| 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), | |
| } | |
| 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 | |
| ], | |
| } | |