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

7.4 KiB
Raw Permalink Blame History

服务端取消支持方案

问题分析

当前取消功能的问题:

  1. 客户端 XMLHttpRequest.abort() 只能终止网络传输,无法停止服务端处理
  2. 服务端可能已经生成并保存了会话记录
  3. 延迟响应导致"取消"的内容稍后出现

服务端需要支持的功能

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);
  }
}

完整取消流程

客户端取消时:

  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. 定期清理机制

通过以上客户端和服务端的协作改进,可以彻底解决取消功能的问题,确保用户取消操作的即时性和有效性。