- 在教练页面中添加用户取消发送或终止回复的能力 - 更新发送按钮状态,支持发送和取消状态切换 - 在流式回复中显示取消按钮,允许用户中断助手的生成 - 增强请求管理,添加请求序列号和有效性验证,防止延迟响应影响用户体验 - 优化错误处理,区分用户主动取消和网络错误,提升系统稳定性 - 更新相关文档,详细描述取消功能的实现和用户体验设计
6.2 KiB
6.2 KiB
完整取消功能解决方案
问题诊断
您遇到的问题确实需要前后端协作解决:
- 客户端问题: XMLHttpRequest.abort() 只能终止网络传输,无法停止服务端处理
- 会话持久化问题: 服务端可能已经开始处理并保存会话记录
- 延迟响应问题: 服务端处理完成后,响应仍会到达客户端
已实现的客户端增强方案
1. 请求序列号机制
// 防止延迟响应影响当前状态
const requestSequenceRef = useRef<number>(0);
const activeRequestIdRef = useRef<string | null>(null);
function cancelCurrentRequest() {
// 增加序列号,使后续响应失效
requestSequenceRef.current += 1;
activeRequestIdRef.current = null;
// 中断网络请求
streamAbortRef.current?.abort();
// 清理状态和UI
setIsSending(false);
setIsStreaming(false);
// ...
}
2. 请求有效性验证
const isRequestValid = () => {
return activeRequestIdRef.current === requestId &&
requestSequenceRef.current === currentSequence;
};
// 在所有回调中验证
const onChunk = (chunk: string) => {
if (!isRequestValid()) {
console.log('Ignoring chunk from invalidated request');
return;
}
// 处理chunk...
};
3. 延迟会话ID生成
// 修改前:立即生成会话ID
function ensureConversationId(): string {
const cid = `mobile-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
setConversationId(cid); // 立即设置
return cid;
}
// 修改后:延迟生成,只在请求成功时设置
function ensureConversationId(): string {
if (conversationId && conversationId.trim()) return conversationId;
return ''; // 返回空,让服务端生成
}
4. 请求追踪机制
// 在 api.ts 中添加请求ID
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
const requestHeaders = {
'Content-Type': 'application/json',
'X-Request-Id': requestId, // 用于服务端追踪
// ...
};
需要服务端配合的方案
方案1: 连接断开检测(推荐)
原理: 服务端检测客户端连接状态,断开时停止处理
# 服务端伪代码
async def ai_chat_stream(request):
conversation_id = request.json.get('conversationId')
is_new_conversation = not conversation_id
try:
# 检查客户端连接
if await request.is_disconnected():
return
# 延迟创建会话记录
temp_conv_id = None
if is_new_conversation:
temp_conv_id = create_temp_conversation()
async for chunk in ai_generate_stream():
# 每次生成前检查连接
if await request.is_disconnected():
if temp_conv_id:
delete_temp_conversation(temp_conv_id)
return
yield chunk
# 只有成功完成才保存会话
if temp_conv_id:
save_permanent_conversation(temp_conv_id)
except ClientDisconnectedError:
if temp_conv_id:
delete_temp_conversation(temp_conv_id)
优点:
- 自动检测,无需额外API
- 资源清理及时
- 实现相对简单
方案2: 主动取消端点(备选)
原理: 提供专门的取消API,客户端主动通知
@app.post("/api/ai-coach/cancel")
async def cancel_request(request):
request_id = request.json.get('requestId')
conversation_id = request.json.get('conversationId')
# 标记请求取消
cancel_request_processing(request_id)
# 清理临时会话
if is_temp_conversation(conversation_id):
delete_conversation(conversation_id)
return {"status": "cancelled"}
客户端调用:
function cancelCurrentRequest() {
// 现有逻辑...
// 主动通知服务端
if (activeRequestIdRef.current) {
notifyServerCancel(conversationId, activeRequestIdRef.current);
}
}
async function notifyServerCancel(conversationId?: string, requestId?: string) {
try {
await api.post('/api/ai-coach/cancel', {
requestId,
conversationId
});
} catch (error) {
console.warn('Failed to notify server:', error);
}
}
数据库设计建议
-- 会话状态管理
CREATE TABLE conversations (
id VARCHAR(50) PRIMARY KEY,
user_id INT,
status ENUM('temp', 'active', 'cancelled') DEFAULT 'temp',
created_at TIMESTAMP,
confirmed_at TIMESTAMP NULL,
messages JSON,
INDEX idx_status_created (status, created_at)
);
-- 定期清理临时会话(防止内存泄漏)
-- 可以用定时任务或在新请求时清理
DELETE FROM conversations
WHERE status = 'temp'
AND created_at < NOW() - INTERVAL 10 MINUTE;
立即可用的解决方案
当前已实现(无需服务端修改)
客户端增强已经可以解决大部分问题:
- ✅ 防止延迟响应: 请求序列号机制
- ✅ 严格状态管理: 请求有效性验证
- ✅ 延迟会话创建: 避免空会话记录
- ✅ 即时UI反馈: 取消后立即清理界面
效果评估
改进前:
- 点击取消 → 网络中断 → 但服务端继续处理 → 稍后响应仍然到达
- 会话记录已创建且保留
改进后:
- 点击取消 → 序列号增加 → 界面立即清理 → 延迟响应被忽略
- 会话ID延迟生成,减少空记录
推荐实施步骤
第一阶段(立即可用):
- ✅ 使用当前客户端增强方案
- 测试取消功能的改善效果
第二阶段(服务端配合):
- 实现连接断开检测
- 临时会话管理机制
- 完善资源清理
第三阶段(可选优化):
- 主动取消通知端点
- 数据库优化和定期清理
- 监控和日志完善
测试验证
建议测试以下场景:
- 快速取消: 发送后立即点击取消
- 生成中取消: 在AI回复过程中取消
- 网络异常: 弱网环境下的取消行为
- 连续操作: 快速发送多条消息并取消
- 状态恢复: 取消后是否能正常发送新消息
通过这个完整方案,可以显著改善取消功能的用户体验,即使在服务端暂未配合的情况下,客户端增强也能解决大部分问题。