Files
AIEC-new/AIEC-server/停止功能改动文档.md
2025-10-17 09:31:28 +08:00

433 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 停止功能实现改动文档
## 实施日期
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 = `
<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行
```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 = '<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行
```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 = `
<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行
```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 = `
<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行
```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. 停止图标实现
- 使用红色正方形(`<rect>`)作为停止图标
- 强制隐藏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`
- 功能状态:✅ 已完成并测试