feat: improve chat feedback navigation
This commit is contained in:
@@ -29,6 +29,7 @@ from app.services.growth_profile_service import topic_dict, topic_summary_dict
|
||||
from app.services.help_card_service import help_card_dict
|
||||
from app.services.share_draft_service import share_draft_dict
|
||||
from app.services.chat_export_service import ChatExportService
|
||||
from app.services.chat_message_navigation_service import ChatMessageNavigationService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -119,6 +120,7 @@ def export_chats(
|
||||
@router.get("/chat/{session_id}/messages")
|
||||
def chat_messages(
|
||||
session_id: int,
|
||||
keyword: str = Query(default="", max_length=100),
|
||||
page: int = Query(default=1, ge=1),
|
||||
pageSize: int = Query(default=20, ge=10, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
@@ -127,7 +129,10 @@ def chat_messages(
|
||||
exists_session = db.scalar(select(ChatSession.id).where(ChatSession.id == session_id))
|
||||
if exists_session is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="会话不存在")
|
||||
normalized_keyword = keyword.strip()
|
||||
message_query = select(ChatMessage).where(ChatMessage.session_id == session_id)
|
||||
if normalized_keyword:
|
||||
message_query = message_query.where(ChatMessage.content.contains(normalized_keyword, autoescape=True))
|
||||
total = db.scalar(select(func.count()).select_from(message_query.subquery())) or 0
|
||||
messages = db.scalars(
|
||||
message_query
|
||||
@@ -211,30 +216,18 @@ def chat_detail(
|
||||
)
|
||||
if focused_message is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="触发消息不属于该会话或已不存在")
|
||||
focused_position = db.scalar(
|
||||
select(func.count()).select_from(ChatMessage).where(
|
||||
ChatMessage.session_id == session_id,
|
||||
or_(
|
||||
ChatMessage.created_at < focused_message.created_at,
|
||||
(
|
||||
(ChatMessage.created_at == focused_message.created_at)
|
||||
& (ChatMessage.id <= focused_message.id)
|
||||
),
|
||||
),
|
||||
)
|
||||
) or 1
|
||||
messagePage = (focused_position - 1) // messagePageSize + 1
|
||||
messagePage = ChatMessageNavigationService.page_for_message(
|
||||
db,
|
||||
session_id=session_id,
|
||||
message=focused_message,
|
||||
page_size=messagePageSize,
|
||||
)
|
||||
messages = db.scalars(
|
||||
message_query
|
||||
.order_by(ChatMessage.created_at.asc(), ChatMessage.id.asc())
|
||||
.offset((messagePage - 1) * messagePageSize)
|
||||
.limit(messagePageSize)
|
||||
).all()
|
||||
ai_logs = db.scalars(
|
||||
select(AiRequestLog)
|
||||
.where(AiRequestLog.session_id == session_id)
|
||||
.order_by(AiRequestLog.created_at.asc(), AiRequestLog.id.asc())
|
||||
).all()
|
||||
topics = _topic_rows(db, session_id)
|
||||
topic_ids = [item["id"] for item in topics]
|
||||
help_cards = []
|
||||
@@ -264,7 +257,8 @@ def chat_detail(
|
||||
page=messagePage,
|
||||
page_size=messagePageSize,
|
||||
),
|
||||
"aiLogs": [_ai_log_dict(item, include_prompt=True) for item in ai_logs],
|
||||
# Large prompts and retrieval chunks are loaded asynchronously by the drawer.
|
||||
"aiLogs": [],
|
||||
"topics": topics,
|
||||
"helpCards": [help_card_dict(item) for item in help_cards],
|
||||
"shareDrafts": [share_draft_dict(item) for item in share_drafts],
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, date, datetime, timedelta
|
||||
from datetime import date, timedelta
|
||||
from io import BytesIO
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
@@ -18,13 +19,14 @@ from app.api.pagination import page_result
|
||||
from app.core.database import get_db
|
||||
from app.core.dependencies import get_current_admin, get_current_user
|
||||
from app.core.responses import api_success
|
||||
from app.core.time_utils import business_day_boundary, to_business_naive
|
||||
from app.core.time_utils import business_day_boundary, to_business_naive, utc_now_naive
|
||||
from app.models.admin import Admin
|
||||
from app.models.chat import ChatMessage, ChatSession
|
||||
from app.models.feedback import MessageFeedback
|
||||
from app.models.user import User
|
||||
from app.services.admin_service import OperationLogService
|
||||
from app.services.admin_permission_service import require_permission
|
||||
from app.services.chat_message_navigation_service import ChatMessageNavigationService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -96,7 +98,13 @@ def export_feedback(
|
||||
|
||||
|
||||
@router.get("/admin/{feedback_id}")
|
||||
def feedback_detail(feedback_id: int, db: Session = Depends(get_db), admin: Admin = Depends(get_current_admin)) -> dict:
|
||||
def feedback_detail(
|
||||
feedback_id: int,
|
||||
messagePage: Annotated[int | None, Query(ge=1)] = None,
|
||||
messagePageSize: Annotated[int, Query(ge=10, le=100)] = 20,
|
||||
db: Session = Depends(get_db),
|
||||
admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
require_permission(admin, "feedback.detail")
|
||||
row = db.execute(select(MessageFeedback, User, ChatMessage, ChatSession).join(User, User.id == MessageFeedback.user_id).join(ChatMessage, ChatMessage.id == MessageFeedback.message_id).join(ChatSession, ChatSession.id == MessageFeedback.session_id).where(MessageFeedback.id == feedback_id)).first()
|
||||
if row is None:
|
||||
@@ -105,10 +113,43 @@ def feedback_detail(feedback_id: int, db: Session = Depends(get_db), admin: Admi
|
||||
if not feedback.is_read:
|
||||
feedback.is_read = 1
|
||||
feedback.read_by = admin.id
|
||||
feedback.read_at = datetime.now(UTC).replace(tzinfo=None)
|
||||
feedback.read_at = utc_now_naive()
|
||||
db.commit()
|
||||
messages = db.scalars(select(ChatMessage).where(ChatMessage.session_id == feedback.session_id, ChatMessage.id <= target.id).order_by(ChatMessage.id.asc()).limit(200)).all()
|
||||
return api_success({**_summary(feedback, user, target), "sessionTitle": session.title, "messages": [{"id": m.id, "role": m.role, "content": m.content, "createdAt": m.created_at, "isTarget": m.id == target.id} for m in messages]})
|
||||
message_query = select(ChatMessage).where(ChatMessage.session_id == feedback.session_id)
|
||||
message_total = db.scalar(select(func.count()).select_from(message_query.subquery())) or 0
|
||||
resolved_page = messagePage or ChatMessageNavigationService.page_for_message(
|
||||
db,
|
||||
session_id=feedback.session_id,
|
||||
message=target,
|
||||
page_size=messagePageSize,
|
||||
)
|
||||
messages = db.scalars(
|
||||
message_query
|
||||
.order_by(ChatMessage.created_at.asc(), ChatMessage.id.asc())
|
||||
.offset((resolved_page - 1) * messagePageSize)
|
||||
.limit(messagePageSize)
|
||||
).all()
|
||||
serialized_messages = [
|
||||
{
|
||||
"id": message.id,
|
||||
"role": message.role,
|
||||
"content": message.content,
|
||||
"createdAt": message.created_at,
|
||||
"isTarget": message.id == target.id,
|
||||
}
|
||||
for message in messages
|
||||
]
|
||||
return api_success({
|
||||
**_summary(feedback, user, target),
|
||||
"sessionTitle": session.title,
|
||||
"messages": serialized_messages,
|
||||
"messagesPage": page_result(
|
||||
serialized_messages,
|
||||
total=message_total,
|
||||
page=resolved_page,
|
||||
page_size=messagePageSize,
|
||||
),
|
||||
})
|
||||
|
||||
|
||||
@router.delete("/admin/{feedback_id}")
|
||||
|
||||
Reference in New Issue
Block a user