- Implement a new API endpoint for deleting conversations in the backend. - Enhance the HistoryPage component to include a dropdown menu for conversation actions, allowing users to delete conversations. - Update the pagination logic to handle conversation removal and improve user experience during deletion. - Adjust the loading state and error handling for the delete operation, ensuring smooth interaction.
131 lines
4.0 KiB
Python
131 lines
4.0 KiB
Python
"""对话历史查询 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}
|