- 在教练页面中添加用户取消发送或终止回复的能力 - 更新发送按钮状态,支持发送和取消状态切换 - 在流式回复中显示取消按钮,允许用户中断助手的生成 - 增强请求管理,添加请求序列号和有效性验证,防止延迟响应影响用户体验 - 优化错误处理,区分用户主动取消和网络错误,提升系统稳定性 - 更新相关文档,详细描述取消功能的实现和用户体验设计
7.4 KiB
7.4 KiB
服务端取消支持方案
问题分析
当前取消功能的问题:
- 客户端
XMLHttpRequest.abort()只能终止网络传输,无法停止服务端处理 - 服务端可能已经生成并保存了会话记录
- 延迟响应导致"取消"的内容稍后出现
服务端需要支持的功能
1. 请求中断检测
方案A:连接断开检测(推荐)
# 伪代码示例
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:取消端点(备选)
# 添加专门的取消端点
@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. 会话管理优化
延迟会话创建:
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. 流式响应优化
分段提交策略:
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 修改
请求头支持:
POST /api/ai-coach/chat
X-Request-Id: req_1234567890_abcdef
Content-Type: application/json
{
"conversationId": "existing_conv_id", // 可选,新会话时为空
"messages": [...],
"stream": true
}
响应头:
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. 数据库设计优化
会话状态管理:
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头:
// 在 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 || {}),
};
// ... 其余代码
}
主动取消通知(可选):
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);
}
}
完整取消流程
客户端取消时:
- 增加请求序列号(防止延迟响应)
- 调用
XMLHttpRequest.abort() - 清理本地状态
- (可选)通知服务端取消
服务端检测到断开:
- 立即停止 AI 内容生成
- 检查会话状态(临时/已确认)
- 如果是临时会话,删除记录
- 清理相关资源
防止延迟响应:
- 客户端使用请求序列号验证
- 服务端检查连接状态
- 分段式会话确认机制
优先级建议
立即实现(高优先级):
- 客户端请求序列号验证 ✅ 已实现
- 延迟会话ID生成 ✅ 已实现
- 客户端状态严格管理 ✅ 已实现
服务端配合(中优先级):
- 连接断开检测
- 临时会话管理
- 请求ID追踪
可选优化(低优先级):
- 主动取消通知端点
- 会话状态数据库优化
- 定期清理机制
通过以上客户端和服务端的协作改进,可以彻底解决取消功能的问题,确保用户取消操作的即时性和有效性。