Files
digital-pilates/docs/server-side-cancel-support.md
richarjiang d52981ab29 feat: 实现聊天取消功能,提升用户交互体验
- 在教练页面中添加用户取消发送或终止回复的能力
- 更新发送按钮状态,支持发送和取消状态切换
- 在流式回复中显示取消按钮,允许用户中断助手的生成
- 增强请求管理,添加请求序列号和有效性验证,防止延迟响应影响用户体验
- 优化错误处理,区分用户主动取消和网络错误,提升系统稳定性
- 更新相关文档,详细描述取消功能的实现和用户体验设计
2025-08-18 18:59:23 +08:00

267 lines
7.4 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.

# 服务端取消支持方案
## 问题分析
当前取消功能的问题:
1. 客户端 `XMLHttpRequest.abort()` 只能终止网络传输,无法停止服务端处理
2. 服务端可能已经生成并保存了会话记录
3. 延迟响应导致"取消"的内容稍后出现
## 服务端需要支持的功能
### 1. 请求中断检测
**方案A连接断开检测推荐**
```python
# 伪代码示例
async def ai_chat_stream(request):
conversation_id = request.json.get('conversationId')
messages = request.json.get('messages', [])
# 如果是新会话且客户端断开,不创建会话记录
is_new_conversation = not conversation_id
temp_conversation_id = None
try:
# 检查客户端连接状态
if await request.is_disconnected():
print("Client disconnected before processing")
return
# 只有在开始生成内容时才创建会话
if is_new_conversation:
temp_conversation_id = create_conversation()
async for chunk in ai_generate_stream(messages):
# 每次生成前检查连接状态
if await request.is_disconnected():
print("Client disconnected during generation")
# 如果是新会话,删除临时创建的会话
if temp_conversation_id:
delete_conversation(temp_conversation_id)
return
yield chunk
# 成功完成,保存会话
if temp_conversation_id:
save_conversation(temp_conversation_id, messages + [ai_response])
except ClientDisconnectedError:
# 清理未完成的会话
if temp_conversation_id:
delete_conversation(temp_conversation_id)
```
**方案B取消端点备选**
```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_new_conversation(conversation_id):
delete_conversation(conversation_id)
return {"status": "cancelled"}
# 在主处理函数中检查取消状态
async def ai_chat_stream(request):
request_id = request.headers.get('X-Request-Id')
while generating:
if is_request_cancelled(request_id):
cleanup_and_exit()
return
# 继续处理...
```
### 2. 会话管理优化
**延迟会话创建:**
```python
class ConversationManager:
def __init__(self):
self.temp_conversations = {}
async def start_conversation(self, client_id):
"""开始新会话,但不立即持久化"""
temp_id = f"temp_{uuid4()}"
self.temp_conversations[temp_id] = {
'client_id': client_id,
'messages': [],
'created_at': datetime.now(),
'confirmed': False
}
return temp_id
async def confirm_conversation(self, temp_id, final_messages):
"""确认会话并持久化"""
if temp_id in self.temp_conversations:
# 保存到数据库
real_id = self.save_to_database(final_messages)
del self.temp_conversations[temp_id]
return real_id
async def cancel_conversation(self, temp_id):
"""取消临时会话"""
if temp_id in self.temp_conversations:
del self.temp_conversations[temp_id]
return True
return False
```
### 3. 流式响应优化
**分段提交策略:**
```python
async def ai_chat_stream(request):
try:
# 第一阶段:验证请求
if await request.is_disconnected():
return
# 第二阶段:开始生成(创建临时会话)
temp_conv_id = await start_temp_conversation()
yield json.dumps({"temp_conversation_id": temp_conv_id})
# 第三阶段:流式生成内容
full_response = ""
for chunk in ai_generate():
if await request.is_disconnected():
await cancel_temp_conversation(temp_conv_id)
return
full_response += chunk
yield chunk
# 第四阶段:确认并保存会话
final_conv_id = await confirm_conversation(temp_conv_id, full_response)
yield json.dumps({"final_conversation_id": final_conv_id})
except ClientDisconnectedError:
await cancel_temp_conversation(temp_conv_id)
```
## 具体实现建议
### 1. 服务端 API 修改
**请求头支持:**
```http
POST /api/ai-coach/chat
X-Request-Id: req_1234567890_abcdef
Content-Type: application/json
{
"conversationId": "existing_conv_id", // 可选,新会话时为空
"messages": [...],
"stream": true
}
```
**响应头:**
```http
HTTP/1.1 200 OK
X-Conversation-Id: mobile-1701234567-abcdef123
X-Request-Id: req_1234567890_abcdef
Content-Type: text/plain; charset=utf-8
```
### 2. 数据库设计优化
**会话状态管理:**
```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;
```
### 3. 客户端配合改进
**添加请求ID头**
```typescript
// 在 api.ts 中添加
export function postTextStream(path: string, body: any, callbacks: TextStreamCallbacks, options: TextStreamOptions = {}) {
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
const requestHeaders: Record<string, string> = {
'Content-Type': 'application/json',
'X-Request-Id': requestId,
...(options.headers || {}),
};
// ... 其余代码
}
```
**主动取消通知(可选):**
```typescript
async function notifyServerCancel(conversationId?: string, requestId?: string) {
try {
if (requestId) {
await api.post('/api/ai-coach/cancel', {
requestId,
conversationId
});
}
} catch (error) {
console.warn('Failed to notify server of cancellation:', error);
}
}
```
## 完整取消流程
### 客户端取消时:
1. 增加请求序列号(防止延迟响应)
2. 调用 `XMLHttpRequest.abort()`
3. 清理本地状态
4. (可选)通知服务端取消
### 服务端检测到断开:
1. 立即停止 AI 内容生成
2. 检查会话状态(临时/已确认)
3. 如果是临时会话,删除记录
4. 清理相关资源
### 防止延迟响应:
1. 客户端使用请求序列号验证
2. 服务端检查连接状态
3. 分段式会话确认机制
## 优先级建议
**立即实现(高优先级):**
1. 客户端请求序列号验证 ✅ 已实现
2. 延迟会话ID生成 ✅ 已实现
3. 客户端状态严格管理 ✅ 已实现
**服务端配合(中优先级):**
1. 连接断开检测
2. 临时会话管理
3. 请求ID追踪
**可选优化(低优先级):**
1. 主动取消通知端点
2. 会话状态数据库优化
3. 定期清理机制
通过以上客户端和服务端的协作改进,可以彻底解决取消功能的问题,确保用户取消操作的即时性和有效性。