Complete admin chat records

This commit is contained in:
2026-07-07 12:17:23 +08:00
parent 08dd371480
commit 08c301c136
9 changed files with 608 additions and 61 deletions

View File

@@ -1,7 +1,12 @@
from __future__ import annotations
import csv
from datetime import datetime
from io import StringIO
from fastapi import APIRouter, Depends, Query
from sqlalchemy import select
from fastapi.responses import Response
from sqlalchemy import exists, or_, select
from sqlalchemy.orm import Session
from app.core.database import get_db
@@ -10,33 +15,65 @@ from app.core.responses import api_success
from app.models.admin import Admin
from app.models.chat import ChatMessage, ChatSession
from app.models.logs import AiRequestLog, OperationLog
from app.models.user import User
router = APIRouter()
@router.get("/chat/list")
def chat_list(
keyword: str = Query(default=""),
userId: int | None = Query(default=None),
status: str = Query(default=""),
dateFrom: datetime | None = Query(default=None),
dateTo: datetime | None = Query(default=None),
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
sessions = db.scalars(
select(ChatSession)
.where(ChatSession.is_deleted == 0)
rows = db.execute(
_chat_query(keyword=keyword, user_id=userId, status=status, date_from=dateFrom, date_to=dateTo)
.order_by(ChatSession.updated_at.desc())
.limit(200)
).all()
return api_success(
[
{
"id": item.id,
"userId": item.user_id,
"title": item.title,
"messageCount": item.message_count,
"lastMessageAt": item.last_message_at,
"updatedAt": item.updated_at,
}
for item in sessions
]
return api_success([_chat_row_dict(session, user) for session, user in rows])
@router.get("/chat/export")
def export_chats(
keyword: str = Query(default=""),
userId: int | None = Query(default=None),
status: str = Query(default=""),
dateFrom: datetime | None = Query(default=None),
dateTo: datetime | None = Query(default=None),
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> Response:
rows = db.execute(
_chat_query(keyword=keyword, user_id=userId, status=status, date_from=dateFrom, date_to=dateTo)
.order_by(ChatSession.updated_at.desc())
.limit(1000)
).all()
output = StringIO()
writer = csv.writer(output)
writer.writerow(["会话ID", "用户ID", "手机号", "姓名", "标题", "消息数", "最后消息时间", "更新时间"])
for session, user in rows:
writer.writerow(
[
session.id,
session.user_id,
user.phone if user else "",
user.name if user else "",
session.title,
session.message_count,
session.last_message_at,
session.updated_at,
]
)
content = "\ufeff" + output.getvalue()
return Response(
content=content,
media_type="text/csv; charset=utf-8",
headers={"Content-Disposition": 'attachment; filename="chat_records.csv"'},
)
@@ -46,25 +83,33 @@ def chat_detail(
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
session_row = db.execute(
select(ChatSession, User)
.join(User, User.id == ChatSession.user_id, isouter=True)
.where(ChatSession.id == session_id)
).first()
if session_row is None:
from fastapi import HTTPException, status
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="会话不存在")
session, user = session_row
messages = db.scalars(
select(ChatMessage)
.where(ChatMessage.session_id == session_id)
.order_by(ChatMessage.created_at.asc(), ChatMessage.id.asc())
).all()
ai_logs = db.scalars(
select(AiRequestLog)
.where(AiRequestLog.session_id == session_id)
.order_by(AiRequestLog.created_at.asc(), AiRequestLog.id.asc())
).all()
return api_success(
[
{
"id": item.id,
"role": item.role,
"content": item.content,
"messageStatus": item.message_status,
"tokenInput": item.token_input,
"tokenOutput": item.token_output,
"responseTimeMs": item.response_time_ms,
"createdAt": item.created_at,
}
for item in messages
]
{
"session": _chat_row_dict(session, user),
"messages": [_message_dict(item) for item in messages],
"aiLogs": [_ai_log_dict(item, include_prompt=True) for item in ai_logs],
}
)
@@ -95,24 +140,110 @@ def operation_logs(
@router.get("/ai-log/list")
def ai_logs(db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin)) -> dict:
logs = db.scalars(select(AiRequestLog).order_by(AiRequestLog.created_at.desc()).limit(200)).all()
return api_success(
[
{
"id": item.id,
"sessionId": item.session_id,
"messageId": item.message_id,
"userId": item.user_id,
"modelName": item.model_name,
"knowledgeIds": item.knowledge_ids,
"retrieveCount": item.retrieve_count,
"totalToken": item.total_token,
"costMs": item.cost_ms,
"status": item.status,
"errorMessage": item.error_message,
"createdAt": item.created_at,
}
for item in logs
]
def ai_logs(
sessionId: int | None = Query(default=None),
userId: int | None = Query(default=None),
status: str = Query(default=""),
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
query = select(AiRequestLog).order_by(AiRequestLog.created_at.desc()).limit(200)
if sessionId is not None:
query = query.where(AiRequestLog.session_id == sessionId)
if userId is not None:
query = query.where(AiRequestLog.user_id == userId)
if status:
query = query.where(AiRequestLog.status == status)
logs = db.scalars(query).all()
return api_success([_ai_log_dict(item) for item in logs])
def _chat_query(
*,
keyword: str,
user_id: int | None,
status: str,
date_from: datetime | None,
date_to: datetime | None,
):
query = (
select(ChatSession, User)
.join(User, User.id == ChatSession.user_id, isouter=True)
.where(ChatSession.is_deleted == 0)
)
if user_id is not None:
query = query.where(ChatSession.user_id == user_id)
if date_from is not None:
query = query.where(ChatSession.updated_at >= date_from.replace(tzinfo=None))
if date_to is not None:
query = query.where(ChatSession.updated_at <= date_to.replace(tzinfo=None))
if status:
query = query.where(
exists()
.where(ChatMessage.session_id == ChatSession.id)
.where(ChatMessage.message_status == status)
)
if keyword:
like = f"%{keyword}%"
query = query.where(
or_(
ChatSession.title.like(like),
User.phone.like(like),
User.name.like(like),
exists()
.where(ChatMessage.session_id == ChatSession.id)
.where(ChatMessage.content.like(like)),
)
)
return query
def _chat_row_dict(session: ChatSession, user: User | None) -> dict:
return {
"id": session.id,
"userId": session.user_id,
"userPhone": user.phone if user else "",
"userName": user.name if user else "",
"title": session.title,
"messageCount": session.message_count,
"lastMessageAt": session.last_message_at,
"updatedAt": session.updated_at,
}
def _message_dict(message: ChatMessage) -> dict:
return {
"id": message.id,
"sessionId": message.session_id,
"userId": message.user_id,
"role": message.role,
"content": message.content,
"messageStatus": message.message_status,
"tokenInput": message.token_input,
"tokenOutput": message.token_output,
"responseTimeMs": message.response_time_ms,
"modelId": message.model_id,
"createdAt": message.created_at,
}
def _ai_log_dict(log: AiRequestLog, *, include_prompt: bool = False) -> dict:
data = {
"id": log.id,
"sessionId": log.session_id,
"messageId": log.message_id,
"userId": log.user_id,
"modelName": log.model_name,
"knowledgeIds": log.knowledge_ids,
"retrieveCount": log.retrieve_count,
"inputToken": log.input_token,
"outputToken": log.output_token,
"totalToken": log.total_token,
"costMs": log.cost_ms,
"status": log.status,
"errorMessage": log.error_message,
"createdAt": log.created_at,
}
if include_prompt:
data["prompt"] = log.prompt
return data