Complete admin chat records
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user