From 96ce4dbe8af78e80ed7601362da8b596995eaa9c Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Fri, 6 Mar 2026 10:14:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=E6=96=B0=E5=A2=9E=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E7=BA=BF=E7=A8=8B=E5=88=97=E8=A1=A8=E7=9A=84=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81limit?= =?UTF-8?q?=E5=92=8Coffset=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/chat_router.py | 10 +- src/repositories/conversation_repository.py | 11 +- src/services/conversation_service.py | 4 + web/src/apis/agent_api.js | 6 +- web/src/components/AgentChatComponent.vue | 38 ++++- web/src/components/ChatSidebarComponent.vue | 159 +++++++++----------- web/src/components/DebugComponent.vue | 1 - web/src/utils/time.js | 5 +- 8 files changed, 137 insertions(+), 97 deletions(-) diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py index acc3575d..0f29ef43 100644 --- a/server/routers/chat_router.py +++ b/server/routers/chat_router.py @@ -706,10 +706,16 @@ async def create_thread( @chat.get("/threads", response_model=list[ThreadResponse]) async def list_threads( - agent_id: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_required_user) + agent_id: str, + limit: int = Query(100, ge=1, le=500), + offset: int = Query(0, ge=0), + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_required_user), ): """获取用户的所有对话线程 (使用新存储系统)""" - return await list_threads_view(agent_id=agent_id, db=db, current_user_id=str(current_user.id)) + return await list_threads_view( + agent_id=agent_id, db=db, current_user_id=str(current_user.id), limit=limit, offset=offset + ) @chat.delete("/thread/{thread_id}") diff --git a/src/repositories/conversation_repository.py b/src/repositories/conversation_repository.py index 018fb180..7e0fabcd 100644 --- a/src/repositories/conversation_repository.py +++ b/src/repositories/conversation_repository.py @@ -196,7 +196,12 @@ class ConversationRepository: return await self.get_messages(conversation.id, limit, offset) async def list_conversations( - self, user_id: str | None = None, agent_id: str | None = None, status: str = "active" + self, + user_id: str | None = None, + agent_id: str | None = None, + status: str = "active", + limit: int | None = None, + offset: int = 0, ) -> list[Conversation]: query = select(Conversation).where(Conversation.status == status) @@ -206,6 +211,10 @@ class ConversationRepository: query = query.where(Conversation.agent_id == agent_id) query = query.order_by(Conversation.updated_at.desc()) + + if limit: + query = query.limit(limit).offset(offset) + result = await self.db.execute(query) return list(result.scalars().all()) diff --git a/src/services/conversation_service.py b/src/services/conversation_service.py index d03d478d..7c9743b1 100644 --- a/src/services/conversation_service.py +++ b/src/services/conversation_service.py @@ -156,6 +156,8 @@ async def list_threads_view( agent_id: str, db: AsyncSession, current_user_id: str, + limit: int | None = None, + offset: int = 0, ) -> list[dict]: if not agent_id: raise HTTPException(status_code=422, detail="agent_id 不能为空") @@ -165,6 +167,8 @@ async def list_threads_view( user_id=str(current_user_id), agent_id=agent_id, status="active", + limit=limit, + offset=offset, ) return [ diff --git a/web/src/apis/agent_api.js b/web/src/apis/agent_api.js index cd2ec56a..4dcce5d9 100644 --- a/web/src/apis/agent_api.js +++ b/web/src/apis/agent_api.js @@ -286,10 +286,12 @@ export const threadApi = { /** * 获取对话线程列表 * @param {string} agentId - 智能体ID + * @param {number} limit - 返回数量限制,默认100 + * @param {number} offset - 偏移量,默认0 * @returns {Promise} - 对话线程列表 */ - getThreads: (agentId) => { - const url = `/api/chat/threads?agent_id=${agentId}` + getThreads: (agentId, limit = 100, offset = 0) => { + const url = `/api/chat/threads?agent_id=${agentId}&limit=${limit}&offset=${offset}` return apiGet(url) }, diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 08a48235..cf2c836b 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -9,12 +9,15 @@ :agents="agents" :selected-agent-id="currentAgentId" :is-creating-new-chat="chatUIStore.creatingNewChat" + :has-more-chats="hasMoreChats" + :is-loading-more="isLoadingMoreChats" @create-chat="createNewChat" @select-chat="selectChat" @delete-chat="deleteChat" @rename-chat="renameChat" @toggle-sidebar="toggleSidebar" @open-agent-modal="openAgentModal" + @load-more-chats="loadMoreChats" :class="{ 'sidebar-open': chatUIStore.isSidebarOpen, 'no-transition': localUIState.isInitialRender @@ -286,6 +289,8 @@ const chatState = reactive({ // 组件级别的线程和消息状态 const threads = ref([]) const threadMessages = ref({}) +const hasMoreChats = ref(true) // 是否还有更多对话可加载 +const isLoadingMoreChats = ref(false) // 加载更多对话中 // 本地 UI 状态(仅在本组件使用) const localUIState = reactive({ @@ -588,8 +593,10 @@ const fetchThreads = async (agentId = null) => { chatUIStore.isLoadingThreads = true try { - const fetchedThreads = await threadApi.getThreads(targetAgentId) + const fetchedThreads = await threadApi.getThreads(targetAgentId, 100, 0) threads.value = fetchedThreads || [] + // 如果返回的数量小于limit,说明没有更多了 + hasMoreChats.value = fetchedThreads && fetchedThreads.length >= 100 } catch (error) { console.error('Failed to fetch threads:', error) handleChatError(error, 'fetch') @@ -599,6 +606,31 @@ const fetchThreads = async (agentId = null) => { } } +// 加载更多对话 +const loadMoreChats = async () => { + if (isLoadingMoreChats.value || !hasMoreChats.value) return + + const targetAgentId = currentAgentId.value + if (!targetAgentId) return + + isLoadingMoreChats.value = true + try { + const offset = threads.value.length + const fetchedThreads = await threadApi.getThreads(targetAgentId, 100, offset) + if (fetchedThreads && fetchedThreads.length > 0) { + threads.value = [...threads.value, ...fetchedThreads] + hasMoreChats.value = fetchedThreads.length >= 100 + } else { + hasMoreChats.value = false + } + } catch (error) { + console.error('Failed to load more chats:', error) + handleChatError(error, 'fetch') + } finally { + isLoadingMoreChats.value = false + } +} + // 创建新线程 const createThread = async (agentId, title = '新的对话') => { if (!agentId) return null @@ -1234,10 +1266,6 @@ const createNewChat = async () => { // 如果第一个对话为空,直接切换到第一个对话而不是创建新对话 if (await switchToFirstChatIfEmpty()) return - // 只有当当前对话是第一个对话且为空时,才阻止创建新对话 - const currentThreadIndex = threads.value.findIndex((thread) => thread.id === currentChatId.value) - if (currentChatId.value && conversations.value.length === 0 && currentThreadIndex === 0) return - chatUIStore.creatingNewChat = true try { const newThread = await createThread(currentAgentId.value, '新的对话') diff --git a/web/src/components/ChatSidebarComponent.vue b/web/src/components/ChatSidebarComponent.vue index a0a86c40..01bd4e6e 100644 --- a/web/src/components/ChatSidebarComponent.vue +++ b/web/src/components/ChatSidebarComponent.vue @@ -29,54 +29,61 @@
-