16 KiB
16 KiB
停止功能实现改动文档
实施日期
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行)
// 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 = `
<div style="text-align: center; padding: 10px;">
<div style="display: inline-block; background: #fff3cd; color: #856404; padding: 8px 16px; border-radius: 8px; font-size: 14px;">
⚠️ 已通过ESC键取消请求。注意:后端可能仍在处理,建议稍等片刻再发送新请求。
</div>
</div>
`;
chatMessages.appendChild(cancelMsg);
// 5秒后自动移除提示
setTimeout(() => {
if (cancelMsg.parentNode) {
cancelMsg.remove();
}
}, 5000);
}
}
}
});
// ... 其他初始化代码
2. 全局updateSendUI函数(第279-375行)
/* ===== 聊天功能相关全局函数 ===== */
// 更新发送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 = '<rect x="6" y="6" width="12" height="12" fill="#dc3545"/>';
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 = '<rect x="6" y="6" width="12" height="12" fill="#dc3545" stroke="none"/>';
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 = '<path d="m3 3 3 9-3 9 19-9Z"/><path d="m6 12 13 0"/>';
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 = '<path d="m3 3 3 9-3 9 19-9Z"/><path d="m6 12 13 0"/>';
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行)
// 发送消息 - 支持停止功能
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 = `
<div style="text-align: center; padding: 10px;">
<div style="display: inline-block; background: #fff3cd; color: #856404; padding: 8px 16px; border-radius: 8px; font-size: 14px;">
⚠️ 请求已取消。注意:后端可能仍在处理,建议稍等片刻再发送新请求。
</div>
</div>
`;
chatMessages.appendChild(cancelMsg);
// 5秒后自动移除提示
setTimeout(() => {
if (cancelMsg.parentNode) {
cancelMsg.remove();
}
}, 5000);
}
} else {
// 正常发送消息
console.log('发送按钮被点击');
sendMessage();
}
});
4. 聊天模式发送按钮事件处理(第535-580行)
// 发送消息 - 支持停止功能
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 = `
<div style="text-align: center; padding: 10px;">
<div style="display: inline-block; background: #fff3cd; color: #856404; padding: 8px 16px; border-radius: 8px; font-size: 14px;">
⚠️ 请求已取消。注意:后端可能仍在处理,建议稍等片刻再发送新请求。
</div>
</div>
`;
chatMessages.appendChild(cancelMsg);
// 5秒后自动移除提示
setTimeout(() => {
if (cancelMsg.parentNode) {
cancelMsg.remove();
}
}, 5000);
}
} else {
// 正常发送消息
sendChatModeMessage();
}
});
5. 保存AbortController到全局(第780行、1016行)
// 保存AbortController到全局,以便中断
window.currentAbortController = await window.ChatAPIService.sendMessageStream(
{
message: message,
conversationId: targetChatId,
mode: isDeepSearchActive ? 'research' : 'chat',
deepResearch: isDeepResearch,
showThinking: showThinking
},
// ... 回调函数
6. 简化updateChatModeSendUI函数(第769-771行)
// 聊天模式UI状态更新 - 直接调用全局的updateSendUI
function updateChatModeSendUI(isLoading) {
updateSendUI(isLoading);
}
7. 界面切换时的状态保持
startNewChat函数(第1457-1462行)
// 如果当前正在等待中,保持等待状态
if (window.isSubmitting) {
// 确保首页也显示等待状态
updateSendUI(true);
console.log('返回首页,保持等待状态');
}
showChatContainer函数(第1534-1537行)
// 如果当前正在提交消息,确保聊天模式也显示等待状态
if (window.isSubmitting) {
// 使用统一的updateSendUI函数来同步两个界面的状态
updateSendUI(true);
}
关键改动说明
1. 统一的UI状态管理
- 将原本分散在initChat内部的updateSendUI函数提升到全局作用域
- 删除了重复的函数定义,确保整个应用使用同一个状态更新函数
- updateChatModeSendUI简化为直接调用全局updateSendUI
2. 停止图标实现
- 使用红色正方形(
<rect>)作为停止图标 - 强制隐藏loading spinner,避免与停止图标冲突
- 正确设置SVG属性确保颜色显示
3. AbortController管理
- 将sendMessageStream返回的AbortController保存到
window.currentAbortController - 停止时调用
abort()方法中断请求 - 中断后将其设为null避免重复调用
4. 用户体验优化
- 停止按钮在发送过程中保持可点击状态
- 提供清晰的视觉反馈(红色停止图标)
- 显示友好的取消提示信息
- 警告用户后端可能仍在处理
测试要点
-
基本功能测试
- 发送消息后按钮变为红色停止图标
- 点击停止图标能中断请求
- ESC键能中断请求
-
状态同步测试
- 首页发送后切换到聊天模式,仍显示停止状态
- 聊天模式发送后返回首页,仍显示停止状态
- 停止后两个界面同时恢复
-
边界情况测试
- 快速切换界面
- 连续点击停止
- 网络延迟情况
注意事项
- 前端限制:前端中断只是断开连接,后端可能继续处理
- 后端配合:需要后端配合才能真正停止处理
- 用户提醒:建议用户等待片刻再发送新请求
参考来源
本功能实现参考了以下项目:
- 项目路径:
D:\云大阁备份\yundage\yundage-backserver-rag-agent - 参考文件:
js/main.js - 参考文档:
停止功能实现总结.md
版本信息
- 实施日期:2024-12-25
- 影响文件:
F:\YXL_self\RAG-TEST\AIEC-server\js\main.js - 功能状态:✅ 已完成并测试