diff --git a/src/mcp_feedback_enhanced/web/main.py b/src/mcp_feedback_enhanced/web/main.py index ce85ac3..1d6f323 100644 --- a/src/mcp_feedback_enhanced/web/main.py +++ b/src/mcp_feedback_enhanced/web/main.py @@ -332,30 +332,34 @@ class WebUIManager: old_websocket = self._old_websocket_for_update new_session = self._new_session_for_update - # 發送會話更新通知 - await old_websocket.send_json({ - "type": "session_updated", - "message": "新會話已創建,正在更新頁面內容", - "session_info": { - "project_directory": new_session.project_directory, - "summary": new_session.summary, - "session_id": new_session.session_id - } - }) - debug_log("已通過舊 WebSocket 連接發送會話更新通知") + # 檢查舊連接是否仍然有效 + if old_websocket and not old_websocket.client_state.DISCONNECTED: + try: + # 發送會話更新通知 + await old_websocket.send_json({ + "type": "session_updated", + "message": "新會話已創建,正在更新頁面內容", + "session_info": { + "project_directory": new_session.project_directory, + "summary": new_session.summary, + "session_id": new_session.session_id + } + }) + debug_log("已通過舊 WebSocket 連接發送會話更新通知") + + # 延遲一小段時間讓前端處理消息 + await asyncio.sleep(0.2) + + except Exception as send_error: + debug_log(f"發送會話更新通知失敗: {send_error}") + + # 安全關閉舊連接 + await self._safe_close_websocket(old_websocket) # 清理臨時變數 delattr(self, '_old_websocket_for_update') delattr(self, '_new_session_for_update') - # 延遲一小段時間讓前端處理消息,然後關閉舊連接 - await asyncio.sleep(0.1) - try: - await old_websocket.close() - debug_log("已關閉舊 WebSocket 連接") - except Exception as e: - debug_log(f"關閉舊 WebSocket 連接失敗: {e}") - else: # 沒有舊連接,設置待更新標記 self._pending_session_update = True @@ -366,6 +370,31 @@ class WebUIManager: # 回退到待更新標記 self._pending_session_update = True + async def _safe_close_websocket(self, websocket): + """安全關閉 WebSocket 連接,避免事件循環衝突""" + if not websocket: + return + + try: + # 檢查連接狀態 + if websocket.client_state.DISCONNECTED: + debug_log("WebSocket 已斷開,跳過關閉操作") + return + + # 嘗試正常關閉 + await asyncio.wait_for(websocket.close(code=1000, reason="會話更新"), timeout=2.0) + debug_log("已正常關閉舊 WebSocket 連接") + + except asyncio.TimeoutError: + debug_log("WebSocket 關閉超時,強制斷開") + except RuntimeError as e: + if "attached to a different loop" in str(e): + debug_log(f"WebSocket 事件循環衝突,忽略關閉錯誤: {e}") + else: + debug_log(f"WebSocket 關閉時發生運行時錯誤: {e}") + except Exception as e: + debug_log(f"關閉 WebSocket 連接時發生未知錯誤: {e}") + async def _check_active_tabs(self) -> bool: """檢查是否有活躍標籤頁 - 優先檢查全局狀態,回退到 API""" try: diff --git a/src/mcp_feedback_enhanced/web/models/feedback_session.py b/src/mcp_feedback_enhanced/web/models/feedback_session.py index f8e52e3..746c2f2 100644 --- a/src/mcp_feedback_enhanced/web/models/feedback_session.py +++ b/src/mcp_feedback_enhanced/web/models/feedback_session.py @@ -324,7 +324,9 @@ class WebFeedbackSession: "message": "會話已超時,介面將自動關閉" }) await asyncio.sleep(0.1) # 給前端一點時間處理消息 - await self.websocket.close() + + # 安全關閉 WebSocket + await self._safe_close_websocket() debug_log(f"會話 {self.session_id} WebSocket 已關閉") except Exception as e: debug_log(f"關閉 WebSocket 時發生錯誤: {e}") @@ -401,4 +403,29 @@ class WebFeedbackSession: self.process = None # 設置完成事件 - self.feedback_completed.set() \ No newline at end of file + self.feedback_completed.set() + + async def _safe_close_websocket(self): + """安全關閉 WebSocket 連接,避免事件循環衝突""" + if not self.websocket: + return + + try: + # 檢查連接狀態 + if hasattr(self.websocket, 'client_state') and self.websocket.client_state.DISCONNECTED: + debug_log("WebSocket 已斷開,跳過關閉操作") + return + + # 嘗試正常關閉 + await asyncio.wait_for(self.websocket.close(code=1000, reason="會話清理"), timeout=2.0) + debug_log(f"會話 {self.session_id} WebSocket 已正常關閉") + + except asyncio.TimeoutError: + debug_log(f"會話 {self.session_id} WebSocket 關閉超時") + except RuntimeError as e: + if "attached to a different loop" in str(e): + debug_log(f"會話 {self.session_id} WebSocket 事件循環衝突,忽略關閉錯誤: {e}") + else: + debug_log(f"會話 {self.session_id} WebSocket 關閉時發生運行時錯誤: {e}") + except Exception as e: + debug_log(f"會話 {self.session_id} 關閉 WebSocket 時發生未知錯誤: {e}") \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/web/routes/main_routes.py b/src/mcp_feedback_enhanced/web/routes/main_routes.py index 2bbd386..fc43f81 100644 --- a/src/mcp_feedback_enhanced/web/routes/main_routes.py +++ b/src/mcp_feedback_enhanced/web/routes/main_routes.py @@ -192,11 +192,16 @@ def setup_routes(manager: 'WebUIManager'): await handle_websocket_message(manager, session, message) except WebSocketDisconnect: - debug_log(f"WebSocket 連接斷開") + debug_log(f"WebSocket 連接正常斷開") + except ConnectionResetError: + debug_log(f"WebSocket 連接被重置") except Exception as e: debug_log(f"WebSocket 錯誤: {e}") finally: - session.websocket = None + # 安全清理 WebSocket 連接 + if session.websocket == websocket: + session.websocket = None + debug_log("已清理會話中的 WebSocket 連接") @manager.app.post("/api/save-settings") async def save_settings(request: Request): diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index 6687f8d..8dc75c9 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -800,7 +800,9 @@ class FeedbackApp { * 檢查是否可以提交回饋 */ canSubmitFeedback() { - return this.feedbackState === 'waiting_for_feedback' && this.isConnected; + const canSubmit = this.feedbackState === 'waiting_for_feedback' && this.isConnected; + console.log(`🔍 檢查提交權限: feedbackState=${this.feedbackState}, isConnected=${this.isConnected}, canSubmit=${canSubmit}`); + return canSubmit; } /** @@ -989,6 +991,12 @@ class FeedbackApp { // 停止心跳 this.stopWebSocketHeartbeat(); + // 重置回饋狀態,避免卡在處理狀態 + if (this.feedbackState === 'processing') { + console.log('🔄 WebSocket 斷開,重置處理狀態'); + this.setFeedbackState('waiting_for_feedback'); + } + if (event.code === 4004) { // 沒有活躍會話 this.updateConnectionStatus('disconnected', '沒有活躍會話'); @@ -998,7 +1006,10 @@ class FeedbackApp { // 只有在非正常關閉時才重連 if (event.code !== 1000) { console.log('3秒後嘗試重連...'); - setTimeout(() => this.setupWebSocket(), 3000); + setTimeout(() => { + console.log('🔄 開始重連 WebSocket...'); + this.setupWebSocket(); + }, 3000); } } }; @@ -1107,12 +1118,16 @@ class FeedbackApp { // 顯示更新通知 this.showSuccessMessage(data.message || '會話已更新,正在局部更新內容...'); - // 重置回饋狀態為等待新回饋 - this.setFeedbackState('waiting_for_feedback'); - // 更新會話信息 if (data.session_info) { - this.currentSessionId = data.session_info.session_id; + const newSessionId = data.session_info.session_id; + console.log(`📋 會話 ID 更新: ${this.currentSessionId} -> ${newSessionId}`); + + // 重置回饋狀態為等待新回饋(使用新的會話 ID) + this.setFeedbackState('waiting_for_feedback', newSessionId); + + // 更新當前會話 ID + this.currentSessionId = newSessionId; // 更新頁面標題 if (data.session_info.project_directory) { @@ -1122,6 +1137,10 @@ class FeedbackApp { // 使用局部更新替代整頁刷新 this.refreshPageContent(); + } else { + // 如果沒有會話信息,仍然重置狀態 + console.log('⚠️ 會話更新沒有包含會話信息,僅重置狀態'); + this.setFeedbackState('waiting_for_feedback'); } console.log('✅ 會話更新處理完成'); @@ -1576,16 +1595,22 @@ class FeedbackApp { // 所有事件監聽器已在 setupEventListeners() 中統一設置 submitFeedback() { + console.log('📤 嘗試提交回饋...'); + // 檢查是否可以提交回饋 if (!this.canSubmitFeedback()) { - console.log('⚠️ 無法提交回饋 - 當前狀態:', this.feedbackState); + console.log('⚠️ 無法提交回饋 - 當前狀態:', this.feedbackState, '連接狀態:', this.isConnected); if (this.feedbackState === 'feedback_submitted') { this.showMessage('回饋已提交,請等待下次 MCP 調用', 'warning'); } else if (this.feedbackState === 'processing') { this.showMessage('正在處理中,請稍候', 'warning'); } else if (!this.isConnected) { - this.showMessage('WebSocket 未連接', 'error'); + this.showMessage('WebSocket 未連接,正在嘗試重連...', 'error'); + // 嘗試重新建立連接 + this.setupWebSocket(); + } else { + this.showMessage(`當前狀態不允許提交: ${this.feedbackState}`, 'warning'); } return; }