From 8e2dc1d7817b60c96fa7c0e028d848e56bf87587 Mon Sep 17 00:00:00 2001 From: Nelson <1475262689@qq.com> Date: Thu, 30 Jul 2026 15:09:05 +0800 Subject: [PATCH] fix: paginate chat detail messages --- .../apps/admin-web/src/App.vue | 43 +++++++++++++++++-- .../apps/admin-web/src/services/api.ts | 3 +- .../apps/admin-web/src/styles.css | 4 ++ .../apps/admin-web/src/types/api.ts | 1 + .../apps/backend/app/api/admin_records.py | 15 ++++++- .../backend/tests/test_admin_pagination.py | 24 ++++++++++- 6 files changed, 83 insertions(+), 7 deletions(-) diff --git a/ai_knowledge_base_v2/apps/admin-web/src/App.vue b/ai_knowledge_base_v2/apps/admin-web/src/App.vue index a65b86d..647fc98 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/App.vue +++ b/ai_knowledge_base_v2/apps/admin-web/src/App.vue @@ -52,6 +52,8 @@ const retrievalDetailOpen = ref(false); const previewKnowledgeId = ref(null); const chatDetail = ref(null); const chatDetailOpen = ref(false); +const chatDetailLoading = ref(false); +const chatDetailSessionId = ref(null); const selectedAiLog = ref(null); const aiLogDetailOpen = ref(false); const recordTab = ref("chats"); @@ -63,6 +65,7 @@ const pagers = reactive({ retrievals: { page: 1, pageSize: 20, total: 0 }, attention: { page: 1, pageSize: 20, total: 0 }, }); +const chatDetailMessagePager = reactive({ page: 1, pageSize: 20, total: 0 }); const editingModelId = ref(null); const editingKnowledgeId = ref(null); const batchNodeToken = ref(""); @@ -739,13 +742,38 @@ async function resetChatFilters() { } async function openChatDetail(row: ChatRecord) { - chatDetail.value = await api.chatDetail(row.id); - chatDetailOpen.value = true; + await openChatSession(row.id); } async function openChatSession(sessionId: number) { - chatDetail.value = await api.chatDetail(sessionId); chatDetailOpen.value = true; + chatDetail.value = null; + chatDetailSessionId.value = sessionId; + Object.assign(chatDetailMessagePager, { page: 1, pageSize: chatDetailMessagePager.pageSize, total: 0 }); + await loadChatDetail(sessionId, 1, chatDetailMessagePager.pageSize); +} + +async function loadChatDetail(sessionId = chatDetailSessionId.value, page = chatDetailMessagePager.page, pageSize = chatDetailMessagePager.pageSize) { + if (!sessionId) return; + chatDetailLoading.value = true; + try { + const detail = await api.chatDetail(sessionId, { messagePage: page, messagePageSize: pageSize }); + chatDetail.value = detail; + const messagePage = detail.messagesPage; + Object.assign(chatDetailMessagePager, { + page: messagePage?.page ?? page, + pageSize: messagePage?.pageSize ?? pageSize, + total: messagePage?.total ?? detail.messages.length, + }); + } catch (error) { + ElMessage.error(error instanceof Error ? error.message : "聊天详情加载失败"); + } finally { + chatDetailLoading.value = false; + } +} + +async function changeChatDetailMessagePage(page: number, pageSize: number) { + await loadChatDetail(chatDetailSessionId.value, page, pageSize); } async function openAiLogDetail(row: AiLogRecord) { @@ -1206,6 +1234,7 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") { +
+ +
diff --git a/ai_knowledge_base_v2/apps/admin-web/src/services/api.ts b/ai_knowledge_base_v2/apps/admin-web/src/services/api.ts index c57c362..9c7ffc5 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/services/api.ts +++ b/ai_knowledge_base_v2/apps/admin-web/src/services/api.ts @@ -201,7 +201,8 @@ export const api = { saveConfig: (payload: Record) => request("/admin/config", { method: "PUT", body: JSON.stringify(payload) }), chats: (query: ChatRecordQuery = {}) => request>(`/admin/chat/list${queryString(query)}`), - chatDetail: (sessionId: number) => request(`/admin/chat/${sessionId}`), + chatDetail: (sessionId: number, query: { messagePage?: number; messagePageSize?: number } = {}) => + request(`/admin/chat/${sessionId}${queryString(query)}`), exportChats: (query: ChatRecordQuery = {}) => download(`/admin/chat/export${queryString(query)}`, "chat_records.csv"), aiLogs: (query: { sessionId?: number; userId?: number; status?: string; page?: number; pageSize?: number } = {}) => request>(`/admin/ai-log/list${queryString(query)}`), diff --git a/ai_knowledge_base_v2/apps/admin-web/src/styles.css b/ai_knowledge_base_v2/apps/admin-web/src/styles.css index c9f7ce5..ce90ae0 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/styles.css +++ b/ai_knowledge_base_v2/apps/admin-web/src/styles.css @@ -1738,6 +1738,10 @@ textarea { align-items: center; } +.chat-detail-body { + min-height: 320px; +} + .chat-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); diff --git a/ai_knowledge_base_v2/apps/admin-web/src/types/api.ts b/ai_knowledge_base_v2/apps/admin-web/src/types/api.ts index 627a2be..5c8bc88 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/types/api.ts +++ b/ai_knowledge_base_v2/apps/admin-web/src/types/api.ts @@ -356,6 +356,7 @@ export interface RetrievedKnowledgeChunk { export interface ChatDetail { session: ChatRecord; messages: ChatMessageRecord[]; + messagesPage?: PageResult; aiLogs: AiLogRecord[]; } diff --git a/ai_knowledge_base_v2/apps/backend/app/api/admin_records.py b/ai_knowledge_base_v2/apps/backend/app/api/admin_records.py index 137c18e..d8c8b57 100644 --- a/ai_knowledge_base_v2/apps/backend/app/api/admin_records.py +++ b/ai_knowledge_base_v2/apps/backend/app/api/admin_records.py @@ -83,6 +83,8 @@ def export_chats( @router.get("/chat/{session_id}") def chat_detail( session_id: int, + messagePage: int = Query(default=1, ge=1), + messagePageSize: int = Query(default=20, ge=10, le=100), db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin), ) -> dict: @@ -97,10 +99,13 @@ def chat_detail( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="会话不存在") session, user = session_row + message_query = select(ChatMessage).where(ChatMessage.session_id == session_id) + message_total = db.scalar(select(func.count()).select_from(message_query.subquery())) or 0 messages = db.scalars( - select(ChatMessage) - .where(ChatMessage.session_id == session_id) + message_query .order_by(ChatMessage.created_at.asc(), ChatMessage.id.asc()) + .offset((messagePage - 1) * messagePageSize) + .limit(messagePageSize) ).all() ai_logs = db.scalars( select(AiRequestLog) @@ -111,6 +116,12 @@ def chat_detail( { "session": _chat_row_dict(session, user), "messages": [_message_dict(item) for item in messages], + "messagesPage": page_result( + [_message_dict(item) for item in messages], + total=message_total, + page=messagePage, + page_size=messagePageSize, + ), "aiLogs": [_ai_log_dict(item, include_prompt=True) for item in ai_logs], } ) diff --git a/ai_knowledge_base_v2/apps/backend/tests/test_admin_pagination.py b/ai_knowledge_base_v2/apps/backend/tests/test_admin_pagination.py index a122127..5fbb762 100644 --- a/ai_knowledge_base_v2/apps/backend/tests/test_admin_pagination.py +++ b/ai_knowledge_base_v2/apps/backend/tests/test_admin_pagination.py @@ -3,9 +3,10 @@ from sqlalchemy.orm import Session from sqlalchemy.pool import StaticPool from app.api.admin_agent_records import attention_list, retrieval_logs -from app.api.admin_records import ai_logs +from app.api.admin_records import ai_logs, chat_detail from app.api.admin_users import list_users from app.models import Base +from app.models.chat import ChatMessage, ChatSession from app.models.knowledge import HumanAttentionRecord, KnowledgeRetrievalLog from app.models.logs import AiRequestLog from app.models.user import User @@ -43,6 +44,27 @@ def test_ai_log_page_does_not_load_large_detail_fields(): assert all(item["retrievedChunks"] == [] for item in response["data"]["items"]) +def test_chat_detail_messages_are_paginated(): + with _database() as db: + user = User(id=1, phone="13800000000", name="学员", daily_chat_limit=10) + session = ChatSession(id=1, user_id=1, title="长会话", message_count=25) + db.add_all([user, session]) + db.add_all([ + ChatMessage(id=index + 1, session_id=1, user_id=1, role="user", content=f"消息{index + 1}") + for index in range(25) + ]) + db.commit() + + response = chat_detail(1, messagePage=2, messagePageSize=10, db=db, current_admin=object()) + + data = response["data"] + assert data["session"]["messageCount"] == 25 + assert data["messagesPage"]["total"] == 25 + assert data["messagesPage"]["page"] == 2 + assert len(data["messages"]) == 10 + assert data["messages"][0]["content"] == "消息11" + + def test_retrieval_and_attention_lists_are_paginated(): with _database() as db: for index in range(21):