- 在教练页面中添加用户取消发送或终止回复的能力 - 更新发送按钮状态,支持发送和取消状态切换 - 在流式回复中显示取消按钮,允许用户中断助手的生成 - 增强请求管理,添加请求序列号和有效性验证,防止延迟响应影响用户体验 - 优化错误处理,区分用户主动取消和网络错误,提升系统稳定性 - 更新相关文档,详细描述取消功能的实现和用户体验设计
267 lines
7.4 KiB
Markdown
267 lines
7.4 KiB
Markdown
# 服务端取消支持方案
|
||
|
||
## 问题分析
|
||
|
||
当前取消功能的问题:
|
||
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. 定期清理机制
|
||
|
||
通过以上客户端和服务端的协作改进,可以彻底解决取消功能的问题,确保用户取消操作的即时性和有效性。
|