# 停止功能实现改动文档 ## 实施日期 2024-12-25 ## 功能概述 为AIEC-server添加了完整的请求停止功能,允许用户在发送消息后通过点击停止按钮或按ESC键中断正在处理的请求。该功能参考了yundage项目(D:\云大阁备份\yundage\yundage-backserver-rag-agent)的实现方式。 ## 改动的功能 ### 1. 核心功能 - ✅ **停止按钮功能**:发送消息时,发送按钮变为红色停止图标,点击可中断请求 - ✅ **ESC键支持**:按下ESC键可快速中断正在进行的请求 - ✅ **状态同步**:首页和聊天模式的UI状态完全同步 - ✅ **界面切换保持**:切换界面时保持等待状态 - ✅ **用户友好提示**:显示取消提示和警告信息 ### 2. UI表现 - 发送中显示红色停止图标(正方形)代替转圈动画 - 停止按钮在发送过程中保持可点击状态 - 取消后显示"已取消请求"文本提示 - 聊天窗口显示黄色警告框提醒后端可能仍在处理 - 所有提示信息5秒后自动消失 ### 3. 技术实现 - 使用AbortController API实现请求中断 - 全局状态管理确保UI同步 - 统一的updateSendUI函数管理两个界面 ## 改动的文件 ### 1. F:\YXL_self\RAG-TEST\AIEC-server\js\main.js 主要的功能实现文件,包含所有核心逻辑。 ## 改动的代码 ### 1. 添加ESC键监听器(第12-67行) ```javascript // DOM加载完成后执行 document.addEventListener('DOMContentLoaded', function() { // 添加ESC键监听,用于中断流式响应 document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { // 如果正在发送中,执行停止操作 if (window.isSubmitting) { console.log('用户按下ESC,中断请求'); // 中断请求 if (window.currentAbortController) { window.currentAbortController.abort(); window.currentAbortController = null; } // 重置状态 window.isSubmitting = false; // 更新UI if (typeof updateSendUI === 'function') { updateSendUI(false); } else if (window.updateSendUI) { window.updateSendUI(false); } // 显示提示 const statusText = document.querySelector('.status-text'); if (statusText) { statusText.textContent = '已通过ESC取消请求'; setTimeout(() => { statusText.textContent = ''; }, 3000); } // 添加系统提示到聊天窗口 const chatMessages = document.getElementById('chatMessages'); if (chatMessages) { const cancelMsg = document.createElement('div'); cancelMsg.className = 'message-item mb-4'; cancelMsg.innerHTML = `
⚠️ 已通过ESC键取消请求。注意:后端可能仍在处理,建议稍等片刻再发送新请求。
`; chatMessages.appendChild(cancelMsg); // 5秒后自动移除提示 setTimeout(() => { if (cancelMsg.parentNode) { cancelMsg.remove(); } }, 5000); } } } }); // ... 其他初始化代码 ``` ### 2. 全局updateSendUI函数(第279-375行) ```javascript /* ===== 聊天功能相关全局函数 ===== */ // 更新发送UI状态 - 移到全局作用域,同时更新两个界面 function updateSendUI(isLoading) { // 首页元素 const sendIcon = document.getElementById('sendIcon'); const loadingSpinner = document.getElementById('loadingSpinner'); const statusText = document.querySelector('.status-text'); const messageInput = document.getElementById('messageInput'); const sendBtn = document.getElementById('sendBtn'); // 聊天模式元素 const chatModeSendIcon = document.getElementById('chatModeSendIcon'); const chatModeLoadingSpinner = document.getElementById('chatModeLoadingSpinner'); const chatModeMessageInput = document.getElementById('chatModeMessageInput'); const chatModeSendBtn = document.getElementById('chatModeSendBtn'); if (isLoading) { // 强制隐藏loading spinner if (loadingSpinner) { loadingSpinner.style.setProperty('display', 'none', 'important'); loadingSpinner.style.visibility = 'hidden'; } // 显示停止图标 if (sendIcon) { sendIcon.style.display = 'block'; sendIcon.style.visibility = 'visible'; // 将SVG图标改为停止图标(红色正方形) sendIcon.innerHTML = ''; sendIcon.style.color = '#dc3545'; // 确保按钮可点击 if (sendBtn) { sendBtn.style.cursor = 'pointer'; sendBtn.disabled = false; // 确保按钮不被禁用 } } if (statusText) statusText.textContent = 'AI is thinking...'; if (messageInput) messageInput.disabled = true; // 更新聊天模式UI - 同样显示停止图标 if (chatModeLoadingSpinner) { chatModeLoadingSpinner.style.setProperty('display', 'none', 'important'); chatModeLoadingSpinner.style.visibility = 'hidden'; } if (chatModeSendIcon) { chatModeSendIcon.style.display = 'block'; chatModeSendIcon.style.visibility = 'visible'; // 对于SVG元素,设置停止图标(红色方块) chatModeSendIcon.innerHTML = ''; chatModeSendIcon.style.color = '#dc3545'; // 确保SVG的fill属性生效 chatModeSendIcon.setAttribute('fill', '#dc3545'); chatModeSendIcon.setAttribute('stroke', 'none'); } if (chatModeSendBtn) { chatModeSendBtn.style.cursor = 'pointer'; chatModeSendBtn.disabled = false; // 确保按钮可点击以便停止 } if (chatModeMessageInput) chatModeMessageInput.disabled = true; } else { // 恢复发送图标 if (sendIcon) { sendIcon.style.display = 'block'; // 恢复原始的SVG发送图标 sendIcon.innerHTML = ''; sendIcon.style.color = '#999'; } if (loadingSpinner) { loadingSpinner.style.setProperty('display', 'none', 'important'); } if (statusText) statusText.textContent = 'Ready to submit!'; if (messageInput) messageInput.disabled = false; // 更新发送按钮状态 const hasText = messageInput && messageInput.value.trim().length > 0; if (sendIcon) { sendIcon.style.opacity = hasText && !window.isSubmitting ? '1' : '0.3'; } // 更新聊天模式UI - 恢复发送图标 if (chatModeLoadingSpinner) { chatModeLoadingSpinner.style.setProperty('display', 'none', 'important'); } if (chatModeSendIcon) { chatModeSendIcon.style.display = 'block'; // 恢复原始的发送图标 chatModeSendIcon.innerHTML = ''; chatModeSendIcon.style.color = '#999'; // 恢复SVG的默认属性 chatModeSendIcon.setAttribute('fill', 'none'); chatModeSendIcon.setAttribute('stroke', 'currentColor'); } if (chatModeSendBtn) { chatModeSendBtn.style.cursor = 'pointer'; } if (chatModeMessageInput) chatModeMessageInput.disabled = false; // 更新聊天模式按钮状态 const chatHasText = chatModeMessageInput && chatModeMessageInput.value.trim().length > 0; if (chatModeSendIcon) { chatModeSendIcon.style.opacity = chatHasText && !window.isSubmitting ? '1' : '0.3'; } } } // 将updateSendUI暴露到全局 window.updateSendUI = updateSendUI; ``` ### 3. 首页发送按钮事件处理(第442-497行) ```javascript // 发送消息 - 支持停止功能 sendBtn.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); console.log('发送按钮被点击,当前状态:', window.isSubmitting); // 如果正在发送中,执行停止操作 if (window.isSubmitting) { console.log('正在发送中,尝试停止...'); // 中断请求 if (window.currentAbortController) { console.log('找到AbortController,执行中断'); window.currentAbortController.abort(); window.currentAbortController = null; } // 重置状态 window.isSubmitting = false; updateSendUI(false); // 显示提示 const statusText = document.querySelector('.status-text'); if (statusText) { statusText.textContent = '已取消请求'; setTimeout(() => { statusText.textContent = ''; }, 3000); } // 添加系统提示到聊天窗口 const chatMessages = document.getElementById('chatMessages'); if (chatMessages) { const cancelMsg = document.createElement('div'); cancelMsg.className = 'message-item mb-4'; cancelMsg.innerHTML = `
⚠️ 请求已取消。注意:后端可能仍在处理,建议稍等片刻再发送新请求。
`; chatMessages.appendChild(cancelMsg); // 5秒后自动移除提示 setTimeout(() => { if (cancelMsg.parentNode) { cancelMsg.remove(); } }, 5000); } } else { // 正常发送消息 console.log('发送按钮被点击'); sendMessage(); } }); ``` ### 4. 聊天模式发送按钮事件处理(第535-580行) ```javascript // 发送消息 - 支持停止功能 chatModeSendBtn.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); console.log('聊天模式发送按钮被点击,当前状态:', window.isSubmitting); // 如果正在发送中,执行停止操作 if (window.isSubmitting) { console.log('聊天模式:正在发送中,尝试停止...'); // 中断请求 if (window.currentAbortController) { console.log('聊天模式:找到AbortController,执行中断'); window.currentAbortController.abort(); window.currentAbortController = null; } // 重置状态 window.isSubmitting = false; updateSendUI(false); // 添加系统提示到聊天窗口 const chatMessages = document.getElementById('chatMessages'); if (chatMessages) { const cancelMsg = document.createElement('div'); cancelMsg.className = 'message-item mb-4'; cancelMsg.innerHTML = `
⚠️ 请求已取消。注意:后端可能仍在处理,建议稍等片刻再发送新请求。
`; chatMessages.appendChild(cancelMsg); // 5秒后自动移除提示 setTimeout(() => { if (cancelMsg.parentNode) { cancelMsg.remove(); } }, 5000); } } else { // 正常发送消息 sendChatModeMessage(); } }); ``` ### 5. 保存AbortController到全局(第780行、1016行) ```javascript // 保存AbortController到全局,以便中断 window.currentAbortController = await window.ChatAPIService.sendMessageStream( { message: message, conversationId: targetChatId, mode: isDeepSearchActive ? 'research' : 'chat', deepResearch: isDeepResearch, showThinking: showThinking }, // ... 回调函数 ``` ### 6. 简化updateChatModeSendUI函数(第769-771行) ```javascript // 聊天模式UI状态更新 - 直接调用全局的updateSendUI function updateChatModeSendUI(isLoading) { updateSendUI(isLoading); } ``` ### 7. 界面切换时的状态保持 #### startNewChat函数(第1457-1462行) ```javascript // 如果当前正在等待中,保持等待状态 if (window.isSubmitting) { // 确保首页也显示等待状态 updateSendUI(true); console.log('返回首页,保持等待状态'); } ``` #### showChatContainer函数(第1534-1537行) ```javascript // 如果当前正在提交消息,确保聊天模式也显示等待状态 if (window.isSubmitting) { // 使用统一的updateSendUI函数来同步两个界面的状态 updateSendUI(true); } ``` ## 关键改动说明 ### 1. 统一的UI状态管理 - 将原本分散在initChat内部的updateSendUI函数提升到全局作用域 - 删除了重复的函数定义,确保整个应用使用同一个状态更新函数 - updateChatModeSendUI简化为直接调用全局updateSendUI ### 2. 停止图标实现 - 使用红色正方形(``)作为停止图标 - 强制隐藏loading spinner,避免与停止图标冲突 - 正确设置SVG属性确保颜色显示 ### 3. AbortController管理 - 将sendMessageStream返回的AbortController保存到`window.currentAbortController` - 停止时调用`abort()`方法中断请求 - 中断后将其设为null避免重复调用 ### 4. 用户体验优化 - 停止按钮在发送过程中保持可点击状态 - 提供清晰的视觉反馈(红色停止图标) - 显示友好的取消提示信息 - 警告用户后端可能仍在处理 ## 测试要点 1. **基本功能测试** - 发送消息后按钮变为红色停止图标 - 点击停止图标能中断请求 - ESC键能中断请求 2. **状态同步测试** - 首页发送后切换到聊天模式,仍显示停止状态 - 聊天模式发送后返回首页,仍显示停止状态 - 停止后两个界面同时恢复 3. **边界情况测试** - 快速切换界面 - 连续点击停止 - 网络延迟情况 ## 注意事项 1. **前端限制**:前端中断只是断开连接,后端可能继续处理 2. **后端配合**:需要后端配合才能真正停止处理 3. **用户提醒**:建议用户等待片刻再发送新请求 ## 参考来源 本功能实现参考了以下项目: - 项目路径:`D:\云大阁备份\yundage\yundage-backserver-rag-agent` - 参考文件:`js/main.js` - 参考文档:`停止功能实现总结.md` ## 版本信息 - 实施日期:2024-12-25 - 影响文件:`F:\YXL_self\RAG-TEST\AIEC-server\js\main.js` - 功能状态:✅ 已完成并测试