"""对话历史查询 API。""" from db.models import ConversationMessage, ConversationSession from db.session import get_session from fastapi import APIRouter, Depends, HTTPException, Query from schemas import ( ConversationDetailOut, ConversationListOut, ConversationMessageOut, ConversationOut, ) from services.auth import require_admin from sqlalchemy import func, or_, select from sqlalchemy.ext.asyncio import AsyncSession router = APIRouter( prefix="/api/conversations", tags=["conversations"], dependencies=[Depends(require_admin)], ) def _session_out(row: ConversationSession) -> ConversationOut: return ConversationOut( id=row.id, assistant_id=row.assistant_id, assistant_name=row.assistant_name, channel=row.channel, runtime_mode=row.runtime_mode, status=row.status, message_count=row.message_count, started_at=row.started_at, ended_at=row.ended_at, ) @router.get("", response_model=ConversationListOut) async def list_conversations( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), assistant_id: str | None = None, search: str | None = Query(None, max_length=128), channel: str | None = None, status: str | None = None, sort_order: str = Query("newest", pattern="^(newest|oldest)$"), session: AsyncSession = Depends(get_session), ): filters = [] if assistant_id: filters.append(ConversationSession.assistant_id == assistant_id) if search and search.strip(): keyword = f"%{search.strip()}%" filters.append( or_( ConversationSession.assistant_name.ilike(keyword), ConversationSession.id.ilike(keyword), ) ) if channel: filters.append(ConversationSession.channel == channel) if status: filters.append(ConversationSession.status == status) total = await session.scalar( select(func.count()).select_from(ConversationSession).where(*filters) ) rows = ( await session.execute( select(ConversationSession) .where(*filters) .order_by( ConversationSession.started_at.asc() if sort_order == "oldest" else ConversationSession.started_at.desc() ) .offset((page - 1) * page_size) .limit(page_size) ) ).scalars().all() return ConversationListOut( items=[_session_out(row) for row in rows], total=int(total or 0), page=page, page_size=page_size, ) @router.get("/{conversation_id}", response_model=ConversationDetailOut) async def get_conversation( conversation_id: str, session: AsyncSession = Depends(get_session), ): conversation = await session.get(ConversationSession, conversation_id) if not conversation: raise HTTPException(404, "对话记录不存在") messages = ( await session.execute( select(ConversationMessage) .where(ConversationMessage.session_id == conversation_id) .order_by(ConversationMessage.sequence) ) ).scalars().all() return ConversationDetailOut( **_session_out(conversation).model_dump(), messages=[ ConversationMessageOut( id=message.id, sequence=message.sequence, role=message.role, content_type=message.content_type, content=message.content, occurred_at=message.occurred_at, extra=message.extra or {}, ) for message in messages ], ) @router.delete("/{conversation_id}") async def delete_conversation( conversation_id: str, session: AsyncSession = Depends(get_session), ): conversation = await session.get(ConversationSession, conversation_id) if not conversation: raise HTTPException(404, "对话记录不存在") await session.delete(conversation) await session.commit() return {"ok": True}