- 在教练页面中添加用户取消发送或终止回复的能力 - 更新发送按钮状态,支持发送和取消状态切换 - 在流式回复中显示取消按钮,允许用户中断助手的生成 - 增强请求管理,添加请求序列号和有效性验证,防止延迟响应影响用户体验 - 优化错误处理,区分用户主动取消和网络错误,提升系统稳定性 - 更新相关文档,详细描述取消功能的实现和用户体验设计
235 lines
6.2 KiB
Markdown
235 lines
6.2 KiB
Markdown
# 完整取消功能解决方案
|
||
|
||
## 问题诊断
|
||
|
||
您遇到的问题确实需要前后端协作解决:
|
||
|
||
1. **客户端问题**: XMLHttpRequest.abort() 只能终止网络传输,无法停止服务端处理
|
||
2. **会话持久化问题**: 服务端可能已经开始处理并保存会话记录
|
||
3. **延迟响应问题**: 服务端处理完成后,响应仍会到达客户端
|
||
|
||
## 已实现的客户端增强方案
|
||
|
||
### 1. 请求序列号机制
|
||
```typescript
|
||
// 防止延迟响应影响当前状态
|
||
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. 请求有效性验证
|
||
```typescript
|
||
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生成
|
||
```typescript
|
||
// 修改前:立即生成会话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. 请求追踪机制
|
||
```typescript
|
||
// 在 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: 连接断开检测(推荐)
|
||
|
||
**原理**: 服务端检测客户端连接状态,断开时停止处理
|
||
|
||
```python
|
||
# 服务端伪代码
|
||
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,客户端主动通知
|
||
|
||
```python
|
||
@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"}
|
||
```
|
||
|
||
**客户端调用**:
|
||
```typescript
|
||
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);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 数据库设计建议
|
||
|
||
```sql
|
||
-- 会话状态管理
|
||
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;
|
||
```
|
||
|
||
## 立即可用的解决方案
|
||
|
||
### 当前已实现(无需服务端修改)
|
||
|
||
客户端增强已经可以解决大部分问题:
|
||
|
||
1. ✅ **防止延迟响应**: 请求序列号机制
|
||
2. ✅ **严格状态管理**: 请求有效性验证
|
||
3. ✅ **延迟会话创建**: 避免空会话记录
|
||
4. ✅ **即时UI反馈**: 取消后立即清理界面
|
||
|
||
### 效果评估
|
||
|
||
**改进前**:
|
||
- 点击取消 → 网络中断 → 但服务端继续处理 → 稍后响应仍然到达
|
||
- 会话记录已创建且保留
|
||
|
||
**改进后**:
|
||
- 点击取消 → 序列号增加 → 界面立即清理 → 延迟响应被忽略
|
||
- 会话ID延迟生成,减少空记录
|
||
|
||
### 推荐实施步骤
|
||
|
||
**第一阶段(立即可用)**:
|
||
1. ✅ 使用当前客户端增强方案
|
||
2. 测试取消功能的改善效果
|
||
|
||
**第二阶段(服务端配合)**:
|
||
1. 实现连接断开检测
|
||
2. 临时会话管理机制
|
||
3. 完善资源清理
|
||
|
||
**第三阶段(可选优化)**:
|
||
1. 主动取消通知端点
|
||
2. 数据库优化和定期清理
|
||
3. 监控和日志完善
|
||
|
||
## 测试验证
|
||
|
||
建议测试以下场景:
|
||
|
||
1. **快速取消**: 发送后立即点击取消
|
||
2. **生成中取消**: 在AI回复过程中取消
|
||
3. **网络异常**: 弱网环境下的取消行为
|
||
4. **连续操作**: 快速发送多条消息并取消
|
||
5. **状态恢复**: 取消后是否能正常发送新消息
|
||
|
||
通过这个完整方案,可以显著改善取消功能的用户体验,即使在服务端暂未配合的情况下,客户端增强也能解决大部分问题。
|