diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py index 95ce2ebb..07983c17 100644 --- a/server/routers/chat_router.py +++ b/server/routers/chat_router.py @@ -322,6 +322,26 @@ async def chat_agent( conv_mgr=conv_manager, config_dict=langgraph_config, ) + except (asyncio.CancelledError, ConnectionError) as e: + # 客户端主动中断连接,尝试保存已生成的部分内容 + logger.info(f"Client disconnected for thread {thread_id}: {e}") + try: + if conversation: + langgraph_config = {"configurable": {"thread_id": thread_id, "user_id": user_id}} + await save_messages_from_langgraph_state( + agent_instance=agent, + conversation=conversation, + conv_mgr=conv_manager, + config_dict=langgraph_config, + ) + except Exception as save_error: + logger.error(f"Error saving partial messages after disconnect: {save_error}") + # 通知前端中断(可能发送不到,但用于一致性) + try: + yield make_chunk(status="interrupted", message="对话已中断", meta=meta) + except Exception: + pass + return except Exception as e: logger.error(f"Error streaming messages: {e}, {traceback.format_exc()}") yield make_chunk(message=f"Error streaming messages: {e}", status="error") diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index e31ae1f3..5735ad44 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -71,9 +71,9 @@ v-model="userInput" :is-loading="isProcessing" :disabled="!currentAgent" - :send-button-disabled="!userInput || !currentAgent || isProcessing" + :send-button-disabled="(!userInput || !currentAgent) && !isProcessing" :placeholder="'输入问题...'" - @send="handleSendMessage" + @send="handleSendOrStop" @keydown="handleKeyDown" /> @@ -131,9 +131,9 @@ v-model="userInput" :is-loading="isProcessing" :disabled="!currentAgent" - :send-button-disabled="!userInput || !currentAgent || isProcessing" + :send-button-disabled="(!userInput || !currentAgent) && !isProcessing" :placeholder="'输入问题...'" - @send="handleSendMessage" + @send="handleSendOrStop" @keydown="handleKeyDown" />
@@ -416,6 +416,11 @@ const _processStreamChunk = (chunk, threadId) => { fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId }); resetOnGoingConv(threadId); break; + case 'interrupted': + // 中断状态,刷新消息历史 + fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId }); + resetOnGoingConv(threadId); + break; } }; @@ -726,6 +731,27 @@ const handleSendMessage = async () => { } }; +// 发送或中断 +const handleSendOrStop = async () => { + const threadId = currentChatId.value; + const threadState = getThreadState(threadId); + if (isProcessing.value && threadState && threadState.streamAbortController) { + // 中断生成 + threadState.streamAbortController.abort(); + + // 中断后刷新消息历史,确保显示最新的状态 + try { + await fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId }); + message.info('已中断对话生成'); + } catch (error) { + console.error('刷新消息历史失败:', error); + message.info('已中断对话生成'); + } + return; + } + await handleSendMessage(); +}; + // ==================== UI HANDLERS ==================== const handleRenameChat = () => { if (!currentChatId.value || !currentThread.value) {